react-native-agentic-ai 0.4.6 → 0.5.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.
Files changed (43) hide show
  1. package/README.md +80 -4
  2. package/lib/module/components/AIAgent.js +179 -38
  3. package/lib/module/components/AIAgent.js.map +1 -1
  4. package/lib/module/components/AgentChatBar.js +53 -29
  5. package/lib/module/components/AgentChatBar.js.map +1 -1
  6. package/lib/module/components/Icons.js +337 -0
  7. package/lib/module/components/Icons.js.map +1 -0
  8. package/lib/module/core/AgentRuntime.js +74 -3
  9. package/lib/module/core/AgentRuntime.js.map +1 -1
  10. package/lib/module/core/systemPrompt.js +66 -39
  11. package/lib/module/core/systemPrompt.js.map +1 -1
  12. package/lib/module/index.js +3 -9
  13. package/lib/module/index.js.map +1 -1
  14. package/lib/module/services/AudioInputService.js +73 -2
  15. package/lib/module/services/AudioInputService.js.map +1 -1
  16. package/lib/module/services/AudioOutputService.js +58 -5
  17. package/lib/module/services/AudioOutputService.js.map +1 -1
  18. package/lib/module/services/VoiceService.js +281 -275
  19. package/lib/module/services/VoiceService.js.map +1 -1
  20. package/lib/typescript/src/components/AIAgent.d.ts.map +1 -1
  21. package/lib/typescript/src/components/AgentChatBar.d.ts.map +1 -1
  22. package/lib/typescript/src/components/Icons.d.ts +43 -0
  23. package/lib/typescript/src/components/Icons.d.ts.map +1 -0
  24. package/lib/typescript/src/core/AgentRuntime.d.ts +12 -0
  25. package/lib/typescript/src/core/AgentRuntime.d.ts.map +1 -1
  26. package/lib/typescript/src/core/systemPrompt.d.ts.map +1 -1
  27. package/lib/typescript/src/index.d.ts +4 -0
  28. package/lib/typescript/src/index.d.ts.map +1 -1
  29. package/lib/typescript/src/services/AudioInputService.d.ts +13 -0
  30. package/lib/typescript/src/services/AudioInputService.d.ts.map +1 -1
  31. package/lib/typescript/src/services/AudioOutputService.d.ts.map +1 -1
  32. package/lib/typescript/src/services/VoiceService.d.ts +38 -29
  33. package/lib/typescript/src/services/VoiceService.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/components/AIAgent.tsx +192 -39
  36. package/src/components/AgentChatBar.tsx +44 -25
  37. package/src/components/Icons.tsx +253 -0
  38. package/src/core/AgentRuntime.ts +70 -3
  39. package/src/core/systemPrompt.ts +66 -39
  40. package/src/index.ts +8 -8
  41. package/src/services/AudioInputService.ts +77 -2
  42. package/src/services/AudioOutputService.ts +59 -5
  43. package/src/services/VoiceService.ts +278 -290
@@ -55,6 +55,21 @@ export class AudioOutputService {
55
55
 
56
56
  const sampleRate = this.config.sampleRate || GEMINI_OUTPUT_SAMPLE_RATE;
57
57
 
58
+ // Configure audio session for duplex audio (simultaneous mic + speaker)
59
+ // BEFORE creating AudioContext. This enables hardware-level AEC, AGC,
60
+ // and noise suppression through the OS — no extra library needed.
61
+ try {
62
+ const { AudioManager } = audioApi;
63
+ AudioManager.setAudioSessionOptions({
64
+ iosCategory: 'playAndRecord',
65
+ iosMode: 'voiceChat',
66
+ iosOptions: ['defaultToSpeaker', 'allowBluetoothHFP'],
67
+ });
68
+ logger.info('AudioOutput', '🔊 Audio session configured: playAndRecord + voiceChat (hardware AEC enabled)');
69
+ } catch (sessionErr: any) {
70
+ logger.warn('AudioOutput', `⚠️ AudioManager setup failed: ${sessionErr?.message || sessionErr} — continuing with default session`);
71
+ }
72
+
58
73
  // Create AudioContext at Gemini's output sample rate
59
74
  this.audioContext = new audioApi.AudioContext({ sampleRate });
60
75
 
@@ -68,6 +83,14 @@ export class AudioOutputService {
68
83
  this.queueSourceNode.connect(this.gainNode);
69
84
 
70
85
  logger.info('AudioOutput', `Initialized (${sampleRate}Hz, AudioBufferQueueSourceNode)`);
86
+
87
+ // CRITICAL: Resume AudioContext — it starts in 'suspended' state.
88
+ // Per Web Audio API spec, audio won't render until context is 'running'.
89
+ if (this.audioContext.state === 'suspended') {
90
+ await this.audioContext.resume();
91
+ logger.info('AudioOutput', `AudioContext resumed: state=${this.audioContext.state}`);
92
+ }
93
+
71
94
  return true;
72
95
  } catch (error: any) {
73
96
  logger.error('AudioOutput', `Failed to initialize: ${error.message}`);
@@ -80,7 +103,15 @@ export class AudioOutputService {
80
103
 
81
104
  /** Add a base64-encoded PCM chunk from Gemini to the playback queue */
82
105
  enqueue(base64Audio: string): void {
83
- if (this.muted || !this.audioContext || !this.queueSourceNode) return;
106
+ // LOG EVERY CALL we need full visibility
107
+ if (this.chunkCount < 5 || this.chunkCount % 50 === 0) {
108
+ logger.info('AudioOutput', `📥 enqueue() called #${this.chunkCount + 1}: b64len=${base64Audio.length}, muted=${this.muted}, ctx=${!!this.audioContext}, ctxState=${this.audioContext?.state || 'null'}, queue=${!!this.queueSourceNode}, started=${this.isStarted}, gain=${this.gainNode?.gain?.value}`);
109
+ }
110
+
111
+ if (this.muted || !this.audioContext || !this.queueSourceNode) {
112
+ logger.warn('AudioOutput', `⚠️ enqueue() SKIPPED #${this.chunkCount}: muted=${this.muted}, ctx=${!!this.audioContext}, queue=${!!this.queueSourceNode}`);
113
+ return;
114
+ }
84
115
 
85
116
  try {
86
117
  this.chunkCount++;
@@ -89,26 +120,39 @@ export class AudioOutputService {
89
120
  const float32Data = base64ToFloat32(base64Audio);
90
121
  const sampleRate = this.config.sampleRate || GEMINI_OUTPUT_SAMPLE_RATE;
91
122
 
123
+ // Diagnostic on first 3 chunks
124
+ if (this.chunkCount <= 3) {
125
+ let peakAmp = 0;
126
+ for (let i = 0; i < float32Data.length; i++) {
127
+ const abs = Math.abs(float32Data[i] || 0);
128
+ if (abs > peakAmp) peakAmp = abs;
129
+ }
130
+ logger.info('AudioOutput', `🔍 Chunk #${this.chunkCount}: ${base64Audio.length} b64 → ${float32Data.length} samples, peakAmp=${peakAmp.toFixed(4)}, rate=${sampleRate}`);
131
+ }
132
+
92
133
  // Create an AudioBuffer and fill it with PCM data
93
134
  const audioBuffer = this.audioContext.createBuffer(1, float32Data.length, sampleRate);
94
135
  audioBuffer.copyToChannel(float32Data, 0);
95
136
 
96
137
  // Enqueue the buffer for gapless playback
97
138
  this.queueSourceNode.enqueueBuffer(audioBuffer);
139
+ if (this.chunkCount <= 3) {
140
+ logger.info('AudioOutput', `✅ Buffer enqueued #${this.chunkCount}`);
141
+ }
98
142
 
99
143
  // Start playback on first enqueue
100
144
  if (!this.isStarted) {
101
145
  this.queueSourceNode.start();
102
146
  this.isStarted = true;
103
147
  this.config.onPlaybackStart?.();
104
- logger.info('AudioOutput', '▶️ Playback started');
148
+ logger.info('AudioOutput', `▶️ Playback started — ctxState=${this.audioContext?.state}`);
105
149
  }
106
150
 
107
151
  if (this.chunkCount % 20 === 0) {
108
- logger.debug('AudioOutput', `Queued chunk #${this.chunkCount}`);
152
+ logger.info('AudioOutput', `Queued chunk #${this.chunkCount}`);
109
153
  }
110
154
  } catch (error: any) {
111
- logger.error('AudioOutput', `Enqueue error: ${error.message}`);
155
+ logger.error('AudioOutput', `❌ Enqueue error #${this.chunkCount}: ${error.message}\n${error.stack?.substring(0, 300)}`);
112
156
  }
113
157
  }
114
158
 
@@ -144,8 +188,18 @@ export class AudioOutputService {
144
188
  }
145
189
  this.isStarted = false;
146
190
  this.chunkCount = 0;
191
+
192
+ // Web Audio API: once a source node is stopped, it CANNOT be restarted.
193
+ // We must create a fresh AudioBufferQueueSourceNode for the next session.
194
+ if (this.audioContext && this.gainNode) {
195
+ this.queueSourceNode = this.audioContext.createBufferQueueSource();
196
+ this.queueSourceNode.connect(this.gainNode);
197
+ logger.info('AudioOutput', 'Playback stopped — fresh queue node created for next session');
198
+ } else {
199
+ logger.info('AudioOutput', 'Playback stopped');
200
+ }
201
+
147
202
  this.config.onPlaybackEnd?.();
148
- logger.info('AudioOutput', 'Playback stopped');
149
203
  } catch (error: any) {
150
204
  logger.error('AudioOutput', `Stop error: ${error.message}`);
151
205
  }