@mobileai/react-native 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.
Files changed (65) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +190 -0
  3. package/lib/module/components/AIAgent.js +149 -0
  4. package/lib/module/components/AIAgent.js.map +1 -0
  5. package/lib/module/components/AgentChatBar.js +120 -0
  6. package/lib/module/components/AgentChatBar.js.map +1 -0
  7. package/lib/module/components/AgentOverlay.js +53 -0
  8. package/lib/module/components/AgentOverlay.js.map +1 -0
  9. package/lib/module/core/AgentRuntime.js +498 -0
  10. package/lib/module/core/AgentRuntime.js.map +1 -0
  11. package/lib/module/core/FiberTreeWalker.js +308 -0
  12. package/lib/module/core/FiberTreeWalker.js.map +1 -0
  13. package/lib/module/core/MCPBridge.js +98 -0
  14. package/lib/module/core/MCPBridge.js.map +1 -0
  15. package/lib/module/core/ScreenDehydrator.js +46 -0
  16. package/lib/module/core/ScreenDehydrator.js.map +1 -0
  17. package/lib/module/core/types.js +2 -0
  18. package/lib/module/core/types.js.map +1 -0
  19. package/lib/module/hooks/useAction.js +32 -0
  20. package/lib/module/hooks/useAction.js.map +1 -0
  21. package/lib/module/index.js +17 -0
  22. package/lib/module/index.js.map +1 -0
  23. package/lib/module/package.json +1 -0
  24. package/lib/module/providers/GeminiProvider.js +178 -0
  25. package/lib/module/providers/GeminiProvider.js.map +1 -0
  26. package/lib/module/utils/logger.js +17 -0
  27. package/lib/module/utils/logger.js.map +1 -0
  28. package/lib/typescript/package.json +1 -0
  29. package/lib/typescript/src/components/AIAgent.d.ts +57 -0
  30. package/lib/typescript/src/components/AIAgent.d.ts.map +1 -0
  31. package/lib/typescript/src/components/AgentChatBar.d.ts +14 -0
  32. package/lib/typescript/src/components/AgentChatBar.d.ts.map +1 -0
  33. package/lib/typescript/src/components/AgentOverlay.d.ts +10 -0
  34. package/lib/typescript/src/components/AgentOverlay.d.ts.map +1 -0
  35. package/lib/typescript/src/core/AgentRuntime.d.ts +37 -0
  36. package/lib/typescript/src/core/AgentRuntime.d.ts.map +1 -0
  37. package/lib/typescript/src/core/FiberTreeWalker.d.ts +26 -0
  38. package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +1 -0
  39. package/lib/typescript/src/core/MCPBridge.d.ts +23 -0
  40. package/lib/typescript/src/core/MCPBridge.d.ts.map +1 -0
  41. package/lib/typescript/src/core/ScreenDehydrator.d.ts +20 -0
  42. package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +1 -0
  43. package/lib/typescript/src/core/types.d.ts +138 -0
  44. package/lib/typescript/src/core/types.d.ts.map +1 -0
  45. package/lib/typescript/src/hooks/useAction.d.ts +13 -0
  46. package/lib/typescript/src/hooks/useAction.d.ts.map +1 -0
  47. package/lib/typescript/src/index.d.ts +10 -0
  48. package/lib/typescript/src/index.d.ts.map +1 -0
  49. package/lib/typescript/src/providers/GeminiProvider.d.ts +23 -0
  50. package/lib/typescript/src/providers/GeminiProvider.d.ts.map +1 -0
  51. package/lib/typescript/src/utils/logger.d.ts +7 -0
  52. package/lib/typescript/src/utils/logger.d.ts.map +1 -0
  53. package/package.json +143 -0
  54. package/src/components/AIAgent.tsx +222 -0
  55. package/src/components/AgentChatBar.tsx +136 -0
  56. package/src/components/AgentOverlay.tsx +48 -0
  57. package/src/core/AgentRuntime.ts +505 -0
  58. package/src/core/FiberTreeWalker.ts +349 -0
  59. package/src/core/MCPBridge.ts +110 -0
  60. package/src/core/ScreenDehydrator.ts +53 -0
  61. package/src/core/types.ts +185 -0
  62. package/src/hooks/useAction.ts +40 -0
  63. package/src/index.ts +22 -0
  64. package/src/providers/GeminiProvider.ts +210 -0
  65. package/src/utils/logger.ts +21 -0
@@ -0,0 +1,40 @@
1
+ /**
2
+ * useAction — Optional hook to register non-UI actions for the AI agent.
3
+ *
4
+ * Use this for business logic that doesn't correspond to a visible UI element,
5
+ * e.g., API calls, cart operations, calculations.
6
+ *
7
+ * The Fiber tree walker handles visible UI elements automatically.
8
+ * useAction is for invisible operations the AI should be able to trigger.
9
+ */
10
+
11
+ import { useEffect, useContext, createContext } from 'react';
12
+ import type { AgentRuntime } from '../core/AgentRuntime';
13
+
14
+ // Re-export context creation — AIAgent.tsx will provide the value
15
+ export const AgentContext = createContext<AgentRuntime | null>(null);
16
+
17
+ export function useAction(
18
+ name: string,
19
+ description: string,
20
+ parameters: Record<string, string>,
21
+ handler: (args: Record<string, any>) => any,
22
+ ): void {
23
+ const agentRuntime = useContext(AgentContext);
24
+
25
+ useEffect(() => {
26
+ if (!agentRuntime) return;
27
+
28
+ agentRuntime.registerAction({
29
+ name,
30
+ description,
31
+ parameters,
32
+ handler,
33
+ });
34
+
35
+ return () => {
36
+ agentRuntime.unregisterAction(name);
37
+ };
38
+ // eslint-disable-next-line react-hooks/exhaustive-deps
39
+ }, [name, description]);
40
+ }
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @mobileai/react-native — Page-Agent Architecture
3
+ *
4
+ * Zero-wrapper AI agent for React Native.
5
+ * Auto-detects interactive elements via React Fiber tree traversal.
6
+ */
7
+
8
+ // ─── Components ──────────────────────────────────────────────
9
+ export { AIAgent } from './components/AIAgent';
10
+
11
+ // ─── Hooks ───────────────────────────────────────────────────
12
+ export { useAction } from './hooks/useAction';
13
+
14
+ // ─── Types ───────────────────────────────────────────────────
15
+ export type {
16
+ AgentConfig,
17
+ ExecutionResult,
18
+ InteractiveElement,
19
+ DehydratedScreen,
20
+ ToolDefinition,
21
+ ActionDefinition,
22
+ } from './core/types';
@@ -0,0 +1,210 @@
1
+ /**
2
+ * GeminiProvider — Simplified Gemini API integration.
3
+ * Sends dehydrated screen state + tools to Gemini and parses tool call responses.
4
+ */
5
+
6
+ import { logger } from '../utils/logger';
7
+ import type { AIProvider, ToolDefinition, AgentStep } from '../core/types';
8
+
9
+ // ─── Gemini API Types ──────────────────────────────────────────
10
+
11
+ interface GeminiTool {
12
+ functionDeclarations: GeminiFunctionDeclaration[];
13
+ }
14
+
15
+ interface GeminiFunctionDeclaration {
16
+ name: string;
17
+ description: string;
18
+ parameters: {
19
+ type: string;
20
+ properties: Record<string, { type: string; description: string; enum?: string[] }>;
21
+ required: string[];
22
+ };
23
+ }
24
+
25
+ interface GeminiContent {
26
+ role: 'user' | 'model';
27
+ parts: Array<{ text?: string; functionCall?: { name: string; args: any }; functionResponse?: { name: string; response: any } }>;
28
+ }
29
+
30
+ // ─── Provider ──────────────────────────────────────────────────
31
+
32
+ export class GeminiProvider implements AIProvider {
33
+ private apiKey: string;
34
+ private model: string;
35
+
36
+
37
+ constructor(apiKey: string, model: string = 'gemini-2.5-flash') {
38
+ this.apiKey = apiKey;
39
+ this.model = model;
40
+ }
41
+
42
+ async generateContent(
43
+ systemPrompt: string,
44
+ userMessage: string,
45
+ tools: ToolDefinition[],
46
+ history: AgentStep[],
47
+ ): Promise<{ toolCalls: Array<{ name: string; args: Record<string, any> }>; text?: string }> {
48
+
49
+ logger.info('GeminiProvider', `Sending request. Model: ${this.model}, Tools: ${tools.length}`);
50
+
51
+ // Build Gemini tools
52
+ const geminiTools = this.buildGeminiTools(tools);
53
+
54
+ // Build conversation history
55
+ const contents = this.buildContents(userMessage, history);
56
+
57
+ // Make API request
58
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`;
59
+
60
+ const body: any = {
61
+ contents,
62
+ tools: geminiTools.length > 0 ? geminiTools : undefined,
63
+ systemInstruction: { parts: [{ text: systemPrompt }] },
64
+ generationConfig: {
65
+ temperature: 0.2,
66
+ maxOutputTokens: 2048,
67
+ },
68
+ };
69
+
70
+ const startTime = Date.now();
71
+
72
+ try {
73
+ const response = await fetch(url, {
74
+ method: 'POST',
75
+ headers: { 'Content-Type': 'application/json' },
76
+ body: JSON.stringify(body),
77
+ });
78
+
79
+ const elapsed = Date.now() - startTime;
80
+ logger.info('GeminiProvider', `Response received in ${elapsed}ms`);
81
+
82
+ if (!response.ok) {
83
+ const errorText = await response.text();
84
+ logger.error('GeminiProvider', `API error ${response.status}: ${errorText}`);
85
+ throw new Error(`Gemini API error ${response.status}: ${errorText}`);
86
+ }
87
+
88
+ const data = await response.json();
89
+
90
+ // Parse response
91
+ return this.parseResponse(data);
92
+ } catch (error: any) {
93
+ logger.error('GeminiProvider', 'Request failed:', error.message);
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ // ─── Build Gemini Tools ────────────────────────────────────
99
+
100
+ private buildGeminiTools(tools: ToolDefinition[]): GeminiTool[] {
101
+ const declarations: GeminiFunctionDeclaration[] = tools.map(tool => ({
102
+ name: tool.name,
103
+ description: tool.description,
104
+ parameters: {
105
+ type: 'OBJECT',
106
+ properties: Object.fromEntries(
107
+ Object.entries(tool.parameters).map(([key, param]) => [
108
+ key,
109
+ {
110
+ type: this.mapParamType(param.type),
111
+ description: param.description,
112
+ ...(param.enum ? { enum: param.enum } : {}),
113
+ },
114
+ ]),
115
+ ),
116
+ required: Object.entries(tool.parameters)
117
+ .filter(([, param]) => param.required !== false)
118
+ .map(([key]) => key),
119
+ },
120
+ }));
121
+
122
+ return [{ functionDeclarations: declarations }];
123
+ }
124
+
125
+ private mapParamType(type: string): string {
126
+ switch (type) {
127
+ case 'number': return 'NUMBER';
128
+ case 'boolean': return 'BOOLEAN';
129
+ case 'string':
130
+ default: return 'STRING';
131
+ }
132
+ }
133
+
134
+ // ─── Build Contents ────────────────────────────────────────
135
+
136
+ private buildContents(userMessage: string, history: AgentStep[]): GeminiContent[] {
137
+ const contents: GeminiContent[] = [];
138
+
139
+ // Add history as conversation turns
140
+ for (const step of history) {
141
+ // User turn (screen state was sent)
142
+ contents.push({
143
+ role: 'user',
144
+ parts: [{ text: `Step ${step.stepIndex + 1} result: ${step.action.output}` }],
145
+ });
146
+ }
147
+
148
+ // Current user message
149
+ contents.push({
150
+ role: 'user',
151
+ parts: [{ text: userMessage }],
152
+ });
153
+
154
+ // Ensure alternating roles (Gemini requirement)
155
+ return this.ensureAlternatingRoles(contents);
156
+ }
157
+
158
+ private ensureAlternatingRoles(contents: GeminiContent[]): GeminiContent[] {
159
+ if (contents.length <= 1) return contents;
160
+
161
+ const merged: GeminiContent[] = [contents[0]!];
162
+
163
+ for (let i = 1; i < contents.length; i++) {
164
+ const prev = merged[merged.length - 1]!;
165
+ const curr = contents[i]!;
166
+
167
+ if (prev.role === curr.role) {
168
+ // Merge same-role messages
169
+ prev.parts.push(...curr.parts);
170
+ } else {
171
+ merged.push(curr);
172
+ }
173
+ }
174
+
175
+ return merged;
176
+ }
177
+
178
+ // ─── Parse Response ────────────────────────────────────────
179
+
180
+ private parseResponse(data: any): { toolCalls: Array<{ name: string; args: Record<string, any> }>; text?: string } {
181
+ const toolCalls: Array<{ name: string; args: Record<string, any> }> = [];
182
+ let text: string | undefined;
183
+
184
+ if (!data.candidates || data.candidates.length === 0) {
185
+ logger.warn('GeminiProvider', 'No candidates in response');
186
+ return { toolCalls, text: 'No response generated.' };
187
+ }
188
+
189
+ const candidate = data.candidates[0];
190
+ const parts = candidate.content?.parts || [];
191
+
192
+ for (const part of parts) {
193
+ if (part.functionCall) {
194
+ toolCalls.push({
195
+ name: part.functionCall.name,
196
+ args: part.functionCall.args || {},
197
+ });
198
+ }
199
+ if (part.text) {
200
+ text = (text || '') + part.text;
201
+ }
202
+ }
203
+
204
+ logger.info('GeminiProvider', `Parsed: ${toolCalls.length} tool calls, text: ${text ? 'yes' : 'no'}`);
205
+
206
+ return { toolCalls, text };
207
+ }
208
+
209
+
210
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Logger utility — prefixed console output for easy filtering.
3
+ */
4
+ const TAG = '[AIAgent]';
5
+
6
+ export const logger = {
7
+ info: (context: string, ...args: any[]) =>
8
+ console.log(`${TAG} [${context}]`, ...args),
9
+
10
+ warn: (context: string, ...args: any[]) =>
11
+ console.warn(`${TAG} [${context}]`, ...args),
12
+
13
+ error: (context: string, ...args: any[]) =>
14
+ console.error(`${TAG} [${context}]`, ...args),
15
+
16
+ debug: (context: string, ...args: any[]) => {
17
+ if (__DEV__) {
18
+ console.log(`${TAG} [${context}] 🐛`, ...args);
19
+ }
20
+ },
21
+ };