@journeyrewards/hive-react-native 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,447 @@
1
+ // src/streaming.ts
2
+ async function* parseSSEFromFetch(response) {
3
+ const reader = response.body?.getReader();
4
+ if (!reader) {
5
+ throw new Error("Response body is not readable");
6
+ }
7
+ let buffer = "";
8
+ let currentEvent = "";
9
+ const decode = (chunk) => {
10
+ let result = "";
11
+ for (let i = 0; i < chunk.length; i++) {
12
+ result += String.fromCharCode(chunk[i]);
13
+ }
14
+ return result;
15
+ };
16
+ try {
17
+ while (true) {
18
+ const { done, value } = await reader.read();
19
+ if (done) break;
20
+ buffer += value instanceof Uint8Array ? decode(value) : String(value);
21
+ const lines = buffer.split("\n");
22
+ buffer = lines.pop() || "";
23
+ for (const line of lines) {
24
+ const trimmed = line.trim();
25
+ if (trimmed === "") {
26
+ continue;
27
+ }
28
+ if (trimmed.startsWith("event: ")) {
29
+ currentEvent = trimmed.slice(7).trim();
30
+ } else if (trimmed.startsWith("data: ")) {
31
+ const dataStr = trimmed.slice(6);
32
+ try {
33
+ const data = JSON.parse(dataStr);
34
+ yield { event: currentEvent, data };
35
+ } catch {
36
+ }
37
+ }
38
+ }
39
+ }
40
+ if (buffer.trim()) {
41
+ const remaining = buffer.trim();
42
+ if (remaining.startsWith("data: ")) {
43
+ try {
44
+ const data = JSON.parse(remaining.slice(6));
45
+ yield { event: currentEvent, data };
46
+ } catch {
47
+ }
48
+ }
49
+ }
50
+ } finally {
51
+ reader.releaseLock();
52
+ }
53
+ }
54
+ var RNEventSource = class {
55
+ constructor(url, headers = {}) {
56
+ this.abortController = null;
57
+ this.onmessage = null;
58
+ this.onerror = null;
59
+ this.onopen = null;
60
+ this.url = url;
61
+ this.headers = headers;
62
+ }
63
+ async connect(body) {
64
+ this.abortController = new AbortController();
65
+ try {
66
+ const response = await fetch(this.url, {
67
+ method: body ? "POST" : "GET",
68
+ headers: {
69
+ ...this.headers,
70
+ Accept: "text/event-stream",
71
+ ...body ? { "Content-Type": "application/json" } : {}
72
+ },
73
+ body: body ? JSON.stringify(body) : void 0,
74
+ signal: this.abortController.signal
75
+ });
76
+ if (!response.ok) {
77
+ throw new Error(`EventSource connection failed: ${response.status}`);
78
+ }
79
+ this.onopen?.();
80
+ for await (const event of parseSSEFromFetch(response)) {
81
+ if (this.abortController?.signal.aborted) break;
82
+ this.onmessage?.(event);
83
+ }
84
+ } catch (err) {
85
+ if (!(err instanceof DOMException && err.name === "AbortError")) {
86
+ this.onerror?.(err instanceof Error ? err : new Error(String(err)));
87
+ }
88
+ }
89
+ }
90
+ close() {
91
+ this.abortController?.abort();
92
+ this.abortController = null;
93
+ }
94
+ };
95
+
96
+ // src/client.ts
97
+ var JourneyHiveRNClient = class {
98
+ constructor(config) {
99
+ this.offlineQueue = [];
100
+ this.isConnected = true;
101
+ this.connectionListeners = [];
102
+ this.appStateListeners = [];
103
+ this.appState = "active";
104
+ this.apiKey = config.apiKey;
105
+ this.baseUrl = config.baseUrl || "https://journey-hive.replit.app";
106
+ this.timeout = config.timeout || 3e4;
107
+ this.defaultAgentId = config.defaultAgentId;
108
+ this.debug = config.debug || false;
109
+ }
110
+ resolveAgentId(agentId) {
111
+ return agentId || this.defaultAgentId;
112
+ }
113
+ get headers() {
114
+ return {
115
+ "Content-Type": "application/json",
116
+ Authorization: `Bearer ${this.apiKey}`
117
+ };
118
+ }
119
+ log(message, data) {
120
+ if (this.debug) {
121
+ console.log(`[JourneyHive] ${message}`, data !== void 0 ? data : "");
122
+ }
123
+ }
124
+ async request(method, path, body) {
125
+ if (!this.isConnected && method === "POST") {
126
+ return new Promise((resolve, reject) => {
127
+ if (body?.input) {
128
+ this.offlineQueue.push({
129
+ id: `queue_${Date.now()}_${Math.random().toString(36).slice(2)}`,
130
+ params: body,
131
+ resolve,
132
+ reject
133
+ });
134
+ this.log("Queued offline message", { path });
135
+ } else {
136
+ reject(new Error("No network connection"));
137
+ }
138
+ });
139
+ }
140
+ this.log(`${method} ${path}`, body);
141
+ const controller = new AbortController();
142
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
143
+ try {
144
+ const res = await fetch(`${this.baseUrl}${path}`, {
145
+ method,
146
+ headers: this.headers,
147
+ body: body ? JSON.stringify(body) : void 0,
148
+ signal: controller.signal
149
+ });
150
+ clearTimeout(timeoutId);
151
+ if (!res.ok) {
152
+ const err = await res.json().catch(() => ({}));
153
+ const error = new Error(err?.error?.message || `Request failed with status ${res.status}`);
154
+ this.log(`${method} ${path} failed`, { status: res.status, error: err });
155
+ throw error;
156
+ }
157
+ const result = await res.json();
158
+ this.log(`${method} ${path} response`, { status: res.status });
159
+ return result;
160
+ } catch (err) {
161
+ clearTimeout(timeoutId);
162
+ throw err;
163
+ }
164
+ }
165
+ async createResponse(params) {
166
+ const resolved = { ...params, agent_id: this.resolveAgentId(params.agent_id) };
167
+ if (params.stream) {
168
+ return this.createStreamingResponse(resolved);
169
+ }
170
+ return this.request("POST", "/v1/responses", resolved);
171
+ }
172
+ async *createStreamingResponse(params) {
173
+ const resolved = { ...params, agent_id: this.resolveAgentId(params.agent_id) };
174
+ const controller = new AbortController();
175
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout * 3);
176
+ try {
177
+ this.log("POST /v1/responses (streaming)", resolved);
178
+ const response = await fetch(`${this.baseUrl}/v1/responses`, {
179
+ method: "POST",
180
+ headers: this.headers,
181
+ body: JSON.stringify(resolved),
182
+ signal: controller.signal
183
+ });
184
+ clearTimeout(timeoutId);
185
+ if (!response.ok) {
186
+ const err = await response.json().catch(() => ({}));
187
+ throw new Error(err?.error?.message || `Request failed with status ${response.status}`);
188
+ }
189
+ yield* parseSSEFromFetch(response);
190
+ } catch (err) {
191
+ clearTimeout(timeoutId);
192
+ throw err;
193
+ }
194
+ }
195
+ async getAgent(agentId) {
196
+ return this.request("GET", `/v1/agents/${agentId}`);
197
+ }
198
+ async listAgents() {
199
+ return this.request("GET", "/v1/agents");
200
+ }
201
+ async updateAgent(agentId, params) {
202
+ return this.request("PATCH", `/v1/agents/${agentId}`, params);
203
+ }
204
+ async createConversation(params) {
205
+ const resolved = { ...params, agent_id: this.resolveAgentId(params.agent_id) };
206
+ return this.request("POST", "/v1/conversations", resolved);
207
+ }
208
+ async getConversation(conversationId) {
209
+ return this.request("GET", `/v1/conversations/${conversationId}`);
210
+ }
211
+ async getMessages(conversationId) {
212
+ return this.request("GET", `/v1/conversations/${conversationId}/messages`);
213
+ }
214
+ setConnectionStatus(connected) {
215
+ const changed = this.isConnected !== connected;
216
+ this.isConnected = connected;
217
+ if (changed) {
218
+ this.connectionListeners.forEach((listener) => listener(connected));
219
+ if (connected) {
220
+ this.flushOfflineQueue();
221
+ }
222
+ }
223
+ }
224
+ getConnectionStatus() {
225
+ return this.isConnected;
226
+ }
227
+ onConnectionChange(listener) {
228
+ this.connectionListeners.push(listener);
229
+ return () => {
230
+ this.connectionListeners = this.connectionListeners.filter((l) => l !== listener);
231
+ };
232
+ }
233
+ setAppState(state) {
234
+ const prev = this.appState;
235
+ this.appState = state;
236
+ if (prev !== state) {
237
+ this.appStateListeners.forEach((listener) => listener(state));
238
+ }
239
+ }
240
+ onAppStateChange(listener) {
241
+ this.appStateListeners.push(listener);
242
+ return () => {
243
+ this.appStateListeners = this.appStateListeners.filter((l) => l !== listener);
244
+ };
245
+ }
246
+ getQueuedMessages() {
247
+ return [...this.offlineQueue];
248
+ }
249
+ clearQueue() {
250
+ this.offlineQueue.forEach((msg) => msg.reject(new Error("Queue cleared")));
251
+ this.offlineQueue = [];
252
+ }
253
+ async flushOfflineQueue() {
254
+ const queue = [...this.offlineQueue];
255
+ this.offlineQueue = [];
256
+ for (const item of queue) {
257
+ try {
258
+ const result = await this.request("POST", "/v1/responses", item.params);
259
+ item.resolve(result);
260
+ } catch (err) {
261
+ item.reject(err);
262
+ }
263
+ }
264
+ }
265
+ };
266
+
267
+ // src/hooks.ts
268
+ import {
269
+ useState,
270
+ useEffect,
271
+ useCallback,
272
+ useRef,
273
+ createContext,
274
+ useContext,
275
+ createElement
276
+ } from "react";
277
+ var JourneyHiveContext = createContext(null);
278
+ function JourneyHiveProvider({ apiKey, baseUrl, children }) {
279
+ const clientRef = useRef(null);
280
+ if (!clientRef.current) {
281
+ clientRef.current = new JourneyHiveRNClient({ apiKey, baseUrl });
282
+ }
283
+ return createElement(JourneyHiveContext.Provider, { value: clientRef.current }, children);
284
+ }
285
+ function useJourneyHive() {
286
+ const client = useContext(JourneyHiveContext);
287
+ if (!client) {
288
+ throw new Error("useJourneyHive must be used within a JourneyHiveProvider");
289
+ }
290
+ return client;
291
+ }
292
+ function useResponse(params) {
293
+ const client = useJourneyHive();
294
+ const [data, setData] = useState(null);
295
+ const [isLoading, setIsLoading] = useState(false);
296
+ const [isStreaming, setIsStreaming] = useState(false);
297
+ const [streamedText, setStreamedText] = useState("");
298
+ const [error, setError] = useState(null);
299
+ const send = useCallback(
300
+ async (input) => {
301
+ setIsLoading(true);
302
+ setIsStreaming(false);
303
+ setStreamedText("");
304
+ setError(null);
305
+ setData(null);
306
+ try {
307
+ const stream = client.createStreamingResponse({
308
+ agent_id: params?.agent_id || "",
309
+ input,
310
+ stream: true,
311
+ conversation_id: params?.conversation_id,
312
+ instructions: params?.instructions
313
+ });
314
+ setIsStreaming(true);
315
+ let accumulated = "";
316
+ for await (const { event, data: eventData } of stream) {
317
+ if (event === "response.output_text.delta") {
318
+ accumulated += eventData.delta;
319
+ setStreamedText(accumulated);
320
+ } else if (event === "response.completed") {
321
+ setData(eventData);
322
+ } else if (event === "response.failed") {
323
+ throw new Error(eventData.error?.message || "Response failed");
324
+ }
325
+ }
326
+ setIsStreaming(false);
327
+ } catch (err) {
328
+ setError(err instanceof Error ? err : new Error(String(err)));
329
+ } finally {
330
+ setIsLoading(false);
331
+ setIsStreaming(false);
332
+ }
333
+ },
334
+ [client, params]
335
+ );
336
+ return { data, isLoading, isStreaming, streamedText, error, send };
337
+ }
338
+ function useConversation(conversationId) {
339
+ const client = useJourneyHive();
340
+ const [conversation, setConversation] = useState(null);
341
+ const [messages, setMessages] = useState([]);
342
+ const [isLoading, setIsLoading] = useState(true);
343
+ const [error, setError] = useState(null);
344
+ useEffect(() => {
345
+ if (!conversationId) return;
346
+ setIsLoading(true);
347
+ Promise.all([
348
+ client.getConversation(conversationId),
349
+ client.getMessages(conversationId)
350
+ ]).then(([conv, msgResult]) => {
351
+ setConversation(conv);
352
+ setMessages(msgResult.data || []);
353
+ }).catch((err) => setError(err instanceof Error ? err : new Error(String(err)))).finally(() => setIsLoading(false));
354
+ }, [conversationId, client]);
355
+ const sendMessage = useCallback(
356
+ async (input) => {
357
+ if (!conversation) return;
358
+ const optimisticMsg = {
359
+ id: `temp_${Date.now()}`,
360
+ object: "message",
361
+ role: "user",
362
+ content: [{ type: "output_text", text: input }],
363
+ created_at: Math.floor(Date.now() / 1e3)
364
+ };
365
+ setMessages((prev) => [...prev, optimisticMsg]);
366
+ try {
367
+ const result = await client.createResponse({
368
+ agent_id: conversation.agent_id,
369
+ input,
370
+ conversation_id: conversationId
371
+ });
372
+ if (result?.output) {
373
+ const assistantMsg = {
374
+ id: result.output[0]?.id || `msg_${Date.now()}`,
375
+ object: "message",
376
+ role: "assistant",
377
+ content: result.output[0]?.content || [],
378
+ created_at: result.created_at
379
+ };
380
+ setMessages((prev) => [...prev, assistantMsg]);
381
+ }
382
+ } catch (err) {
383
+ setError(err instanceof Error ? err : new Error(String(err)));
384
+ setMessages((prev) => prev.filter((m) => m.id !== optimisticMsg.id));
385
+ }
386
+ },
387
+ [client, conversation, conversationId]
388
+ );
389
+ return { conversation, messages, isLoading, error, sendMessage };
390
+ }
391
+ function useAgent(agentId) {
392
+ const client = useJourneyHive();
393
+ const [agent, setAgent] = useState(null);
394
+ const [isLoading, setIsLoading] = useState(true);
395
+ const [error, setError] = useState(null);
396
+ useEffect(() => {
397
+ if (!agentId) return;
398
+ setIsLoading(true);
399
+ client.getAgent(agentId).then((a) => setAgent(a)).catch((err) => setError(err instanceof Error ? err : new Error(String(err)))).finally(() => setIsLoading(false));
400
+ }, [agentId, client]);
401
+ const update = useCallback(
402
+ async (params) => {
403
+ try {
404
+ const updated = await client.updateAgent(agentId, params);
405
+ setAgent(updated);
406
+ } catch (err) {
407
+ setError(err instanceof Error ? err : new Error(String(err)));
408
+ }
409
+ },
410
+ [client, agentId]
411
+ );
412
+ return { agent, isLoading, error, update };
413
+ }
414
+ function useAgents() {
415
+ const client = useJourneyHive();
416
+ const [agents, setAgents] = useState([]);
417
+ const [isLoading, setIsLoading] = useState(true);
418
+ const [error, setError] = useState(null);
419
+ useEffect(() => {
420
+ setIsLoading(true);
421
+ client.listAgents().then((result) => setAgents(result.data || [])).catch((err) => setError(err instanceof Error ? err : new Error(String(err)))).finally(() => setIsLoading(false));
422
+ }, [client]);
423
+ return { agents, isLoading, error };
424
+ }
425
+ function useConnectionStatus() {
426
+ const client = useJourneyHive();
427
+ const [isConnected, setIsConnected] = useState(client.getConnectionStatus());
428
+ useEffect(() => {
429
+ const unsubscribe = client.onConnectionChange((connected) => {
430
+ setIsConnected(connected);
431
+ });
432
+ return unsubscribe;
433
+ }, [client]);
434
+ return { isConnected };
435
+ }
436
+ export {
437
+ JourneyHiveProvider,
438
+ JourneyHiveRNClient,
439
+ RNEventSource,
440
+ parseSSEFromFetch,
441
+ useAgent,
442
+ useAgents,
443
+ useConnectionStatus,
444
+ useConversation,
445
+ useJourneyHive,
446
+ useResponse
447
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@journeyrewards/hive-react-native",
3
+ "version": "1.0.0",
4
+ "description": "React Native SDK for Journey Hive Agent Orchestration",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean --external react --external react-native",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/journeyrewards/hive-react-native"
26
+ },
27
+ "homepage": "https://journey-hive.replit.app/api-guides",
28
+ "license": "MIT",
29
+ "keywords": [
30
+ "journeyrewards",
31
+ "journey-hive",
32
+ "ai-agents",
33
+ "react-native",
34
+ "mobile",
35
+ "sdk"
36
+ ],
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "peerDependencies": {
41
+ "react": ">=18",
42
+ "react-native": ">=0.72"
43
+ },
44
+ "devDependencies": {
45
+ "tsup": "^8.0.0",
46
+ "typescript": "^5.4.0",
47
+ "@types/react": "^18.0.0"
48
+ }
49
+ }