@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 +31 -3
- package/dist/openclaw-extension.js +34 -5
- package/dist/sdk-adapter-default.d.ts +13 -0
- package/dist/sdk-adapter-default.js +133 -0
- package/openclaw.plugin.json +3 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,9 +21,24 @@ After install, verify plugin exists:
|
|
|
21
21
|
openclaw plugins list
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## Zero-Config Bootstrap (v0.1.3+)
|
|
25
25
|
|
|
26
|
-
|
|
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
|
|
53
|
-
const
|
|
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
|
-
|
|
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;
|
package/openclaw.plugin.json
CHANGED
|
@@ -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