openclaw-stepfun 0.2.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/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +18 -0
- package/dist/src/accounts.d.ts +22 -0
- package/dist/src/accounts.js +43 -0
- package/dist/src/bot.d.ts +16 -0
- package/dist/src/bot.js +100 -0
- package/dist/src/channel.d.ts +4 -0
- package/dist/src/channel.js +206 -0
- package/dist/src/client.d.ts +51 -0
- package/dist/src/client.js +206 -0
- package/dist/src/monitor.d.ts +19 -0
- package/dist/src/monitor.js +153 -0
- package/dist/src/proto/capy/botauth/auth_common_pb.d.ts +82 -0
- package/dist/src/proto/capy/botauth/auth_common_pb.js +35 -0
- package/dist/src/proto/capy/botauth/botauth_connect.d.ts +118 -0
- package/dist/src/proto/capy/botauth/botauth_connect.js +118 -0
- package/dist/src/proto/capy/botauth/botauth_pb.d.ts +1065 -0
- package/dist/src/proto/capy/botauth/botauth_pb.js +348 -0
- package/dist/src/proto/capy/botauth/public_connect.d.ts +62 -0
- package/dist/src/proto/capy/botauth/public_connect.js +62 -0
- package/dist/src/proto/capy/botauth/public_pb.d.ts +254 -0
- package/dist/src/proto/capy/botauth/public_pb.js +105 -0
- package/dist/src/proto/capy/botmsg/botmsg_connect.d.ts +72 -0
- package/dist/src/proto/capy/botmsg/botmsg_connect.js +72 -0
- package/dist/src/proto/capy/botmsg/botmsg_pb.d.ts +426 -0
- package/dist/src/proto/capy/botmsg/botmsg_pb.js +160 -0
- package/dist/src/proto/capy/botway/ctrl_connect.d.ts +61 -0
- package/dist/src/proto/capy/botway/ctrl_connect.js +61 -0
- package/dist/src/proto/capy/botway/ctrl_pb.d.ts +267 -0
- package/dist/src/proto/capy/botway/ctrl_pb.js +120 -0
- package/dist/src/proto/capy/botway/stream_connect.d.ts +26 -0
- package/dist/src/proto/capy/botway/stream_connect.js +26 -0
- package/dist/src/proto/capy/botway/stream_pb.d.ts +495 -0
- package/dist/src/proto/capy/botway/stream_pb.js +165 -0
- package/dist/src/reply-dispatcher.d.ts +17 -0
- package/dist/src/reply-dispatcher.js +234 -0
- package/dist/src/runtime.d.ts +4 -0
- package/dist/src/runtime.js +11 -0
- package/dist/src/send.d.ts +19 -0
- package/dist/src/send.js +66 -0
- package/dist/src/types.d.ts +65 -0
- package/dist/src/types.js +2 -0
- package/dist/src/websocket/cacheEvent.d.ts +17 -0
- package/dist/src/websocket/cacheEvent.js +61 -0
- package/dist/src/websocket/connect.d.ts +32 -0
- package/dist/src/websocket/connect.js +79 -0
- package/dist/src/websocket/constant.d.ts +8 -0
- package/dist/src/websocket/constant.js +10 -0
- package/dist/src/websocket/eventBus.d.ts +15 -0
- package/dist/src/websocket/eventBus.js +46 -0
- package/dist/src/websocket/index.d.ts +117 -0
- package/dist/src/websocket/index.js +637 -0
- package/dist/src/websocket/service.d.ts +36 -0
- package/dist/src/websocket/service.js +4 -0
- package/dist/src/websocket/stream.d.ts +10 -0
- package/dist/src/websocket/stream.js +24 -0
- package/dist/src/websocket/streamConnect.d.ts +40 -0
- package/dist/src/websocket/streamConnect.js +48 -0
- package/openclaw.plugin.json +23 -0
- package/package.json +69 -0
- package/scripts/setup.mjs +381 -0
- package/scripts/switch-env.mjs +98 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { resolveStepfunAccount } from "./accounts.js";
|
|
2
|
+
import { getStepfunRuntime } from "./runtime.js";
|
|
3
|
+
import { sendMessageStepfun } from "./send.js";
|
|
4
|
+
/**
|
|
5
|
+
* Detect media type from URL and return placeholder message
|
|
6
|
+
*/
|
|
7
|
+
function getMediaPlaceholder(url) {
|
|
8
|
+
const lowerUrl = url.toLowerCase();
|
|
9
|
+
// Image extensions
|
|
10
|
+
if (/\.(jpg|jpeg|png|gif|webp|bmp|svg|ico)(\?.*)?$/i.test(lowerUrl)) {
|
|
11
|
+
return "【这是一条图片消息】";
|
|
12
|
+
}
|
|
13
|
+
// Video extensions
|
|
14
|
+
if (/\.(mp4|webm|ogg|mov|avi|mkv|flv|m3u8)(\?.*)?$/i.test(lowerUrl)) {
|
|
15
|
+
return "【这是一条视频消息】";
|
|
16
|
+
}
|
|
17
|
+
// Audio extensions
|
|
18
|
+
if (/\.(mp3|wav|aac|ogg|m4a|flac|wma)(\?.*)?$/i.test(lowerUrl)) {
|
|
19
|
+
return "【这是一条语音/音频消息】";
|
|
20
|
+
}
|
|
21
|
+
// Document extensions
|
|
22
|
+
if (/\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|zip|rar)(\?.*)?$/i.test(lowerUrl)) {
|
|
23
|
+
return "【这是一条文件消息】";
|
|
24
|
+
}
|
|
25
|
+
// Default for unknown media types
|
|
26
|
+
return "【这是一条媒体消息】";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* List of error messages that should not be sent to users
|
|
30
|
+
* These are typically API/connection errors from the model provider
|
|
31
|
+
* Note: These are exact matches to avoid false positives
|
|
32
|
+
*/
|
|
33
|
+
const BLOCKED_ERROR_MESSAGES = [
|
|
34
|
+
"Connection error.",
|
|
35
|
+
"APIConnectionError",
|
|
36
|
+
"Connection timeout",
|
|
37
|
+
"Network error",
|
|
38
|
+
"Request timeout",
|
|
39
|
+
"Service unavailable",
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* Maximum length for error messages
|
|
43
|
+
* API errors are typically short (< 50 chars), while normal responses explaining these terms would be longer
|
|
44
|
+
*/
|
|
45
|
+
const MAX_ERROR_LENGTH = 100;
|
|
46
|
+
/**
|
|
47
|
+
* Check if content is an error message that should not be sent to users
|
|
48
|
+
* Uses exact match for short messages to avoid false positives
|
|
49
|
+
*/
|
|
50
|
+
function isErrorContent(content) {
|
|
51
|
+
const trimmed = content.trim();
|
|
52
|
+
// Only check relatively short messages to avoid false positives
|
|
53
|
+
// Normal explanations of these terms would be longer
|
|
54
|
+
if (trimmed.length > MAX_ERROR_LENGTH) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
// Check for exact match or the content being mostly the error message
|
|
58
|
+
return BLOCKED_ERROR_MESSAGES.some((errorMsg) => {
|
|
59
|
+
// Exact match
|
|
60
|
+
if (trimmed === errorMsg) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
// Content starts with the error message (error followed by punctuation or whitespace)
|
|
64
|
+
if (trimmed.startsWith(errorMsg) && /^[\s.,;:!?]/.test(trimmed.slice(errorMsg.length))) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
// Content is just the error message with minimal additions (like error code)
|
|
68
|
+
if (trimmed.includes(errorMsg) && trimmed.length < errorMsg.length + 20) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build message content with media placeholders if media is present
|
|
76
|
+
*/
|
|
77
|
+
function buildMessageContent(payload) {
|
|
78
|
+
const text = payload.text ?? "";
|
|
79
|
+
const mediaUrls = [];
|
|
80
|
+
// Collect all media URLs
|
|
81
|
+
if (payload.mediaUrl) {
|
|
82
|
+
mediaUrls.push(payload.mediaUrl);
|
|
83
|
+
}
|
|
84
|
+
if (payload.mediaUrls && payload.mediaUrls.length > 0) {
|
|
85
|
+
mediaUrls.push(...payload.mediaUrls);
|
|
86
|
+
}
|
|
87
|
+
// Build media placeholders
|
|
88
|
+
const mediaPlaceholders = [];
|
|
89
|
+
for (const url of mediaUrls) {
|
|
90
|
+
const placeholder = getMediaPlaceholder(url);
|
|
91
|
+
// Avoid duplicate placeholders
|
|
92
|
+
if (!mediaPlaceholders.includes(placeholder)) {
|
|
93
|
+
mediaPlaceholders.push(placeholder);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Combine text and media placeholders
|
|
97
|
+
const parts = [];
|
|
98
|
+
if (text.trim()) {
|
|
99
|
+
parts.push(text);
|
|
100
|
+
}
|
|
101
|
+
if (mediaPlaceholders.length > 0) {
|
|
102
|
+
parts.push(...mediaPlaceholders);
|
|
103
|
+
}
|
|
104
|
+
return parts.join("\n");
|
|
105
|
+
}
|
|
106
|
+
export function createStepfunReplyDispatcher(params) {
|
|
107
|
+
const core = getStepfunRuntime();
|
|
108
|
+
const { cfg, agentId, runtime, chatSessionId, accountId } = params;
|
|
109
|
+
const account = resolveStepfunAccount({ cfg, accountId });
|
|
110
|
+
// Buffer for accumulating streaming content
|
|
111
|
+
let contentBuffer = "";
|
|
112
|
+
let isProcessing = false;
|
|
113
|
+
let hasPendingFlush = false;
|
|
114
|
+
// Flush the buffer and send accumulated content
|
|
115
|
+
const flushBuffer = async () => {
|
|
116
|
+
if (isProcessing || !contentBuffer.trim()) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
isProcessing = true;
|
|
120
|
+
hasPendingFlush = false;
|
|
121
|
+
const textToSend = contentBuffer;
|
|
122
|
+
contentBuffer = ""; // Clear buffer before async operation
|
|
123
|
+
runtime.log?.(`stepfun[${account.accountId}] flush: sending accumulated text=${textToSend.slice(0, 100)}...`);
|
|
124
|
+
const outboundContent = {
|
|
125
|
+
type: "message",
|
|
126
|
+
content: textToSend,
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
const success = await sendMessageStepfun({
|
|
130
|
+
cfg,
|
|
131
|
+
chatSessionId,
|
|
132
|
+
content: outboundContent,
|
|
133
|
+
accountId,
|
|
134
|
+
runtime: { log: runtime.log, error: runtime.error },
|
|
135
|
+
});
|
|
136
|
+
if (!success) {
|
|
137
|
+
// Restore buffer on error so we don't lose content
|
|
138
|
+
contentBuffer = textToSend + contentBuffer;
|
|
139
|
+
runtime.error?.(`stepfun[${account.accountId}] flush failed: message could not be sent`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
const errorMsg = err instanceof Error ? err.message : JSON.stringify(err);
|
|
144
|
+
runtime.error?.(`stepfun[${account.accountId}] flush failed: ${errorMsg}`);
|
|
145
|
+
// Restore buffer on error so we don't lose content
|
|
146
|
+
contentBuffer = textToSend + contentBuffer;
|
|
147
|
+
// Don't rethrow - errors should not propagate to the gateway
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
isProcessing = false;
|
|
151
|
+
// Check if new content arrived while sending, and flush it
|
|
152
|
+
if (hasPendingFlush && contentBuffer.trim()) {
|
|
153
|
+
runtime.log?.(`stepfun[${account.accountId}] flush: more content pending, flushing again`);
|
|
154
|
+
setImmediate(() => scheduleFlush());
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
// Schedule buffer flush - ensures all messages are sent even if they arrive during send
|
|
159
|
+
const scheduleFlush = () => {
|
|
160
|
+
if (isProcessing) {
|
|
161
|
+
// Mark that we have pending content to flush after current send completes
|
|
162
|
+
hasPendingFlush = true;
|
|
163
|
+
runtime.log?.(`stepfun[${account.accountId}] scheduleFlush: send in progress, marking pending`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
flushBuffer().catch((err) => {
|
|
167
|
+
runtime.error?.(`stepfun[${account.accountId}] flush error: ${String(err)}`);
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
|
|
171
|
+
deliver: async (payload, info) => {
|
|
172
|
+
const { kind } = info;
|
|
173
|
+
// Build message content including media placeholders
|
|
174
|
+
const messageContent = buildMessageContent(payload);
|
|
175
|
+
runtime.log?.(`stepfun[${account.accountId}] deliver called: kind=${kind}, text=${messageContent.slice(0, 100)}, bufferBefore=${contentBuffer.slice(0, 100)}`);
|
|
176
|
+
if (!messageContent.trim()) {
|
|
177
|
+
runtime.log?.(`stepfun[${account.accountId}] deliver: empty content, skipping`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// Check if content is an error message that should not be sent to users
|
|
181
|
+
if (isErrorContent(messageContent)) {
|
|
182
|
+
runtime.error?.(`stepfun[${account.accountId}] deliver: blocked error message from being sent to user: "${messageContent}"`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// For tool results, send immediately without buffering
|
|
186
|
+
if (kind === "tool") {
|
|
187
|
+
runtime.log?.(`stepfun[${account.accountId}] tool result: sending immediately`);
|
|
188
|
+
// Add tool call placeholder prefix
|
|
189
|
+
const toolContent = messageContent.trim()
|
|
190
|
+
? `【这是一条工具调用消息】\n${messageContent}`
|
|
191
|
+
: "【这是一条工具调用消息】";
|
|
192
|
+
contentBuffer += toolContent;
|
|
193
|
+
scheduleFlush();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// For block replies, just accumulate without sending
|
|
197
|
+
// Block replies are streaming chunks that should not be sent individually
|
|
198
|
+
if (kind === "block") {
|
|
199
|
+
contentBuffer += messageContent;
|
|
200
|
+
runtime.log?.(`stepfun[${account.accountId}] block reply: accumulated, buffer=${contentBuffer.slice(0, 100)}...`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// For final replies, flush all accumulated content
|
|
204
|
+
if (kind === "final") {
|
|
205
|
+
contentBuffer += messageContent;
|
|
206
|
+
runtime.log?.(`stepfun[${account.accountId}] final reply: flushing accumulated content, total=${contentBuffer.slice(0, 100)}...`);
|
|
207
|
+
scheduleFlush();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
onError: (err, info) => {
|
|
212
|
+
const errorMsg = err instanceof Error ? err.message : JSON.stringify(err);
|
|
213
|
+
runtime.error?.(`stepfun[${account.accountId}] ${info.kind} reply failed: ${errorMsg}`);
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
// Override markDispatchIdle to flush remaining content when done
|
|
217
|
+
const originalMarkDispatchIdle = markDispatchIdle;
|
|
218
|
+
const wrappedMarkDispatchIdle = () => {
|
|
219
|
+
runtime.log?.(`stepfun[${account.accountId}] markDispatchIdle called, flushing remaining content`);
|
|
220
|
+
// Flush any remaining content
|
|
221
|
+
if (contentBuffer.trim()) {
|
|
222
|
+
flushBuffer().catch((err) => {
|
|
223
|
+
runtime.error?.(`stepfun[${account.accountId}] final flush error: ${String(err)}`);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
originalMarkDispatchIdle();
|
|
227
|
+
};
|
|
228
|
+
return {
|
|
229
|
+
dispatcher,
|
|
230
|
+
replyOptions,
|
|
231
|
+
markDispatchIdle: wrappedMarkDispatchIdle,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=reply-dispatcher.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
let runtime = null;
|
|
2
|
+
export function setStepfunRuntime(next) {
|
|
3
|
+
runtime = next;
|
|
4
|
+
}
|
|
5
|
+
export function getStepfunRuntime() {
|
|
6
|
+
if (!runtime) {
|
|
7
|
+
throw new Error("Stepfun runtime not initialized");
|
|
8
|
+
}
|
|
9
|
+
return runtime;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { OutboundMessageContent } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Send a message to Stepfun gateway using Protobuf format.
|
|
5
|
+
* The content is wrapped in ChatMessage and sent via BotMsg service.
|
|
6
|
+
* Note: Returns boolean indicating success/failure instead of throwing,
|
|
7
|
+
* to ensure errors don't propagate to the OpenClaw gateway.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sendMessageStepfun(params: {
|
|
10
|
+
cfg: OpenClawConfig;
|
|
11
|
+
chatSessionId: string;
|
|
12
|
+
content: OutboundMessageContent;
|
|
13
|
+
accountId?: string;
|
|
14
|
+
runtime?: {
|
|
15
|
+
log?: (msg: string) => void;
|
|
16
|
+
error?: (msg: string) => void;
|
|
17
|
+
};
|
|
18
|
+
}): Promise<boolean>;
|
|
19
|
+
//# sourceMappingURL=send.d.ts.map
|
package/dist/src/send.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { resolveStepfunAccount } from "./accounts.js";
|
|
2
|
+
import { BotMsgSocketClient } from "./websocket/service.js";
|
|
3
|
+
import { ChatMessage, ChatMessageContent, MessageType, BotType, SendMessagesRequest, } from "./proto/capy/botmsg/botmsg_pb.js";
|
|
4
|
+
import { getSocket } from "./websocket/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* Send a message to Stepfun gateway using Protobuf format.
|
|
7
|
+
* The content is wrapped in ChatMessage and sent via BotMsg service.
|
|
8
|
+
* Note: Returns boolean indicating success/failure instead of throwing,
|
|
9
|
+
* to ensure errors don't propagate to the OpenClaw gateway.
|
|
10
|
+
*/
|
|
11
|
+
export async function sendMessageStepfun(params) {
|
|
12
|
+
const { cfg, chatSessionId, content, accountId, runtime } = params;
|
|
13
|
+
const log = runtime?.log ?? console.log;
|
|
14
|
+
const error = runtime?.error ?? console.error;
|
|
15
|
+
const account = resolveStepfunAccount({ cfg, accountId });
|
|
16
|
+
if (!account.configured) {
|
|
17
|
+
error(`[stepfun] sendMessageStepfun: account not configured`);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
// Ensure socket is initialized with auth from config
|
|
21
|
+
const socket = getSocket();
|
|
22
|
+
const token = account.config.appToken;
|
|
23
|
+
const appId = account.config.appId;
|
|
24
|
+
if (!token || !appId) {
|
|
25
|
+
error(`[stepfun] sendMessageStepfun: appId and appToken are required`);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
socket.setAuth(token, appId);
|
|
29
|
+
// Convert content to string
|
|
30
|
+
let contentStr;
|
|
31
|
+
if (typeof content.content === "string") {
|
|
32
|
+
contentStr = content.content;
|
|
33
|
+
}
|
|
34
|
+
else if (content.event) {
|
|
35
|
+
// Handle streaming events
|
|
36
|
+
contentStr = JSON.stringify(content.event);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
contentStr = JSON.stringify(content);
|
|
40
|
+
}
|
|
41
|
+
// Build Protobuf message
|
|
42
|
+
const chatMessage = new ChatMessage({
|
|
43
|
+
appId: appId,
|
|
44
|
+
sessionId: chatSessionId,
|
|
45
|
+
senderUid: "0", // Robot sends as 0
|
|
46
|
+
msgType: MessageType.MessageType_BOT_MSG,
|
|
47
|
+
botType: BotType.BotType_OPENCLAW,
|
|
48
|
+
content: new ChatMessageContent({
|
|
49
|
+
content: contentStr,
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
const request = new SendMessagesRequest({
|
|
53
|
+
message: chatMessage,
|
|
54
|
+
});
|
|
55
|
+
// Send via BotMsg service
|
|
56
|
+
try {
|
|
57
|
+
await BotMsgSocketClient.sendMessages(request);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
const errorMsg = err instanceof Error ? err.message : JSON.stringify(err);
|
|
62
|
+
error(`[stepfun] Failed to send message: ${errorMsg}`);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=send.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stepfun channel configuration in clawdbot config.
|
|
3
|
+
*/
|
|
4
|
+
export type StepfunConfig = {
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
/** App ID for bot authentication */
|
|
7
|
+
appId?: string;
|
|
8
|
+
/** App token for WebSocket connection authentication */
|
|
9
|
+
appToken?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Resolved stepfun account with merged configuration.
|
|
13
|
+
*/
|
|
14
|
+
export type ResolvedStepfunAccount = {
|
|
15
|
+
accountId: string;
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
configured: boolean;
|
|
18
|
+
name?: string;
|
|
19
|
+
config: StepfunConfig;
|
|
20
|
+
};
|
|
21
|
+
export type InboundMessageType = "text";
|
|
22
|
+
export type InboundMessageConfig = {};
|
|
23
|
+
/**
|
|
24
|
+
* Inbound message from Stepfun gateway.
|
|
25
|
+
*/
|
|
26
|
+
export type StepfunInboundMessage = {
|
|
27
|
+
chatSessionId: string;
|
|
28
|
+
type?: InboundMessageType;
|
|
29
|
+
config?: InboundMessageConfig;
|
|
30
|
+
appId: string;
|
|
31
|
+
messageId: string;
|
|
32
|
+
userId: string;
|
|
33
|
+
userName?: string;
|
|
34
|
+
content: string;
|
|
35
|
+
timestamp?: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Outbound message to Stepfun gateway (stepfun format).
|
|
39
|
+
*/
|
|
40
|
+
export type StepfunOutboundMessage = {
|
|
41
|
+
chatSessionId: string;
|
|
42
|
+
role: "assistant";
|
|
43
|
+
content: string;
|
|
44
|
+
timestamp?: number;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Message context for internal processing.
|
|
48
|
+
*/
|
|
49
|
+
export type StepfunMessageContext = {
|
|
50
|
+
chatSessionId: string;
|
|
51
|
+
messageId: string;
|
|
52
|
+
userId: string;
|
|
53
|
+
userName?: string;
|
|
54
|
+
content: string;
|
|
55
|
+
};
|
|
56
|
+
export type OutboundMessageContent = {
|
|
57
|
+
type: "message" | "stream";
|
|
58
|
+
content?: string;
|
|
59
|
+
messageId?: string;
|
|
60
|
+
event?: {
|
|
61
|
+
eventName: "start" | "text" | "done";
|
|
62
|
+
data: string;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type CacheStatusType = Record<string, unknown>;
|
|
2
|
+
type WatchListType = Record<string, (() => void)[]>;
|
|
3
|
+
export declare class CacheEvent {
|
|
4
|
+
eventList: any[];
|
|
5
|
+
eventKeys: CacheStatusType;
|
|
6
|
+
watchList: WatchListType;
|
|
7
|
+
cacheStatus: CacheStatusType;
|
|
8
|
+
once(key: string): boolean;
|
|
9
|
+
set(key: string, data: unknown): void;
|
|
10
|
+
get(key: string): unknown;
|
|
11
|
+
do(cb: () => void | Promise<void>, cond: string[], cache?: boolean): void;
|
|
12
|
+
complete(key: string): void;
|
|
13
|
+
clear(): void;
|
|
14
|
+
}
|
|
15
|
+
export declare const globalCache: CacheEvent;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=cacheEvent.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export class CacheEvent {
|
|
2
|
+
eventList = [];
|
|
3
|
+
eventKeys = {};
|
|
4
|
+
watchList = {};
|
|
5
|
+
cacheStatus = {};
|
|
6
|
+
once(key) {
|
|
7
|
+
if (!this.cacheStatus[key]) {
|
|
8
|
+
this.cacheStatus[key] = 1;
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
set(key, data) {
|
|
14
|
+
this.cacheStatus[key] = data;
|
|
15
|
+
}
|
|
16
|
+
get(key) {
|
|
17
|
+
return this.cacheStatus[key];
|
|
18
|
+
}
|
|
19
|
+
do(cb, cond, cache = true) {
|
|
20
|
+
const canDo = cond?.every((i) => this.eventKeys[i]) || !cond;
|
|
21
|
+
if (!canDo) {
|
|
22
|
+
if (cache) {
|
|
23
|
+
cond.forEach((key) => {
|
|
24
|
+
if (!this.watchList[key]) {
|
|
25
|
+
this.watchList[key] = [];
|
|
26
|
+
}
|
|
27
|
+
this.watchList[key].push(() => this.do(cb, cond, false));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
try {
|
|
33
|
+
const result = cb();
|
|
34
|
+
// Handle async callbacks - catch any promise rejections
|
|
35
|
+
if (result instanceof Promise) {
|
|
36
|
+
result.catch((err) => {
|
|
37
|
+
console.error("[cacheEvent] async callback error:", err);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
console.error("[cacheEvent] callback error:", err);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
complete(key) {
|
|
47
|
+
this.eventKeys[key] = 1;
|
|
48
|
+
if (this.watchList[key]) {
|
|
49
|
+
this.watchList[key].forEach((e) => e());
|
|
50
|
+
this.watchList[key] = [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
clear() {
|
|
54
|
+
this.eventList = [];
|
|
55
|
+
this.eventKeys = {};
|
|
56
|
+
this.watchList = {};
|
|
57
|
+
this.cacheStatus = {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export const globalCache = new CacheEvent();
|
|
61
|
+
//# sourceMappingURL=cacheEvent.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MethodInfo, MethodInfoBiDiStreaming, MethodInfoClientStreaming, MethodInfoServerStreaming, MethodInfoUnary, PartialMessage, ServiceType } from '@bufbuild/protobuf';
|
|
2
|
+
import { ErrorRes } from './streamConnect.js';
|
|
3
|
+
export interface SocketCallbacks<O, T = any> {
|
|
4
|
+
onStart?: (params: {
|
|
5
|
+
msgId: string;
|
|
6
|
+
method: string;
|
|
7
|
+
}) => void;
|
|
8
|
+
onData?: (params: {
|
|
9
|
+
msgId: string;
|
|
10
|
+
method: string;
|
|
11
|
+
data: O;
|
|
12
|
+
throwError: (error: ErrorRes) => void;
|
|
13
|
+
resolve: (data: T) => void;
|
|
14
|
+
close: () => void;
|
|
15
|
+
}) => void;
|
|
16
|
+
onError?: (params: {
|
|
17
|
+
msgId: string;
|
|
18
|
+
method: string;
|
|
19
|
+
error: ErrorRes | Uint8Array;
|
|
20
|
+
}) => void;
|
|
21
|
+
throwError?: (error: ErrorRes) => void;
|
|
22
|
+
onClose?: () => void;
|
|
23
|
+
}
|
|
24
|
+
type PromiseMethod<I, O> = (request: I | Promise<I>, callbacks?: SocketCallbacks<O>, once?: boolean) => Promise<O>;
|
|
25
|
+
export type PromiseClient<T extends ServiceType> = {
|
|
26
|
+
[P in keyof T['methods']]: T['methods'][P] extends MethodInfoUnary<infer I, infer O> ? PromiseMethod<PartialMessage<I>, O> : T['methods'][P] extends MethodInfoServerStreaming<infer I, infer O> ? PromiseMethod<PartialMessage<I>, O> : T['methods'][P] extends MethodInfoClientStreaming<infer I, infer O> ? PromiseMethod<PartialMessage<I>, O> : T['methods'][P] extends MethodInfoBiDiStreaming<infer I, infer O> ? PromiseMethod<PartialMessage<I>, O> : never;
|
|
27
|
+
};
|
|
28
|
+
export declare function createSocketConnect<T extends ServiceType>(module: string, Service: T): PromiseClient<T>;
|
|
29
|
+
export declare function removeMsgListener(msgId: string): void;
|
|
30
|
+
export declare function createPromiseClient(module: string, methodInfo: MethodInfo): (payload: import("@bufbuild/protobuf").MessageType<import("@bufbuild/protobuf").AnyMessage>, callbacks?: SocketCallbacks<import("@bufbuild/protobuf").MessageType<import("@bufbuild/protobuf").AnyMessage>>, once?: boolean) => Promise<unknown>;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { makeAnyClient } from '@connectrpc/connect';
|
|
2
|
+
import { getSocket } from './index.js';
|
|
3
|
+
import { ErrorRes } from './streamConnect.js';
|
|
4
|
+
export function createSocketConnect(module, Service) {
|
|
5
|
+
return makeAnyClient(Service, (methodInfo) => {
|
|
6
|
+
return createPromiseClient(module, methodInfo);
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export function removeMsgListener(msgId) {
|
|
10
|
+
const socket = getSocket();
|
|
11
|
+
socket?.events.off(msgId);
|
|
12
|
+
socket?.events.off(`error:${msgId}`);
|
|
13
|
+
socket._requestStreams.delete(msgId);
|
|
14
|
+
}
|
|
15
|
+
export function createPromiseClient(module, methodInfo) {
|
|
16
|
+
const { I, O } = methodInfo;
|
|
17
|
+
return (payload, callbacks, once = true) => {
|
|
18
|
+
const command = methodInfo.name;
|
|
19
|
+
const socket = getSocket();
|
|
20
|
+
const msgId = socket.send({
|
|
21
|
+
module,
|
|
22
|
+
command,
|
|
23
|
+
body: new I(payload).toBinary(),
|
|
24
|
+
type: methodInfo.kind,
|
|
25
|
+
});
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const throwError = (error) => {
|
|
28
|
+
reject(error);
|
|
29
|
+
};
|
|
30
|
+
const socket = getSocket();
|
|
31
|
+
const closeHandler = () => {
|
|
32
|
+
removeMsgListener(msgId);
|
|
33
|
+
callbacks?.onClose?.();
|
|
34
|
+
};
|
|
35
|
+
socket?.on(msgId, (e) => {
|
|
36
|
+
if (!e || e instanceof ErrorRes) {
|
|
37
|
+
const error = e instanceof ErrorRes ? e : new ErrorRes({ code: 1, reason: 'data error' });
|
|
38
|
+
if (callbacks?.onError) {
|
|
39
|
+
callbacks.onError({ msgId, method: command, error });
|
|
40
|
+
}
|
|
41
|
+
reject(error);
|
|
42
|
+
removeMsgListener(msgId);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const data = O.fromBinary(e);
|
|
46
|
+
if (callbacks?.onData) {
|
|
47
|
+
callbacks.onData({
|
|
48
|
+
msgId,
|
|
49
|
+
method: command,
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
data,
|
|
52
|
+
throwError,
|
|
53
|
+
resolve,
|
|
54
|
+
close: closeHandler,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
resolve({ ...data, traceid: msgId });
|
|
59
|
+
}
|
|
60
|
+
if (once) {
|
|
61
|
+
removeMsgListener(msgId);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
let rejected = 0;
|
|
65
|
+
socket?.on(`error:${msgId}`, (error) => {
|
|
66
|
+
if (callbacks?.onError) {
|
|
67
|
+
callbacks.onError({ msgId, method: command, error });
|
|
68
|
+
}
|
|
69
|
+
if (!rejected) {
|
|
70
|
+
reject(error);
|
|
71
|
+
rejected = 1;
|
|
72
|
+
}
|
|
73
|
+
removeMsgListener(msgId);
|
|
74
|
+
});
|
|
75
|
+
// todo 超时逻辑
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=connect.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type Fn = {
|
|
2
|
+
fn: (args: object | null | undefined) => void;
|
|
3
|
+
once?: boolean;
|
|
4
|
+
};
|
|
5
|
+
type EventType<K extends string> = Record<K, Fn[]>;
|
|
6
|
+
export declare class Event<K extends string> {
|
|
7
|
+
eventList: EventType<K>;
|
|
8
|
+
on(key: K, fn: (args: any) => void, once?: boolean): void;
|
|
9
|
+
emit(key: K, args?: object): boolean;
|
|
10
|
+
off(key: K, fn?: (args: any) => void): void;
|
|
11
|
+
checkListenersCount(key: K): number;
|
|
12
|
+
}
|
|
13
|
+
export declare const commonEventBus: Event<string>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=eventBus.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class Event {
|
|
2
|
+
eventList = {};
|
|
3
|
+
on(key, fn, once) {
|
|
4
|
+
if (!this.eventList[key])
|
|
5
|
+
this.eventList[key] = [];
|
|
6
|
+
// console.log('on-------', Object.keys(this.eventList));
|
|
7
|
+
this.eventList[key].push({
|
|
8
|
+
fn,
|
|
9
|
+
once,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
emit(key, args) {
|
|
13
|
+
if (this.eventList?.[key]) {
|
|
14
|
+
this.eventList[key].forEach((fn) => {
|
|
15
|
+
try {
|
|
16
|
+
fn.fn(args);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
console.error(`[eventBus] Error in listener for "${key}":`, err);
|
|
20
|
+
// Continue to next listener even if one fails
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
this.eventList[key] = this.eventList[key].filter((item) => !item.once);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
off(key, fn) {
|
|
31
|
+
if (fn) {
|
|
32
|
+
const index = this.eventList[key]?.findIndex((item) => item.fn === fn);
|
|
33
|
+
if (index !== undefined && index !== -1) {
|
|
34
|
+
this.eventList[key]?.splice(index, 1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
this.eventList[key] = [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
checkListenersCount(key) {
|
|
42
|
+
return this.eventList[key]?.length;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export const commonEventBus = new Event();
|
|
46
|
+
//# sourceMappingURL=eventBus.js.map
|