omnimind-sdk 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.
- package/README.md +43 -0
- package/package.json +35 -0
- package/src/omnimind/client.js +258 -0
- package/src/omnimind/generated/omnimind_pb.d.ts +1241 -0
- package/src/omnimind/generated/omnimind_pb.js +3083 -0
- package/src/omnimind/index.js +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# OmniMind NodeJS SDK
|
|
2
|
+
|
|
3
|
+
NodeJS SDK for the OmniMind Protocol, supporting real-time voice streaming, text messaging, and hardware capability registration (MCP).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install omnimind-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
const { OmniMindClient } = require('omnimind-sdk');
|
|
15
|
+
|
|
16
|
+
const client = new OmniMindClient(
|
|
17
|
+
"ws://127.0.0.1:8082/v1/ws",
|
|
18
|
+
"YOUR_DEVICE_SN",
|
|
19
|
+
"YOUR_TOKEN"
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
client.setTextCallback((text, isFinal) => {
|
|
23
|
+
console.log(`[AI] ${text}`);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
client.setOnOpenCallback(() => {
|
|
27
|
+
console.log("Connected!");
|
|
28
|
+
client.sendText("Hello, what is the weather today?");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
client.connect();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **Protobuf-based**: Efficient binary communication.
|
|
37
|
+
- **Voice Streaming**: High-quality audio chunk transmission.
|
|
38
|
+
- **Tool Registration**: Easily register hardware functions for the AI to call.
|
|
39
|
+
- **Automatic Heartbeat**: Reliable long-term connection management.
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "omnimind-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OmniMind Protocol NodeJS SDK for AIoT Devices",
|
|
5
|
+
"main": "src/omnimind/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"src/omnimind"
|
|
8
|
+
],
|
|
9
|
+
"author": "XiaoRGEEK",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"omnimind",
|
|
13
|
+
"aiot",
|
|
14
|
+
"protocol",
|
|
15
|
+
"sdk",
|
|
16
|
+
"voice-streaming",
|
|
17
|
+
"protobuf"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/xiaorgeek/omnimind-sdk"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"ws": "^8.16.0",
|
|
25
|
+
"protobufjs": "^7.2.5"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/ws": "^8.5.10",
|
|
29
|
+
"protobufjs-cli": "^1.1.1"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"start": "node examples/client_example.js",
|
|
33
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
const WebSocket = require('ws');
|
|
2
|
+
const omnimind_pb = require('./generated/omnimind_pb');
|
|
3
|
+
|
|
4
|
+
// Using the root namespace generated by protobufjs
|
|
5
|
+
const omnimind = omnimind_pb.omnimind;
|
|
6
|
+
|
|
7
|
+
class OmniMindClient {
|
|
8
|
+
constructor(serverUrl, sn, token = "", config = {}) {
|
|
9
|
+
this.serverUrl = serverUrl;
|
|
10
|
+
this.sn = sn;
|
|
11
|
+
this.token = token;
|
|
12
|
+
this.inputAudioFormat = config.inputAudioFormat || null;
|
|
13
|
+
this.outputAudioFormat = config.outputAudioFormat || null;
|
|
14
|
+
this.enableTTS = config.enableTTS || false;
|
|
15
|
+
|
|
16
|
+
this.ws = null;
|
|
17
|
+
this.running = false;
|
|
18
|
+
this.heartbeatInterval = 30000;
|
|
19
|
+
this.heartbeatTimer = null;
|
|
20
|
+
|
|
21
|
+
// Callbacks
|
|
22
|
+
this.onVoiceCallback = null;
|
|
23
|
+
this.onTextCallback = null;
|
|
24
|
+
this.onToolCallCallback = null;
|
|
25
|
+
this.onOpenCallback = null;
|
|
26
|
+
this.onCloseCallback = null;
|
|
27
|
+
this.onErrorCallback = null;
|
|
28
|
+
|
|
29
|
+
this.toolHandlers = new Map();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setVoiceCallback(callback) {
|
|
33
|
+
this.onVoiceCallback = callback;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setTextCallback(callback) {
|
|
37
|
+
this.onTextCallback = callback;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setToolCallCallback(callback) {
|
|
41
|
+
this.onToolCallCallback = callback;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setOnOpenCallback(callback) {
|
|
45
|
+
this.onOpenCallback = callback;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setOnCloseCallback(callback) {
|
|
49
|
+
this.onCloseCallback = callback;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setOnErrorCallback(callback) {
|
|
53
|
+
this.onErrorCallback = callback;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
connect() {
|
|
57
|
+
this.ws = new WebSocket(this.serverUrl);
|
|
58
|
+
this.ws.binaryType = 'arraybuffer';
|
|
59
|
+
|
|
60
|
+
this.ws.on('open', () => {
|
|
61
|
+
console.log(`[Client ${this.sn}] Connected to ${this.serverUrl}`);
|
|
62
|
+
this.running = true;
|
|
63
|
+
this._handleOpen();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
this.ws.on('message', (data) => {
|
|
67
|
+
this._handleMessage(data);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.ws.on('error', (err) => {
|
|
71
|
+
console.error(`[Client ${this.sn}] WebSocket Error:`, err);
|
|
72
|
+
if (this.onErrorCallback) this.onErrorCallback(err);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.ws.on('close', (code, reason) => {
|
|
76
|
+
console.log(`[Client ${this.sn}] Connection closed. Code: ${code}, Reason: ${reason}`);
|
|
77
|
+
this.running = false;
|
|
78
|
+
this._stopHeartbeat();
|
|
79
|
+
if (this.onCloseCallback) this.onCloseCallback(code, reason);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
close() {
|
|
84
|
+
this.running = false;
|
|
85
|
+
this._stopHeartbeat();
|
|
86
|
+
if (this.ws) {
|
|
87
|
+
this.ws.close();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_handleOpen() {
|
|
92
|
+
// Send AUTH
|
|
93
|
+
const auth = {
|
|
94
|
+
firmwareVersion: "1.0.0",
|
|
95
|
+
token: this.token,
|
|
96
|
+
enableTts: this.enableTTS
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
if (this.inputAudioFormat) {
|
|
100
|
+
auth.inputAudioFormat = this.inputAudioFormat;
|
|
101
|
+
}
|
|
102
|
+
if (this.outputAudioFormat) {
|
|
103
|
+
auth.outputAudioFormat = this.outputAudioFormat;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const msg = {
|
|
107
|
+
sn: this.sn,
|
|
108
|
+
type: omnimind.MessageType.AUTH,
|
|
109
|
+
payload: omnimind.AuthPayload.encode(auth).finish(),
|
|
110
|
+
timestamp: Date.now()
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
this.ws.send(omnimind.OmniMessage.encode(msg).finish());
|
|
114
|
+
this._startHeartbeat();
|
|
115
|
+
if (this.onOpenCallback) this.onOpenCallback();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_handleMessage(data) {
|
|
119
|
+
try {
|
|
120
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
121
|
+
const msg = omnimind.OmniMessage.decode(buffer);
|
|
122
|
+
|
|
123
|
+
switch (msg.type) {
|
|
124
|
+
case omnimind.MessageType.VOICE_STREAM:
|
|
125
|
+
if (this.onVoiceCallback) this.onVoiceCallback(msg.payload);
|
|
126
|
+
break;
|
|
127
|
+
case omnimind.MessageType.TEXT_MSG:
|
|
128
|
+
const textMsg = omnimind.TextMessage.decode(msg.payload);
|
|
129
|
+
if (this.onTextCallback) this.onTextCallback(textMsg.content, textMsg.role);
|
|
130
|
+
break;
|
|
131
|
+
case omnimind.MessageType.TOOL_CALL:
|
|
132
|
+
this._handleToolCall(msg.payload);
|
|
133
|
+
break;
|
|
134
|
+
case omnimind.MessageType.PONG:
|
|
135
|
+
// Heartbeat response
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
// console.log(`[Client ${this.sn}] Message type: ${msg.type}`);
|
|
139
|
+
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error(`[Client ${this.sn}] Message decode error:`, err);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
_handleToolCall(payload) {
|
|
146
|
+
try {
|
|
147
|
+
const toolCall = omnimind.ToolCall.decode(payload);
|
|
148
|
+
const handler = this.toolHandlers.get(toolCall.functionName);
|
|
149
|
+
|
|
150
|
+
if (handler) {
|
|
151
|
+
const args = JSON.parse(toolCall.argsJson);
|
|
152
|
+
const result = handler(args);
|
|
153
|
+
|
|
154
|
+
const response = {
|
|
155
|
+
callId: toolCall.callId,
|
|
156
|
+
resultJson: JSON.stringify(result)
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const msg = {
|
|
160
|
+
sn: this.sn,
|
|
161
|
+
type: omnimind.MessageType.TOOL_RESP,
|
|
162
|
+
payload: omnimind.ToolResponse.encode(response).finish(),
|
|
163
|
+
timestamp: Date.now()
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
this.ws.send(omnimind.OmniMessage.encode(msg).finish());
|
|
167
|
+
} else if (this.onToolCallCallback) {
|
|
168
|
+
this.onToolCallCallback(toolCall.functionName, toolCall.argsJson, toolCall.callId);
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(`[Client ${this.sn}] Tool call error:`, err);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
sendVoice(audioData) {
|
|
176
|
+
if (!this.running || !this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
177
|
+
|
|
178
|
+
const msg = {
|
|
179
|
+
sn: this.sn,
|
|
180
|
+
type: omnimind.MessageType.VOICE_STREAM,
|
|
181
|
+
payload: audioData,
|
|
182
|
+
timestamp: Date.now()
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
this.ws.send(omnimind.OmniMessage.encode(msg).finish());
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
sendText(text) {
|
|
189
|
+
if (!this.running || !this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
190
|
+
|
|
191
|
+
const textMsg = {
|
|
192
|
+
content: text,
|
|
193
|
+
role: "user"
|
|
194
|
+
};
|
|
195
|
+
const msg = {
|
|
196
|
+
sn: this.sn,
|
|
197
|
+
type: omnimind.MessageType.TEXT_MSG,
|
|
198
|
+
payload: omnimind.TextMessage.encode(textMsg).finish(),
|
|
199
|
+
timestamp: Date.now()
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
this.ws.send(omnimind.OmniMessage.encode(msg).finish());
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
registerTool(name, description, argsSchema, handler) {
|
|
206
|
+
this.toolHandlers.set(name, handler);
|
|
207
|
+
// Note: In current protocol, tool registration can be done via MCP_REG
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
sendToolManifest(tools, events = []) {
|
|
211
|
+
if (!this.running || !this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
212
|
+
|
|
213
|
+
const manifest = {
|
|
214
|
+
tools: tools.map(t => ({
|
|
215
|
+
name: t.name,
|
|
216
|
+
description: t.description,
|
|
217
|
+
argsSchema: JSON.stringify(t.argsSchema || {})
|
|
218
|
+
})),
|
|
219
|
+
events: events.map(e => ({
|
|
220
|
+
name: e.name,
|
|
221
|
+
description: e.description,
|
|
222
|
+
payloadSchema: JSON.stringify(e.payloadSchema || {})
|
|
223
|
+
}))
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const msg = {
|
|
227
|
+
sn: this.sn,
|
|
228
|
+
type: omnimind.MessageType.MCP_REG,
|
|
229
|
+
payload: omnimind.ToolManifest.encode(manifest).finish(),
|
|
230
|
+
timestamp: Date.now()
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
this.ws.send(omnimind.OmniMessage.encode(msg).finish());
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
_startHeartbeat() {
|
|
237
|
+
this._stopHeartbeat();
|
|
238
|
+
this.heartbeatTimer = setInterval(() => {
|
|
239
|
+
if (this.running && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
240
|
+
const msg = {
|
|
241
|
+
sn: this.sn,
|
|
242
|
+
type: omnimind.MessageType.PING,
|
|
243
|
+
timestamp: Date.now()
|
|
244
|
+
};
|
|
245
|
+
this.ws.send(omnimind.OmniMessage.encode(msg).finish());
|
|
246
|
+
}
|
|
247
|
+
}, this.heartbeatInterval);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
_stopHeartbeat() {
|
|
251
|
+
if (this.heartbeatTimer) {
|
|
252
|
+
clearInterval(this.heartbeatTimer);
|
|
253
|
+
this.heartbeatTimer = null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
module.exports = OmniMindClient;
|