@sw-market/openclaw-opencode-bridge 0.1.2 → 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 +21 -4
- package/dist/openclaw-extension.js +28 -3
- package/dist/sdk-adapter-default.d.ts +13 -0
- package/dist/sdk-adapter-default.js +133 -0
- package/openclaw.plugin.json +2 -2
- 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
|
+
## Zero-Config Bootstrap (v0.1.3+)
|
|
25
|
+
|
|
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
|
+
|
|
24
39
|
## OpenClaw Config
|
|
25
40
|
|
|
26
|
-
|
|
41
|
+
Optional explicit config in `~/.openclaw/openclaw.json`:
|
|
27
42
|
|
|
28
43
|
```json
|
|
29
44
|
{
|
|
@@ -35,7 +50,8 @@ In `~/.openclaw/openclaw.json`, enable plugin and set 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
|
}
|
|
@@ -55,8 +71,9 @@ Adapter module contract:
|
|
|
55
71
|
- returned object must implement `createSession(args)`.
|
|
56
72
|
|
|
57
73
|
Note:
|
|
58
|
-
- Plugin installation
|
|
59
|
-
-
|
|
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`.
|
|
60
77
|
|
|
61
78
|
## Gateway Methods Provided
|
|
62
79
|
|
|
@@ -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) {
|
|
@@ -65,11 +67,34 @@ function parseConfig(pluginConfig) {
|
|
|
65
67
|
emitToAllClients,
|
|
66
68
|
};
|
|
67
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
|
+
}
|
|
68
88
|
function resolveModuleSpecifier(api, moduleSpecifier) {
|
|
69
89
|
const raw = moduleSpecifier.trim();
|
|
70
90
|
if (!raw) {
|
|
71
|
-
|
|
72
|
-
|
|
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();
|
|
73
98
|
}
|
|
74
99
|
const looksLikePath = raw.startsWith("./") ||
|
|
75
100
|
raw.startsWith("../") ||
|
|
@@ -104,7 +129,7 @@ async function loadSdkAdapter(api, config) {
|
|
|
104
129
|
}));
|
|
105
130
|
}
|
|
106
131
|
if (!isOpenCodeSdkAdapter(candidate)) {
|
|
107
|
-
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}". ` +
|
|
108
133
|
"Expected an object with createSession(args) function.");
|
|
109
134
|
}
|
|
110
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 (optional
|
|
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",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"uiHints": {
|
|
32
32
|
"sdkAdapterModule": {
|
|
33
33
|
"label": "SDK Adapter Module",
|
|
34
|
-
"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."
|
|
35
35
|
},
|
|
36
36
|
"sdkAdapterExport": {
|
|
37
37
|
"label": "SDK Adapter Export",
|
package/package.json
CHANGED