@charivo/realtime-core 0.0.1 โ†’ 0.1.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 CHANGED
@@ -1,252 +1,48 @@
1
1
  # @charivo/realtime-core
2
2
 
3
- Core Realtime API functionality with session management and event relay for low-latency voice conversations.
3
+ Realtime session manager and typed OpenAI Realtime helpers for Charivo.
4
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
5
+ ## Install
13
6
 
14
7
  ```bash
15
- pnpm add @charivo/realtime-core @charivo/core
8
+ pnpm add @charivo/realtime-core
16
9
  ```
17
10
 
18
11
  ## Usage
19
12
 
20
- ### Basic Setup
21
-
22
- ```typescript
23
- import { createRealtimeManager } from "@charivo/realtime-core";
13
+ ```ts
14
+ import {
15
+ createRealtimeManager,
16
+ getEmotionSessionConfig,
17
+ } from "@charivo/realtime-core";
24
18
  import { createOpenAIRealtimeClient } from "@charivo/realtime-client-openai";
25
19
 
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
20
+ const client = createOpenAIRealtimeClient({ apiEndpoint: "/api/realtime" });
21
+ const manager = createRealtimeManager(client);
146
22
 
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 }
23
+ await manager.startSession(
24
+ getEmotionSessionConfig({
25
+ model: "gpt-realtime-mini",
26
+ voice: "marin",
27
+ }),
28
+ );
192
29
  ```
193
30
 
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
31
+ ## Exports
244
32
 
245
- ## Related Packages
33
+ - `createRealtimeManager(client)`
34
+ - `getEmotionSessionConfig(overrides?)`
35
+ - `setEmotionTool`
36
+ - `DEFAULT_EMOTION_INSTRUCTIONS`
37
+ - realtime-related types re-exported from `@charivo/core`
246
38
 
247
- - [@charivo/realtime-client-openai](../realtime-client-openai) - OpenAI Realtime API client with WebRTC
248
- - [@charivo/core](../core) - Core types and interfaces
39
+ ## Event Bridge
249
40
 
250
- ## License
41
+ When connected to the Charivo event bus, the manager relays:
251
42
 
252
- MIT
43
+ - `realtime:text:delta`
44
+ - `realtime:emotion`
45
+ - `realtime:error`
46
+ - `tts:lipsync:update`
47
+ - `tts:audio:start`
48
+ - `tts:audio:end`
package/dist/index.d.mts CHANGED
@@ -1,27 +1,6 @@
1
- import { Emotion } from '@charivo/core';
1
+ import { RealtimeSessionConfig, RealtimeManager, CharivoEventEmitter, RealtimeTool, Emotion } from '@charivo/core';
2
+ export { CharivoEventEmitter, RealtimeManager, RealtimeSessionConfig, RealtimeTool } from '@charivo/core';
2
3
 
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
4
  interface RealtimeClient {
26
5
  connect(config?: RealtimeSessionConfig): Promise<void>;
27
6
  disconnect(): Promise<void>;
@@ -31,18 +10,9 @@ interface RealtimeClient {
31
10
  onAudioDelta(callback: (base64Audio: string) => void): void;
32
11
  onLipSyncUpdate?(callback: (rms: number) => void): void;
33
12
  onAudioDone(callback: () => void): void;
34
- onToolCall?(callback: (name: string, args: any) => void): void;
13
+ onToolCall?(callback: (name: string, args: Record<string, unknown>) => void): void;
35
14
  onError(callback: (error: Error) => void): void;
36
15
  }
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
16
 
47
17
  /**
48
18
  * Realtime Manager - Realtime API ์„ธ์…˜ ๊ด€๋ฆฌ
@@ -64,9 +34,7 @@ declare class RealtimeManagerImpl implements RealtimeManager {
64
34
  /**
65
35
  * ์ด๋ฒคํŠธ ๋ฐœ์‹ ์ž ์„ค์ •
66
36
  */
67
- setEventEmitter(eventEmitter: {
68
- emit: (event: string, data: any) => void;
69
- }): void;
37
+ setEventEmitter(eventEmitter: CharivoEventEmitter): void;
70
38
  /**
71
39
  * Realtime ์„ธ์…˜ ์‹œ์ž‘
72
40
  */
@@ -131,4 +99,4 @@ declare function getEmotionSessionConfig(overrides?: RealtimeSessionConfig): {
131
99
  tool_choice: "auto" | "none" | "required";
132
100
  };
133
101
 
134
- export { DEFAULT_EMOTION_INSTRUCTIONS, type EmotionArgs, type RealtimeClient, type RealtimeManager, RealtimeManagerImpl, type RealtimeSessionConfig, type RealtimeTool, createRealtimeManager, getEmotionSessionConfig, setEmotionTool };
102
+ export { DEFAULT_EMOTION_INSTRUCTIONS, type EmotionArgs, type RealtimeClient, RealtimeManagerImpl, createRealtimeManager, getEmotionSessionConfig, setEmotionTool };
package/dist/index.d.ts CHANGED
@@ -1,27 +1,6 @@
1
- import { Emotion } from '@charivo/core';
1
+ import { RealtimeSessionConfig, RealtimeManager, CharivoEventEmitter, RealtimeTool, Emotion } from '@charivo/core';
2
+ export { CharivoEventEmitter, RealtimeManager, RealtimeSessionConfig, RealtimeTool } from '@charivo/core';
2
3
 
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
4
  interface RealtimeClient {
26
5
  connect(config?: RealtimeSessionConfig): Promise<void>;
27
6
  disconnect(): Promise<void>;
@@ -31,18 +10,9 @@ interface RealtimeClient {
31
10
  onAudioDelta(callback: (base64Audio: string) => void): void;
32
11
  onLipSyncUpdate?(callback: (rms: number) => void): void;
33
12
  onAudioDone(callback: () => void): void;
34
- onToolCall?(callback: (name: string, args: any) => void): void;
13
+ onToolCall?(callback: (name: string, args: Record<string, unknown>) => void): void;
35
14
  onError(callback: (error: Error) => void): void;
36
15
  }
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
16
 
47
17
  /**
48
18
  * Realtime Manager - Realtime API ์„ธ์…˜ ๊ด€๋ฆฌ
@@ -64,9 +34,7 @@ declare class RealtimeManagerImpl implements RealtimeManager {
64
34
  /**
65
35
  * ์ด๋ฒคํŠธ ๋ฐœ์‹ ์ž ์„ค์ •
66
36
  */
67
- setEventEmitter(eventEmitter: {
68
- emit: (event: string, data: any) => void;
69
- }): void;
37
+ setEventEmitter(eventEmitter: CharivoEventEmitter): void;
70
38
  /**
71
39
  * Realtime ์„ธ์…˜ ์‹œ์ž‘
72
40
  */
@@ -131,4 +99,4 @@ declare function getEmotionSessionConfig(overrides?: RealtimeSessionConfig): {
131
99
  tool_choice: "auto" | "none" | "required";
132
100
  };
133
101
 
134
- export { DEFAULT_EMOTION_INSTRUCTIONS, type EmotionArgs, type RealtimeClient, type RealtimeManager, RealtimeManagerImpl, type RealtimeSessionConfig, type RealtimeTool, createRealtimeManager, getEmotionSessionConfig, setEmotionTool };
102
+ export { DEFAULT_EMOTION_INSTRUCTIONS, type EmotionArgs, type RealtimeClient, RealtimeManagerImpl, createRealtimeManager, getEmotionSessionConfig, setEmotionTool };
package/dist/index.js CHANGED
@@ -17,7 +17,6 @@ var RealtimeManagerImpl = class {
17
17
  */
18
18
  setEventEmitter(eventEmitter) {
19
19
  this.eventEmitter = eventEmitter;
20
- console.log("\u{1F517} Realtime Manager: Event emitter connected");
21
20
  }
22
21
  /**
23
22
  * Realtime ์„ธ์…˜ ์‹œ์ž‘
@@ -26,21 +25,17 @@ var RealtimeManagerImpl = class {
26
25
  if (this.isSessionActive) {
27
26
  throw new Error("Realtime session already active");
28
27
  }
29
- console.log("\u{1F680} Starting Realtime session with config:", config);
30
28
  await this.client.connect(config);
31
29
  this.isSessionActive = true;
32
- console.log("\u2705 Realtime session started");
33
30
  }
34
31
  /**
35
32
  * Realtime ์„ธ์…˜ ์ข…๋ฃŒ
36
33
  */
37
34
  async stopSession() {
38
35
  if (!this.isSessionActive) return;
39
- console.log("\u{1F6D1} Stopping Realtime session");
40
36
  await this.client.disconnect();
41
37
  this.emitAudioEnd();
42
38
  this.isSessionActive = false;
43
- console.log("\u2705 Realtime session stopped");
44
39
  }
45
40
  /**
46
41
  * ํ…์ŠคํŠธ ๋ฉ”์‹œ์ง€ ์ „์†ก
@@ -90,7 +85,6 @@ var RealtimeManagerImpl = class {
90
85
  });
91
86
  }
92
87
  this.client.onError((error) => {
93
- console.error("Realtime client error:", error);
94
88
  this.emitAudioEnd();
95
89
  this.eventEmitter?.emit("realtime:error", { error });
96
90
  });
@@ -99,10 +93,10 @@ var RealtimeManagerImpl = class {
99
93
  * Tool call ์ฒ˜๋ฆฌ
100
94
  */
101
95
  handleToolCall(name, args) {
102
- if (name === "setEmotion") {
103
- console.log(`\u{1F3AD} [Realtime] Emotion update:`, args.emotion);
96
+ const emotion = args.emotion;
97
+ if (name === "setEmotion" && typeof emotion === "string") {
104
98
  this.eventEmitter?.emit("realtime:emotion", {
105
- emotion: args.emotion
99
+ emotion
106
100
  });
107
101
  }
108
102
  }
package/dist/index.js.map CHANGED
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/realtime-manager.ts","../src/tools.ts"],"names":["Emotion"],"mappings":"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","file":"index.js","sourcesContent":["import {\n CharivoEventEmitter,\n RealtimeManager as CoreRealtimeManager,\n RealtimeSessionConfig,\n RealtimeClient,\n} from \"./types\";\nimport { Emotion } from \"@charivo/core\";\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 CoreRealtimeManager {\n private client: RealtimeClient;\n private eventEmitter?: CharivoEventEmitter;\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: CharivoEventEmitter): void {\n this.eventEmitter = eventEmitter;\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 // ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ\n await this.client.connect(config);\n\n this.isSessionActive = true;\n }\n\n /**\n * Realtime ์„ธ์…˜ ์ข…๋ฃŒ\n */\n async stopSession(): Promise<void> {\n if (!this.isSessionActive) return;\n\n // ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ํ•ด์ œ\n await this.client.disconnect();\n this.emitAudioEnd();\n\n this.isSessionActive = false;\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: Record<string, unknown>) => {\n this.handleToolCall(name, args);\n });\n }\n\n // ์—๋Ÿฌ ์ฒ˜๋ฆฌ\n this.client.onError((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: Record<string, unknown>): void {\n const emotion = args.emotion;\n\n if (name === \"setEmotion\" && typeof emotion === \"string\") {\n this.eventEmitter?.emit(\"realtime:emotion\", {\n emotion: emotion as 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): CoreRealtimeManager {\n return new RealtimeManagerImpl(client);\n}\n","import {\n Emotion,\n type RealtimeSessionConfig,\n type RealtimeTool,\n} 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 CHANGED
@@ -15,7 +15,6 @@ var RealtimeManagerImpl = class {
15
15
  */
16
16
  setEventEmitter(eventEmitter) {
17
17
  this.eventEmitter = eventEmitter;
18
- console.log("\u{1F517} Realtime Manager: Event emitter connected");
19
18
  }
20
19
  /**
21
20
  * Realtime ์„ธ์…˜ ์‹œ์ž‘
@@ -24,21 +23,17 @@ var RealtimeManagerImpl = class {
24
23
  if (this.isSessionActive) {
25
24
  throw new Error("Realtime session already active");
26
25
  }
27
- console.log("\u{1F680} Starting Realtime session with config:", config);
28
26
  await this.client.connect(config);
29
27
  this.isSessionActive = true;
30
- console.log("\u2705 Realtime session started");
31
28
  }
32
29
  /**
33
30
  * Realtime ์„ธ์…˜ ์ข…๋ฃŒ
34
31
  */
35
32
  async stopSession() {
36
33
  if (!this.isSessionActive) return;
37
- console.log("\u{1F6D1} Stopping Realtime session");
38
34
  await this.client.disconnect();
39
35
  this.emitAudioEnd();
40
36
  this.isSessionActive = false;
41
- console.log("\u2705 Realtime session stopped");
42
37
  }
43
38
  /**
44
39
  * ํ…์ŠคํŠธ ๋ฉ”์‹œ์ง€ ์ „์†ก
@@ -88,7 +83,6 @@ var RealtimeManagerImpl = class {
88
83
  });
89
84
  }
90
85
  this.client.onError((error) => {
91
- console.error("Realtime client error:", error);
92
86
  this.emitAudioEnd();
93
87
  this.eventEmitter?.emit("realtime:error", { error });
94
88
  });
@@ -97,10 +91,10 @@ var RealtimeManagerImpl = class {
97
91
  * Tool call ์ฒ˜๋ฆฌ
98
92
  */
99
93
  handleToolCall(name, args) {
100
- if (name === "setEmotion") {
101
- console.log(`\u{1F3AD} [Realtime] Emotion update:`, args.emotion);
94
+ const emotion = args.emotion;
95
+ if (name === "setEmotion" && typeof emotion === "string") {
102
96
  this.eventEmitter?.emit("realtime:emotion", {
103
- emotion: args.emotion
97
+ emotion
104
98
  });
105
99
  }
106
100
  }
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/realtime-manager.ts","../src/tools.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import {\n CharivoEventEmitter,\n RealtimeManager as CoreRealtimeManager,\n RealtimeSessionConfig,\n RealtimeClient,\n} from \"./types\";\nimport { Emotion } from \"@charivo/core\";\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 CoreRealtimeManager {\n private client: RealtimeClient;\n private eventEmitter?: CharivoEventEmitter;\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: CharivoEventEmitter): void {\n this.eventEmitter = eventEmitter;\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 // ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ\n await this.client.connect(config);\n\n this.isSessionActive = true;\n }\n\n /**\n * Realtime ์„ธ์…˜ ์ข…๋ฃŒ\n */\n async stopSession(): Promise<void> {\n if (!this.isSessionActive) return;\n\n // ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ํ•ด์ œ\n await this.client.disconnect();\n this.emitAudioEnd();\n\n this.isSessionActive = false;\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: Record<string, unknown>) => {\n this.handleToolCall(name, args);\n });\n }\n\n // ์—๋Ÿฌ ์ฒ˜๋ฆฌ\n this.client.onError((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: Record<string, unknown>): void {\n const emotion = args.emotion;\n\n if (name === \"setEmotion\" && typeof emotion === \"string\") {\n this.eventEmitter?.emit(\"realtime:emotion\", {\n emotion: emotion as 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): CoreRealtimeManager {\n return new RealtimeManagerImpl(client);\n}\n","import {\n Emotion,\n type RealtimeSessionConfig,\n type RealtimeTool,\n} 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 CHANGED
@@ -1,11 +1,19 @@
1
1
  {
2
2
  "name": "@charivo/realtime-core",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Core utilities for Realtime API functionality in Charivo",
5
5
  "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
6
7
  "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
7
15
  "dependencies": {
8
- "@charivo/core": "0.0.1"
16
+ "@charivo/core": "0.1.0"
9
17
  },
10
18
  "devDependencies": {
11
19
  "tsup": "^8.0.0",
@@ -34,6 +42,7 @@
34
42
  "scripts": {
35
43
  "build": "tsup",
36
44
  "dev": "tsup --watch",
37
- "clean": "rm -rf dist"
45
+ "clean": "rm -rf dist",
46
+ "typecheck": "tsc --noEmit"
38
47
  }
39
48
  }