@zeyiy/openclaw-channel 0.3.4

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/dist/tools.js ADDED
@@ -0,0 +1,131 @@
1
+ import { getConnectedClient } from "./clients";
2
+ import { sendFileToTarget, sendImageToTarget, sendTextToTarget, sendVideoToTarget } from "./media";
3
+ import { parseTarget } from "./targets";
4
+ import { formatSdkError } from "./utils";
5
+ export function registerOpenIMTools(api) {
6
+ if (typeof api.registerTool !== "function")
7
+ return;
8
+ const ensureTargetAndClient = (params) => {
9
+ const target = parseTarget(params.target);
10
+ if (!target) {
11
+ return {
12
+ ok: false,
13
+ result: {
14
+ content: [{ type: "text", text: "Invalid target format. Expected user:<id> or group:<id>." }],
15
+ },
16
+ };
17
+ }
18
+ const client = getConnectedClient(params.accountId);
19
+ if (!client) {
20
+ return {
21
+ ok: false,
22
+ result: {
23
+ content: [{ type: "text", text: "OpenIM is not connected." }],
24
+ },
25
+ };
26
+ }
27
+ return { ok: true, target, client };
28
+ };
29
+ api.registerTool({
30
+ name: "openim_send_text",
31
+ description: "Send a text message via OpenIM. target format: user:ID or group:ID.",
32
+ parameters: {
33
+ type: "object",
34
+ properties: {
35
+ target: { type: "string", description: "user:123 or group:456" },
36
+ text: { type: "string", description: "Text to send" },
37
+ accountId: { type: "string", description: "Optional account ID. Defaults to `default` or the first connected account." },
38
+ },
39
+ required: ["target", "text"],
40
+ },
41
+ async execute(_id, params) {
42
+ const checked = ensureTargetAndClient(params);
43
+ if (!checked.ok)
44
+ return checked.result;
45
+ try {
46
+ await sendTextToTarget(checked.client, checked.target, params.text);
47
+ return { content: [{ type: "text", text: "Sent successfully" }] };
48
+ }
49
+ catch (e) {
50
+ return { content: [{ type: "text", text: `Send failed: ${formatSdkError(e)}` }] };
51
+ }
52
+ },
53
+ });
54
+ api.registerTool({
55
+ name: "openim_send_image",
56
+ description: "Send an image via OpenIM. `image` supports a local path or an http(s) URL.",
57
+ parameters: {
58
+ type: "object",
59
+ properties: {
60
+ target: { type: "string", description: "user:123 or group:456" },
61
+ image: { type: "string", description: "Local path (`file://` supported) or URL" },
62
+ accountId: { type: "string", description: "Optional account ID" },
63
+ },
64
+ required: ["target", "image"],
65
+ },
66
+ async execute(_id, params) {
67
+ const checked = ensureTargetAndClient(params);
68
+ if (!checked.ok)
69
+ return checked.result;
70
+ try {
71
+ await sendImageToTarget(checked.client, checked.target, params.image);
72
+ return { content: [{ type: "text", text: "Image sent successfully" }] };
73
+ }
74
+ catch (e) {
75
+ return { content: [{ type: "text", text: `Send failed: ${formatSdkError(e)}` }] };
76
+ }
77
+ },
78
+ });
79
+ api.registerTool({
80
+ name: "openim_send_video",
81
+ description: "Send a video via OpenIM (delivered as a file message). `video` supports a local path or URL.",
82
+ parameters: {
83
+ type: "object",
84
+ properties: {
85
+ target: { type: "string", description: "user:123 or group:456" },
86
+ video: { type: "string", description: "Local path (`file://` supported) or URL" },
87
+ name: { type: "string", description: "Optional filename (recommended for URL input)" },
88
+ accountId: { type: "string", description: "Optional account ID" },
89
+ },
90
+ required: ["target", "video"],
91
+ },
92
+ async execute(_id, params) {
93
+ const checked = ensureTargetAndClient(params);
94
+ if (!checked.ok)
95
+ return checked.result;
96
+ try {
97
+ await sendVideoToTarget(checked.client, checked.target, params.video, params.name);
98
+ return { content: [{ type: "text", text: "Video sent successfully as a file" }] };
99
+ }
100
+ catch (e) {
101
+ return { content: [{ type: "text", text: `Send failed: ${formatSdkError(e)}` }] };
102
+ }
103
+ },
104
+ });
105
+ api.registerTool({
106
+ name: "openim_send_file",
107
+ description: "Send a file via OpenIM. `file` supports a local path or URL; `name` is optional.",
108
+ parameters: {
109
+ type: "object",
110
+ properties: {
111
+ target: { type: "string", description: "user:123 or group:456" },
112
+ file: { type: "string", description: "Local path (`file://` supported) or URL" },
113
+ name: { type: "string", description: "Optional filename (recommended for URL input)" },
114
+ accountId: { type: "string", description: "Optional account ID" },
115
+ },
116
+ required: ["target", "file"],
117
+ },
118
+ async execute(_id, params) {
119
+ const checked = ensureTargetAndClient(params);
120
+ if (!checked.ok)
121
+ return checked.result;
122
+ try {
123
+ await sendFileToTarget(checked.client, checked.target, params.file, params.name);
124
+ return { content: [{ type: "text", text: "File sent successfully" }] };
125
+ }
126
+ catch (e) {
127
+ return { content: [{ type: "text", text: `Send failed: ${formatSdkError(e)}` }] };
128
+ }
129
+ },
130
+ });
131
+ }
@@ -0,0 +1,96 @@
1
+ import type { ApiService, CallbackEvent, MessageItem } from "@openim/client-sdk";
2
+ export type ChatType = "direct" | "group";
3
+ export interface OpenIMAccountConfig {
4
+ accountId: string;
5
+ enabled: boolean;
6
+ userID: string;
7
+ token: string;
8
+ wsAddr: string;
9
+ apiAddr: string;
10
+ platformID: number;
11
+ requireMention: boolean;
12
+ inboundWhitelist: string[];
13
+ botId?: string;
14
+ portalWsAddr?: string;
15
+ }
16
+ export interface OpenIMClientState {
17
+ sdk: ApiService;
18
+ config: OpenIMAccountConfig;
19
+ handlers: {
20
+ onRecvNewMessage: (event: CallbackEvent<MessageItem>) => void;
21
+ onRecvNewMessages: (event: CallbackEvent<MessageItem[]>) => void;
22
+ onRecvOfflineNewMessages: (event: CallbackEvent<MessageItem[]>) => void;
23
+ };
24
+ }
25
+ export interface ParsedTarget {
26
+ kind: "user" | "group";
27
+ id: string;
28
+ }
29
+ export interface InboundMediaItem {
30
+ kind: "image" | "video" | "file";
31
+ url?: string;
32
+ mimeType?: string;
33
+ fileName?: string;
34
+ size?: number;
35
+ snapshotUrl?: string;
36
+ }
37
+ export interface InboundBodyResult {
38
+ body: string;
39
+ kind: "text" | "image" | "video" | "file" | "mixed" | "unknown";
40
+ media?: InboundMediaItem[];
41
+ }
42
+ export type PortalMethod = "models.list" | "agents.list" | "agents.files.list" | "agents.files.get" | "agents.files.set" | "agents.create" | "ping";
43
+ export interface PortalRequest {
44
+ id: string;
45
+ method: PortalMethod;
46
+ params: Record<string, unknown>;
47
+ }
48
+ export interface PortalResponse {
49
+ id: string;
50
+ result?: unknown;
51
+ error?: {
52
+ code: number;
53
+ message: string;
54
+ };
55
+ }
56
+ export interface PortalBridgeState {
57
+ ws: WebSocket | null;
58
+ accountId: string;
59
+ botId: string;
60
+ portalWsAddr: string;
61
+ reconnectTimer?: ReturnType<typeof setTimeout>;
62
+ heartbeatTimer?: ReturnType<typeof setInterval>;
63
+ stopped: boolean;
64
+ }
65
+ export interface ModelEntry {
66
+ id: string;
67
+ name: string;
68
+ provider: string;
69
+ contextWindow?: number;
70
+ reasoning?: boolean;
71
+ active?: boolean;
72
+ }
73
+ export interface AgentIdentity {
74
+ name?: string;
75
+ theme?: string;
76
+ emoji?: string;
77
+ avatar?: string;
78
+ }
79
+ export interface AgentSummary {
80
+ id: string;
81
+ name?: string;
82
+ identity?: AgentIdentity;
83
+ workspace?: string;
84
+ model?: {
85
+ primary?: string;
86
+ fallbacks?: string[];
87
+ };
88
+ }
89
+ export interface AgentFileEntry {
90
+ name: string;
91
+ path: string;
92
+ missing: boolean;
93
+ size?: number;
94
+ updatedAtMs?: number;
95
+ content?: string;
96
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare function safeStringify(value: unknown): string;
2
+ export declare function formatSdkError(error: unknown): string;
3
+ export declare function toFiniteNumber(value: unknown, fallback: number): number;
package/dist/utils.js ADDED
@@ -0,0 +1,31 @@
1
+ export function safeStringify(value) {
2
+ try {
3
+ return JSON.stringify(value);
4
+ }
5
+ catch {
6
+ return String(value);
7
+ }
8
+ }
9
+ export function formatSdkError(error) {
10
+ const e = error;
11
+ const fields = [];
12
+ if (e?.event)
13
+ fields.push(`event=${e.event}`);
14
+ if (e?.errCode !== undefined)
15
+ fields.push(`errCode=${e.errCode}`);
16
+ if (e?.errMsg)
17
+ fields.push(`errMsg=${e.errMsg}`);
18
+ if (e?.operationID)
19
+ fields.push(`operationID=${e.operationID}`);
20
+ if (e?.data !== undefined && e?.data !== null)
21
+ fields.push(`data=${safeStringify(e.data)}`);
22
+ if (fields.length > 0)
23
+ return fields.join(", ");
24
+ if (e instanceof Error)
25
+ return e.message;
26
+ return safeStringify(error);
27
+ }
28
+ export function toFiniteNumber(value, fallback) {
29
+ const n = typeof value === "number" ? value : parseInt(String(value ?? ""), 10);
30
+ return Number.isFinite(n) ? n : fallback;
31
+ }
@@ -0,0 +1,116 @@
1
+ {
2
+ "id": "openclaw-channel",
3
+ "name": "OpenIM Channel",
4
+ "version": "0.3.4",
5
+ "description": "OpenIM protocol channel for OpenClaw",
6
+ "author": "ZeyiY",
7
+ "channels": [
8
+ "openim"
9
+ ],
10
+ "configSchema": {
11
+ "type": "object",
12
+ "additionalProperties": false,
13
+ "properties": {
14
+ "enabled": {
15
+ "type": "boolean",
16
+ "default": true
17
+ },
18
+ "token": {
19
+ "type": "string",
20
+ "description": "OpenIM JWT token (used to derive userID/platformID when omitted)"
21
+ },
22
+ "wsAddr": {
23
+ "type": "string",
24
+ "description": "OpenIM WebSocket endpoint, e.g. ws://127.0.0.1:10001"
25
+ },
26
+ "apiAddr": {
27
+ "type": "string",
28
+ "description": "OpenIM API endpoint, e.g. http://127.0.0.1:10002"
29
+ },
30
+ "requireMention": {
31
+ "type": "boolean",
32
+ "default": true,
33
+ "description": "Whether replies in groups require mentioning the current account"
34
+ },
35
+ "inboundWhitelist": {
36
+ "type": "array",
37
+ "items": {
38
+ "type": "string"
39
+ },
40
+ "default": [],
41
+ "description": "Optional sender whitelist. When set, only direct messages from listed users or their @mentions in groups are processed."
42
+ },
43
+ "accounts": {
44
+ "type": "object",
45
+ "description": "Multi-account configuration keyed by accountId",
46
+ "additionalProperties": {
47
+ "type": "object",
48
+ "additionalProperties": false,
49
+ "properties": {
50
+ "enabled": {
51
+ "type": "boolean",
52
+ "default": true
53
+ },
54
+ "token": {
55
+ "type": "string",
56
+ "description": "OpenIM JWT token"
57
+ },
58
+ "wsAddr": {
59
+ "type": "string"
60
+ },
61
+ "apiAddr": {
62
+ "type": "string"
63
+ },
64
+ "requireMention": {
65
+ "type": "boolean",
66
+ "default": true
67
+ },
68
+ "inboundWhitelist": {
69
+ "type": "array",
70
+ "items": {
71
+ "type": "string"
72
+ },
73
+ "default": []
74
+ },
75
+ "botId": {
76
+ "type": "string",
77
+ "description": "Unique bot identifier for agent-portal WebSocket connection"
78
+ },
79
+ "portalWsAddr": {
80
+ "type": "string",
81
+ "description": "Agent-portal WebSocket endpoint, e.g. wss://portal.example.com/ws"
82
+ }
83
+ }
84
+ }
85
+ },
86
+ "botId": {
87
+ "type": "string",
88
+ "description": "Unique bot identifier for agent-portal WebSocket connection"
89
+ },
90
+ "portalWsAddr": {
91
+ "type": "string",
92
+ "description": "Agent-portal WebSocket endpoint, e.g. wss://portal.example.com/ws"
93
+ }
94
+ }
95
+ },
96
+ "uiHints": {
97
+ "token": {
98
+ "label": "Token",
99
+ "sensitive": true
100
+ },
101
+ "wsAddr": {
102
+ "label": "WebSocket Endpoint",
103
+ "placeholder": "ws://127.0.0.1:10001"
104
+ },
105
+ "apiAddr": {
106
+ "label": "API Endpoint",
107
+ "placeholder": "http://127.0.0.1:10002"
108
+ },
109
+ "requireMention": {
110
+ "label": "Require Mention in Group"
111
+ },
112
+ "inboundWhitelist": {
113
+ "label": "Inbound Sender Whitelist"
114
+ }
115
+ }
116
+ }
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@zeyiy/openclaw-channel",
3
+ "version": "0.3.4",
4
+ "description": "OpenIM channel plugin for OpenClaw gateway (fork of @openim/openclaw-channel)",
5
+ "license": "AGPL-3.0-only",
6
+ "author": "ZeyiY",
7
+ "contributors": [
8
+ "blooming",
9
+ "zongzi"
10
+ ],
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/ZeyiY/openclaw-channel.git"
14
+ },
15
+ "homepage": "https://github.com/ZeyiY/openclaw-channel#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/ZeyiY/openclaw-channel/issues"
18
+ },
19
+ "type": "module",
20
+ "main": "./dist/index.js",
21
+ "exports": "./dist/index.js",
22
+ "files": [
23
+ "dist",
24
+ "openclaw.plugin.json",
25
+ "README.md",
26
+ "README.zh-CN.md"
27
+ ],
28
+ "openclaw": {
29
+ "extensions": [
30
+ "./dist/index.js"
31
+ ],
32
+ "channel": {
33
+ "id": "openim",
34
+ "label": "OpenIM",
35
+ "selectionLabel": "OpenIM",
36
+ "docsPath": "/channels/openim",
37
+ "blurb": "OpenIM protocol channel via @openim/client-sdk",
38
+ "order": 86,
39
+ "aliases": [
40
+ "openim",
41
+ "im"
42
+ ]
43
+ }
44
+ },
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "test:connect": "tsx scripts/test-connect.ts",
48
+ "prepublishOnly": "npm run build"
49
+ },
50
+ "dependencies": {
51
+ "@clack/prompts": "^1.0.0",
52
+ "@openim/client-sdk": "^3.8.3"
53
+ },
54
+ "peerDependencies": {
55
+ "clawdbot": "*",
56
+ "openclaw": "*"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "clawdbot": {
60
+ "optional": true
61
+ },
62
+ "openclaw": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "devDependencies": {
67
+ "@types/node": "^22.0.0",
68
+ "tsx": "^4.0.0",
69
+ "typescript": "^5.4.0"
70
+ },
71
+ "engines": {
72
+ "node": ">=22"
73
+ }
74
+ }