@charivo/realtime-core 0.0.1
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 +252 -0
- package/dist/index.d.mts +134 -0
- package/dist/index.d.ts +134 -0
- package/dist/index.js +177 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +171 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Zeikar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# @charivo/realtime-core
|
|
2
|
+
|
|
3
|
+
Core Realtime API functionality with session management and event relay for low-latency voice conversations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ๐ **Session Management** - Start/stop Realtime API sessions
|
|
8
|
+
- ๐ก **Event Relay** - Bridge between Realtime client and Charivo event system
|
|
9
|
+
- ๐ **Lip-Sync Support** - Real-time RMS values for character mouth animation
|
|
10
|
+
- ๐ **Client Agnostic** - Works with any Realtime client implementation
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @charivo/realtime-core @charivo/core
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Basic Setup
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { createRealtimeManager } from "@charivo/realtime-core";
|
|
24
|
+
import { createOpenAIRealtimeClient } from "@charivo/realtime-client-openai";
|
|
25
|
+
|
|
26
|
+
// Create a Realtime client
|
|
27
|
+
const client = createOpenAIRealtimeClient({
|
|
28
|
+
apiEndpoint: "/api/realtime" // Your WebRTC handshake endpoint
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Wrap with RealtimeManager for state management
|
|
32
|
+
const realtimeManager = createRealtimeManager(client);
|
|
33
|
+
|
|
34
|
+
// Start Realtime session
|
|
35
|
+
await realtimeManager.startSession({
|
|
36
|
+
model: "gpt-4o-realtime-preview-2024-12-17",
|
|
37
|
+
voice: "verse"
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Send text message
|
|
41
|
+
await realtimeManager.sendMessage("Hello!");
|
|
42
|
+
|
|
43
|
+
// Stop session when done
|
|
44
|
+
await realtimeManager.stopSession();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### With Charivo Integration
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { Charivo } from "@charivo/core";
|
|
51
|
+
import { createRealtimeManager } from "@charivo/realtime-core";
|
|
52
|
+
import { createOpenAIRealtimeClient } from "@charivo/realtime-client-openai";
|
|
53
|
+
|
|
54
|
+
const charivo = new Charivo();
|
|
55
|
+
|
|
56
|
+
// Create and attach Realtime manager
|
|
57
|
+
const client = createOpenAIRealtimeClient({
|
|
58
|
+
apiEndpoint: "/api/realtime"
|
|
59
|
+
});
|
|
60
|
+
const realtimeManager = createRealtimeManager(client);
|
|
61
|
+
charivo.attachRealtime(realtimeManager);
|
|
62
|
+
|
|
63
|
+
// Start session
|
|
64
|
+
await realtimeManager.startSession({
|
|
65
|
+
model: "gpt-4o-realtime-preview-2024-12-17",
|
|
66
|
+
voice: "verse"
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Enable lip-sync on renderer
|
|
70
|
+
charivo.emit("tts:audio:start", { audioElement: new Audio() });
|
|
71
|
+
|
|
72
|
+
// Listen to events
|
|
73
|
+
charivo.on("realtime:text:delta", ({ text }) => {
|
|
74
|
+
console.log("Streaming text:", text);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
charivo.on("tts:lipsync:update", ({ rms }) => {
|
|
78
|
+
console.log("Lip-sync RMS:", rms);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Cleanup
|
|
82
|
+
await realtimeManager.stopSession();
|
|
83
|
+
charivo.emit("tts:audio:end", {});
|
|
84
|
+
charivo.detachRealtime();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Custom Realtime Client
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { RealtimeClient } from "@charivo/realtime-core";
|
|
91
|
+
import { createRealtimeManager } from "@charivo/realtime-core";
|
|
92
|
+
|
|
93
|
+
class MyRealtimeClient implements RealtimeClient {
|
|
94
|
+
async connect(): Promise<void> {
|
|
95
|
+
// Connect to your Realtime service
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async disconnect(): Promise<void> {
|
|
99
|
+
// Disconnect
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async sendText(text: string): Promise<void> {
|
|
103
|
+
// Send text message
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async sendAudio(audio: ArrayBuffer): Promise<void> {
|
|
107
|
+
// Send audio chunk
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onTextDelta(callback: (text: string) => void): void {
|
|
111
|
+
// Register text streaming callback
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
onAudioDelta(callback: (base64Audio: string) => void): void {
|
|
115
|
+
// Register audio streaming callback
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
onLipSyncUpdate?(callback: (rms: number) => void): void {
|
|
119
|
+
// Optional: Register direct RMS callback for WebRTC clients
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
onAudioDone(callback: () => void): void {
|
|
123
|
+
// Register audio end callback
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
onError(callback: (error: Error) => void): void {
|
|
127
|
+
// Register error callback
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const realtimeManager = createRealtimeManager(new MyRealtimeClient());
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## API Reference
|
|
135
|
+
|
|
136
|
+
### `RealtimeManager`
|
|
137
|
+
|
|
138
|
+
#### Methods
|
|
139
|
+
|
|
140
|
+
##### `setEventEmitter(eventEmitter)`
|
|
141
|
+
|
|
142
|
+
Connect to Charivo's event system.
|
|
143
|
+
|
|
144
|
+
**Parameters:**
|
|
145
|
+
- `eventEmitter` - Object with `emit(event, data)` method
|
|
146
|
+
|
|
147
|
+
##### `startSession(config)`
|
|
148
|
+
|
|
149
|
+
Start a Realtime session.
|
|
150
|
+
|
|
151
|
+
**Parameters:**
|
|
152
|
+
- `config.model` - Model name (e.g., "gpt-4o-realtime-preview-2024-12-17")
|
|
153
|
+
- `config.voice` - Voice name (e.g., "verse", "alloy", "echo")
|
|
154
|
+
|
|
155
|
+
**Returns:** `Promise<void>`
|
|
156
|
+
|
|
157
|
+
##### `stopSession()`
|
|
158
|
+
|
|
159
|
+
Stop the current Realtime session.
|
|
160
|
+
|
|
161
|
+
**Returns:** `Promise<void>`
|
|
162
|
+
|
|
163
|
+
##### `sendMessage(text)`
|
|
164
|
+
|
|
165
|
+
Send a text message to the Realtime API.
|
|
166
|
+
|
|
167
|
+
**Parameters:**
|
|
168
|
+
- `text` - Message text
|
|
169
|
+
|
|
170
|
+
**Returns:** `Promise<void>`
|
|
171
|
+
|
|
172
|
+
##### `sendAudioChunk(audio)`
|
|
173
|
+
|
|
174
|
+
Send an audio chunk (user's voice).
|
|
175
|
+
|
|
176
|
+
**Parameters:**
|
|
177
|
+
- `audio` - Audio data as ArrayBuffer
|
|
178
|
+
|
|
179
|
+
**Returns:** `Promise<void>`
|
|
180
|
+
|
|
181
|
+
### Events
|
|
182
|
+
|
|
183
|
+
When integrated with Charivo, the following events are emitted:
|
|
184
|
+
|
|
185
|
+
#### `realtime:text:delta`
|
|
186
|
+
|
|
187
|
+
Streamed text response from the AI.
|
|
188
|
+
|
|
189
|
+
**Payload:**
|
|
190
|
+
```typescript
|
|
191
|
+
{ text: string }
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### `tts:lipsync:update`
|
|
195
|
+
|
|
196
|
+
Real-time lip-sync RMS values (0.0 - 1.0).
|
|
197
|
+
|
|
198
|
+
**Payload:**
|
|
199
|
+
```typescript
|
|
200
|
+
{ rms: number }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### `tts:audio:end`
|
|
204
|
+
|
|
205
|
+
Audio playback finished.
|
|
206
|
+
|
|
207
|
+
**Payload:**
|
|
208
|
+
```typescript
|
|
209
|
+
{}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### `realtime:error`
|
|
213
|
+
|
|
214
|
+
Error occurred during Realtime session.
|
|
215
|
+
|
|
216
|
+
**Payload:**
|
|
217
|
+
```typescript
|
|
218
|
+
{ error: Error }
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Architecture
|
|
222
|
+
|
|
223
|
+
The Realtime Manager follows Charivo's **Manager Pattern**:
|
|
224
|
+
|
|
225
|
+
- **Stateful Manager** (`RealtimeManager`) - Manages session lifecycle and events
|
|
226
|
+
- **Stateless Client** (e.g., `OpenAIRealtimeClient`) - Handles WebRTC/WebSocket connection
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
โโโโโโโโโโโโโโโโโโโโโโโ
|
|
230
|
+
โ RealtimeManager โ โโ Stateful (session, events)
|
|
231
|
+
โโโโโโโโโโโโฌโโโโโโโโโโโ
|
|
232
|
+
โผ
|
|
233
|
+
โโโโโโโโโโโโโโโโโโโโโโโ
|
|
234
|
+
โ RealtimeClient โ โโ Stateless (WebRTC/WS)
|
|
235
|
+
โโโโโโโโโโโโโโโโโโโโโโโ
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Why This Design?
|
|
239
|
+
|
|
240
|
+
- **Separation of Concerns**: Manager handles state, client handles protocol
|
|
241
|
+
- **Testability**: Easy to mock clients for testing
|
|
242
|
+
- **Flexibility**: Swap clients without changing app code
|
|
243
|
+
- **Reusability**: Same client can be used in different contexts
|
|
244
|
+
|
|
245
|
+
## Related Packages
|
|
246
|
+
|
|
247
|
+
- [@charivo/realtime-client-openai](../realtime-client-openai) - OpenAI Realtime API client with WebRTC
|
|
248
|
+
- [@charivo/core](../core) - Core types and interfaces
|
|
249
|
+
|
|
250
|
+
## License
|
|
251
|
+
|
|
252
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Emotion } from '@charivo/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Realtime API Types
|
|
5
|
+
*/
|
|
6
|
+
interface RealtimeTool {
|
|
7
|
+
type: "function";
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
parameters: {
|
|
11
|
+
type: "object";
|
|
12
|
+
properties: Record<string, any>;
|
|
13
|
+
required?: string[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
interface RealtimeSessionConfig {
|
|
17
|
+
voice?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
instructions?: string;
|
|
20
|
+
temperature?: number;
|
|
21
|
+
maxTokens?: number;
|
|
22
|
+
tools?: RealtimeTool[];
|
|
23
|
+
tool_choice?: "auto" | "none" | "required";
|
|
24
|
+
}
|
|
25
|
+
interface RealtimeClient {
|
|
26
|
+
connect(config?: RealtimeSessionConfig): Promise<void>;
|
|
27
|
+
disconnect(): Promise<void>;
|
|
28
|
+
sendText(text: string): Promise<void>;
|
|
29
|
+
sendAudio(audio: ArrayBuffer): Promise<void>;
|
|
30
|
+
onTextDelta(callback: (text: string) => void): void;
|
|
31
|
+
onAudioDelta(callback: (base64Audio: string) => void): void;
|
|
32
|
+
onLipSyncUpdate?(callback: (rms: number) => void): void;
|
|
33
|
+
onAudioDone(callback: () => void): void;
|
|
34
|
+
onToolCall?(callback: (name: string, args: any) => void): void;
|
|
35
|
+
onError(callback: (error: Error) => void): void;
|
|
36
|
+
}
|
|
37
|
+
interface RealtimeManager {
|
|
38
|
+
startSession(config: RealtimeSessionConfig): Promise<void>;
|
|
39
|
+
stopSession(): Promise<void>;
|
|
40
|
+
sendMessage(text: string): Promise<void>;
|
|
41
|
+
sendAudioChunk(audio: ArrayBuffer): Promise<void>;
|
|
42
|
+
setEventEmitter(eventEmitter: {
|
|
43
|
+
emit: (event: string, data: any) => void;
|
|
44
|
+
}): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Realtime Manager - Realtime API ์ธ์
๊ด๋ฆฌ
|
|
49
|
+
*
|
|
50
|
+
* ์ญํ :
|
|
51
|
+
* - Realtime ํด๋ผ์ด์ธํธ ๊ด๋ฆฌ (WebRTC)
|
|
52
|
+
* - ๋ฆฝ์ฑํฌ ์ด๋ฒคํธ ์ค๊ณ
|
|
53
|
+
* - ํ
์คํธ ์คํธ๋ฆฌ๋ฐ ์ฒ๋ฆฌ
|
|
54
|
+
*
|
|
55
|
+
* Note: WebRTC ํด๋ผ์ด์ธํธ๋ ์ค๋์ค๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ฏ๋ก
|
|
56
|
+
* ์ด Manager๋ ์ด๋ฒคํธ ์ค๊ณ์ ์ง์คํฉ๋๋ค.
|
|
57
|
+
*/
|
|
58
|
+
declare class RealtimeManagerImpl implements RealtimeManager {
|
|
59
|
+
private client;
|
|
60
|
+
private eventEmitter?;
|
|
61
|
+
private isSessionActive;
|
|
62
|
+
private isAudioPlaybackActive;
|
|
63
|
+
constructor(client: RealtimeClient);
|
|
64
|
+
/**
|
|
65
|
+
* ์ด๋ฒคํธ ๋ฐ์ ์ ์ค์
|
|
66
|
+
*/
|
|
67
|
+
setEventEmitter(eventEmitter: {
|
|
68
|
+
emit: (event: string, data: any) => void;
|
|
69
|
+
}): void;
|
|
70
|
+
/**
|
|
71
|
+
* Realtime ์ธ์
์์
|
|
72
|
+
*/
|
|
73
|
+
startSession(config: RealtimeSessionConfig): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Realtime ์ธ์
์ข
๋ฃ
|
|
76
|
+
*/
|
|
77
|
+
stopSession(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* ํ
์คํธ ๋ฉ์์ง ์ ์ก
|
|
80
|
+
*/
|
|
81
|
+
sendMessage(text: string): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* ์ค๋์ค ์ฒญํฌ ์ ์ก (์ฌ์ฉ์ ์์ฑ)
|
|
84
|
+
*/
|
|
85
|
+
sendAudioChunk(audio: ArrayBuffer): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* ํด๋ผ์ด์ธํธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ค์
|
|
88
|
+
*/
|
|
89
|
+
private setupClientListeners;
|
|
90
|
+
/**
|
|
91
|
+
* Tool call ์ฒ๋ฆฌ
|
|
92
|
+
*/
|
|
93
|
+
private handleToolCall;
|
|
94
|
+
private emitAudioStart;
|
|
95
|
+
private emitAudioEnd;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Realtime Manager ์์ฑ ํฌํผ ํจ์
|
|
99
|
+
*/
|
|
100
|
+
declare function createRealtimeManager(client: RealtimeClient): RealtimeManager;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* setEmotion tool definition for OpenAI Realtime API
|
|
104
|
+
*
|
|
105
|
+
* This tool allows the LLM to update the Live2D character's emotion.
|
|
106
|
+
*/
|
|
107
|
+
declare const setEmotionTool: RealtimeTool;
|
|
108
|
+
/**
|
|
109
|
+
* Emotion tool call arguments type
|
|
110
|
+
*/
|
|
111
|
+
interface EmotionArgs {
|
|
112
|
+
emotion: Emotion;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Default instructions for character with emotion support
|
|
116
|
+
*/
|
|
117
|
+
declare const DEFAULT_EMOTION_INSTRUCTIONS: string;
|
|
118
|
+
/**
|
|
119
|
+
* Get default Realtime session config with emotion support
|
|
120
|
+
*/
|
|
121
|
+
declare function getEmotionSessionConfig(overrides?: RealtimeSessionConfig): {
|
|
122
|
+
type: string;
|
|
123
|
+
model: string;
|
|
124
|
+
audio: {
|
|
125
|
+
output: {
|
|
126
|
+
voice: string;
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
instructions: string;
|
|
130
|
+
tools: RealtimeTool[];
|
|
131
|
+
tool_choice: "auto" | "none" | "required";
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export { DEFAULT_EMOTION_INSTRUCTIONS, type EmotionArgs, type RealtimeClient, type RealtimeManager, RealtimeManagerImpl, type RealtimeSessionConfig, type RealtimeTool, createRealtimeManager, getEmotionSessionConfig, setEmotionTool };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Emotion } from '@charivo/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Realtime API Types
|
|
5
|
+
*/
|
|
6
|
+
interface RealtimeTool {
|
|
7
|
+
type: "function";
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
parameters: {
|
|
11
|
+
type: "object";
|
|
12
|
+
properties: Record<string, any>;
|
|
13
|
+
required?: string[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
interface RealtimeSessionConfig {
|
|
17
|
+
voice?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
instructions?: string;
|
|
20
|
+
temperature?: number;
|
|
21
|
+
maxTokens?: number;
|
|
22
|
+
tools?: RealtimeTool[];
|
|
23
|
+
tool_choice?: "auto" | "none" | "required";
|
|
24
|
+
}
|
|
25
|
+
interface RealtimeClient {
|
|
26
|
+
connect(config?: RealtimeSessionConfig): Promise<void>;
|
|
27
|
+
disconnect(): Promise<void>;
|
|
28
|
+
sendText(text: string): Promise<void>;
|
|
29
|
+
sendAudio(audio: ArrayBuffer): Promise<void>;
|
|
30
|
+
onTextDelta(callback: (text: string) => void): void;
|
|
31
|
+
onAudioDelta(callback: (base64Audio: string) => void): void;
|
|
32
|
+
onLipSyncUpdate?(callback: (rms: number) => void): void;
|
|
33
|
+
onAudioDone(callback: () => void): void;
|
|
34
|
+
onToolCall?(callback: (name: string, args: any) => void): void;
|
|
35
|
+
onError(callback: (error: Error) => void): void;
|
|
36
|
+
}
|
|
37
|
+
interface RealtimeManager {
|
|
38
|
+
startSession(config: RealtimeSessionConfig): Promise<void>;
|
|
39
|
+
stopSession(): Promise<void>;
|
|
40
|
+
sendMessage(text: string): Promise<void>;
|
|
41
|
+
sendAudioChunk(audio: ArrayBuffer): Promise<void>;
|
|
42
|
+
setEventEmitter(eventEmitter: {
|
|
43
|
+
emit: (event: string, data: any) => void;
|
|
44
|
+
}): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Realtime Manager - Realtime API ์ธ์
๊ด๋ฆฌ
|
|
49
|
+
*
|
|
50
|
+
* ์ญํ :
|
|
51
|
+
* - Realtime ํด๋ผ์ด์ธํธ ๊ด๋ฆฌ (WebRTC)
|
|
52
|
+
* - ๋ฆฝ์ฑํฌ ์ด๋ฒคํธ ์ค๊ณ
|
|
53
|
+
* - ํ
์คํธ ์คํธ๋ฆฌ๋ฐ ์ฒ๋ฆฌ
|
|
54
|
+
*
|
|
55
|
+
* Note: WebRTC ํด๋ผ์ด์ธํธ๋ ์ค๋์ค๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ฏ๋ก
|
|
56
|
+
* ์ด Manager๋ ์ด๋ฒคํธ ์ค๊ณ์ ์ง์คํฉ๋๋ค.
|
|
57
|
+
*/
|
|
58
|
+
declare class RealtimeManagerImpl implements RealtimeManager {
|
|
59
|
+
private client;
|
|
60
|
+
private eventEmitter?;
|
|
61
|
+
private isSessionActive;
|
|
62
|
+
private isAudioPlaybackActive;
|
|
63
|
+
constructor(client: RealtimeClient);
|
|
64
|
+
/**
|
|
65
|
+
* ์ด๋ฒคํธ ๋ฐ์ ์ ์ค์
|
|
66
|
+
*/
|
|
67
|
+
setEventEmitter(eventEmitter: {
|
|
68
|
+
emit: (event: string, data: any) => void;
|
|
69
|
+
}): void;
|
|
70
|
+
/**
|
|
71
|
+
* Realtime ์ธ์
์์
|
|
72
|
+
*/
|
|
73
|
+
startSession(config: RealtimeSessionConfig): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Realtime ์ธ์
์ข
๋ฃ
|
|
76
|
+
*/
|
|
77
|
+
stopSession(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* ํ
์คํธ ๋ฉ์์ง ์ ์ก
|
|
80
|
+
*/
|
|
81
|
+
sendMessage(text: string): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* ์ค๋์ค ์ฒญํฌ ์ ์ก (์ฌ์ฉ์ ์์ฑ)
|
|
84
|
+
*/
|
|
85
|
+
sendAudioChunk(audio: ArrayBuffer): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* ํด๋ผ์ด์ธํธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ค์
|
|
88
|
+
*/
|
|
89
|
+
private setupClientListeners;
|
|
90
|
+
/**
|
|
91
|
+
* Tool call ์ฒ๋ฆฌ
|
|
92
|
+
*/
|
|
93
|
+
private handleToolCall;
|
|
94
|
+
private emitAudioStart;
|
|
95
|
+
private emitAudioEnd;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Realtime Manager ์์ฑ ํฌํผ ํจ์
|
|
99
|
+
*/
|
|
100
|
+
declare function createRealtimeManager(client: RealtimeClient): RealtimeManager;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* setEmotion tool definition for OpenAI Realtime API
|
|
104
|
+
*
|
|
105
|
+
* This tool allows the LLM to update the Live2D character's emotion.
|
|
106
|
+
*/
|
|
107
|
+
declare const setEmotionTool: RealtimeTool;
|
|
108
|
+
/**
|
|
109
|
+
* Emotion tool call arguments type
|
|
110
|
+
*/
|
|
111
|
+
interface EmotionArgs {
|
|
112
|
+
emotion: Emotion;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Default instructions for character with emotion support
|
|
116
|
+
*/
|
|
117
|
+
declare const DEFAULT_EMOTION_INSTRUCTIONS: string;
|
|
118
|
+
/**
|
|
119
|
+
* Get default Realtime session config with emotion support
|
|
120
|
+
*/
|
|
121
|
+
declare function getEmotionSessionConfig(overrides?: RealtimeSessionConfig): {
|
|
122
|
+
type: string;
|
|
123
|
+
model: string;
|
|
124
|
+
audio: {
|
|
125
|
+
output: {
|
|
126
|
+
voice: string;
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
instructions: string;
|
|
130
|
+
tools: RealtimeTool[];
|
|
131
|
+
tool_choice: "auto" | "none" | "required";
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export { DEFAULT_EMOTION_INSTRUCTIONS, type EmotionArgs, type RealtimeClient, type RealtimeManager, RealtimeManagerImpl, type RealtimeSessionConfig, type RealtimeTool, createRealtimeManager, getEmotionSessionConfig, setEmotionTool };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@charivo/core');
|
|
4
|
+
|
|
5
|
+
// src/realtime-manager.ts
|
|
6
|
+
var RealtimeManagerImpl = class {
|
|
7
|
+
client;
|
|
8
|
+
eventEmitter;
|
|
9
|
+
isSessionActive = false;
|
|
10
|
+
isAudioPlaybackActive = false;
|
|
11
|
+
constructor(client) {
|
|
12
|
+
this.client = client;
|
|
13
|
+
this.setupClientListeners();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* ์ด๋ฒคํธ ๋ฐ์ ์ ์ค์
|
|
17
|
+
*/
|
|
18
|
+
setEventEmitter(eventEmitter) {
|
|
19
|
+
this.eventEmitter = eventEmitter;
|
|
20
|
+
console.log("\u{1F517} Realtime Manager: Event emitter connected");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Realtime ์ธ์
์์
|
|
24
|
+
*/
|
|
25
|
+
async startSession(config) {
|
|
26
|
+
if (this.isSessionActive) {
|
|
27
|
+
throw new Error("Realtime session already active");
|
|
28
|
+
}
|
|
29
|
+
console.log("\u{1F680} Starting Realtime session with config:", config);
|
|
30
|
+
await this.client.connect(config);
|
|
31
|
+
this.isSessionActive = true;
|
|
32
|
+
console.log("\u2705 Realtime session started");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Realtime ์ธ์
์ข
๋ฃ
|
|
36
|
+
*/
|
|
37
|
+
async stopSession() {
|
|
38
|
+
if (!this.isSessionActive) return;
|
|
39
|
+
console.log("\u{1F6D1} Stopping Realtime session");
|
|
40
|
+
await this.client.disconnect();
|
|
41
|
+
this.emitAudioEnd();
|
|
42
|
+
this.isSessionActive = false;
|
|
43
|
+
console.log("\u2705 Realtime session stopped");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* ํ
์คํธ ๋ฉ์์ง ์ ์ก
|
|
47
|
+
*/
|
|
48
|
+
async sendMessage(text) {
|
|
49
|
+
if (!this.isSessionActive) {
|
|
50
|
+
throw new Error("Realtime session not active");
|
|
51
|
+
}
|
|
52
|
+
this.emitAudioStart();
|
|
53
|
+
try {
|
|
54
|
+
await this.client.sendText(text);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
this.emitAudioEnd();
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* ์ค๋์ค ์ฒญํฌ ์ ์ก (์ฌ์ฉ์ ์์ฑ)
|
|
62
|
+
*/
|
|
63
|
+
async sendAudioChunk(audio) {
|
|
64
|
+
if (!this.isSessionActive) {
|
|
65
|
+
throw new Error("Realtime session not active");
|
|
66
|
+
}
|
|
67
|
+
await this.client.sendAudio(audio);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* ํด๋ผ์ด์ธํธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ค์
|
|
71
|
+
*/
|
|
72
|
+
setupClientListeners() {
|
|
73
|
+
this.client.onTextDelta((text) => {
|
|
74
|
+
this.eventEmitter?.emit("realtime:text:delta", { text });
|
|
75
|
+
});
|
|
76
|
+
if (this.client.onLipSyncUpdate) {
|
|
77
|
+
this.client.onLipSyncUpdate((rms) => {
|
|
78
|
+
if (rms > 1e-3 && !this.isAudioPlaybackActive) {
|
|
79
|
+
this.emitAudioStart();
|
|
80
|
+
}
|
|
81
|
+
this.eventEmitter?.emit("tts:lipsync:update", { rms });
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
this.client.onAudioDone(() => {
|
|
85
|
+
this.emitAudioEnd();
|
|
86
|
+
});
|
|
87
|
+
if (this.client.onToolCall) {
|
|
88
|
+
this.client.onToolCall((name, args) => {
|
|
89
|
+
this.handleToolCall(name, args);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
this.client.onError((error) => {
|
|
93
|
+
console.error("Realtime client error:", error);
|
|
94
|
+
this.emitAudioEnd();
|
|
95
|
+
this.eventEmitter?.emit("realtime:error", { error });
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Tool call ์ฒ๋ฆฌ
|
|
100
|
+
*/
|
|
101
|
+
handleToolCall(name, args) {
|
|
102
|
+
if (name === "setEmotion") {
|
|
103
|
+
console.log(`\u{1F3AD} [Realtime] Emotion update:`, args.emotion);
|
|
104
|
+
this.eventEmitter?.emit("realtime:emotion", {
|
|
105
|
+
emotion: args.emotion
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
emitAudioStart() {
|
|
110
|
+
if (this.isAudioPlaybackActive) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.isAudioPlaybackActive = true;
|
|
114
|
+
this.eventEmitter?.emit("tts:audio:start", {});
|
|
115
|
+
}
|
|
116
|
+
emitAudioEnd() {
|
|
117
|
+
if (!this.isAudioPlaybackActive) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.isAudioPlaybackActive = false;
|
|
121
|
+
this.eventEmitter?.emit("tts:lipsync:update", { rms: 0 });
|
|
122
|
+
this.eventEmitter?.emit("tts:audio:end", {});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function createRealtimeManager(client) {
|
|
126
|
+
return new RealtimeManagerImpl(client);
|
|
127
|
+
}
|
|
128
|
+
var EMOTION_VALUES = Object.values(core.Emotion);
|
|
129
|
+
var setEmotionTool = {
|
|
130
|
+
type: "function",
|
|
131
|
+
name: "setEmotion",
|
|
132
|
+
description: "Update Live2D character emotion. Call this when the conversation mood or emotion changes.",
|
|
133
|
+
parameters: {
|
|
134
|
+
type: "object",
|
|
135
|
+
properties: {
|
|
136
|
+
emotion: {
|
|
137
|
+
type: "string",
|
|
138
|
+
description: "Emotion of the character.",
|
|
139
|
+
enum: EMOTION_VALUES
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
required: ["emotion"]
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
var DEFAULT_EMOTION_INSTRUCTIONS = `
|
|
146
|
+
You are a Live2D character with emotions and expressions.
|
|
147
|
+
Respond naturally to the user's messages.
|
|
148
|
+
When the conversation mood or emotion changes, call the 'setEmotion' tool to update your expression and motion.
|
|
149
|
+
Do NOT mention the tool call in your response - just respond naturally and let the emotion show through your character.
|
|
150
|
+
`.trim();
|
|
151
|
+
function getEmotionSessionConfig(overrides) {
|
|
152
|
+
const toolsFromConfig = overrides?.tools ?? [];
|
|
153
|
+
const hasEmotionTool = toolsFromConfig.some(
|
|
154
|
+
(tool) => tool.type === "function" && tool.name === setEmotionTool.name
|
|
155
|
+
);
|
|
156
|
+
const tools = hasEmotionTool ? toolsFromConfig : [setEmotionTool, ...toolsFromConfig];
|
|
157
|
+
return {
|
|
158
|
+
type: "realtime",
|
|
159
|
+
model: overrides?.model || "gpt-realtime-mini",
|
|
160
|
+
audio: {
|
|
161
|
+
output: {
|
|
162
|
+
voice: overrides?.voice || "marin"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
instructions: overrides?.instructions || DEFAULT_EMOTION_INSTRUCTIONS,
|
|
166
|
+
tools,
|
|
167
|
+
tool_choice: overrides?.tool_choice ?? "auto"
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
exports.DEFAULT_EMOTION_INSTRUCTIONS = DEFAULT_EMOTION_INSTRUCTIONS;
|
|
172
|
+
exports.RealtimeManagerImpl = RealtimeManagerImpl;
|
|
173
|
+
exports.createRealtimeManager = createRealtimeManager;
|
|
174
|
+
exports.getEmotionSessionConfig = getEmotionSessionConfig;
|
|
175
|
+
exports.setEmotionTool = setEmotionTool;
|
|
176
|
+
//# sourceMappingURL=index.js.map
|
|
177
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/realtime-manager.ts","../src/tools.ts"],"names":["Emotion"],"mappings":"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","file":"index.js","sourcesContent":["import {\n RealtimeClient,\n RealtimeManager as IRealtimeManager,\n RealtimeSessionConfig,\n} from \"./types\";\n\n/**\n * Realtime Manager - Realtime API ์ธ์
๊ด๋ฆฌ\n *\n * ์ญํ :\n * - Realtime ํด๋ผ์ด์ธํธ ๊ด๋ฆฌ (WebRTC)\n * - ๋ฆฝ์ฑํฌ ์ด๋ฒคํธ ์ค๊ณ\n * - ํ
์คํธ ์คํธ๋ฆฌ๋ฐ ์ฒ๋ฆฌ\n *\n * Note: WebRTC ํด๋ผ์ด์ธํธ๋ ์ค๋์ค๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ฏ๋ก\n * ์ด Manager๋ ์ด๋ฒคํธ ์ค๊ณ์ ์ง์คํฉ๋๋ค.\n */\nexport class RealtimeManagerImpl implements IRealtimeManager {\n private client: RealtimeClient;\n private eventEmitter?: { emit: (event: string, data: any) => void };\n private isSessionActive = false;\n private isAudioPlaybackActive = false;\n\n constructor(client: RealtimeClient) {\n this.client = client;\n this.setupClientListeners();\n }\n\n /**\n * ์ด๋ฒคํธ ๋ฐ์ ์ ์ค์ \n */\n setEventEmitter(eventEmitter: {\n emit: (event: string, data: any) => void;\n }): void {\n this.eventEmitter = eventEmitter;\n console.log(\"๐ Realtime Manager: Event emitter connected\");\n }\n\n /**\n * Realtime ์ธ์
์์\n */\n async startSession(config: RealtimeSessionConfig): Promise<void> {\n if (this.isSessionActive) {\n throw new Error(\"Realtime session already active\");\n }\n\n console.log(\"๐ Starting Realtime session with config:\", config);\n\n // ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ\n await this.client.connect(config);\n\n this.isSessionActive = true;\n\n console.log(\"โ
Realtime session started\");\n }\n\n /**\n * Realtime ์ธ์
์ข
๋ฃ\n */\n async stopSession(): Promise<void> {\n if (!this.isSessionActive) return;\n\n console.log(\"๐ Stopping Realtime session\");\n\n // ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ ํด์ \n await this.client.disconnect();\n this.emitAudioEnd();\n\n this.isSessionActive = false;\n\n console.log(\"โ
Realtime session stopped\");\n }\n\n /**\n * ํ
์คํธ ๋ฉ์์ง ์ ์ก\n */\n async sendMessage(text: string): Promise<void> {\n if (!this.isSessionActive) {\n throw new Error(\"Realtime session not active\");\n }\n\n this.emitAudioStart();\n try {\n await this.client.sendText(text);\n } catch (error) {\n this.emitAudioEnd();\n throw error;\n }\n }\n\n /**\n * ์ค๋์ค ์ฒญํฌ ์ ์ก (์ฌ์ฉ์ ์์ฑ)\n */\n async sendAudioChunk(audio: ArrayBuffer): Promise<void> {\n if (!this.isSessionActive) {\n throw new Error(\"Realtime session not active\");\n }\n\n await this.client.sendAudio(audio);\n }\n\n /**\n * ํด๋ผ์ด์ธํธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ค์ \n */\n private setupClientListeners(): void {\n // ํ
์คํธ ์คํธ๋ฆฌ๋ฐ\n this.client.onTextDelta((text: string) => {\n this.eventEmitter?.emit(\"realtime:text:delta\", { text });\n });\n\n // Direct RMS callback (for WebRTC clients)\n if (this.client.onLipSyncUpdate) {\n this.client.onLipSyncUpdate((rms: number) => {\n if (rms > 0.001 && !this.isAudioPlaybackActive) {\n this.emitAudioStart();\n }\n this.eventEmitter?.emit(\"tts:lipsync:update\", { rms });\n });\n }\n\n // ์ค๋์ค ์คํธ๋ฆฌ๋ฐ ์ข
๋ฃ\n this.client.onAudioDone(() => {\n this.emitAudioEnd();\n });\n\n // Tool call ์ฒ๋ฆฌ\n if (this.client.onToolCall) {\n this.client.onToolCall((name: string, args: any) => {\n this.handleToolCall(name, args);\n });\n }\n\n // ์๋ฌ ์ฒ๋ฆฌ\n this.client.onError((error: Error) => {\n console.error(\"Realtime client error:\", error);\n this.emitAudioEnd();\n this.eventEmitter?.emit(\"realtime:error\", { error });\n });\n }\n\n /**\n * Tool call ์ฒ๋ฆฌ\n */\n private handleToolCall(name: string, args: any): void {\n if (name === \"setEmotion\") {\n console.log(`๐ญ [Realtime] Emotion update:`, args.emotion);\n // Emit emotion event for RenderManager\n this.eventEmitter?.emit(\"realtime:emotion\", {\n emotion: args.emotion,\n });\n }\n }\n\n private emitAudioStart(): void {\n if (this.isAudioPlaybackActive) {\n return;\n }\n this.isAudioPlaybackActive = true;\n this.eventEmitter?.emit(\"tts:audio:start\", {});\n }\n\n private emitAudioEnd(): void {\n if (!this.isAudioPlaybackActive) {\n return;\n }\n this.isAudioPlaybackActive = false;\n this.eventEmitter?.emit(\"tts:lipsync:update\", { rms: 0 });\n this.eventEmitter?.emit(\"tts:audio:end\", {});\n }\n}\n\n/**\n * Realtime Manager ์์ฑ ํฌํผ ํจ์\n */\nexport function createRealtimeManager(\n client: RealtimeClient,\n): IRealtimeManager {\n return new RealtimeManagerImpl(client);\n}\n","import type { RealtimeSessionConfig, RealtimeTool } from \"./types\";\nimport { Emotion } from \"@charivo/core\";\n\n/**\n * Get emotion enum values for tool definition\n */\nconst EMOTION_VALUES = Object.values(Emotion);\n\n/**\n * setEmotion tool definition for OpenAI Realtime API\n *\n * This tool allows the LLM to update the Live2D character's emotion.\n */\nexport const setEmotionTool: RealtimeTool = {\n type: \"function\",\n name: \"setEmotion\",\n description:\n \"Update Live2D character emotion. Call this when the conversation mood or emotion changes.\",\n parameters: {\n type: \"object\",\n properties: {\n emotion: {\n type: \"string\",\n description: \"Emotion of the character.\",\n enum: EMOTION_VALUES,\n },\n },\n required: [\"emotion\"],\n },\n};\n\n/**\n * Emotion tool call arguments type\n */\nexport interface EmotionArgs {\n emotion: Emotion;\n}\n\n/**\n * Default instructions for character with emotion support\n */\nexport const DEFAULT_EMOTION_INSTRUCTIONS = `\nYou are a Live2D character with emotions and expressions.\nRespond naturally to the user's messages.\nWhen the conversation mood or emotion changes, call the 'setEmotion' tool to update your expression and motion.\nDo NOT mention the tool call in your response - just respond naturally and let the emotion show through your character.\n`.trim();\n\n/**\n * Get default Realtime session config with emotion support\n */\nexport function getEmotionSessionConfig(overrides?: RealtimeSessionConfig): {\n type: string;\n model: string;\n audio: { output: { voice: string } };\n instructions: string;\n tools: RealtimeTool[];\n tool_choice: \"auto\" | \"none\" | \"required\";\n} {\n const toolsFromConfig = overrides?.tools ?? [];\n const hasEmotionTool = toolsFromConfig.some(\n (tool) => tool.type === \"function\" && tool.name === setEmotionTool.name,\n );\n const tools = hasEmotionTool\n ? toolsFromConfig\n : [setEmotionTool, ...toolsFromConfig];\n\n return {\n type: \"realtime\",\n model: overrides?.model || \"gpt-realtime-mini\",\n audio: {\n output: {\n voice: overrides?.voice || \"marin\",\n },\n },\n instructions: overrides?.instructions || DEFAULT_EMOTION_INSTRUCTIONS,\n tools,\n tool_choice: overrides?.tool_choice ?? \"auto\",\n };\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Emotion } from '@charivo/core';
|
|
2
|
+
|
|
3
|
+
// src/realtime-manager.ts
|
|
4
|
+
var RealtimeManagerImpl = class {
|
|
5
|
+
client;
|
|
6
|
+
eventEmitter;
|
|
7
|
+
isSessionActive = false;
|
|
8
|
+
isAudioPlaybackActive = false;
|
|
9
|
+
constructor(client) {
|
|
10
|
+
this.client = client;
|
|
11
|
+
this.setupClientListeners();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* ์ด๋ฒคํธ ๋ฐ์ ์ ์ค์
|
|
15
|
+
*/
|
|
16
|
+
setEventEmitter(eventEmitter) {
|
|
17
|
+
this.eventEmitter = eventEmitter;
|
|
18
|
+
console.log("\u{1F517} Realtime Manager: Event emitter connected");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Realtime ์ธ์
์์
|
|
22
|
+
*/
|
|
23
|
+
async startSession(config) {
|
|
24
|
+
if (this.isSessionActive) {
|
|
25
|
+
throw new Error("Realtime session already active");
|
|
26
|
+
}
|
|
27
|
+
console.log("\u{1F680} Starting Realtime session with config:", config);
|
|
28
|
+
await this.client.connect(config);
|
|
29
|
+
this.isSessionActive = true;
|
|
30
|
+
console.log("\u2705 Realtime session started");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Realtime ์ธ์
์ข
๋ฃ
|
|
34
|
+
*/
|
|
35
|
+
async stopSession() {
|
|
36
|
+
if (!this.isSessionActive) return;
|
|
37
|
+
console.log("\u{1F6D1} Stopping Realtime session");
|
|
38
|
+
await this.client.disconnect();
|
|
39
|
+
this.emitAudioEnd();
|
|
40
|
+
this.isSessionActive = false;
|
|
41
|
+
console.log("\u2705 Realtime session stopped");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* ํ
์คํธ ๋ฉ์์ง ์ ์ก
|
|
45
|
+
*/
|
|
46
|
+
async sendMessage(text) {
|
|
47
|
+
if (!this.isSessionActive) {
|
|
48
|
+
throw new Error("Realtime session not active");
|
|
49
|
+
}
|
|
50
|
+
this.emitAudioStart();
|
|
51
|
+
try {
|
|
52
|
+
await this.client.sendText(text);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
this.emitAudioEnd();
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* ์ค๋์ค ์ฒญํฌ ์ ์ก (์ฌ์ฉ์ ์์ฑ)
|
|
60
|
+
*/
|
|
61
|
+
async sendAudioChunk(audio) {
|
|
62
|
+
if (!this.isSessionActive) {
|
|
63
|
+
throw new Error("Realtime session not active");
|
|
64
|
+
}
|
|
65
|
+
await this.client.sendAudio(audio);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* ํด๋ผ์ด์ธํธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ค์
|
|
69
|
+
*/
|
|
70
|
+
setupClientListeners() {
|
|
71
|
+
this.client.onTextDelta((text) => {
|
|
72
|
+
this.eventEmitter?.emit("realtime:text:delta", { text });
|
|
73
|
+
});
|
|
74
|
+
if (this.client.onLipSyncUpdate) {
|
|
75
|
+
this.client.onLipSyncUpdate((rms) => {
|
|
76
|
+
if (rms > 1e-3 && !this.isAudioPlaybackActive) {
|
|
77
|
+
this.emitAudioStart();
|
|
78
|
+
}
|
|
79
|
+
this.eventEmitter?.emit("tts:lipsync:update", { rms });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
this.client.onAudioDone(() => {
|
|
83
|
+
this.emitAudioEnd();
|
|
84
|
+
});
|
|
85
|
+
if (this.client.onToolCall) {
|
|
86
|
+
this.client.onToolCall((name, args) => {
|
|
87
|
+
this.handleToolCall(name, args);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
this.client.onError((error) => {
|
|
91
|
+
console.error("Realtime client error:", error);
|
|
92
|
+
this.emitAudioEnd();
|
|
93
|
+
this.eventEmitter?.emit("realtime:error", { error });
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Tool call ์ฒ๋ฆฌ
|
|
98
|
+
*/
|
|
99
|
+
handleToolCall(name, args) {
|
|
100
|
+
if (name === "setEmotion") {
|
|
101
|
+
console.log(`\u{1F3AD} [Realtime] Emotion update:`, args.emotion);
|
|
102
|
+
this.eventEmitter?.emit("realtime:emotion", {
|
|
103
|
+
emotion: args.emotion
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
emitAudioStart() {
|
|
108
|
+
if (this.isAudioPlaybackActive) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
this.isAudioPlaybackActive = true;
|
|
112
|
+
this.eventEmitter?.emit("tts:audio:start", {});
|
|
113
|
+
}
|
|
114
|
+
emitAudioEnd() {
|
|
115
|
+
if (!this.isAudioPlaybackActive) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.isAudioPlaybackActive = false;
|
|
119
|
+
this.eventEmitter?.emit("tts:lipsync:update", { rms: 0 });
|
|
120
|
+
this.eventEmitter?.emit("tts:audio:end", {});
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
function createRealtimeManager(client) {
|
|
124
|
+
return new RealtimeManagerImpl(client);
|
|
125
|
+
}
|
|
126
|
+
var EMOTION_VALUES = Object.values(Emotion);
|
|
127
|
+
var setEmotionTool = {
|
|
128
|
+
type: "function",
|
|
129
|
+
name: "setEmotion",
|
|
130
|
+
description: "Update Live2D character emotion. Call this when the conversation mood or emotion changes.",
|
|
131
|
+
parameters: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
emotion: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Emotion of the character.",
|
|
137
|
+
enum: EMOTION_VALUES
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
required: ["emotion"]
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var DEFAULT_EMOTION_INSTRUCTIONS = `
|
|
144
|
+
You are a Live2D character with emotions and expressions.
|
|
145
|
+
Respond naturally to the user's messages.
|
|
146
|
+
When the conversation mood or emotion changes, call the 'setEmotion' tool to update your expression and motion.
|
|
147
|
+
Do NOT mention the tool call in your response - just respond naturally and let the emotion show through your character.
|
|
148
|
+
`.trim();
|
|
149
|
+
function getEmotionSessionConfig(overrides) {
|
|
150
|
+
const toolsFromConfig = overrides?.tools ?? [];
|
|
151
|
+
const hasEmotionTool = toolsFromConfig.some(
|
|
152
|
+
(tool) => tool.type === "function" && tool.name === setEmotionTool.name
|
|
153
|
+
);
|
|
154
|
+
const tools = hasEmotionTool ? toolsFromConfig : [setEmotionTool, ...toolsFromConfig];
|
|
155
|
+
return {
|
|
156
|
+
type: "realtime",
|
|
157
|
+
model: overrides?.model || "gpt-realtime-mini",
|
|
158
|
+
audio: {
|
|
159
|
+
output: {
|
|
160
|
+
voice: overrides?.voice || "marin"
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
instructions: overrides?.instructions || DEFAULT_EMOTION_INSTRUCTIONS,
|
|
164
|
+
tools,
|
|
165
|
+
tool_choice: overrides?.tool_choice ?? "auto"
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export { DEFAULT_EMOTION_INSTRUCTIONS, RealtimeManagerImpl, createRealtimeManager, getEmotionSessionConfig, setEmotionTool };
|
|
170
|
+
//# sourceMappingURL=index.mjs.map
|
|
171
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/realtime-manager.ts","../src/tools.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import {\n RealtimeClient,\n RealtimeManager as IRealtimeManager,\n RealtimeSessionConfig,\n} from \"./types\";\n\n/**\n * Realtime Manager - Realtime API ์ธ์
๊ด๋ฆฌ\n *\n * ์ญํ :\n * - Realtime ํด๋ผ์ด์ธํธ ๊ด๋ฆฌ (WebRTC)\n * - ๋ฆฝ์ฑํฌ ์ด๋ฒคํธ ์ค๊ณ\n * - ํ
์คํธ ์คํธ๋ฆฌ๋ฐ ์ฒ๋ฆฌ\n *\n * Note: WebRTC ํด๋ผ์ด์ธํธ๋ ์ค๋์ค๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ฏ๋ก\n * ์ด Manager๋ ์ด๋ฒคํธ ์ค๊ณ์ ์ง์คํฉ๋๋ค.\n */\nexport class RealtimeManagerImpl implements IRealtimeManager {\n private client: RealtimeClient;\n private eventEmitter?: { emit: (event: string, data: any) => void };\n private isSessionActive = false;\n private isAudioPlaybackActive = false;\n\n constructor(client: RealtimeClient) {\n this.client = client;\n this.setupClientListeners();\n }\n\n /**\n * ์ด๋ฒคํธ ๋ฐ์ ์ ์ค์ \n */\n setEventEmitter(eventEmitter: {\n emit: (event: string, data: any) => void;\n }): void {\n this.eventEmitter = eventEmitter;\n console.log(\"๐ Realtime Manager: Event emitter connected\");\n }\n\n /**\n * Realtime ์ธ์
์์\n */\n async startSession(config: RealtimeSessionConfig): Promise<void> {\n if (this.isSessionActive) {\n throw new Error(\"Realtime session already active\");\n }\n\n console.log(\"๐ Starting Realtime session with config:\", config);\n\n // ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ\n await this.client.connect(config);\n\n this.isSessionActive = true;\n\n console.log(\"โ
Realtime session started\");\n }\n\n /**\n * Realtime ์ธ์
์ข
๋ฃ\n */\n async stopSession(): Promise<void> {\n if (!this.isSessionActive) return;\n\n console.log(\"๐ Stopping Realtime session\");\n\n // ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ ํด์ \n await this.client.disconnect();\n this.emitAudioEnd();\n\n this.isSessionActive = false;\n\n console.log(\"โ
Realtime session stopped\");\n }\n\n /**\n * ํ
์คํธ ๋ฉ์์ง ์ ์ก\n */\n async sendMessage(text: string): Promise<void> {\n if (!this.isSessionActive) {\n throw new Error(\"Realtime session not active\");\n }\n\n this.emitAudioStart();\n try {\n await this.client.sendText(text);\n } catch (error) {\n this.emitAudioEnd();\n throw error;\n }\n }\n\n /**\n * ์ค๋์ค ์ฒญํฌ ์ ์ก (์ฌ์ฉ์ ์์ฑ)\n */\n async sendAudioChunk(audio: ArrayBuffer): Promise<void> {\n if (!this.isSessionActive) {\n throw new Error(\"Realtime session not active\");\n }\n\n await this.client.sendAudio(audio);\n }\n\n /**\n * ํด๋ผ์ด์ธํธ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ์ค์ \n */\n private setupClientListeners(): void {\n // ํ
์คํธ ์คํธ๋ฆฌ๋ฐ\n this.client.onTextDelta((text: string) => {\n this.eventEmitter?.emit(\"realtime:text:delta\", { text });\n });\n\n // Direct RMS callback (for WebRTC clients)\n if (this.client.onLipSyncUpdate) {\n this.client.onLipSyncUpdate((rms: number) => {\n if (rms > 0.001 && !this.isAudioPlaybackActive) {\n this.emitAudioStart();\n }\n this.eventEmitter?.emit(\"tts:lipsync:update\", { rms });\n });\n }\n\n // ์ค๋์ค ์คํธ๋ฆฌ๋ฐ ์ข
๋ฃ\n this.client.onAudioDone(() => {\n this.emitAudioEnd();\n });\n\n // Tool call ์ฒ๋ฆฌ\n if (this.client.onToolCall) {\n this.client.onToolCall((name: string, args: any) => {\n this.handleToolCall(name, args);\n });\n }\n\n // ์๋ฌ ์ฒ๋ฆฌ\n this.client.onError((error: Error) => {\n console.error(\"Realtime client error:\", error);\n this.emitAudioEnd();\n this.eventEmitter?.emit(\"realtime:error\", { error });\n });\n }\n\n /**\n * Tool call ์ฒ๋ฆฌ\n */\n private handleToolCall(name: string, args: any): void {\n if (name === \"setEmotion\") {\n console.log(`๐ญ [Realtime] Emotion update:`, args.emotion);\n // Emit emotion event for RenderManager\n this.eventEmitter?.emit(\"realtime:emotion\", {\n emotion: args.emotion,\n });\n }\n }\n\n private emitAudioStart(): void {\n if (this.isAudioPlaybackActive) {\n return;\n }\n this.isAudioPlaybackActive = true;\n this.eventEmitter?.emit(\"tts:audio:start\", {});\n }\n\n private emitAudioEnd(): void {\n if (!this.isAudioPlaybackActive) {\n return;\n }\n this.isAudioPlaybackActive = false;\n this.eventEmitter?.emit(\"tts:lipsync:update\", { rms: 0 });\n this.eventEmitter?.emit(\"tts:audio:end\", {});\n }\n}\n\n/**\n * Realtime Manager ์์ฑ ํฌํผ ํจ์\n */\nexport function createRealtimeManager(\n client: RealtimeClient,\n): IRealtimeManager {\n return new RealtimeManagerImpl(client);\n}\n","import type { RealtimeSessionConfig, RealtimeTool } from \"./types\";\nimport { Emotion } from \"@charivo/core\";\n\n/**\n * Get emotion enum values for tool definition\n */\nconst EMOTION_VALUES = Object.values(Emotion);\n\n/**\n * setEmotion tool definition for OpenAI Realtime API\n *\n * This tool allows the LLM to update the Live2D character's emotion.\n */\nexport const setEmotionTool: RealtimeTool = {\n type: \"function\",\n name: \"setEmotion\",\n description:\n \"Update Live2D character emotion. Call this when the conversation mood or emotion changes.\",\n parameters: {\n type: \"object\",\n properties: {\n emotion: {\n type: \"string\",\n description: \"Emotion of the character.\",\n enum: EMOTION_VALUES,\n },\n },\n required: [\"emotion\"],\n },\n};\n\n/**\n * Emotion tool call arguments type\n */\nexport interface EmotionArgs {\n emotion: Emotion;\n}\n\n/**\n * Default instructions for character with emotion support\n */\nexport const DEFAULT_EMOTION_INSTRUCTIONS = `\nYou are a Live2D character with emotions and expressions.\nRespond naturally to the user's messages.\nWhen the conversation mood or emotion changes, call the 'setEmotion' tool to update your expression and motion.\nDo NOT mention the tool call in your response - just respond naturally and let the emotion show through your character.\n`.trim();\n\n/**\n * Get default Realtime session config with emotion support\n */\nexport function getEmotionSessionConfig(overrides?: RealtimeSessionConfig): {\n type: string;\n model: string;\n audio: { output: { voice: string } };\n instructions: string;\n tools: RealtimeTool[];\n tool_choice: \"auto\" | \"none\" | \"required\";\n} {\n const toolsFromConfig = overrides?.tools ?? [];\n const hasEmotionTool = toolsFromConfig.some(\n (tool) => tool.type === \"function\" && tool.name === setEmotionTool.name,\n );\n const tools = hasEmotionTool\n ? toolsFromConfig\n : [setEmotionTool, ...toolsFromConfig];\n\n return {\n type: \"realtime\",\n model: overrides?.model || \"gpt-realtime-mini\",\n audio: {\n output: {\n voice: overrides?.voice || \"marin\",\n },\n },\n instructions: overrides?.instructions || DEFAULT_EMOTION_INSTRUCTIONS,\n tools,\n tool_choice: overrides?.tool_choice ?? \"auto\",\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@charivo/realtime-core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Core utilities for Realtime API functionality in Charivo",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@charivo/core": "0.0.1"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"tsup": "^8.0.0",
|
|
12
|
+
"typescript": "^5.0.0"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/zeikar/charivo.git",
|
|
24
|
+
"directory": "packages/realtime-core"
|
|
25
|
+
},
|
|
26
|
+
"author": {
|
|
27
|
+
"name": "Zeikar",
|
|
28
|
+
"url": "https://github.com/zeikar"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/zeikar/charivo#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/zeikar/charivo/issues"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"dev": "tsup --watch",
|
|
37
|
+
"clean": "rm -rf dist"
|
|
38
|
+
}
|
|
39
|
+
}
|