@hedgehog2026/ciwei-ai 1.0.2
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/index.ts +25 -0
- package/openclaw.plugin.json +16 -0
- package/package.json +47 -0
- package/src/channel.ts +389 -0
- package/src/runtime.ts +14 -0
- package/src/types.ts +35 -0
- package/tsconfig.json +16 -0
package/index.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
3
|
+
import { ciweiAIPlugin } from "./src/channel";
|
|
4
|
+
import { setCiweiAIRuntime } from "./src/runtime";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ciweiAI
|
|
8
|
+
*/
|
|
9
|
+
const plugin: any = {
|
|
10
|
+
id: "ciweiAI",
|
|
11
|
+
name: "ciwei AI Channel",
|
|
12
|
+
description: "Custom WebSocket-based channel for Ciwei AI",
|
|
13
|
+
configSchema: emptyPluginConfigSchema(),
|
|
14
|
+
|
|
15
|
+
register(api: OpenClawPluginApi): void {
|
|
16
|
+
console.log("[ciweiAI] Registering plugin...");
|
|
17
|
+
setCiweiAIRuntime(api.runtime);
|
|
18
|
+
|
|
19
|
+
api.registerChannel({ plugin: ciweiAIPlugin });
|
|
20
|
+
|
|
21
|
+
// api.registerGatewayMethod("ciweiai.some.method", async (...) => { ... });
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default plugin;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "ciweiAI",
|
|
3
|
+
"name": "ciweiAI",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Custom WebSocket-based channel plugin for Ciwei AI app",
|
|
6
|
+
"entry": "./index.ts",
|
|
7
|
+
"type": "channel",
|
|
8
|
+
"channels": [
|
|
9
|
+
"ciweiAI"
|
|
10
|
+
],
|
|
11
|
+
"configSchema": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"properties": {}
|
|
15
|
+
}
|
|
16
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hedgehog2026/ciwei-ai",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "ciwei AI WebSocket channel for OpenClaw",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bot",
|
|
7
|
+
"channel",
|
|
8
|
+
"clawdbot",
|
|
9
|
+
"openclaw",
|
|
10
|
+
"stream",
|
|
11
|
+
"ciweiAI"
|
|
12
|
+
],
|
|
13
|
+
"repository": "github:hedgehog2026/ciwei-ai",
|
|
14
|
+
"main": "index.ts",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"author": "OpenClaw Community",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"ws": "^8.16.0",
|
|
20
|
+
"zod": "^3.23.0"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"openclaw": ">=2026.2.13"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^20.11.0",
|
|
27
|
+
"@types/ws": "^8.5.10",
|
|
28
|
+
"typescript": "^5.3.3"
|
|
29
|
+
},
|
|
30
|
+
"openclaw": {
|
|
31
|
+
"extensions": [
|
|
32
|
+
"./index.ts"
|
|
33
|
+
],
|
|
34
|
+
"channels": [
|
|
35
|
+
"ciweiAI"
|
|
36
|
+
],
|
|
37
|
+
"installDependencies": true,
|
|
38
|
+
"channel": {
|
|
39
|
+
"id": "ciweiAI",
|
|
40
|
+
"label": "ciwei AI",
|
|
41
|
+
"selectionLabel": "ciwei AI",
|
|
42
|
+
"docsPath": "/channels/ciweiai",
|
|
43
|
+
"blurb": "ciwei AI Custom Relay Channel.",
|
|
44
|
+
"order": 100
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/channel.ts
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { WebSocket } from "ws";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { buildChannelConfigSchema } from "openclaw/plugin-sdk";
|
|
4
|
+
import {
|
|
5
|
+
ChannelPlugin,
|
|
6
|
+
OpenClawConfig,
|
|
7
|
+
ChannelGatewayContext,
|
|
8
|
+
ChannelAccountSnapshot,
|
|
9
|
+
ChannelStatusIssue
|
|
10
|
+
} from "openclaw/plugin-sdk";
|
|
11
|
+
import { getCiweiAIRuntime } from "./runtime";
|
|
12
|
+
import type {
|
|
13
|
+
CiweiAIResolvedAccount,
|
|
14
|
+
RelayInboundMessage,
|
|
15
|
+
RelayReplyMessage
|
|
16
|
+
} from "./types";
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Ciwei AI Schema
|
|
21
|
+
* accountId , token
|
|
22
|
+
*/
|
|
23
|
+
const CiweiAIConfigSchema = z.object({
|
|
24
|
+
token: z.string().describe("Relay server access token").optional(),
|
|
25
|
+
accountId: z.string().describe("Relay server account ID").optional(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Unix
|
|
30
|
+
*/
|
|
31
|
+
function getCurrentTimestamp(): number {
|
|
32
|
+
return Date.now();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Ciwei AI Channel Plugin
|
|
37
|
+
* accountId + token
|
|
38
|
+
*
|
|
39
|
+
* "ciweiAI": {
|
|
40
|
+
* "enabled": true,
|
|
41
|
+
* "accountId": "13333333333",
|
|
42
|
+
* "token": "your-token"
|
|
43
|
+
* }
|
|
44
|
+
*/
|
|
45
|
+
export const ciweiAIPlugin: ChannelPlugin<CiweiAIResolvedAccount> = {
|
|
46
|
+
id: "ciweiAI",
|
|
47
|
+
|
|
48
|
+
meta: {
|
|
49
|
+
id: "ciweiAI",
|
|
50
|
+
label: "Ciwei AI",
|
|
51
|
+
selectionLabel: "Ciwei AI",
|
|
52
|
+
blurb: "Custom WebSocket relay channel for Ciwei AI",
|
|
53
|
+
docsPath: "/channels/ciweiai",
|
|
54
|
+
order: 100,
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
configSchema: buildChannelConfigSchema(CiweiAIConfigSchema),
|
|
58
|
+
|
|
59
|
+
capabilities: {
|
|
60
|
+
chatTypes: ["direct"],
|
|
61
|
+
media: false,
|
|
62
|
+
reactions: false,
|
|
63
|
+
threads: false,
|
|
64
|
+
blockStreaming: true,
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
config: {
|
|
68
|
+
listAccountIds: (cfg: OpenClawConfig): string[] => {
|
|
69
|
+
const channelConfig = cfg.channels?.ciweiAI;
|
|
70
|
+
if (!channelConfig) return [];
|
|
71
|
+
|
|
72
|
+
if (channelConfig.accountId) {
|
|
73
|
+
return [channelConfig.accountId];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (channelConfig.accounts) {
|
|
77
|
+
if (Array.isArray(channelConfig.accounts)) {
|
|
78
|
+
return channelConfig.accounts.map((a: any) => a.accountId);
|
|
79
|
+
}
|
|
80
|
+
return Object.keys(channelConfig.accounts);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return ["default"];
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
resolveAccount: (cfg: OpenClawConfig, accountId?: string | null): CiweiAIResolvedAccount => {
|
|
87
|
+
const channelConfig = cfg.channels?.ciweiAI;
|
|
88
|
+
|
|
89
|
+
if (channelConfig?.accountId) {
|
|
90
|
+
const id = channelConfig.accountId;
|
|
91
|
+
const token = channelConfig.token;
|
|
92
|
+
return {
|
|
93
|
+
accountId: id,
|
|
94
|
+
config: { token },
|
|
95
|
+
enabled: true,
|
|
96
|
+
configured: Boolean(token),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const id = accountId || "default";
|
|
101
|
+
let accountInfo: any;
|
|
102
|
+
|
|
103
|
+
if (Array.isArray(channelConfig?.accounts)) {
|
|
104
|
+
accountInfo = channelConfig.accounts.find((a: any) => a.accountId === id);
|
|
105
|
+
} else {
|
|
106
|
+
accountInfo = channelConfig?.accounts?.[id];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let finalConfig: any = {};
|
|
110
|
+
if (typeof accountInfo === "string") {
|
|
111
|
+
finalConfig = { token: accountInfo };
|
|
112
|
+
} else if (accountInfo && typeof accountInfo === "object") {
|
|
113
|
+
finalConfig = accountInfo.config || accountInfo;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
accountId: id,
|
|
118
|
+
config: finalConfig,
|
|
119
|
+
enabled: accountInfo?.enabled !== false,
|
|
120
|
+
configured: Boolean(finalConfig.token),
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
defaultAccountId: (cfg: OpenClawConfig): string => {
|
|
125
|
+
const channelConfig = (cfg as any)?.channels?.ciweiAI;
|
|
126
|
+
return channelConfig?.accountId || "default";
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
gateway: {
|
|
131
|
+
startAccount: async (ctx: ChannelGatewayContext<CiweiAIResolvedAccount>) => {
|
|
132
|
+
const { account, cfg, log, abortSignal } = ctx;
|
|
133
|
+
const rt = getCiweiAIRuntime();
|
|
134
|
+
const accountId = String(account.accountId);
|
|
135
|
+
const token = account.config.token || "";
|
|
136
|
+
const relayUrl = `ws://47.110.64.126/relay?id=${accountId}&token=${token}&role=provider`;
|
|
137
|
+
|
|
138
|
+
let ws: WebSocket | null = null;
|
|
139
|
+
let isClosing = false;
|
|
140
|
+
let heartbeatInterval: NodeJS.Timeout | null = null;
|
|
141
|
+
|
|
142
|
+
// 用于阻塞 startAccount 的生命周期
|
|
143
|
+
let resolveStop: (value: void | PromiseLike<void>) => void;
|
|
144
|
+
const stopPromise = new Promise<void>((resolve) => {
|
|
145
|
+
resolveStop = resolve;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const stopClient = () => {
|
|
149
|
+
if (isClosing) return;
|
|
150
|
+
isClosing = true;
|
|
151
|
+
|
|
152
|
+
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
153
|
+
log?.info?.(`[ciweiAI][${accountId}] Stopping gateway...`);
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
ws?.close();
|
|
157
|
+
} catch (err: any) {
|
|
158
|
+
log?.warn?.(`[ciweiAI][${accountId}] Error during close: ${err.message}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
ctx.setStatus({
|
|
162
|
+
...ctx.getStatus(),
|
|
163
|
+
running: false,
|
|
164
|
+
lastStopAt: getCurrentTimestamp(),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
resolveStop();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const connect = () => {
|
|
171
|
+
if (isClosing) return;
|
|
172
|
+
|
|
173
|
+
log?.info?.(`[ciweiAI][${accountId}] Connecting to relay: ${relayUrl}`);
|
|
174
|
+
ws = new WebSocket(relayUrl);
|
|
175
|
+
|
|
176
|
+
ws.on("open", () => {
|
|
177
|
+
log?.info?.(`[ciweiAI][${accountId}] Connected successfully.`);
|
|
178
|
+
ctx.setStatus({
|
|
179
|
+
...ctx.getStatus(),
|
|
180
|
+
running: true,
|
|
181
|
+
lastStartAt: getCurrentTimestamp(),
|
|
182
|
+
lastEventAt: getCurrentTimestamp(),
|
|
183
|
+
lastError: null,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
187
|
+
heartbeatInterval = setInterval(() => {
|
|
188
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
189
|
+
ws.ping();
|
|
190
|
+
}
|
|
191
|
+
}, 30000);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const sentLengthMap: Record<string, number> = {};
|
|
195
|
+
|
|
196
|
+
ws.on("message", async (data) => {
|
|
197
|
+
ctx.setStatus({
|
|
198
|
+
...ctx.getStatus(),
|
|
199
|
+
lastEventAt: getCurrentTimestamp(),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const appPayload: RelayInboundMessage = JSON.parse(data.toString());
|
|
204
|
+
const { from, text, chatId, id } = appPayload;
|
|
205
|
+
|
|
206
|
+
if (!text) return;
|
|
207
|
+
|
|
208
|
+
const context = rt.channel.reply.finalizeInboundContext({
|
|
209
|
+
Body: text,
|
|
210
|
+
From: from,
|
|
211
|
+
To: chatId,
|
|
212
|
+
SessionKey: `ws:${chatId}`,
|
|
213
|
+
AccountId: String(accountId),
|
|
214
|
+
Provider: "ciweiAI",
|
|
215
|
+
MessageSid: id,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Force streaming response as per OpenClaw docs
|
|
219
|
+
const streamingCfg = JSON.parse(JSON.stringify(cfg));
|
|
220
|
+
|
|
221
|
+
// 1. Enable block streaming for this specific channel
|
|
222
|
+
streamingCfg.channels = streamingCfg.channels || {};
|
|
223
|
+
streamingCfg.channels.ciweiAI = streamingCfg.channels.ciweiAI || {};
|
|
224
|
+
streamingCfg.channels.ciweiAI.blockStreaming = true;
|
|
225
|
+
// Also enable preview streaming as fallback
|
|
226
|
+
streamingCfg.channels.ciweiAI.streaming = "block";
|
|
227
|
+
|
|
228
|
+
// 2. Configure agents to emit chunks as they arrive instead of bundling
|
|
229
|
+
streamingCfg.agents = streamingCfg.agents || {};
|
|
230
|
+
streamingCfg.agents.defaults = streamingCfg.agents.defaults || {};
|
|
231
|
+
streamingCfg.agents.defaults.blockStreamingDefault = "on";
|
|
232
|
+
streamingCfg.agents.defaults.blockStreamingBreak = "text_end";
|
|
233
|
+
|
|
234
|
+
await rt.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
235
|
+
ctx: context,
|
|
236
|
+
cfg,
|
|
237
|
+
dispatcherOptions: {
|
|
238
|
+
deliver: async (payload: any) => {
|
|
239
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
240
|
+
ws.send(JSON.stringify({
|
|
241
|
+
type: "reply",
|
|
242
|
+
to: from,
|
|
243
|
+
chatId: chatId,
|
|
244
|
+
replyTo: id,
|
|
245
|
+
isFinal: true
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
delete sentLengthMap[chatId];
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
replyOptions: {
|
|
252
|
+
onPartialReply: (payload: any) => {
|
|
253
|
+
if (payload.text && ws?.readyState === WebSocket.OPEN) {
|
|
254
|
+
const prev = sentLengthMap[chatId] || 0;
|
|
255
|
+
const delta = payload.text.slice(prev);
|
|
256
|
+
sentLengthMap[chatId] = payload.text.length;
|
|
257
|
+
if (delta) {
|
|
258
|
+
ws.send(JSON.stringify({
|
|
259
|
+
type: "reply",
|
|
260
|
+
to: from,
|
|
261
|
+
chatId: chatId,
|
|
262
|
+
text: delta,
|
|
263
|
+
replyTo: id,
|
|
264
|
+
isPartial: true
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
onReasoningStream: (payload: any) => {
|
|
270
|
+
if (payload.text && ws?.readyState === WebSocket.OPEN) {
|
|
271
|
+
const prev = sentLengthMap[chatId] || 0;
|
|
272
|
+
const delta = payload.text.slice(prev);
|
|
273
|
+
sentLengthMap[chatId] = payload.text.length;
|
|
274
|
+
if (delta) {
|
|
275
|
+
ws.send(JSON.stringify({
|
|
276
|
+
type: "reply",
|
|
277
|
+
to: from,
|
|
278
|
+
chatId: chatId,
|
|
279
|
+
text: delta,
|
|
280
|
+
replyTo: id,
|
|
281
|
+
isPartial: true
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
} catch (err: any) {
|
|
289
|
+
log?.error?.(`[ciweiAI][${accountId}] Dispatch error: ${err.message}`);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
ws.on("error", (err) => {
|
|
294
|
+
log?.error?.(`[ciweiAI][${accountId}] WebSocket error: ${err.message}`);
|
|
295
|
+
ctx.setStatus({
|
|
296
|
+
...ctx.getStatus(),
|
|
297
|
+
lastError: `Connection error: ${err.message}`,
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
ws.on("close", (code, reason) => {
|
|
302
|
+
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
303
|
+
if (!isClosing) {
|
|
304
|
+
log?.warn?.(`[ciweiAI][${accountId}] Connection dropped (code=${code}). Retrying in 5s...`);
|
|
305
|
+
ctx.setStatus({
|
|
306
|
+
...ctx.getStatus(),
|
|
307
|
+
running: false,
|
|
308
|
+
});
|
|
309
|
+
setTimeout(connect, 5000);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
connect();
|
|
315
|
+
|
|
316
|
+
abortSignal?.addEventListener("abort", () => {
|
|
317
|
+
log?.info?.(`[ciweiAI][${accountId}] Abort signal received`);
|
|
318
|
+
stopClient();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
await stopPromise;
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
stop: () => {
|
|
325
|
+
stopClient();
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
status: {
|
|
332
|
+
defaultRuntime: {
|
|
333
|
+
accountId: "default",
|
|
334
|
+
running: false,
|
|
335
|
+
lastEventAt: null,
|
|
336
|
+
lastStartAt: null,
|
|
337
|
+
lastStopAt: null,
|
|
338
|
+
lastError: null,
|
|
339
|
+
},
|
|
340
|
+
collectStatusIssues: (accounts: ChannelAccountSnapshot[]): ChannelStatusIssue[] => {
|
|
341
|
+
return accounts.flatMap((account) => {
|
|
342
|
+
if (!account.configured) {
|
|
343
|
+
return [
|
|
344
|
+
{
|
|
345
|
+
channel: "ciweiAI",
|
|
346
|
+
accountId: account.accountId,
|
|
347
|
+
kind: "config" as const,
|
|
348
|
+
message: "Account not configured (missing relay token)",
|
|
349
|
+
},
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
return [];
|
|
353
|
+
});
|
|
354
|
+
},
|
|
355
|
+
buildChannelSummary: ({ snapshot }: { snapshot: ChannelAccountSnapshot }) => ({
|
|
356
|
+
configured: snapshot?.configured ?? false,
|
|
357
|
+
running: snapshot?.running ?? false,
|
|
358
|
+
lastStartAt: snapshot?.lastStartAt ?? null,
|
|
359
|
+
lastStopAt: snapshot?.lastStopAt ?? null,
|
|
360
|
+
lastError: snapshot?.lastError ?? null,
|
|
361
|
+
}),
|
|
362
|
+
probeAccount: async ({ account }: { account: CiweiAIResolvedAccount }) => {
|
|
363
|
+
if (!account.configured || !account.config?.token) {
|
|
364
|
+
return { ok: false, error: "Token not configured" };
|
|
365
|
+
}
|
|
366
|
+
return { ok: true, details: { relay: "47.110.64.126" } };
|
|
367
|
+
},
|
|
368
|
+
buildAccountSnapshot: ({ account, runtime, snapshot, probe }: {
|
|
369
|
+
account: CiweiAIResolvedAccount,
|
|
370
|
+
runtime?: ChannelAccountSnapshot,
|
|
371
|
+
snapshot?: ChannelAccountSnapshot,
|
|
372
|
+
probe?: any
|
|
373
|
+
}): ChannelAccountSnapshot => {
|
|
374
|
+
const running = runtime?.running ?? snapshot?.running ?? false;
|
|
375
|
+
return {
|
|
376
|
+
...snapshot,
|
|
377
|
+
accountId: account.accountId,
|
|
378
|
+
enabled: account.enabled,
|
|
379
|
+
configured: account.configured,
|
|
380
|
+
running,
|
|
381
|
+
lastEventAt: runtime?.lastEventAt ?? snapshot?.lastEventAt ?? null,
|
|
382
|
+
lastStartAt: runtime?.lastStartAt ?? snapshot?.lastStartAt ?? null,
|
|
383
|
+
lastStopAt: runtime?.lastStopAt ?? snapshot?.lastStopAt ?? null,
|
|
384
|
+
lastError: runtime?.lastError ?? snapshot?.lastError ?? null,
|
|
385
|
+
probe,
|
|
386
|
+
};
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
};
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
let runtime: PluginRuntime | null = null;
|
|
4
|
+
|
|
5
|
+
export function setCiweiAIRuntime(next: PluginRuntime): void {
|
|
6
|
+
runtime = next;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getCiweiAIRuntime(): PluginRuntime {
|
|
10
|
+
if (!runtime) {
|
|
11
|
+
throw new Error("CiweiAI runtime not initialized");
|
|
12
|
+
}
|
|
13
|
+
return runtime;
|
|
14
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ciwei AI
|
|
3
|
+
*/
|
|
4
|
+
export interface CiweiAIResolvedAccount {
|
|
5
|
+
accountId: string;
|
|
6
|
+
config: {
|
|
7
|
+
token?: string;
|
|
8
|
+
};
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
configured: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Inbound
|
|
15
|
+
*/
|
|
16
|
+
export interface RelayInboundMessage {
|
|
17
|
+
type?: string;
|
|
18
|
+
from: string;
|
|
19
|
+
text: string;
|
|
20
|
+
chatId: string;
|
|
21
|
+
id: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Outbound
|
|
26
|
+
*/
|
|
27
|
+
export interface RelayReplyMessage {
|
|
28
|
+
type: "reply";
|
|
29
|
+
to: string;
|
|
30
|
+
chatId: string;
|
|
31
|
+
text: string;
|
|
32
|
+
replyTo: string;
|
|
33
|
+
isPartial?: boolean;
|
|
34
|
+
isFinal?: boolean;
|
|
35
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"baseUrl": ".",
|
|
10
|
+
"types": ["node"],
|
|
11
|
+
"paths": {
|
|
12
|
+
"openclaw/*": ["node_modules/openclaw/*"]
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"include": ["index.ts", "src/**/*.ts"]
|
|
16
|
+
}
|