@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 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
@@ -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 };
@@ -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
+ }