@sw-market/openclaw-opencode-bridge 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -21,9 +21,24 @@ After install, verify plugin exists:
21
21
  openclaw plugins list
22
22
  ```
23
23
 
24
- ## Required OpenClaw Config
24
+ ## Zero-Config Bootstrap (v0.1.3+)
25
25
 
26
- In `~/.openclaw/openclaw.json`, add plugin entry and SDK adapter config:
26
+ After install, if `sdkAdapterModule` is not configured:
27
+
28
+ 1. Plugin will auto-discover these common adapter files:
29
+ - `<gateway cwd>/opencode_sdk_adapter.mjs`
30
+ - `<gateway cwd>/opencode_sdk_adapter.js`
31
+ - `~/.openclaw/opencode_sdk_adapter.mjs`
32
+ - `~/.openclaw/opencode_sdk_adapter.js`
33
+ 2. If none found, plugin falls back to bundled guided adapter.
34
+ 3. Bundled guided adapter will auto-generate template file:
35
+ - `~/.openclaw/opencode_sdk_adapter.mjs`
36
+
37
+ This means plugin install can be simplified: install first, then edit generated template on target machine.
38
+
39
+ ## OpenClaw Config
40
+
41
+ Optional explicit config in `~/.openclaw/openclaw.json`:
27
42
 
28
43
  ```json
29
44
  {
@@ -35,7 +50,8 @@ In `~/.openclaw/openclaw.json`, add plugin entry and SDK adapter config:
35
50
  "sdkAdapterModule": "/abs/path/to/opencode_sdk_adapter.mjs",
36
51
  "sdkAdapterExport": "createOpenCodeSdkAdapter",
37
52
  "sessionTtlMs": 1800000,
38
- "cleanupIntervalMs": 60000
53
+ "cleanupIntervalMs": 60000,
54
+ "emitToAllClients": false
39
55
  }
40
56
  }
41
57
  }
@@ -43,10 +59,22 @@ In `~/.openclaw/openclaw.json`, add plugin entry and SDK adapter config:
43
59
  }
44
60
  ```
45
61
 
62
+ You can also set adapter location by environment variable:
63
+
64
+ ```bash
65
+ export OPENCODE_SDK_ADAPTER_MODULE=/abs/path/to/opencode_sdk_adapter.mjs
66
+ export OPENCODE_SDK_ADAPTER_EXPORT=createOpenCodeSdkAdapter
67
+ ```
68
+
46
69
  Adapter module contract:
47
70
  - export function `createOpenCodeSdkAdapter(ctx)` or export object directly.
48
71
  - returned object must implement `createSession(args)`.
49
72
 
73
+ Note:
74
+ - Plugin installation does not require `sdkAdapterModule` in config.
75
+ - If not configured, plugin can auto-discover local adapter file names.
76
+ - If still missing, plugin now falls back to guided adapter and generates template at `~/.openclaw/opencode_sdk_adapter.mjs`.
77
+
50
78
  ## Gateway Methods Provided
51
79
 
52
80
  This plugin registers:
@@ -1,10 +1,12 @@
1
1
  import { existsSync } from "node:fs";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import { pathToFileURL } from "node:url";
4
5
  import { OpenClawOpenCodeBridge, } from "./index.js";
5
6
  const CHAT_SEND_METHOD = "opencode.chat.send";
6
7
  const CHAT_ACTION_METHOD = "opencode.chat.action";
7
8
  const DEFAULT_ADAPTER_EXPORT = "createOpenCodeSdkAdapter";
9
+ const DEFAULT_TEMPLATE_FILE = "opencode_sdk_adapter.mjs";
8
10
  let bridgePromise = null;
9
11
  let bridgeInstance = null;
10
12
  function asObject(value) {
@@ -49,8 +51,11 @@ function toErrorMessage(error) {
49
51
  }
50
52
  function parseConfig(pluginConfig) {
51
53
  const raw = pluginConfig ?? {};
52
- const sdkAdapterModule = readString(raw, "sdkAdapterModule");
53
- const sdkAdapterExport = readString(raw, "sdkAdapterExport") || DEFAULT_ADAPTER_EXPORT;
54
+ const sdkAdapterModuleRaw = readString(raw, "sdkAdapterModule") || String(process.env.OPENCODE_SDK_ADAPTER_MODULE || "").trim();
55
+ const sdkAdapterModule = sdkAdapterModuleRaw || undefined;
56
+ const sdkAdapterExport = readString(raw, "sdkAdapterExport") ||
57
+ String(process.env.OPENCODE_SDK_ADAPTER_EXPORT || "").trim() ||
58
+ DEFAULT_ADAPTER_EXPORT;
54
59
  const sessionTtlMs = readOptionalPositiveInt(raw, "sessionTtlMs");
55
60
  const cleanupIntervalMs = readOptionalPositiveInt(raw, "cleanupIntervalMs");
56
61
  const emitToAllClients = raw.emitToAllClients === true;
@@ -62,10 +67,34 @@ function parseConfig(pluginConfig) {
62
67
  emitToAllClients,
63
68
  };
64
69
  }
70
+ function discoverAdapterPath(api) {
71
+ const candidates = [
72
+ path.resolve(process.cwd(), DEFAULT_TEMPLATE_FILE),
73
+ path.resolve(process.cwd(), "opencode_sdk_adapter.js"),
74
+ path.resolve(os.homedir(), ".openclaw", DEFAULT_TEMPLATE_FILE),
75
+ path.resolve(os.homedir(), ".openclaw", "opencode_sdk_adapter.js"),
76
+ api.resolvePath(`./${DEFAULT_TEMPLATE_FILE}`),
77
+ ];
78
+ for (const candidate of candidates) {
79
+ if (existsSync(candidate)) {
80
+ return candidate;
81
+ }
82
+ }
83
+ return undefined;
84
+ }
85
+ function bundledAdapterSpecifier() {
86
+ return new URL("./sdk-adapter-default.js", import.meta.url).href;
87
+ }
65
88
  function resolveModuleSpecifier(api, moduleSpecifier) {
66
89
  const raw = moduleSpecifier.trim();
67
90
  if (!raw) {
68
- throw new Error("Missing plugin config `sdkAdapterModule`.");
91
+ const discovered = discoverAdapterPath(api);
92
+ if (discovered) {
93
+ api.logger.info(`[${api.id}] discovered sdkAdapterModule: ${discovered}`);
94
+ return pathToFileURL(discovered).href;
95
+ }
96
+ api.logger.warn(`[${api.id}] sdkAdapterModule is not configured; falling back to bundled guided adapter.`);
97
+ return bundledAdapterSpecifier();
69
98
  }
70
99
  const looksLikePath = raw.startsWith("./") ||
71
100
  raw.startsWith("../") ||
@@ -89,7 +118,7 @@ function isOpenCodeSdkAdapter(value) {
89
118
  return typeof obj.createSession === "function";
90
119
  }
91
120
  async function loadSdkAdapter(api, config) {
92
- const moduleSpecifier = resolveModuleSpecifier(api, config.sdkAdapterModule);
121
+ const moduleSpecifier = resolveModuleSpecifier(api, config.sdkAdapterModule ?? "");
93
122
  const loaded = await import(moduleSpecifier);
94
123
  const exported = loaded[config.sdkAdapterExport];
95
124
  let candidate = exported;
@@ -100,7 +129,7 @@ async function loadSdkAdapter(api, config) {
100
129
  }));
101
130
  }
102
131
  if (!isOpenCodeSdkAdapter(candidate)) {
103
- throw new Error(`Invalid OpenCode SDK adapter from "${config.sdkAdapterModule}" export "${config.sdkAdapterExport}". ` +
132
+ throw new Error(`Invalid OpenCode SDK adapter from "${config.sdkAdapterModule || moduleSpecifier}" export "${config.sdkAdapterExport}". ` +
104
133
  "Expected an object with createSession(args) function.");
105
134
  }
106
135
  return candidate;
@@ -0,0 +1,13 @@
1
+ import type { OpenCodeSdkAdapter } from "./index.js";
2
+ type Logger = {
3
+ info?: (message: string) => void;
4
+ warn?: (message: string) => void;
5
+ error?: (message: string) => void;
6
+ };
7
+ type CreateAdapterContext = {
8
+ api?: {
9
+ logger?: Logger;
10
+ };
11
+ };
12
+ export declare function createOpenCodeSdkAdapter(ctx?: CreateAdapterContext): OpenCodeSdkAdapter;
13
+ export default createOpenCodeSdkAdapter;
@@ -0,0 +1,133 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ const DEFAULT_TEMPLATE_PATH = path.join(os.homedir(), ".openclaw", "opencode_sdk_adapter.mjs");
5
+ const ADAPTER_TEMPLATE = `// Auto-generated by @sw-market/openclaw-opencode-bridge.
6
+ // Fill this file with your real OpenCode SDK wiring.
7
+ // Then set in OpenClaw:
8
+ // plugins.entries["openclaw-opencode-bridge"].config.sdkAdapterModule = "<this file path>"
9
+ // or set env:
10
+ // OPENCODE_SDK_ADAPTER_MODULE=<this file path>
11
+
12
+ export function createOpenCodeSdkAdapter() {
13
+ return {
14
+ async createSession({ sessionKey }) {
15
+ return {
16
+ async *sendMessage({ runId, message, idempotencyKey }) {
17
+ // TODO: Replace this with real OpenCode SDK call + stream mapping.
18
+ yield {
19
+ kind: "assistant_final",
20
+ text:
21
+ "Template adapter loaded, but real SDK is not connected yet. " +
22
+ "Please implement sendMessage/sendAction in opencode_sdk_adapter.mjs.",
23
+ summary: "Template adapter loaded",
24
+ status: "failed",
25
+ };
26
+ yield {
27
+ kind: "run_completed",
28
+ status: "failed",
29
+ error: "Template adapter not implemented",
30
+ };
31
+ },
32
+ async *sendAction({ runId, action, idempotencyKey }) {
33
+ // TODO: Replace this with real action callback into OpenCode SDK.
34
+ if (action.type === "interaction.reply") {
35
+ yield {
36
+ kind: "interaction_resolved",
37
+ interactionId: action.interactionId,
38
+ decision: action.decision,
39
+ status: "resolved",
40
+ source: "template_adapter",
41
+ };
42
+ }
43
+ yield {
44
+ kind: "run_completed",
45
+ status: "failed",
46
+ error: "Template adapter not implemented",
47
+ };
48
+ },
49
+ };
50
+ },
51
+ };
52
+ }
53
+ `;
54
+ function toErrorMessage(error) {
55
+ if (error instanceof Error) {
56
+ return error.message;
57
+ }
58
+ return String(error);
59
+ }
60
+ function ensureTemplateFile(logger) {
61
+ try {
62
+ if (!existsSync(DEFAULT_TEMPLATE_PATH)) {
63
+ mkdirSync(path.dirname(DEFAULT_TEMPLATE_PATH), { recursive: true });
64
+ writeFileSync(DEFAULT_TEMPLATE_PATH, ADAPTER_TEMPLATE, "utf-8");
65
+ logger?.info?.(`[openclaw-opencode-bridge] generated SDK adapter template at ${DEFAULT_TEMPLATE_PATH}`);
66
+ }
67
+ return DEFAULT_TEMPLATE_PATH;
68
+ }
69
+ catch (error) {
70
+ logger?.warn?.(`[openclaw-opencode-bridge] failed to write adapter template: ${toErrorMessage(error)}`);
71
+ return undefined;
72
+ }
73
+ }
74
+ function buildGuidanceText(templatePath) {
75
+ const lines = [
76
+ "OpenCode SDK adapter is not configured yet.",
77
+ "Set plugins.entries.openclaw-opencode-bridge.config.sdkAdapterModule",
78
+ "or set env OPENCODE_SDK_ADAPTER_MODULE, then restart openclaw-gateway.",
79
+ ];
80
+ if (templatePath) {
81
+ lines.push(`Template generated at: ${templatePath}`);
82
+ }
83
+ return lines.join("\n");
84
+ }
85
+ function createGuidanceSession(guidanceText) {
86
+ return {
87
+ async *sendMessage() {
88
+ yield {
89
+ kind: "progress",
90
+ stage: "configuration_required",
91
+ message: "OpenCode SDK adapter module is required.",
92
+ };
93
+ yield {
94
+ kind: "assistant_final",
95
+ text: guidanceText,
96
+ summary: "SDK adapter not configured",
97
+ status: "failed",
98
+ };
99
+ yield {
100
+ kind: "run_completed",
101
+ status: "failed",
102
+ error: "OpenCode SDK adapter is not configured",
103
+ };
104
+ },
105
+ async *sendAction(args) {
106
+ if (args.action.type === "interaction.reply") {
107
+ yield {
108
+ kind: "interaction_resolved",
109
+ interactionId: args.action.interactionId,
110
+ decision: args.action.decision,
111
+ status: "resolved",
112
+ source: "openclaw-opencode-bridge",
113
+ };
114
+ }
115
+ yield {
116
+ kind: "run_completed",
117
+ status: "failed",
118
+ error: "OpenCode SDK adapter is not configured",
119
+ };
120
+ },
121
+ };
122
+ }
123
+ export function createOpenCodeSdkAdapter(ctx) {
124
+ const logger = ctx?.api?.logger;
125
+ const templatePath = ensureTemplateFile(logger);
126
+ const guidanceText = buildGuidanceText(templatePath);
127
+ return {
128
+ async createSession() {
129
+ return createGuidanceSession(guidanceText);
130
+ },
131
+ };
132
+ }
133
+ export default createOpenCodeSdkAdapter;
@@ -8,7 +8,7 @@
8
8
  "properties": {
9
9
  "sdkAdapterModule": {
10
10
  "type": "string",
11
- "description": "Node module specifier or path to SDK adapter module"
11
+ "description": "Node module specifier or path to SDK adapter module (optional; plugin auto-discovers common paths and has a bundled guided fallback)"
12
12
  },
13
13
  "sdkAdapterExport": {
14
14
  "type": "string",
@@ -26,15 +26,12 @@
26
26
  "type": "boolean",
27
27
  "description": "Broadcast events to all gateway clients instead of only requester"
28
28
  }
29
- },
30
- "required": [
31
- "sdkAdapterModule"
32
- ]
29
+ }
33
30
  },
34
31
  "uiHints": {
35
32
  "sdkAdapterModule": {
36
33
  "label": "SDK Adapter Module",
37
- "help": "Absolute/relative path or npm package that exports OpenCode SDK adapter."
34
+ "help": "Absolute/relative path or npm package that exports OpenCode SDK adapter. If omitted, plugin will auto-discover and then fallback to bundled guided adapter."
38
35
  },
39
36
  "sdkAdapterExport": {
40
37
  "label": "SDK Adapter Export",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sw-market/openclaw-opencode-bridge",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "OpenClaw plugin bridge for OpenCode realtime streaming and interaction actions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",