@mobileai/react-native 0.9.17 → 0.9.19

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 (292) hide show
  1. package/LICENSE +28 -20
  2. package/MobileAIFloatingOverlay.podspec +25 -0
  3. package/android/build.gradle +61 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/java/com/mobileai/overlay/FloatingOverlayView.kt +151 -0
  6. package/android/src/main/java/com/mobileai/overlay/MobileAIOverlayPackage.kt +23 -0
  7. package/android/src/newarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +45 -0
  8. package/android/src/oldarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +29 -0
  9. package/ios/MobileAIFloatingOverlayComponentView.mm +73 -0
  10. package/lib/module/components/AIAgent.js +902 -136
  11. package/lib/module/components/AIConsentDialog.js +439 -0
  12. package/lib/module/components/AgentChatBar.js +828 -134
  13. package/lib/module/components/AgentOverlay.js +2 -1
  14. package/lib/module/components/DiscoveryTooltip.js +21 -9
  15. package/lib/module/components/FloatingOverlayWrapper.js +108 -0
  16. package/lib/module/components/Icons.js +123 -0
  17. package/lib/module/config/endpoints.js +12 -2
  18. package/lib/module/core/AgentRuntime.js +373 -27
  19. package/lib/module/core/FiberAdapter.js +56 -0
  20. package/lib/module/core/FiberTreeWalker.js +186 -80
  21. package/lib/module/core/IdleDetector.js +19 -0
  22. package/lib/module/core/NativeAlertInterceptor.js +191 -0
  23. package/lib/module/core/systemPrompt.js +203 -45
  24. package/lib/module/index.js +3 -0
  25. package/lib/module/providers/GeminiProvider.js +72 -56
  26. package/lib/module/providers/ProviderFactory.js +6 -2
  27. package/lib/module/services/AudioInputService.js +3 -12
  28. package/lib/module/services/AudioOutputService.js +1 -13
  29. package/lib/module/services/ConversationService.js +166 -0
  30. package/lib/module/services/MobileAIKnowledgeRetriever.js +41 -0
  31. package/lib/module/services/VoiceService.js +29 -8
  32. package/lib/module/services/telemetry/MobileAI.js +44 -0
  33. package/lib/module/services/telemetry/TelemetryService.js +13 -1
  34. package/lib/module/services/telemetry/TouchAutoCapture.js +44 -18
  35. package/lib/module/specs/FloatingOverlayNativeComponent.ts +19 -0
  36. package/lib/module/support/CSATSurvey.js +95 -12
  37. package/lib/module/support/EscalationSocket.js +70 -1
  38. package/lib/module/support/ReportedIssueEventSource.js +148 -0
  39. package/lib/module/support/escalateTool.js +4 -2
  40. package/lib/module/support/index.js +1 -0
  41. package/lib/module/support/reportIssueTool.js +127 -0
  42. package/lib/module/support/supportPrompt.js +77 -9
  43. package/lib/module/tools/guideTool.js +2 -1
  44. package/lib/module/tools/longPressTool.js +4 -3
  45. package/lib/module/tools/pickerTool.js +6 -4
  46. package/lib/module/tools/tapTool.js +12 -3
  47. package/lib/module/tools/typeTool.js +19 -10
  48. package/lib/module/utils/logger.js +175 -6
  49. package/lib/typescript/react-native.config.d.ts +11 -0
  50. package/lib/typescript/src/components/AIAgent.d.ts +28 -2
  51. package/lib/typescript/src/components/AIConsentDialog.d.ts +153 -0
  52. package/lib/typescript/src/components/AgentChatBar.d.ts +15 -2
  53. package/lib/typescript/src/components/DiscoveryTooltip.d.ts +3 -1
  54. package/lib/typescript/src/components/FloatingOverlayWrapper.d.ts +51 -0
  55. package/lib/typescript/src/components/Icons.d.ts +8 -0
  56. package/lib/typescript/src/config/endpoints.d.ts +5 -3
  57. package/lib/typescript/src/core/AgentRuntime.d.ts +4 -0
  58. package/lib/typescript/src/core/FiberAdapter.d.ts +25 -0
  59. package/lib/typescript/src/core/FiberTreeWalker.d.ts +2 -0
  60. package/lib/typescript/src/core/IdleDetector.d.ts +11 -0
  61. package/lib/typescript/src/core/NativeAlertInterceptor.d.ts +55 -0
  62. package/lib/typescript/src/core/types.d.ts +106 -1
  63. package/lib/typescript/src/index.d.ts +9 -4
  64. package/lib/typescript/src/providers/GeminiProvider.d.ts +6 -5
  65. package/lib/typescript/src/services/ConversationService.d.ts +55 -0
  66. package/lib/typescript/src/services/MobileAIKnowledgeRetriever.d.ts +9 -0
  67. package/lib/typescript/src/services/telemetry/MobileAI.d.ts +7 -0
  68. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +1 -1
  69. package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts +9 -6
  70. package/lib/typescript/src/services/telemetry/types.d.ts +3 -1
  71. package/lib/typescript/src/specs/FloatingOverlayNativeComponent.d.ts +17 -0
  72. package/lib/typescript/src/support/EscalationSocket.d.ts +17 -0
  73. package/lib/typescript/src/support/ReportedIssueEventSource.d.ts +24 -0
  74. package/lib/typescript/src/support/escalateTool.d.ts +5 -0
  75. package/lib/typescript/src/support/index.d.ts +2 -1
  76. package/lib/typescript/src/support/reportIssueTool.d.ts +20 -0
  77. package/lib/typescript/src/support/types.d.ts +56 -1
  78. package/lib/typescript/src/utils/logger.d.ts +15 -0
  79. package/package.json +20 -9
  80. package/react-native.config.js +12 -0
  81. package/lib/module/__cli_tmp__.js.map +0 -1
  82. package/lib/module/components/AIAgent.js.map +0 -1
  83. package/lib/module/components/AIZone.js.map +0 -1
  84. package/lib/module/components/AgentChatBar.js.map +0 -1
  85. package/lib/module/components/AgentErrorBoundary.js.map +0 -1
  86. package/lib/module/components/AgentOverlay.js.map +0 -1
  87. package/lib/module/components/DiscoveryTooltip.js.map +0 -1
  88. package/lib/module/components/HighlightOverlay.js.map +0 -1
  89. package/lib/module/components/Icons.js.map +0 -1
  90. package/lib/module/components/ProactiveHint.js.map +0 -1
  91. package/lib/module/components/cards/InfoCard.js.map +0 -1
  92. package/lib/module/components/cards/ReviewSummary.js.map +0 -1
  93. package/lib/module/config/endpoints.js.map +0 -1
  94. package/lib/module/core/ActionRegistry.js.map +0 -1
  95. package/lib/module/core/AgentRuntime.js.map +0 -1
  96. package/lib/module/core/FiberTreeWalker.js.map +0 -1
  97. package/lib/module/core/IdleDetector.js.map +0 -1
  98. package/lib/module/core/MCPBridge.js.map +0 -1
  99. package/lib/module/core/ScreenDehydrator.js.map +0 -1
  100. package/lib/module/core/ZoneRegistry.js.map +0 -1
  101. package/lib/module/core/systemPrompt.js.map +0 -1
  102. package/lib/module/core/types.js.map +0 -1
  103. package/lib/module/hooks/useAction.js.map +0 -1
  104. package/lib/module/index.js.map +0 -1
  105. package/lib/module/plugin/withAppIntents.js.map +0 -1
  106. package/lib/module/providers/GeminiProvider.js.map +0 -1
  107. package/lib/module/providers/OpenAIProvider.js.map +0 -1
  108. package/lib/module/providers/ProviderFactory.js.map +0 -1
  109. package/lib/module/services/AudioInputService.js.map +0 -1
  110. package/lib/module/services/AudioOutputService.js.map +0 -1
  111. package/lib/module/services/KnowledgeBaseService.js.map +0 -1
  112. package/lib/module/services/VoiceService.js.map +0 -1
  113. package/lib/module/services/flags/FlagService.js.map +0 -1
  114. package/lib/module/services/telemetry/MobileAI.js.map +0 -1
  115. package/lib/module/services/telemetry/PiiScrubber.js.map +0 -1
  116. package/lib/module/services/telemetry/TelemetryService.js.map +0 -1
  117. package/lib/module/services/telemetry/TouchAutoCapture.js.map +0 -1
  118. package/lib/module/services/telemetry/device.js.map +0 -1
  119. package/lib/module/services/telemetry/deviceMetadata.js.map +0 -1
  120. package/lib/module/services/telemetry/index.js.map +0 -1
  121. package/lib/module/services/telemetry/types.js.map +0 -1
  122. package/lib/module/support/CSATSurvey.js.map +0 -1
  123. package/lib/module/support/EscalationEventSource.js.map +0 -1
  124. package/lib/module/support/EscalationSocket.js.map +0 -1
  125. package/lib/module/support/SupportChatModal.js.map +0 -1
  126. package/lib/module/support/SupportGreeting.js.map +0 -1
  127. package/lib/module/support/TicketStore.js.map +0 -1
  128. package/lib/module/support/escalateTool.js.map +0 -1
  129. package/lib/module/support/index.js.map +0 -1
  130. package/lib/module/support/supportPrompt.js.map +0 -1
  131. package/lib/module/support/types.js.map +0 -1
  132. package/lib/module/tools/datePickerTool.js.map +0 -1
  133. package/lib/module/tools/guideTool.js.map +0 -1
  134. package/lib/module/tools/index.js.map +0 -1
  135. package/lib/module/tools/keyboardTool.js.map +0 -1
  136. package/lib/module/tools/longPressTool.js.map +0 -1
  137. package/lib/module/tools/pickerTool.js.map +0 -1
  138. package/lib/module/tools/restoreTool.js.map +0 -1
  139. package/lib/module/tools/scrollTool.js.map +0 -1
  140. package/lib/module/tools/simplifyTool.js.map +0 -1
  141. package/lib/module/tools/sliderTool.js.map +0 -1
  142. package/lib/module/tools/tapTool.js.map +0 -1
  143. package/lib/module/tools/typeTool.js.map +0 -1
  144. package/lib/module/tools/types.js.map +0 -1
  145. package/lib/module/types/jsx.d.js.map +0 -1
  146. package/lib/module/utils/audioUtils.js.map +0 -1
  147. package/lib/module/utils/logger.js.map +0 -1
  148. package/lib/typescript/babel.config.d.ts.map +0 -1
  149. package/lib/typescript/bin/generate-map.d.cts.map +0 -1
  150. package/lib/typescript/eslint.config.d.mts.map +0 -1
  151. package/lib/typescript/generate-map.d.ts.map +0 -1
  152. package/lib/typescript/src/__cli_tmp__.d.ts.map +0 -1
  153. package/lib/typescript/src/components/AIAgent.d.ts.map +0 -1
  154. package/lib/typescript/src/components/AIZone.d.ts.map +0 -1
  155. package/lib/typescript/src/components/AgentChatBar.d.ts.map +0 -1
  156. package/lib/typescript/src/components/AgentErrorBoundary.d.ts.map +0 -1
  157. package/lib/typescript/src/components/AgentOverlay.d.ts.map +0 -1
  158. package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +0 -1
  159. package/lib/typescript/src/components/HighlightOverlay.d.ts.map +0 -1
  160. package/lib/typescript/src/components/Icons.d.ts.map +0 -1
  161. package/lib/typescript/src/components/ProactiveHint.d.ts.map +0 -1
  162. package/lib/typescript/src/components/cards/InfoCard.d.ts.map +0 -1
  163. package/lib/typescript/src/components/cards/ReviewSummary.d.ts.map +0 -1
  164. package/lib/typescript/src/config/endpoints.d.ts.map +0 -1
  165. package/lib/typescript/src/core/ActionRegistry.d.ts.map +0 -1
  166. package/lib/typescript/src/core/AgentRuntime.d.ts.map +0 -1
  167. package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +0 -1
  168. package/lib/typescript/src/core/IdleDetector.d.ts.map +0 -1
  169. package/lib/typescript/src/core/MCPBridge.d.ts.map +0 -1
  170. package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +0 -1
  171. package/lib/typescript/src/core/ZoneRegistry.d.ts.map +0 -1
  172. package/lib/typescript/src/core/systemPrompt.d.ts.map +0 -1
  173. package/lib/typescript/src/core/types.d.ts.map +0 -1
  174. package/lib/typescript/src/hooks/useAction.d.ts.map +0 -1
  175. package/lib/typescript/src/index.d.ts.map +0 -1
  176. package/lib/typescript/src/plugin/withAppIntents.d.ts.map +0 -1
  177. package/lib/typescript/src/providers/GeminiProvider.d.ts.map +0 -1
  178. package/lib/typescript/src/providers/OpenAIProvider.d.ts.map +0 -1
  179. package/lib/typescript/src/providers/ProviderFactory.d.ts.map +0 -1
  180. package/lib/typescript/src/services/AudioInputService.d.ts.map +0 -1
  181. package/lib/typescript/src/services/AudioOutputService.d.ts.map +0 -1
  182. package/lib/typescript/src/services/KnowledgeBaseService.d.ts.map +0 -1
  183. package/lib/typescript/src/services/VoiceService.d.ts.map +0 -1
  184. package/lib/typescript/src/services/flags/FlagService.d.ts.map +0 -1
  185. package/lib/typescript/src/services/telemetry/MobileAI.d.ts.map +0 -1
  186. package/lib/typescript/src/services/telemetry/PiiScrubber.d.ts.map +0 -1
  187. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts.map +0 -1
  188. package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts.map +0 -1
  189. package/lib/typescript/src/services/telemetry/device.d.ts.map +0 -1
  190. package/lib/typescript/src/services/telemetry/deviceMetadata.d.ts.map +0 -1
  191. package/lib/typescript/src/services/telemetry/index.d.ts.map +0 -1
  192. package/lib/typescript/src/services/telemetry/types.d.ts.map +0 -1
  193. package/lib/typescript/src/support/CSATSurvey.d.ts.map +0 -1
  194. package/lib/typescript/src/support/EscalationEventSource.d.ts.map +0 -1
  195. package/lib/typescript/src/support/EscalationSocket.d.ts.map +0 -1
  196. package/lib/typescript/src/support/SupportChatModal.d.ts.map +0 -1
  197. package/lib/typescript/src/support/SupportGreeting.d.ts.map +0 -1
  198. package/lib/typescript/src/support/TicketStore.d.ts.map +0 -1
  199. package/lib/typescript/src/support/escalateTool.d.ts.map +0 -1
  200. package/lib/typescript/src/support/index.d.ts.map +0 -1
  201. package/lib/typescript/src/support/supportPrompt.d.ts.map +0 -1
  202. package/lib/typescript/src/support/types.d.ts.map +0 -1
  203. package/lib/typescript/src/tools/datePickerTool.d.ts.map +0 -1
  204. package/lib/typescript/src/tools/guideTool.d.ts.map +0 -1
  205. package/lib/typescript/src/tools/index.d.ts.map +0 -1
  206. package/lib/typescript/src/tools/keyboardTool.d.ts.map +0 -1
  207. package/lib/typescript/src/tools/longPressTool.d.ts.map +0 -1
  208. package/lib/typescript/src/tools/pickerTool.d.ts.map +0 -1
  209. package/lib/typescript/src/tools/restoreTool.d.ts.map +0 -1
  210. package/lib/typescript/src/tools/scrollTool.d.ts.map +0 -1
  211. package/lib/typescript/src/tools/simplifyTool.d.ts.map +0 -1
  212. package/lib/typescript/src/tools/sliderTool.d.ts.map +0 -1
  213. package/lib/typescript/src/tools/tapTool.d.ts.map +0 -1
  214. package/lib/typescript/src/tools/typeTool.d.ts.map +0 -1
  215. package/lib/typescript/src/tools/types.d.ts.map +0 -1
  216. package/lib/typescript/src/utils/audioUtils.d.ts.map +0 -1
  217. package/lib/typescript/src/utils/logger.d.ts.map +0 -1
  218. package/src/__cli_tmp__.tsx +0 -9
  219. package/src/cli/analyzers/chain-analyzer.ts +0 -183
  220. package/src/cli/extractors/ai-extractor.ts +0 -6
  221. package/src/cli/extractors/ast-extractor.ts +0 -551
  222. package/src/cli/generate-intents.ts +0 -140
  223. package/src/cli/generate-map.ts +0 -121
  224. package/src/cli/generate-swift.ts +0 -116
  225. package/src/cli/scanners/expo-scanner.ts +0 -203
  226. package/src/cli/scanners/rn-scanner.ts +0 -445
  227. package/src/components/AIAgent.tsx +0 -1716
  228. package/src/components/AIZone.tsx +0 -147
  229. package/src/components/AgentChatBar.tsx +0 -1143
  230. package/src/components/AgentErrorBoundary.tsx +0 -78
  231. package/src/components/AgentOverlay.tsx +0 -73
  232. package/src/components/DiscoveryTooltip.tsx +0 -148
  233. package/src/components/HighlightOverlay.tsx +0 -136
  234. package/src/components/Icons.tsx +0 -253
  235. package/src/components/ProactiveHint.tsx +0 -145
  236. package/src/components/cards/InfoCard.tsx +0 -58
  237. package/src/components/cards/ReviewSummary.tsx +0 -76
  238. package/src/config/endpoints.ts +0 -22
  239. package/src/core/ActionRegistry.ts +0 -105
  240. package/src/core/AgentRuntime.ts +0 -1471
  241. package/src/core/FiberTreeWalker.ts +0 -930
  242. package/src/core/IdleDetector.ts +0 -72
  243. package/src/core/MCPBridge.ts +0 -163
  244. package/src/core/ScreenDehydrator.ts +0 -53
  245. package/src/core/ZoneRegistry.ts +0 -44
  246. package/src/core/systemPrompt.ts +0 -431
  247. package/src/core/types.ts +0 -521
  248. package/src/hooks/useAction.ts +0 -182
  249. package/src/index.ts +0 -83
  250. package/src/plugin/withAppIntents.ts +0 -98
  251. package/src/providers/GeminiProvider.ts +0 -357
  252. package/src/providers/OpenAIProvider.ts +0 -379
  253. package/src/providers/ProviderFactory.ts +0 -36
  254. package/src/services/AudioInputService.ts +0 -226
  255. package/src/services/AudioOutputService.ts +0 -236
  256. package/src/services/KnowledgeBaseService.ts +0 -156
  257. package/src/services/VoiceService.ts +0 -451
  258. package/src/services/flags/FlagService.ts +0 -137
  259. package/src/services/telemetry/MobileAI.ts +0 -66
  260. package/src/services/telemetry/PiiScrubber.ts +0 -17
  261. package/src/services/telemetry/TelemetryService.ts +0 -323
  262. package/src/services/telemetry/TouchAutoCapture.ts +0 -165
  263. package/src/services/telemetry/device.ts +0 -93
  264. package/src/services/telemetry/deviceMetadata.ts +0 -13
  265. package/src/services/telemetry/index.ts +0 -13
  266. package/src/services/telemetry/types.ts +0 -75
  267. package/src/support/CSATSurvey.tsx +0 -304
  268. package/src/support/EscalationEventSource.ts +0 -190
  269. package/src/support/EscalationSocket.ts +0 -152
  270. package/src/support/SupportChatModal.tsx +0 -563
  271. package/src/support/SupportGreeting.tsx +0 -161
  272. package/src/support/TicketStore.ts +0 -100
  273. package/src/support/escalateTool.ts +0 -174
  274. package/src/support/index.ts +0 -29
  275. package/src/support/supportPrompt.ts +0 -55
  276. package/src/support/types.ts +0 -155
  277. package/src/tools/datePickerTool.ts +0 -60
  278. package/src/tools/guideTool.ts +0 -76
  279. package/src/tools/index.ts +0 -20
  280. package/src/tools/keyboardTool.ts +0 -30
  281. package/src/tools/longPressTool.ts +0 -61
  282. package/src/tools/pickerTool.ts +0 -115
  283. package/src/tools/restoreTool.ts +0 -33
  284. package/src/tools/scrollTool.ts +0 -156
  285. package/src/tools/simplifyTool.ts +0 -33
  286. package/src/tools/sliderTool.ts +0 -65
  287. package/src/tools/tapTool.ts +0 -93
  288. package/src/tools/typeTool.ts +0 -113
  289. package/src/tools/types.ts +0 -58
  290. package/src/types/jsx.d.ts +0 -20
  291. package/src/utils/audioUtils.ts +0 -54
  292. 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
- }