rol-websocket-channel 1.0.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.
Files changed (58) hide show
  1. package/MQTT-API.md +967 -0
  2. package/dist/index.js +430 -0
  3. package/dist/message-handler.js +327 -0
  4. package/dist/src/admin/cli.js +43 -0
  5. package/dist/src/admin/jsonrpc.js +60 -0
  6. package/dist/src/admin/lib/fs.js +30 -0
  7. package/dist/src/admin/lib/paths.js +46 -0
  8. package/dist/src/admin/methods/admin.js +60 -0
  9. package/dist/src/admin/methods/agents-extended.js +235 -0
  10. package/dist/src/admin/methods/index.js +69 -0
  11. package/dist/src/admin/methods/memory.js +360 -0
  12. package/dist/src/admin/methods/models-extended.js +107 -0
  13. package/dist/src/admin/methods/models.js +39 -0
  14. package/dist/src/admin/methods/sessions-extended.js +207 -0
  15. package/dist/src/admin/methods/sessions.js +64 -0
  16. package/dist/src/admin/methods/skills-extended.js +157 -0
  17. package/dist/src/admin/methods/skills-toggle.js +182 -0
  18. package/dist/src/admin/methods/skills.js +384 -0
  19. package/dist/src/admin/methods/system.js +178 -0
  20. package/dist/src/admin/methods/usage.js +1170 -0
  21. package/dist/src/admin/types.js +1 -0
  22. package/dist/src/mqtt/connection-manager.js +155 -0
  23. package/dist/src/mqtt/index.js +5 -0
  24. package/dist/src/mqtt/mqtt-client.js +86 -0
  25. package/dist/src/mqtt/types.js +2 -0
  26. package/dist/src/shared/context.js +24 -0
  27. package/dist/src/shared/wrapper.js +23 -0
  28. package/index.ts +514 -0
  29. package/message-handler.ts +415 -0
  30. package/openclaw.plugin.json +84 -0
  31. package/package.json +35 -0
  32. package/readme.md +32 -0
  33. package/src/admin/cli.ts +60 -0
  34. package/src/admin/jsonrpc.ts +88 -0
  35. package/src/admin/lib/fs.ts +35 -0
  36. package/src/admin/lib/paths.ts +61 -0
  37. package/src/admin/methods/admin.ts +95 -0
  38. package/src/admin/methods/agents-extended.ts +310 -0
  39. package/src/admin/methods/index.ts +103 -0
  40. package/src/admin/methods/memory.ts +546 -0
  41. package/src/admin/methods/models-extended.ts +191 -0
  42. package/src/admin/methods/models.ts +103 -0
  43. package/src/admin/methods/sessions-extended.ts +313 -0
  44. package/src/admin/methods/sessions.ts +122 -0
  45. package/src/admin/methods/skills-extended.ts +249 -0
  46. package/src/admin/methods/skills-toggle.ts +235 -0
  47. package/src/admin/methods/skills.ts +651 -0
  48. package/src/admin/methods/system.ts +203 -0
  49. package/src/admin/methods/usage.ts +1491 -0
  50. package/src/admin/types.ts +46 -0
  51. package/src/mqtt/connection-manager.ts +188 -0
  52. package/src/mqtt/index.ts +6 -0
  53. package/src/mqtt/mqtt-client.ts +119 -0
  54. package/src/mqtt/types.ts +36 -0
  55. package/src/shared/context.ts +33 -0
  56. package/src/shared/wrapper.ts +35 -0
  57. package/tsconfig.json +16 -0
  58. package/types/openclaw.d.ts +74 -0
@@ -0,0 +1,415 @@
1
+ /**
2
+ * 消息处理器类
3
+ * 根据消息类型调用对应的 admin 方法
4
+ */
5
+
6
+ import { getContext } from './src/shared/context.js';
7
+ import { wrapAdminCall } from './src/shared/wrapper.js';
8
+ import { getAgents, getConfig } from './src/admin/methods/admin.js';
9
+ import { createAgent, deleteAgent, listAgents, updateAgent } from './src/admin/methods/agents-extended.js';
10
+ import { listSessions } from './src/admin/methods/sessions.js';
11
+ import { getSession, prepareMessage, attachSkill } from './src/admin/methods/sessions-extended.js';
12
+ import { getModels } from './src/admin/methods/models.js';
13
+ import { updateModels } from './src/admin/methods/models-extended.js';
14
+ import {
15
+ getUsagePageSummary,
16
+ getUsageTimeseries,
17
+ getUsageBreakdown,
18
+ getUsageSummary,
19
+ } from './src/admin/methods/usage.js';
20
+ import {
21
+ listInstalledSkills,
22
+ installSkillFromNpm,
23
+ searchClawHubSkills,
24
+ installSkillFromClawHub,
25
+ updateSkillFromClawHub
26
+ } from './src/admin/methods/skills.js';
27
+ import { uninstallSkill } from './src/admin/methods/skills-extended.js';
28
+ import { toggleSkill } from './src/admin/methods/skills-toggle.js';
29
+ import {
30
+ listMemoryFiles,
31
+ getMemoryFile,
32
+ backupMemory,
33
+ exportMemoryZip,
34
+ getMemoryPresignedPost,
35
+ createMemoryBackupRecord,
36
+ importMemoryZip,
37
+ } from './src/admin/methods/memory.js';
38
+ import { restart, stop, doctorFix, logs } from './src/admin/methods/system.js';
39
+
40
+ export class MessageHandler {
41
+ /**
42
+ * 示例方法:处理 ping 类型的消息
43
+ * @param data - 消息数据
44
+ * @returns 处理结果
45
+ */
46
+ async ping(data: any): Promise<any> {
47
+ return {
48
+ message: "pong",
49
+ timestamp: Date.now(),
50
+ received: data,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * 获取 agents 配置
56
+ */
57
+ async agentsGet(data: any): Promise<any> {
58
+ return wrapAdminCall(async () => {
59
+ const context = getContext();
60
+ return await getAgents(data, context);
61
+ });
62
+ }
63
+
64
+ async agentsList(data: any): Promise<any> {
65
+ return wrapAdminCall(async () => {
66
+ const context = getContext();
67
+ return await listAgents(data, context);
68
+ });
69
+ }
70
+
71
+ async agentsCreate(data: any): Promise<any> {
72
+ return wrapAdminCall(async () => {
73
+ const context = getContext();
74
+ return await createAgent(data, context);
75
+ });
76
+ }
77
+
78
+ async agentsDelete(data: any): Promise<any> {
79
+ return wrapAdminCall(async () => {
80
+ const context = getContext();
81
+ return await deleteAgent(data, context);
82
+ });
83
+ }
84
+
85
+ /**
86
+ * 更新 agent 配置
87
+ */
88
+ async agentsUpdate(data: any): Promise<any> {
89
+ return wrapAdminCall(async () => {
90
+ const context = getContext();
91
+ return await updateAgent(data, context);
92
+ });
93
+ }
94
+
95
+ /**
96
+ * 获取配置
97
+ */
98
+ async configGet(data: any): Promise<any> {
99
+ return wrapAdminCall(async () => {
100
+ const context = getContext();
101
+ return await getConfig(data, context);
102
+ });
103
+ }
104
+
105
+ /**
106
+ * 列出 sessions
107
+ */
108
+ async sessionsList(data: any): Promise<any> {
109
+ return wrapAdminCall(async () => {
110
+ const context = getContext();
111
+ return await listSessions(data, context);
112
+ });
113
+ }
114
+
115
+ /**
116
+ * 获取单个 session 详情和消息记录
117
+ */
118
+ async sessionsGet(data: any): Promise<any> {
119
+ return wrapAdminCall(async () => {
120
+ const context = getContext();
121
+ return await getSession(data, context);
122
+ });
123
+ }
124
+
125
+ /**
126
+ * 准备向 session 发送的消息
127
+ * 注意:这只是准备消息数据,实际发送需要通过 MQTT sender 消息
128
+ */
129
+ async sessionsPrepareMessage(data: any): Promise<any> {
130
+ return wrapAdminCall(async () => {
131
+ const context = getContext();
132
+ return await prepareMessage(data, context);
133
+ });
134
+ }
135
+
136
+ /**
137
+ * 附加 skill 到消息
138
+ */
139
+ async sessionsAttachSkill(data: any): Promise<any> {
140
+ return wrapAdminCall(async () => {
141
+ const context = getContext();
142
+ return await attachSkill(data, context);
143
+ });
144
+ }
145
+
146
+ /**
147
+ * 获取 models 配置
148
+ */
149
+ async modelsGet(data: any): Promise<any> {
150
+ return wrapAdminCall(async () => {
151
+ const context = getContext();
152
+ return await getModels(data, context);
153
+ });
154
+ }
155
+
156
+ /**
157
+ * 更新 models 配置
158
+ */
159
+ async modelsUpdate(data: any): Promise<any> {
160
+ return wrapAdminCall(async () => {
161
+ const context = getContext();
162
+ return await updateModels(data, context);
163
+ });
164
+ }
165
+
166
+ /**
167
+ * 获取 usage summary
168
+ */
169
+ async usageSummary(data: any): Promise<any> {
170
+ return wrapAdminCall(async () => {
171
+ const context = getContext();
172
+ return await getUsageSummary(data, context);
173
+ });
174
+ }
175
+
176
+ /**
177
+ * 获取 usage page summary
178
+ */
179
+ async usagePageSummary(data: any): Promise<any> {
180
+ return wrapAdminCall(async () => {
181
+ const context = getContext();
182
+ return await getUsagePageSummary(data, context);
183
+ });
184
+ }
185
+
186
+ /**
187
+ * 获取 usage timeseries
188
+ */
189
+ async usageTimeseries(data: any): Promise<any> {
190
+ return wrapAdminCall(async () => {
191
+ const context = getContext();
192
+ return await getUsageTimeseries(data, context);
193
+ });
194
+ }
195
+
196
+ /**
197
+ * 获取 usage breakdown
198
+ */
199
+ async usageBreakdown(data: any): Promise<any> {
200
+ return wrapAdminCall(async () => {
201
+ const context = getContext();
202
+ return await getUsageBreakdown(data, context);
203
+ });
204
+ }
205
+
206
+ /**
207
+ * 列出已安装的 skills
208
+ */
209
+ async skillsListInstalled(data: any): Promise<any> {
210
+ return wrapAdminCall(async () => {
211
+ const context = getContext();
212
+ return await listInstalledSkills(data, context);
213
+ });
214
+ }
215
+
216
+ /**
217
+ * 从 npm 安装 skill
218
+ */
219
+ async skillsInstallFromNpm(data: any): Promise<any> {
220
+ return wrapAdminCall(async () => {
221
+ const context = getContext();
222
+ return await installSkillFromNpm(data, context);
223
+ });
224
+ }
225
+
226
+ async skillsSearchClawHub(data: any): Promise<any> {
227
+ return wrapAdminCall(async () => {
228
+ const context = getContext();
229
+ return await searchClawHubSkills(data, context);
230
+ });
231
+ }
232
+
233
+ async skillsInstallFromClawHub(data: any): Promise<any> {
234
+ return wrapAdminCall(async () => {
235
+ const context = getContext();
236
+ return await installSkillFromClawHub(data, context);
237
+ });
238
+ }
239
+
240
+ async skillsUpdateFromClawHub(data: any): Promise<any> {
241
+ return wrapAdminCall(async () => {
242
+ const context = getContext();
243
+ return await updateSkillFromClawHub(data, context);
244
+ });
245
+ }
246
+
247
+ /**
248
+ * 获取已安装 skill 详情
249
+ */
250
+ async skillsGetInstalled(data: any): Promise<any> {
251
+ return wrapAdminCall(async () => {
252
+ const context = getContext();
253
+ const slug = typeof data?.slug === 'string' ? data.slug : null;
254
+ if (!slug) {
255
+ throw new Error('Missing required parameter: slug');
256
+ }
257
+
258
+ const result = await listInstalledSkills({}, context) as any;
259
+ const items = Array.isArray(result?.items) ? result.items : [];
260
+ const skill = items.find((item: any) => item && typeof item === 'object' && item.slug === slug);
261
+ if (!skill) {
262
+ throw new Error(`Skill not installed: ${slug}`);
263
+ }
264
+ return skill;
265
+ });
266
+ }
267
+
268
+ /**
269
+ * 卸载 skill
270
+ */
271
+ async skillsUninstall(data: any): Promise<any> {
272
+ return wrapAdminCall(async () => {
273
+ const context = getContext();
274
+ return await uninstallSkill(data, context);
275
+ });
276
+ }
277
+
278
+ /**
279
+ * 启用或停用 skill
280
+ */
281
+ async skillsToggle(data: any): Promise<any> {
282
+ return wrapAdminCall(async () => {
283
+ const context = getContext();
284
+ return await toggleSkill(data, context);
285
+ });
286
+ }
287
+
288
+ /**
289
+ * 列出 memory 文件
290
+ */
291
+ async memoryListFiles(data: any): Promise<any> {
292
+ return wrapAdminCall(async () => {
293
+ const context = getContext();
294
+ return await listMemoryFiles(data, context);
295
+ });
296
+ }
297
+
298
+ /**
299
+ * 获取 memory 文件内容
300
+ */
301
+ async memoryGetFile(data: any): Promise<any> {
302
+ return wrapAdminCall(async () => {
303
+ const context = getContext();
304
+ return await getMemoryFile(data, context);
305
+ });
306
+ }
307
+
308
+ /**
309
+ * 备份 memory
310
+ */
311
+ async memoryBackup(data: any): Promise<any> {
312
+ return wrapAdminCall(async () => {
313
+ const context = getContext();
314
+ return await backupMemory(data, context);
315
+ });
316
+ }
317
+
318
+ /**
319
+ * 导出 memory zip
320
+ */
321
+ async memoryExportZip(data: any): Promise<any> {
322
+ return wrapAdminCall(async () => {
323
+ const context = getContext();
324
+ return await exportMemoryZip(data, context);
325
+ });
326
+ }
327
+
328
+ async memoryGetPresignedPost(data: any): Promise<any> {
329
+ return wrapAdminCall(async () => {
330
+ const context = getContext();
331
+ return await getMemoryPresignedPost(data, context);
332
+ });
333
+ }
334
+
335
+ async memoryCreateBackupRecord(data: any): Promise<any> {
336
+ return wrapAdminCall(async () => {
337
+ const context = getContext();
338
+ return await createMemoryBackupRecord(data, context);
339
+ });
340
+ }
341
+
342
+ /**
343
+ * 导入 memory zip
344
+ */
345
+ async memoryImportZip(data: any): Promise<any> {
346
+ return wrapAdminCall(async () => {
347
+ const context = getContext();
348
+ return await importMemoryZip(data, context);
349
+ });
350
+ }
351
+
352
+ /**
353
+ * 重启 OpenClaw Gateway
354
+ */
355
+ async systemRestart(data: any): Promise<any> {
356
+ return wrapAdminCall(async () => {
357
+ const context = getContext();
358
+ return await restart(data, context);
359
+ });
360
+ }
361
+
362
+ /**
363
+ * 停止 OpenClaw Gateway
364
+ */
365
+ async systemStop(data: any): Promise<any> {
366
+ return wrapAdminCall(async () => {
367
+ const context = getContext();
368
+ return await stop(data, context);
369
+ });
370
+ }
371
+
372
+ /**
373
+ * 运行诊断并自动修复
374
+ */
375
+ async systemDoctorFix(data: any): Promise<any> {
376
+ return wrapAdminCall(async () => {
377
+ const context = getContext();
378
+ return await doctorFix(data, context);
379
+ });
380
+ }
381
+
382
+ /**
383
+ * 获取最近100条日志
384
+ */
385
+ async systemLogs(data: any): Promise<any> {
386
+ return wrapAdminCall(async () => {
387
+ const context = getContext();
388
+ return await logs(data, context);
389
+ });
390
+ }
391
+
392
+ /**
393
+ * 示例方法:处理 status 类型的消息
394
+ */
395
+ async status(data: any): Promise<any> {
396
+ return {
397
+ status: "running",
398
+ uptime: process.uptime(),
399
+ memory: process.memoryUsage(),
400
+ };
401
+ }
402
+
403
+ /**
404
+ * 示例方法:处理 echo 类型的消息
405
+ */
406
+ async echo(data: any): Promise<any> {
407
+ return {
408
+ echoed: true,
409
+ data: data,
410
+ };
411
+ }
412
+ }
413
+
414
+ // 导出单例实例
415
+ export const messageHandler = new MessageHandler();
@@ -0,0 +1,84 @@
1
+ {
2
+ "id": "rol-websocket-channel",
3
+ "name": "WebSocket Channel",
4
+ "version": "1.0.0",
5
+ "description": "Unified plugin providing MQTT Channel and Admin Bridge capabilities for OpenClaw management",
6
+ "channels": ["rol-websocket-channel"],
7
+ "cli": ["admin-bridge"],
8
+ "configSchema": {
9
+ "type": "object",
10
+ "additionalProperties": false,
11
+ "properties": {
12
+ "channels": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "properties": {
16
+ "rol-websocket-channel": {
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "enabled": {
21
+ "type": "boolean"
22
+ },
23
+ "config": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "properties": {
27
+ "enabled": {
28
+ "type": "boolean"
29
+ },
30
+ "mqttUrl": {
31
+ "type": "string"
32
+ },
33
+ "mqttTopic": {
34
+ "type": "string"
35
+ },
36
+ "groupPolicy": {
37
+ "type": "string",
38
+ "enum": ["pairing", "allowlist", "open", "disabled"]
39
+ }
40
+ },
41
+ "required": ["mqttUrl"]
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ },
49
+
50
+ "uiHints": {
51
+ "channels": {
52
+ "label": "Channels"
53
+ },
54
+ "channels.rol-websocket-channel": {
55
+ "label": "WebSocket Channel"
56
+ },
57
+ "channels.rol-websocket-channel.enabled": {
58
+ "label": "Enabled",
59
+ "description": "Enable MQTT Channel"
60
+ },
61
+ "channels.rol-websocket-channel.config": {
62
+ "label": "Configuration",
63
+ "description": "MQTT/WebSocket connection configuration"
64
+ },
65
+ "channels.rol-websocket-channel.config.enabled": {
66
+ "label": "Enabled",
67
+ "description": "Enable this configuration"
68
+ },
69
+ "channels.rol-websocket-channel.config.mqttUrl": {
70
+ "label": "MQTT Broker URL",
71
+ "placeholder": "ws://192.168.1.23:8083/mqtt",
72
+ "help": "MQTT broker WebSocket URL (e.g., ws://192.168.1.23:8083/mqtt)"
73
+ },
74
+ "channels.rol-websocket-channel.config.mqttTopic": {
75
+ "label": "MQTT Topic",
76
+ "placeholder": "announcement/tester",
77
+ "help": "MQTT topic to subscribe/publish"
78
+ },
79
+ "channels.rol-websocket-channel.config.groupPolicy": {
80
+ "label": "Group Policy",
81
+ "description": "Message policy for group chats"
82
+ }
83
+ }
84
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "rol-websocket-channel",
3
+ "version": "1.0.0",
4
+ "description": "Unified OpenClaw plugin: MQTT Channel + Admin Bridge for remote management",
5
+ "license": "MIT",
6
+ "author": "nixgnehc",
7
+ "type": "module",
8
+ "keywords": [
9
+ "openclaw",
10
+ "mqtt",
11
+ "websocket",
12
+ "admin-bridge",
13
+ "plugin",
14
+ "channel"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "dev": "tsx watch index.ts"
19
+ },
20
+ "dependencies": {
21
+ "mqtt": "^5.0.3"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.0.0",
25
+ "typescript": "^5.0.0"
26
+ },
27
+ "openclaw": {
28
+ "extensions": [
29
+ "./index.ts"
30
+ ]
31
+ },
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ }
35
+ }
package/readme.md ADDED
@@ -0,0 +1,32 @@
1
+
2
+
3
+ ```
4
+ openclaw plugins install "c:\Users\admin\Desktop\openclaw-demo\rol-websocket-channel" --force
5
+
6
+ topic
7
+ announcement/tester
8
+
9
+ # 发送
10
+ # receiver/sender/*
11
+ {
12
+ "type":"echo",
13
+ "trace_id":"11233",
14
+ "source": "default",
15
+ "data": {
16
+ "content": "你好c",
17
+ "attachments": []
18
+ }
19
+ }
20
+
21
+ # 接收
22
+ {"type":"receiver","trace_id":"11233","source":"system","success":true,"timestamp":1776152983607,"data":{"echoed":true,"data":{"content":"你好c","attachments":[]}}}
23
+
24
+ ```
25
+
26
+
27
+
28
+ ```
29
+ npm login
30
+ npm publish
31
+
32
+ ```
@@ -0,0 +1,60 @@
1
+ import { getOpenClawRoot, getProjectRoot } from './lib/paths.ts';
2
+ import { getMethod } from './methods/index.ts';
3
+ import {
4
+ failure,
5
+ JsonRpcException,
6
+ JSON_RPC_ERRORS,
7
+ parseRequest,
8
+ serialize,
9
+ success
10
+ } from './jsonrpc.ts';
11
+ import type { JsonRpcId } from './types.ts';
12
+
13
+ async function main(): Promise<void> {
14
+ const raw = await readStdin();
15
+ const request = parseRequest(raw);
16
+ const context = {
17
+ projectRoot: getProjectRoot(),
18
+ openclawRoot: getOpenClawRoot()
19
+ };
20
+
21
+ try {
22
+ const handler = getMethod(request.method);
23
+ const result = await handler(request.params, context);
24
+ process.stdout.write(serialize(success(normalizeId(request.id), result)));
25
+ } catch (error) {
26
+ process.stdout.write(serialize(toErrorResponse(normalizeId(request.id), error)));
27
+ process.exitCode = 1;
28
+ }
29
+ }
30
+
31
+ async function readStdin(): Promise<string> {
32
+ const chunks: Buffer[] = [];
33
+
34
+ for await (const chunk of process.stdin) {
35
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
36
+ }
37
+
38
+ const raw = Buffer.concat(chunks).toString('utf8').trim();
39
+ if (!raw) {
40
+ throw new JsonRpcException(JSON_RPC_ERRORS.invalidRequest, 'Request body is empty');
41
+ }
42
+
43
+ return raw;
44
+ }
45
+
46
+ function normalizeId(value: JsonRpcId | undefined): JsonRpcId {
47
+ return value ?? null;
48
+ }
49
+
50
+ function toErrorResponse(id: JsonRpcId, error: unknown) {
51
+ if (error instanceof JsonRpcException) {
52
+ return failure(id, error.code, error.message, error.data);
53
+ }
54
+
55
+ return failure(id, JSON_RPC_ERRORS.internalError, 'Internal error', {
56
+ detail: error instanceof Error ? error.message : String(error)
57
+ });
58
+ }
59
+
60
+ await main();
@@ -0,0 +1,88 @@
1
+ import type {
2
+ JsonRpcFailure,
3
+ JsonRpcId,
4
+ JsonRpcRequest,
5
+ JsonRpcResponse,
6
+ JsonRpcSuccess
7
+ } from './types.ts';
8
+
9
+ export const JSON_RPC_VERSION = '2.0';
10
+
11
+ export const JSON_RPC_ERRORS = {
12
+ parseError: -32700,
13
+ invalidRequest: -32600,
14
+ methodNotFound: -32601,
15
+ invalidParams: -32602,
16
+ internalError: -32603
17
+ } as const;
18
+
19
+ export class JsonRpcException extends Error {
20
+ code: number;
21
+ data?: unknown;
22
+
23
+ constructor(code: number, message: string, data?: unknown) {
24
+ super(message);
25
+ this.code = code;
26
+ this.data = data;
27
+ }
28
+ }
29
+
30
+ export function parseRequest(raw: string): JsonRpcRequest {
31
+ let parsed: unknown;
32
+
33
+ try {
34
+ parsed = JSON.parse(raw);
35
+ } catch (error) {
36
+ throw new JsonRpcException(JSON_RPC_ERRORS.parseError, 'Parse error', {
37
+ detail: error instanceof Error ? error.message : String(error)
38
+ });
39
+ }
40
+
41
+ if (!isRequest(parsed)) {
42
+ throw new JsonRpcException(JSON_RPC_ERRORS.invalidRequest, 'Invalid Request');
43
+ }
44
+
45
+ return parsed;
46
+ }
47
+
48
+ export function success(id: JsonRpcId, result: unknown): JsonRpcSuccess {
49
+ return {
50
+ jsonrpc: JSON_RPC_VERSION,
51
+ id,
52
+ result: result as JsonRpcSuccess['result']
53
+ };
54
+ }
55
+
56
+ export function failure(
57
+ id: JsonRpcId,
58
+ code: number,
59
+ message: string,
60
+ data?: unknown
61
+ ): JsonRpcFailure {
62
+ return {
63
+ jsonrpc: JSON_RPC_VERSION,
64
+ id,
65
+ error: {
66
+ code,
67
+ message,
68
+ ...(data === undefined ? {} : { data: data as JsonRpcFailure['error']['data'] })
69
+ }
70
+ };
71
+ }
72
+
73
+ export function serialize(response: JsonRpcResponse): string {
74
+ return `${JSON.stringify(response, null, 2)}\n`;
75
+ }
76
+
77
+ function isRequest(value: unknown): value is JsonRpcRequest {
78
+ if (!value || typeof value !== 'object') {
79
+ return false;
80
+ }
81
+
82
+ const candidate = value as Record<string, unknown>;
83
+ return (
84
+ candidate.jsonrpc === JSON_RPC_VERSION &&
85
+ typeof candidate.method === 'string' &&
86
+ candidate.method.length > 0
87
+ );
88
+ }