@mobileai/react-native 0.9.16 → 0.9.18

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 (214) hide show
  1. package/README.md +2 -2
  2. package/package.json +5 -8
  3. package/lib/module/__cli_tmp__.js.map +0 -1
  4. package/lib/module/components/AIAgent.js.map +0 -1
  5. package/lib/module/components/AIZone.js.map +0 -1
  6. package/lib/module/components/AgentChatBar.js.map +0 -1
  7. package/lib/module/components/AgentErrorBoundary.js.map +0 -1
  8. package/lib/module/components/AgentOverlay.js.map +0 -1
  9. package/lib/module/components/DiscoveryTooltip.js.map +0 -1
  10. package/lib/module/components/HighlightOverlay.js.map +0 -1
  11. package/lib/module/components/Icons.js.map +0 -1
  12. package/lib/module/components/ProactiveHint.js.map +0 -1
  13. package/lib/module/components/cards/InfoCard.js.map +0 -1
  14. package/lib/module/components/cards/ReviewSummary.js.map +0 -1
  15. package/lib/module/config/endpoints.js.map +0 -1
  16. package/lib/module/core/ActionRegistry.js.map +0 -1
  17. package/lib/module/core/AgentRuntime.js.map +0 -1
  18. package/lib/module/core/FiberTreeWalker.js.map +0 -1
  19. package/lib/module/core/IdleDetector.js.map +0 -1
  20. package/lib/module/core/MCPBridge.js.map +0 -1
  21. package/lib/module/core/ScreenDehydrator.js.map +0 -1
  22. package/lib/module/core/ZoneRegistry.js.map +0 -1
  23. package/lib/module/core/systemPrompt.js.map +0 -1
  24. package/lib/module/core/types.js.map +0 -1
  25. package/lib/module/hooks/useAction.js.map +0 -1
  26. package/lib/module/index.js.map +0 -1
  27. package/lib/module/plugin/withAppIntents.js.map +0 -1
  28. package/lib/module/providers/GeminiProvider.js.map +0 -1
  29. package/lib/module/providers/OpenAIProvider.js.map +0 -1
  30. package/lib/module/providers/ProviderFactory.js.map +0 -1
  31. package/lib/module/services/AudioInputService.js.map +0 -1
  32. package/lib/module/services/AudioOutputService.js.map +0 -1
  33. package/lib/module/services/KnowledgeBaseService.js.map +0 -1
  34. package/lib/module/services/VoiceService.js.map +0 -1
  35. package/lib/module/services/flags/FlagService.js.map +0 -1
  36. package/lib/module/services/telemetry/MobileAI.js.map +0 -1
  37. package/lib/module/services/telemetry/PiiScrubber.js.map +0 -1
  38. package/lib/module/services/telemetry/TelemetryService.js.map +0 -1
  39. package/lib/module/services/telemetry/TouchAutoCapture.js.map +0 -1
  40. package/lib/module/services/telemetry/device.js.map +0 -1
  41. package/lib/module/services/telemetry/deviceMetadata.js.map +0 -1
  42. package/lib/module/services/telemetry/index.js.map +0 -1
  43. package/lib/module/services/telemetry/types.js.map +0 -1
  44. package/lib/module/support/CSATSurvey.js.map +0 -1
  45. package/lib/module/support/EscalationEventSource.js.map +0 -1
  46. package/lib/module/support/EscalationSocket.js.map +0 -1
  47. package/lib/module/support/SupportChatModal.js.map +0 -1
  48. package/lib/module/support/SupportGreeting.js.map +0 -1
  49. package/lib/module/support/TicketStore.js.map +0 -1
  50. package/lib/module/support/escalateTool.js.map +0 -1
  51. package/lib/module/support/index.js.map +0 -1
  52. package/lib/module/support/supportPrompt.js.map +0 -1
  53. package/lib/module/support/types.js.map +0 -1
  54. package/lib/module/tools/datePickerTool.js.map +0 -1
  55. package/lib/module/tools/guideTool.js.map +0 -1
  56. package/lib/module/tools/index.js.map +0 -1
  57. package/lib/module/tools/keyboardTool.js.map +0 -1
  58. package/lib/module/tools/longPressTool.js.map +0 -1
  59. package/lib/module/tools/pickerTool.js.map +0 -1
  60. package/lib/module/tools/restoreTool.js.map +0 -1
  61. package/lib/module/tools/scrollTool.js.map +0 -1
  62. package/lib/module/tools/simplifyTool.js.map +0 -1
  63. package/lib/module/tools/sliderTool.js.map +0 -1
  64. package/lib/module/tools/tapTool.js.map +0 -1
  65. package/lib/module/tools/typeTool.js.map +0 -1
  66. package/lib/module/tools/types.js.map +0 -1
  67. package/lib/module/types/jsx.d.js.map +0 -1
  68. package/lib/module/utils/audioUtils.js.map +0 -1
  69. package/lib/module/utils/logger.js.map +0 -1
  70. package/lib/typescript/babel.config.d.ts.map +0 -1
  71. package/lib/typescript/bin/generate-map.d.cts.map +0 -1
  72. package/lib/typescript/eslint.config.d.mts.map +0 -1
  73. package/lib/typescript/generate-map.d.ts.map +0 -1
  74. package/lib/typescript/src/__cli_tmp__.d.ts.map +0 -1
  75. package/lib/typescript/src/components/AIAgent.d.ts.map +0 -1
  76. package/lib/typescript/src/components/AIZone.d.ts.map +0 -1
  77. package/lib/typescript/src/components/AgentChatBar.d.ts.map +0 -1
  78. package/lib/typescript/src/components/AgentErrorBoundary.d.ts.map +0 -1
  79. package/lib/typescript/src/components/AgentOverlay.d.ts.map +0 -1
  80. package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +0 -1
  81. package/lib/typescript/src/components/HighlightOverlay.d.ts.map +0 -1
  82. package/lib/typescript/src/components/Icons.d.ts.map +0 -1
  83. package/lib/typescript/src/components/ProactiveHint.d.ts.map +0 -1
  84. package/lib/typescript/src/components/cards/InfoCard.d.ts.map +0 -1
  85. package/lib/typescript/src/components/cards/ReviewSummary.d.ts.map +0 -1
  86. package/lib/typescript/src/config/endpoints.d.ts.map +0 -1
  87. package/lib/typescript/src/core/ActionRegistry.d.ts.map +0 -1
  88. package/lib/typescript/src/core/AgentRuntime.d.ts.map +0 -1
  89. package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +0 -1
  90. package/lib/typescript/src/core/IdleDetector.d.ts.map +0 -1
  91. package/lib/typescript/src/core/MCPBridge.d.ts.map +0 -1
  92. package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +0 -1
  93. package/lib/typescript/src/core/ZoneRegistry.d.ts.map +0 -1
  94. package/lib/typescript/src/core/systemPrompt.d.ts.map +0 -1
  95. package/lib/typescript/src/core/types.d.ts.map +0 -1
  96. package/lib/typescript/src/hooks/useAction.d.ts.map +0 -1
  97. package/lib/typescript/src/index.d.ts.map +0 -1
  98. package/lib/typescript/src/plugin/withAppIntents.d.ts.map +0 -1
  99. package/lib/typescript/src/providers/GeminiProvider.d.ts.map +0 -1
  100. package/lib/typescript/src/providers/OpenAIProvider.d.ts.map +0 -1
  101. package/lib/typescript/src/providers/ProviderFactory.d.ts.map +0 -1
  102. package/lib/typescript/src/services/AudioInputService.d.ts.map +0 -1
  103. package/lib/typescript/src/services/AudioOutputService.d.ts.map +0 -1
  104. package/lib/typescript/src/services/KnowledgeBaseService.d.ts.map +0 -1
  105. package/lib/typescript/src/services/VoiceService.d.ts.map +0 -1
  106. package/lib/typescript/src/services/flags/FlagService.d.ts.map +0 -1
  107. package/lib/typescript/src/services/telemetry/MobileAI.d.ts.map +0 -1
  108. package/lib/typescript/src/services/telemetry/PiiScrubber.d.ts.map +0 -1
  109. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts.map +0 -1
  110. package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts.map +0 -1
  111. package/lib/typescript/src/services/telemetry/device.d.ts.map +0 -1
  112. package/lib/typescript/src/services/telemetry/deviceMetadata.d.ts.map +0 -1
  113. package/lib/typescript/src/services/telemetry/index.d.ts.map +0 -1
  114. package/lib/typescript/src/services/telemetry/types.d.ts.map +0 -1
  115. package/lib/typescript/src/support/CSATSurvey.d.ts.map +0 -1
  116. package/lib/typescript/src/support/EscalationEventSource.d.ts.map +0 -1
  117. package/lib/typescript/src/support/EscalationSocket.d.ts.map +0 -1
  118. package/lib/typescript/src/support/SupportChatModal.d.ts.map +0 -1
  119. package/lib/typescript/src/support/SupportGreeting.d.ts.map +0 -1
  120. package/lib/typescript/src/support/TicketStore.d.ts.map +0 -1
  121. package/lib/typescript/src/support/escalateTool.d.ts.map +0 -1
  122. package/lib/typescript/src/support/index.d.ts.map +0 -1
  123. package/lib/typescript/src/support/supportPrompt.d.ts.map +0 -1
  124. package/lib/typescript/src/support/types.d.ts.map +0 -1
  125. package/lib/typescript/src/tools/datePickerTool.d.ts.map +0 -1
  126. package/lib/typescript/src/tools/guideTool.d.ts.map +0 -1
  127. package/lib/typescript/src/tools/index.d.ts.map +0 -1
  128. package/lib/typescript/src/tools/keyboardTool.d.ts.map +0 -1
  129. package/lib/typescript/src/tools/longPressTool.d.ts.map +0 -1
  130. package/lib/typescript/src/tools/pickerTool.d.ts.map +0 -1
  131. package/lib/typescript/src/tools/restoreTool.d.ts.map +0 -1
  132. package/lib/typescript/src/tools/scrollTool.d.ts.map +0 -1
  133. package/lib/typescript/src/tools/simplifyTool.d.ts.map +0 -1
  134. package/lib/typescript/src/tools/sliderTool.d.ts.map +0 -1
  135. package/lib/typescript/src/tools/tapTool.d.ts.map +0 -1
  136. package/lib/typescript/src/tools/typeTool.d.ts.map +0 -1
  137. package/lib/typescript/src/tools/types.d.ts.map +0 -1
  138. package/lib/typescript/src/utils/audioUtils.d.ts.map +0 -1
  139. package/lib/typescript/src/utils/logger.d.ts.map +0 -1
  140. package/src/__cli_tmp__.tsx +0 -9
  141. package/src/cli/analyzers/chain-analyzer.ts +0 -183
  142. package/src/cli/extractors/ai-extractor.ts +0 -6
  143. package/src/cli/extractors/ast-extractor.ts +0 -551
  144. package/src/cli/generate-intents.ts +0 -140
  145. package/src/cli/generate-map.ts +0 -121
  146. package/src/cli/generate-swift.ts +0 -116
  147. package/src/cli/scanners/expo-scanner.ts +0 -203
  148. package/src/cli/scanners/rn-scanner.ts +0 -445
  149. package/src/components/AIAgent.tsx +0 -1716
  150. package/src/components/AIZone.tsx +0 -147
  151. package/src/components/AgentChatBar.tsx +0 -1143
  152. package/src/components/AgentErrorBoundary.tsx +0 -78
  153. package/src/components/AgentOverlay.tsx +0 -73
  154. package/src/components/DiscoveryTooltip.tsx +0 -148
  155. package/src/components/HighlightOverlay.tsx +0 -136
  156. package/src/components/Icons.tsx +0 -253
  157. package/src/components/ProactiveHint.tsx +0 -145
  158. package/src/components/cards/InfoCard.tsx +0 -58
  159. package/src/components/cards/ReviewSummary.tsx +0 -76
  160. package/src/config/endpoints.ts +0 -22
  161. package/src/core/ActionRegistry.ts +0 -105
  162. package/src/core/AgentRuntime.ts +0 -1471
  163. package/src/core/FiberTreeWalker.ts +0 -930
  164. package/src/core/IdleDetector.ts +0 -72
  165. package/src/core/MCPBridge.ts +0 -163
  166. package/src/core/ScreenDehydrator.ts +0 -53
  167. package/src/core/ZoneRegistry.ts +0 -44
  168. package/src/core/systemPrompt.ts +0 -431
  169. package/src/core/types.ts +0 -521
  170. package/src/hooks/useAction.ts +0 -182
  171. package/src/index.ts +0 -83
  172. package/src/plugin/withAppIntents.ts +0 -98
  173. package/src/providers/GeminiProvider.ts +0 -357
  174. package/src/providers/OpenAIProvider.ts +0 -379
  175. package/src/providers/ProviderFactory.ts +0 -36
  176. package/src/services/AudioInputService.ts +0 -226
  177. package/src/services/AudioOutputService.ts +0 -236
  178. package/src/services/KnowledgeBaseService.ts +0 -156
  179. package/src/services/VoiceService.ts +0 -451
  180. package/src/services/flags/FlagService.ts +0 -137
  181. package/src/services/telemetry/MobileAI.ts +0 -66
  182. package/src/services/telemetry/PiiScrubber.ts +0 -17
  183. package/src/services/telemetry/TelemetryService.ts +0 -323
  184. package/src/services/telemetry/TouchAutoCapture.ts +0 -165
  185. package/src/services/telemetry/device.ts +0 -93
  186. package/src/services/telemetry/deviceMetadata.ts +0 -13
  187. package/src/services/telemetry/index.ts +0 -13
  188. package/src/services/telemetry/types.ts +0 -75
  189. package/src/support/CSATSurvey.tsx +0 -304
  190. package/src/support/EscalationEventSource.ts +0 -190
  191. package/src/support/EscalationSocket.ts +0 -152
  192. package/src/support/SupportChatModal.tsx +0 -563
  193. package/src/support/SupportGreeting.tsx +0 -161
  194. package/src/support/TicketStore.ts +0 -100
  195. package/src/support/escalateTool.ts +0 -174
  196. package/src/support/index.ts +0 -29
  197. package/src/support/supportPrompt.ts +0 -55
  198. package/src/support/types.ts +0 -155
  199. package/src/tools/datePickerTool.ts +0 -60
  200. package/src/tools/guideTool.ts +0 -76
  201. package/src/tools/index.ts +0 -20
  202. package/src/tools/keyboardTool.ts +0 -30
  203. package/src/tools/longPressTool.ts +0 -61
  204. package/src/tools/pickerTool.ts +0 -115
  205. package/src/tools/restoreTool.ts +0 -33
  206. package/src/tools/scrollTool.ts +0 -156
  207. package/src/tools/simplifyTool.ts +0 -33
  208. package/src/tools/sliderTool.ts +0 -65
  209. package/src/tools/tapTool.ts +0 -93
  210. package/src/tools/typeTool.ts +0 -113
  211. package/src/tools/types.ts +0 -58
  212. package/src/types/jsx.d.ts +0 -20
  213. package/src/utils/audioUtils.ts +0 -54
  214. package/src/utils/logger.ts +0 -38
@@ -1,379 +0,0 @@
1
- /**
2
- * OpenAIProvider — OpenAI Chat Completions API via raw fetch.
3
- *
4
- * Uses the same flat `agent_step` function pattern as GeminiProvider:
5
- * - Reasoning fields (previous_goal_eval, memory, plan) + action in one tool call
6
- * - `tool_choice: "required"` forces a tool call every step
7
- * - `strict: true` guarantees schema adherence
8
- *
9
- * No SDK dependency — raw fetch for full React Native compatibility.
10
- * Implements the AIProvider interface so it can be swapped with GeminiProvider.
11
- */
12
-
13
- import { logger } from '../utils/logger';
14
- import type {
15
- AIProvider,
16
- ToolDefinition,
17
- AgentStep,
18
- ProviderResult,
19
- AgentReasoning,
20
- TokenUsage,
21
- } from '../core/types';
22
-
23
- // ─── Constants ─────────────────────────────────────────────────
24
-
25
- const AGENT_STEP_FN = 'agent_step';
26
- const OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions';
27
- const REASONING_FIELDS = ['previous_goal_eval', 'memory', 'plan'] as const;
28
-
29
- // ─── Provider ──────────────────────────────────────────────────
30
-
31
- export class OpenAIProvider implements AIProvider {
32
- private model: string;
33
- private baseUrl: string;
34
- private headers: Record<string, string>;
35
-
36
- constructor(
37
- apiKey?: string,
38
- model: string = 'gpt-4.1-mini',
39
- proxyUrl?: string,
40
- proxyHeaders?: Record<string, string>,
41
- ) {
42
- if (proxyUrl) {
43
- this.baseUrl = proxyUrl.endsWith('/')
44
- ? `${proxyUrl}v1/chat/completions`
45
- : proxyUrl;
46
- this.headers = {
47
- 'Content-Type': 'application/json',
48
- ...(proxyHeaders || {}),
49
- };
50
- } else if (apiKey) {
51
- this.baseUrl = OPENAI_API_URL;
52
- this.headers = {
53
- 'Content-Type': 'application/json',
54
- Authorization: `Bearer ${apiKey}`,
55
- };
56
- } else {
57
- throw new Error(
58
- '[mobileai] You must provide either "apiKey" or "proxyUrl" to use OpenAI provider.',
59
- );
60
- }
61
-
62
- this.model = model;
63
- }
64
-
65
- async generateContent(
66
- systemPrompt: string,
67
- userMessage: string,
68
- tools: ToolDefinition[],
69
- _history: AgentStep[],
70
- screenshot?: string,
71
- ): Promise<ProviderResult> {
72
- logger.info(
73
- 'OpenAIProvider',
74
- `Sending request. Model: ${this.model}, Tools: ${tools.length}${screenshot ? ', with screenshot' : ''}`,
75
- );
76
-
77
- const agentStepTool = this.buildAgentStepTool(tools);
78
- const messages = this.buildMessages(systemPrompt, userMessage, screenshot);
79
-
80
- const startTime = Date.now();
81
-
82
- try {
83
- const response = await fetch(this.baseUrl, {
84
- method: 'POST',
85
- headers: this.headers,
86
- body: JSON.stringify({
87
- model: this.model,
88
- messages,
89
- tools: [agentStepTool],
90
- tool_choice: 'required',
91
- temperature: 0.2,
92
- max_tokens: 2048,
93
- }),
94
- });
95
-
96
- if (!response.ok) {
97
- const errorBody = await response.text();
98
- throw new Error(`OpenAI API error ${response.status}: ${errorBody}`);
99
- }
100
-
101
- const data = await response.json();
102
- const elapsed = Date.now() - startTime;
103
- logger.info('OpenAIProvider', `Response received in ${elapsed}ms`);
104
-
105
- const tokenUsage = this.extractTokenUsage(data);
106
- if (tokenUsage) {
107
- logger.info(
108
- 'OpenAIProvider',
109
- `Tokens: ${tokenUsage.promptTokens} in / ${tokenUsage.completionTokens} out / $${tokenUsage.estimatedCostUSD.toFixed(6)}`,
110
- );
111
- }
112
-
113
- const result = this.parseAgentStepResponse(data, tools);
114
- result.tokenUsage = tokenUsage;
115
- return result;
116
- } catch (error: any) {
117
- logger.error('OpenAIProvider', 'Request failed:', error.message);
118
- throw error;
119
- }
120
- }
121
-
122
- // ─── Build agent_step Tool ──────────────────────────────────
123
-
124
- /**
125
- * Builds the OpenAI tool definition for `agent_step`.
126
- * Same flat pattern as Gemini — reasoning fields + action in one function.
127
- * Uses `strict: true` for guaranteed schema adherence.
128
- */
129
- private buildAgentStepTool(tools: ToolDefinition[]): any {
130
- const toolNames = tools.map((t) => t.name);
131
-
132
- // Collect all unique parameter fields across all tools
133
- const actionProperties: Record<string, any> = {};
134
- for (const tool of tools) {
135
- for (const [paramName, param] of Object.entries(tool.parameters)) {
136
- if (actionProperties[paramName]) continue;
137
- actionProperties[paramName] = {
138
- type: param.type,
139
- description: param.description,
140
- ...(param.enum ? { enum: param.enum } : {}),
141
- };
142
- }
143
- }
144
-
145
- // Build tool descriptions for enum
146
- const toolDescriptions = tools
147
- .map((t) => {
148
- const params = Object.keys(t.parameters).join(', ');
149
- return `- ${t.name}(${params}): ${t.description}`;
150
- })
151
- .join('\n');
152
-
153
- // OpenAI strict mode requires additionalProperties: false
154
- // and ALL properties in `required` array
155
- const allProperties: Record<string, any> = {
156
- previous_goal_eval: {
157
- type: 'string',
158
- description:
159
- 'One-sentence assessment of your last action. State success, failure, or uncertain. Skip on first step.',
160
- },
161
- memory: {
162
- type: 'string',
163
- description:
164
- 'Key facts to remember for future steps: progress made, items found, counters, field values already collected.',
165
- },
166
- plan: {
167
- type: 'string',
168
- description:
169
- 'Your immediate next goal — what action you will take and why.',
170
- },
171
- action_name: {
172
- type: 'string',
173
- description: 'Which action to execute.',
174
- enum: toolNames,
175
- },
176
- ...actionProperties,
177
- };
178
-
179
- return {
180
- type: 'function',
181
- function: {
182
- name: AGENT_STEP_FN,
183
- description: `Execute one agent step. Choose an action and provide reasoning.\n\nAvailable actions:\n${toolDescriptions}`,
184
- parameters: {
185
- type: 'object',
186
- properties: allProperties,
187
- required: Object.keys(allProperties),
188
- additionalProperties: false,
189
- },
190
- strict: true,
191
- },
192
- };
193
- }
194
-
195
- // ─── Build Messages ────────────────────────────────────────
196
-
197
- private buildMessages(
198
- systemPrompt: string,
199
- userMessage: string,
200
- screenshot?: string,
201
- ): any[] {
202
- const messages: any[] = [
203
- { role: 'system', content: systemPrompt },
204
- ];
205
-
206
- // User message — text or multimodal with screenshot
207
- if (screenshot) {
208
- messages.push({
209
- role: 'user',
210
- content: [
211
- { type: 'text', text: userMessage },
212
- {
213
- type: 'image_url',
214
- image_url: {
215
- url: `data:image/jpeg;base64,${screenshot}`,
216
- detail: 'low',
217
- },
218
- },
219
- ],
220
- });
221
- } else {
222
- messages.push({ role: 'user', content: userMessage });
223
- }
224
-
225
- return messages;
226
- }
227
-
228
- // ─── Parse Response ────────────────────────────────────────
229
-
230
- private parseAgentStepResponse(
231
- data: any,
232
- tools: ToolDefinition[],
233
- ): ProviderResult {
234
- const choice = data.choices?.[0];
235
-
236
- if (!choice) {
237
- logger.warn('OpenAIProvider', 'No choices in response');
238
- return {
239
- toolCalls: [
240
- {
241
- name: 'done',
242
- args: { text: 'No response generated.', success: false },
243
- },
244
- ],
245
- reasoning: { previousGoalEval: '', memory: '', plan: '' },
246
- text: 'No response generated.',
247
- };
248
- }
249
-
250
- const message = choice.message;
251
- const toolCall = message?.tool_calls?.[0];
252
-
253
- if (!toolCall?.function) {
254
- logger.warn(
255
- 'OpenAIProvider',
256
- 'No tool call in response. Text:',
257
- message?.content,
258
- );
259
- return {
260
- toolCalls: [
261
- {
262
- name: 'done',
263
- args: {
264
- text: message?.content || 'No action taken.',
265
- success: false,
266
- },
267
- },
268
- ],
269
- reasoning: { previousGoalEval: '', memory: '', plan: '' },
270
- text: message?.content,
271
- };
272
- }
273
-
274
- // OpenAI returns arguments as a JSON STRING — must parse
275
- let args: Record<string, any>;
276
- try {
277
- args = JSON.parse(toolCall.function.arguments);
278
- } catch (err) {
279
- logger.error(
280
- 'OpenAIProvider',
281
- 'Failed to parse tool arguments:',
282
- toolCall.function.arguments,
283
- );
284
- return {
285
- toolCalls: [
286
- {
287
- name: 'done',
288
- args: { text: 'Failed to parse AI response.', success: false },
289
- },
290
- ],
291
- reasoning: { previousGoalEval: '', memory: '', plan: '' },
292
- };
293
- }
294
-
295
- // Extract reasoning fields
296
- const reasoning: AgentReasoning = {
297
- previousGoalEval: args.previous_goal_eval || '',
298
- memory: args.memory || '',
299
- plan: args.plan || '',
300
- };
301
-
302
- // Extract action
303
- const actionName = args.action_name;
304
- if (!actionName) {
305
- logger.warn(
306
- 'OpenAIProvider',
307
- 'No action_name in agent_step. Falling back to done.',
308
- );
309
- return {
310
- toolCalls: [
311
- {
312
- name: 'done',
313
- args: { text: 'Agent did not choose an action.', success: false },
314
- },
315
- ],
316
- reasoning,
317
- text: message?.content,
318
- };
319
- }
320
-
321
- // Build action args: extract only the params that belong to the matched tool
322
- const actionArgs: Record<string, any> = {};
323
- const reservedKeys = new Set([...REASONING_FIELDS, 'action_name']);
324
-
325
- const matchedTool = tools.find((t) => t.name === actionName);
326
- if (matchedTool) {
327
- for (const paramName of Object.keys(matchedTool.parameters)) {
328
- if (args[paramName] !== undefined) {
329
- actionArgs[paramName] = args[paramName];
330
- }
331
- }
332
- } else {
333
- for (const [key, value] of Object.entries(args)) {
334
- if (!reservedKeys.has(key)) {
335
- actionArgs[key] = value;
336
- }
337
- }
338
- }
339
-
340
- logger.info(
341
- 'OpenAIProvider',
342
- `Parsed: action=${actionName}, plan="${reasoning.plan}"`,
343
- );
344
-
345
- return {
346
- toolCalls: [{ name: actionName, args: actionArgs }],
347
- reasoning,
348
- text: message?.content,
349
- };
350
- }
351
-
352
- // ─── Token Usage Extraction ─────────────────────────────────
353
-
354
- /**
355
- * Extracts token usage from OpenAI response and calculates estimated cost.
356
- *
357
- * Pricing (GPT-4.1-mini):
358
- * - Input: $0.40 / 1M tokens
359
- * - Output: $1.60 / 1M tokens
360
- */
361
- private extractTokenUsage(data: any): TokenUsage | undefined {
362
- const usage = data?.usage;
363
- if (!usage) return undefined;
364
-
365
- const promptTokens = usage.prompt_tokens ?? 0;
366
- const completionTokens = usage.completion_tokens ?? 0;
367
- const totalTokens = usage.total_tokens ?? promptTokens + completionTokens;
368
-
369
- // Cost estimation based on GPT-4.1-mini pricing
370
- const INPUT_COST_PER_M = 0.4;
371
- const OUTPUT_COST_PER_M = 1.6;
372
-
373
- const estimatedCostUSD =
374
- (promptTokens / 1_000_000) * INPUT_COST_PER_M +
375
- (completionTokens / 1_000_000) * OUTPUT_COST_PER_M;
376
-
377
- return { promptTokens, completionTokens, totalTokens, estimatedCostUSD };
378
- }
379
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * ProviderFactory — Creates the appropriate AI provider based on config.
3
- *
4
- * Centralizes provider instantiation so AIAgent.tsx doesn't need to
5
- * know about individual provider implementations.
6
- */
7
-
8
- import type { AIProvider, AIProviderName } from '../core/types';
9
- import { GeminiProvider } from './GeminiProvider';
10
- import { OpenAIProvider } from './OpenAIProvider';
11
-
12
- export function createProvider(
13
- provider: AIProviderName = 'gemini',
14
- apiKey?: string,
15
- model?: string,
16
- proxyUrl?: string,
17
- proxyHeaders?: Record<string, string>,
18
- ): AIProvider {
19
- switch (provider) {
20
- case 'openai':
21
- return new OpenAIProvider(
22
- apiKey,
23
- model || 'gpt-4.1-mini',
24
- proxyUrl,
25
- proxyHeaders,
26
- );
27
- case 'gemini':
28
- default:
29
- return new GeminiProvider(
30
- apiKey,
31
- model || 'gemini-2.5-flash',
32
- proxyUrl,
33
- proxyHeaders,
34
- );
35
- }
36
- }
@@ -1,226 +0,0 @@
1
- /**
2
- * AudioInputService — Real-time microphone capture for voice mode.
3
- *
4
- * Uses react-native-audio-api (Software Mansion) AudioRecorder for native
5
- * PCM streaming from the microphone. Each chunk is converted from Float32
6
- * to Int16 PCM and base64-encoded for the Gemini Live API.
7
- *
8
- * Echo cancellation is handled at the OS/hardware level via
9
- * react-native-incall-manager (VOICE_COMMUNICATION mode) — not in JS.
10
- *
11
- * Requires: react-native-audio-api (development build only, not Expo Go)
12
- */
13
-
14
- import { logger } from '../utils/logger';
15
- import { float32ToInt16Base64 } from '../utils/audioUtils';
16
-
17
- // ─── Types ─────────────────────────────────────────────────────
18
-
19
- export interface AudioInputConfig {
20
- sampleRate?: number;
21
- /** Number of samples per callback buffer (default: 4096) */
22
- bufferLength?: number;
23
- /** Callback with base64 PCM audio chunk */
24
- onAudioChunk: (base64Audio: string) => void;
25
- onError?: (error: string) => void;
26
- onPermissionDenied?: () => void;
27
- }
28
-
29
- type RecordingStatus = 'idle' | 'recording' | 'paused';
30
-
31
- // ─── Service ───────────────────────────────────────────────────
32
-
33
- export class AudioInputService {
34
- private config: AudioInputConfig;
35
- private status: RecordingStatus = 'idle';
36
- private recorder: any = null;
37
-
38
- // Auto-recovery: detect when mic session dies after audio playback.
39
- // This is a react-native-audio-api bug where AudioRecorder loses mic access
40
- // after AudioBufferQueueSourceNode plays audio (audio session conflict).
41
- private consecutiveSilentFrames = 0;
42
- private isRecovering = false;
43
- private static readonly SILENT_THRESHOLD = 0.01;
44
- private static readonly SILENT_FRAMES_BEFORE_RESTART = 15;
45
-
46
- constructor(config: AudioInputConfig) {
47
- this.config = config;
48
- }
49
-
50
- // ─── Lifecycle ─────────────────────────────────────────────
51
-
52
- async start(): Promise<boolean> {
53
- try {
54
- // Lazy-load react-native-audio-api (optional peer dependency)
55
- let audioApi: any;
56
- try {
57
- const { NativeModules } = require('react-native');
58
- if (!NativeModules.AudioApiModule) {
59
- const msg =
60
- '[mobileai] react-native-audio-api native module not found. '
61
- + 'Voice mode requires a development build (not Expo Go).';
62
- logger.warn('AudioInput', msg);
63
- this.config.onError?.(msg);
64
- return false;
65
- }
66
- // Static require — Metro needs a literal string for bundling.
67
- audioApi = require('react-native-audio-api');
68
- } catch {
69
- const msg =
70
- 'Voice mode requires react-native-audio-api. Install with: npm install react-native-audio-api';
71
- logger.error('AudioInput', msg);
72
- this.config.onError?.(msg);
73
- return false;
74
- }
75
-
76
- // Request mic permission (Android)
77
- try {
78
- const { PermissionsAndroid, Platform } = require('react-native');
79
- if (Platform.OS === 'android') {
80
- const result = await PermissionsAndroid.request(
81
- PermissionsAndroid.PERMISSIONS.RECORD_AUDIO
82
- );
83
- if (result !== PermissionsAndroid.RESULTS.GRANTED) {
84
- logger.warn('AudioInput', 'Microphone permission denied');
85
- this.config.onPermissionDenied?.();
86
- return false;
87
- }
88
- }
89
- } catch {
90
- // Permission check failed — continue and let native layer handle it
91
- }
92
-
93
- // Create AudioRecorder
94
- this.recorder = new audioApi.AudioRecorder();
95
- this.consecutiveSilentFrames = 0;
96
-
97
- const sampleRate = this.config.sampleRate || 16000;
98
- const bufferLength = this.config.bufferLength || 4096;
99
-
100
- // Register audio data callback
101
- let frameCount = 0;
102
- this.recorder.onAudioReady(
103
- { sampleRate, bufferLength, channelCount: 1 },
104
- (event: any) => {
105
- frameCount++;
106
- try {
107
- // event.buffer is an AudioBuffer — get Float32 channel data
108
- const float32Data = event.buffer.getChannelData(0);
109
-
110
- // Measure peak amplitude for diagnostics + silent detection
111
- let maxAmp = 0;
112
- for (let i = 0; i < float32Data.length; i++) {
113
- const abs = Math.abs(float32Data[i] || 0);
114
- if (abs > maxAmp) maxAmp = abs;
115
- }
116
-
117
- // Diagnostic: log amplitude on first 5 frames, then every 10th
118
- if (frameCount <= 5 || frameCount % 10 === 0) {
119
- logger.info('AudioInput', `🔬 Frame #${frameCount}: maxAmp=${maxAmp.toFixed(6)}, samples=${float32Data.length}`);
120
- }
121
-
122
- // ─── Auto-Recovery: Silent mic detection ─────────────
123
- // After audio playback, react-native-audio-api's AudioRecorder
124
- // can lose its mic session (all-zero frames). Detect this and
125
- // restart the recorder to re-acquire the audio session.
126
- if (maxAmp < AudioInputService.SILENT_THRESHOLD) {
127
- this.consecutiveSilentFrames++;
128
- if (
129
- this.consecutiveSilentFrames >= AudioInputService.SILENT_FRAMES_BEFORE_RESTART &&
130
- !this.isRecovering
131
- ) {
132
- this.isRecovering = true;
133
- logger.warn('AudioInput', `⚠️ ${this.consecutiveSilentFrames} silent frames — restarting recorder...`);
134
- this.restartRecorder().then(() => {
135
- this.isRecovering = false;
136
- this.consecutiveSilentFrames = 0;
137
- logger.info('AudioInput', '✅ Recorder restarted — mic session re-acquired');
138
- }).catch((err: any) => {
139
- this.isRecovering = false;
140
- logger.error('AudioInput', `❌ Recorder restart failed: ${err?.message || err}`);
141
- });
142
- return; // Skip this frame
143
- }
144
- } else {
145
- // Got real audio — reset counter
146
- if (this.consecutiveSilentFrames > 5) {
147
- logger.info('AudioInput', `🎤 Mic recovered after ${this.consecutiveSilentFrames} silent frames`);
148
- }
149
- this.consecutiveSilentFrames = 0;
150
- }
151
-
152
- const base64Chunk = float32ToInt16Base64(float32Data);
153
- if (frameCount <= 5 || frameCount % 10 === 0) {
154
- logger.info('AudioInput', `🎤 Frame #${frameCount}: chunk=${base64Chunk.length} chars, calling onAudioChunk...`);
155
- }
156
- this.config.onAudioChunk(base64Chunk);
157
- } catch (err: any) {
158
- logger.error('AudioInput', `Frame processing error: ${err.message}`);
159
- }
160
- }
161
- );
162
-
163
- // Register error callback
164
- this.recorder.onError((error: any) => {
165
- logger.error('AudioInput', `Recorder error: ${error.message || error}`);
166
- this.config.onError?.(error.message || String(error));
167
- });
168
-
169
- // Start recording
170
- this.recorder.start();
171
- this.status = 'recording';
172
- logger.info('AudioInput', `Streaming started (${sampleRate}Hz, bufLen=${bufferLength})`);
173
- return true;
174
- } catch (error: any) {
175
- logger.error('AudioInput', `Failed to start: ${error.message}`);
176
- this.config.onError?.(error.message);
177
- return false;
178
- }
179
- }
180
-
181
- async stop(): Promise<void> {
182
- try {
183
- if (this.recorder && this.status !== 'idle') {
184
- this.recorder.clearOnAudioReady();
185
- this.recorder.clearOnError();
186
- this.recorder.stop();
187
- }
188
- this.recorder = null;
189
- this.status = 'idle';
190
- this.consecutiveSilentFrames = 0;
191
- logger.info('AudioInput', 'Streaming stopped');
192
- } catch (error: any) {
193
- logger.error('AudioInput', `Failed to stop: ${error.message}`);
194
- this.recorder = null;
195
- this.status = 'idle';
196
- }
197
- }
198
-
199
- // ─── Auto-Recovery ─────────────────────────────────────────
200
-
201
- /**
202
- * Restart the recorder to re-acquire the audio session.
203
- * Fixes react-native-audio-api bug where AudioRecorder loses mic access
204
- * after AudioBufferQueueSourceNode plays audio.
205
- */
206
- private async restartRecorder(): Promise<void> {
207
- logger.info('AudioInput', '🔄 Restarting recorder for mic recovery...');
208
- await this.stop();
209
- // Brief pause to let the audio system release resources
210
- await new Promise(resolve => setTimeout(resolve, 300));
211
- const ok = await this.start();
212
- if (!ok) {
213
- throw new Error('Recorder restart failed');
214
- }
215
- }
216
-
217
- // ─── Status ───────────────────────────────────────────────
218
-
219
- get isRecording(): boolean {
220
- return this.status === 'recording';
221
- }
222
-
223
- get currentStatus(): RecordingStatus {
224
- return this.status;
225
- }
226
- }