@gr33n-ai/jade-sdk-rn-client 0.1.7 → 0.2.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/README.md CHANGED
@@ -72,14 +72,21 @@ function Chat() {
72
72
  }
73
73
  ```
74
74
 
75
- ## Authentication
75
+ ## Examples
76
76
 
77
- Create an API key in your [organization dashboard](https://jade.gr33n.ai/dashboard/org/).
77
+ See the [jade-sdk-examples][examples] repo for a complete React Native app
78
+ with:
79
+
80
+ [examples]: https://github.com/gr33n-ai/jade-sdk-examples/tree/main/react-native
78
81
 
79
- **Playground app configuration:**
82
+ - Chat UI with streaming
83
+ - Media gallery
84
+ - Custom tool UI components
85
+ - Session management
80
86
 
81
- - **Environment variable**: Create `playground/.env.local` with `JADE_AUTH_TOKEN=your_key`
82
- - **Settings screen**: Enter your key in the app's Settings tab
87
+ ## Authentication
88
+
89
+ Create an API key in your [organization dashboard](https://jade.gr33n.ai/dashboard/org/).
83
90
 
84
91
  ## Features
85
92
 
package/dist/index.js CHANGED
@@ -577,6 +577,13 @@ var SkillNotFoundError = class extends AgentClientError {
577
577
  this.name = "SkillNotFoundError";
578
578
  }
579
579
  };
580
+ var WorkflowNotFoundError = class extends AgentClientError {
581
+ constructor(workflowName) {
582
+ super(`Workflow not found: ${workflowName}`);
583
+ this.workflowName = workflowName;
584
+ this.name = "WorkflowNotFoundError";
585
+ }
586
+ };
580
587
  var RequestError = class extends AgentClientError {
581
588
  constructor(message, statusCode) {
582
589
  super(message);
@@ -660,7 +667,7 @@ async function createSSEStream(options) {
660
667
  emitter.emit("message_delta", data);
661
668
  break;
662
669
  case "message_stop":
663
- emitter.emit("message_stop", {});
670
+ emitter.emit("message_stop", { type: "message_stop" });
664
671
  break;
665
672
  // Anthropic content block events (native, not wrapped)
666
673
  case "content_block_start":
@@ -763,6 +770,8 @@ var AgentClient = class {
763
770
  }
764
771
  async request(path, options = {}) {
765
772
  const url = `${this.baseUrl}${path}`;
773
+ const method = options.method || "GET";
774
+ console.log(`[AgentClient] ${method} ${url}`, options.body ? JSON.parse(options.body) : "");
766
775
  const headers = await this.getHeaders();
767
776
  const controller = new AbortController();
768
777
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
@@ -773,9 +782,12 @@ var AgentClient = class {
773
782
  signal: controller.signal
774
783
  });
775
784
  if (!response.ok) {
785
+ console.log(`[AgentClient] ${method} ${url} -> ${response.status}`);
776
786
  await this.handleErrorResponse(response, path);
777
787
  }
778
- return response.json();
788
+ const json = await response.json();
789
+ console.log(`[AgentClient] ${method} ${url} -> ${response.status}`, JSON.stringify(json).slice(0, 200));
790
+ return json;
779
791
  } finally {
780
792
  clearTimeout(timeoutId);
781
793
  }
@@ -794,6 +806,10 @@ var AgentClient = class {
794
806
  const skillName = path.split("/").pop() || "";
795
807
  throw new SkillNotFoundError(skillName);
796
808
  }
809
+ if (text.toLowerCase().includes("workflow")) {
810
+ const workflowName = path.split("/").pop() || "";
811
+ throw new WorkflowNotFoundError(workflowName);
812
+ }
797
813
  }
798
814
  throw new RequestError(`Request failed: ${response.status}`, response.status);
799
815
  }
@@ -1001,6 +1017,88 @@ var AgentClient = class {
1001
1017
  return this.request(`/plugins/bundle${query}`);
1002
1018
  }
1003
1019
  // ===========================================================================
1020
+ // Workflows API - Personal
1021
+ // ===========================================================================
1022
+ /**
1023
+ * List personal workflows.
1024
+ */
1025
+ async listWorkflows() {
1026
+ return this.request("/workflows");
1027
+ }
1028
+ /**
1029
+ * Get personal workflow by name.
1030
+ */
1031
+ async getWorkflow(name) {
1032
+ return this.request(`/workflows/${name}`);
1033
+ }
1034
+ /**
1035
+ * Create or update a personal workflow.
1036
+ */
1037
+ async saveWorkflow(workflow) {
1038
+ return this.request("/workflows", {
1039
+ method: "POST",
1040
+ body: JSON.stringify(workflow)
1041
+ });
1042
+ }
1043
+ /**
1044
+ * Delete a personal workflow.
1045
+ */
1046
+ async deleteWorkflow(name) {
1047
+ return this.request(`/workflows/${name}`, {
1048
+ method: "DELETE"
1049
+ });
1050
+ }
1051
+ /**
1052
+ * Run a personal workflow.
1053
+ */
1054
+ async runWorkflow(name, inputs) {
1055
+ return this.request(`/workflows/${name}/run`, {
1056
+ method: "POST",
1057
+ body: JSON.stringify({ inputs })
1058
+ });
1059
+ }
1060
+ // ===========================================================================
1061
+ // Workflows API - Organization
1062
+ // ===========================================================================
1063
+ /**
1064
+ * List organization workflows.
1065
+ */
1066
+ async listOrgWorkflows() {
1067
+ return this.request("/workflows/org");
1068
+ }
1069
+ /**
1070
+ * Get organization workflow by name.
1071
+ */
1072
+ async getOrgWorkflow(name) {
1073
+ return this.request(`/workflows/org/${name}`);
1074
+ }
1075
+ /**
1076
+ * Create or update an organization workflow.
1077
+ */
1078
+ async saveOrgWorkflow(workflow) {
1079
+ return this.request("/workflows/org", {
1080
+ method: "POST",
1081
+ body: JSON.stringify(workflow)
1082
+ });
1083
+ }
1084
+ /**
1085
+ * Delete an organization workflow.
1086
+ */
1087
+ async deleteOrgWorkflow(name) {
1088
+ return this.request(`/workflows/org/${name}`, {
1089
+ method: "DELETE"
1090
+ });
1091
+ }
1092
+ /**
1093
+ * Run an organization workflow.
1094
+ */
1095
+ async runOrgWorkflow(name, inputs) {
1096
+ return this.request(`/workflows/org/${name}/run`, {
1097
+ method: "POST",
1098
+ body: JSON.stringify({ inputs })
1099
+ });
1100
+ }
1101
+ // ===========================================================================
1004
1102
  // Utility methods
1005
1103
  // ===========================================================================
1006
1104
  encodeBase64(data) {
@@ -2063,7 +2161,7 @@ async function createRNSSEStream(options) {
2063
2161
  reject(new Error(authError));
2064
2162
  return;
2065
2163
  }
2066
- if (errorMessage.includes("404")) {
2164
+ if (status === 404 || errorMessage.includes("404")) {
2067
2165
  emitter.emit("error", { error: "Session not found or not active" });
2068
2166
  cleanup();
2069
2167
  reject(new Error("Session not found or not active"));
@@ -2443,26 +2541,38 @@ function useJadeSessionCore(options = {}) {
2443
2541
  }
2444
2542
  }, [appState, pauseOnBackground, session.isStreaming]);
2445
2543
  const setupEventHandlers = react.useCallback(
2446
- (emitter) => {
2544
+ (emitter, streamSessionId) => {
2545
+ const resolvedId = { current: streamSessionId };
2546
+ const guard = (prev) => {
2547
+ if (resolvedId.current && prev.sessionId !== resolvedId.current) return true;
2548
+ return false;
2549
+ };
2447
2550
  emitter.on("session", (data) => {
2551
+ resolvedId.current = data.session_id;
2448
2552
  setSession((prev) => ({ ...prev, sessionId: data.session_id }));
2449
2553
  });
2450
2554
  emitter.on("content_block_start", (data) => {
2451
2555
  const block = data.content_block;
2452
2556
  if (block?.type === "text") {
2453
2557
  accumulatedTextRef.current[data.index] = "";
2454
- setSession((prev) => ({ ...prev, showTinkering: false }));
2558
+ setSession((prev) => {
2559
+ if (guard(prev)) return prev;
2560
+ return { ...prev, showTinkering: false };
2561
+ });
2455
2562
  } else if (block?.type === "tool_use") {
2456
- setSession((prev) => ({
2457
- ...prev,
2458
- streamingToolCall: {
2459
- tool_name: block.name || "",
2460
- tool_use_id: block.id || "",
2461
- block_index: data.index,
2462
- accumulated_input: ""
2463
- },
2464
- showTinkering: false
2465
- }));
2563
+ setSession((prev) => {
2564
+ if (guard(prev)) return prev;
2565
+ return {
2566
+ ...prev,
2567
+ streamingToolCall: {
2568
+ tool_name: block.name || "",
2569
+ tool_use_id: block.id || "",
2570
+ block_index: data.index,
2571
+ accumulated_input: ""
2572
+ },
2573
+ showTinkering: false
2574
+ };
2575
+ });
2466
2576
  }
2467
2577
  });
2468
2578
  emitter.on("content_block_delta", (data) => {
@@ -2470,14 +2580,18 @@ function useJadeSessionCore(options = {}) {
2470
2580
  if (delta?.type === "text_delta" && delta.text) {
2471
2581
  const idx = data.index ?? 0;
2472
2582
  accumulatedTextRef.current[idx] = (accumulatedTextRef.current[idx] || "") + delta.text;
2473
- setSession((prev) => ({
2474
- ...prev,
2475
- streamingText: accumulatedTextRef.current[idx],
2476
- streamingBlockIndex: idx,
2477
- showTinkering: false
2478
- }));
2583
+ setSession((prev) => {
2584
+ if (guard(prev)) return prev;
2585
+ return {
2586
+ ...prev,
2587
+ streamingText: accumulatedTextRef.current[idx],
2588
+ streamingBlockIndex: idx,
2589
+ showTinkering: false
2590
+ };
2591
+ });
2479
2592
  } else if (delta?.type === "input_json_delta" && delta.partial_json) {
2480
2593
  setSession((prev) => {
2594
+ if (guard(prev)) return prev;
2481
2595
  if (!prev.streamingToolCall) return prev;
2482
2596
  return {
2483
2597
  ...prev,
@@ -2493,11 +2607,14 @@ function useJadeSessionCore(options = {}) {
2493
2607
  const idx = data.index ?? 0;
2494
2608
  if (accumulatedTextRef.current[idx] !== void 0) {
2495
2609
  delete accumulatedTextRef.current[idx];
2496
- setSession((prev) => ({
2497
- ...prev,
2498
- streamingText: void 0,
2499
- streamingBlockIndex: void 0
2500
- }));
2610
+ setSession((prev) => {
2611
+ if (guard(prev)) return prev;
2612
+ return {
2613
+ ...prev,
2614
+ streamingText: void 0,
2615
+ streamingBlockIndex: void 0
2616
+ };
2617
+ });
2501
2618
  }
2502
2619
  });
2503
2620
  emitter.on("entry", (data) => {
@@ -2509,54 +2626,69 @@ function useJadeSessionCore(options = {}) {
2509
2626
  }
2510
2627
  }
2511
2628
  if (entry.type === "tool_call") {
2512
- setSession((prev) => ({
2513
- ...prev,
2514
- conversation: [...prev.conversation, entry],
2515
- streamingToolCall: void 0,
2516
- isExecutingTool: true,
2517
- executingToolName: entry.tool_name
2518
- }));
2629
+ setSession((prev) => {
2630
+ if (guard(prev)) return prev;
2631
+ return {
2632
+ ...prev,
2633
+ conversation: [...prev.conversation, entry],
2634
+ streamingToolCall: void 0,
2635
+ isExecutingTool: true,
2636
+ executingToolName: entry.tool_name
2637
+ };
2638
+ });
2519
2639
  } else if (entry.type === "tool_result") {
2520
- setSession((prev) => ({
2521
- ...prev,
2522
- conversation: [...prev.conversation, entry],
2523
- isExecutingTool: false,
2524
- executingToolName: void 0
2525
- }));
2640
+ setSession((prev) => {
2641
+ if (guard(prev)) return prev;
2642
+ return {
2643
+ ...prev,
2644
+ conversation: [...prev.conversation, entry],
2645
+ isExecutingTool: false,
2646
+ executingToolName: void 0
2647
+ };
2648
+ });
2526
2649
  } else {
2527
- setSession((prev) => ({
2528
- ...prev,
2529
- conversation: [...prev.conversation, entry]
2530
- }));
2650
+ setSession((prev) => {
2651
+ if (guard(prev)) return prev;
2652
+ return {
2653
+ ...prev,
2654
+ conversation: [...prev.conversation, entry]
2655
+ };
2656
+ });
2531
2657
  }
2532
2658
  });
2533
2659
  emitter.on("complete", (data) => {
2534
- setSession((prev) => ({
2535
- ...prev,
2536
- sessionId: data.session_id || prev.sessionId,
2537
- isStreaming: false,
2538
- showTinkering: false,
2539
- isExecutingTool: false,
2540
- executingToolName: void 0
2541
- }));
2660
+ setSession((prev) => {
2661
+ if (guard(prev)) return prev;
2662
+ return {
2663
+ ...prev,
2664
+ sessionId: data.session_id || prev.sessionId,
2665
+ isStreaming: false,
2666
+ showTinkering: false,
2667
+ isExecutingTool: false,
2668
+ executingToolName: void 0
2669
+ };
2670
+ });
2542
2671
  abortRef.current = null;
2543
2672
  });
2544
2673
  emitter.on("error", (data) => {
2545
- setSession((prev) => ({
2546
- ...prev,
2547
- conversation: [
2548
- ...prev.conversation,
2549
- {
2550
- type: "error",
2551
- text: data.error,
2552
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2553
- }
2554
- ],
2555
- isStreaming: false,
2556
- showTinkering: false,
2557
- isExecutingTool: false,
2558
- executingToolName: void 0
2559
- }));
2674
+ setSession((prev) => {
2675
+ if (guard(prev)) return prev;
2676
+ return {
2677
+ ...prev,
2678
+ conversation: [
2679
+ ...prev.conversation,
2680
+ {
2681
+ type: "error",
2682
+ text: data.error,
2683
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2684
+ }
2685
+ ],
2686
+ isStreaming: false,
2687
+ showTinkering: false,
2688
+ isExecutingTool: false,
2689
+ executingToolName: void 0
2690
+ };
2691
+ });
2560
2692
  abortRef.current = null;
2561
2693
  });
2562
2694
  },
@@ -2583,7 +2715,7 @@ function useJadeSessionCore(options = {}) {
2583
2715
  skills
2584
2716
  });
2585
2717
  abortRef.current = abort;
2586
- setupEventHandlers(emitter);
2718
+ setupEventHandlers(emitter, session.sessionId);
2587
2719
  },
2588
2720
  [client, session.sessionId, session.isStreaming, setupEventHandlers]
2589
2721
  );
@@ -2620,6 +2752,10 @@ function useJadeSessionCore(options = {}) {
2620
2752
  }
2621
2753
  }, [client, session.sessionId]);
2622
2754
  const clear = react.useCallback(() => {
2755
+ abortRef.current?.();
2756
+ abortRef.current = null;
2757
+ accumulatedTextRef.current = {};
2758
+ cancelInProgressRef.current = false;
2623
2759
  setSession({
2624
2760
  conversation: [],
2625
2761
  isStreaming: false
@@ -2665,7 +2801,7 @@ function useJadeSessionCore(options = {}) {
2665
2801
  accumulatedTextRef.current = {};
2666
2802
  const { emitter, abort } = client.reconnect(targetSessionId, -1);
2667
2803
  abortRef.current = abort;
2668
- setupEventHandlers(emitter);
2804
+ setupEventHandlers(emitter, targetSessionId);
2669
2805
  },
2670
2806
  [client, setupEventHandlers]
2671
2807
  );
package/dist/index.mjs CHANGED
@@ -575,6 +575,13 @@ var SkillNotFoundError = class extends AgentClientError {
575
575
  this.name = "SkillNotFoundError";
576
576
  }
577
577
  };
578
+ var WorkflowNotFoundError = class extends AgentClientError {
579
+ constructor(workflowName) {
580
+ super(`Workflow not found: ${workflowName}`);
581
+ this.workflowName = workflowName;
582
+ this.name = "WorkflowNotFoundError";
583
+ }
584
+ };
578
585
  var RequestError = class extends AgentClientError {
579
586
  constructor(message, statusCode) {
580
587
  super(message);
@@ -658,7 +665,7 @@ async function createSSEStream(options) {
658
665
  emitter.emit("message_delta", data);
659
666
  break;
660
667
  case "message_stop":
661
- emitter.emit("message_stop", {});
668
+ emitter.emit("message_stop", { type: "message_stop" });
662
669
  break;
663
670
  // Anthropic content block events (native, not wrapped)
664
671
  case "content_block_start":
@@ -761,6 +768,8 @@ var AgentClient = class {
761
768
  }
762
769
  async request(path, options = {}) {
763
770
  const url = `${this.baseUrl}${path}`;
771
+ const method = options.method || "GET";
772
+ console.log(`[AgentClient] ${method} ${url}`, options.body ? JSON.parse(options.body) : "");
764
773
  const headers = await this.getHeaders();
765
774
  const controller = new AbortController();
766
775
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
@@ -771,9 +780,12 @@ var AgentClient = class {
771
780
  signal: controller.signal
772
781
  });
773
782
  if (!response.ok) {
783
+ console.log(`[AgentClient] ${method} ${url} -> ${response.status}`);
774
784
  await this.handleErrorResponse(response, path);
775
785
  }
776
- return response.json();
786
+ const json = await response.json();
787
+ console.log(`[AgentClient] ${method} ${url} -> ${response.status}`, JSON.stringify(json).slice(0, 200));
788
+ return json;
777
789
  } finally {
778
790
  clearTimeout(timeoutId);
779
791
  }
@@ -792,6 +804,10 @@ var AgentClient = class {
792
804
  const skillName = path.split("/").pop() || "";
793
805
  throw new SkillNotFoundError(skillName);
794
806
  }
807
+ if (text.toLowerCase().includes("workflow")) {
808
+ const workflowName = path.split("/").pop() || "";
809
+ throw new WorkflowNotFoundError(workflowName);
810
+ }
795
811
  }
796
812
  throw new RequestError(`Request failed: ${response.status}`, response.status);
797
813
  }
@@ -999,6 +1015,88 @@ var AgentClient = class {
999
1015
  return this.request(`/plugins/bundle${query}`);
1000
1016
  }
1001
1017
  // ===========================================================================
1018
+ // Workflows API - Personal
1019
+ // ===========================================================================
1020
+ /**
1021
+ * List personal workflows.
1022
+ */
1023
+ async listWorkflows() {
1024
+ return this.request("/workflows");
1025
+ }
1026
+ /**
1027
+ * Get personal workflow by name.
1028
+ */
1029
+ async getWorkflow(name) {
1030
+ return this.request(`/workflows/${name}`);
1031
+ }
1032
+ /**
1033
+ * Create or update a personal workflow.
1034
+ */
1035
+ async saveWorkflow(workflow) {
1036
+ return this.request("/workflows", {
1037
+ method: "POST",
1038
+ body: JSON.stringify(workflow)
1039
+ });
1040
+ }
1041
+ /**
1042
+ * Delete a personal workflow.
1043
+ */
1044
+ async deleteWorkflow(name) {
1045
+ return this.request(`/workflows/${name}`, {
1046
+ method: "DELETE"
1047
+ });
1048
+ }
1049
+ /**
1050
+ * Run a personal workflow.
1051
+ */
1052
+ async runWorkflow(name, inputs) {
1053
+ return this.request(`/workflows/${name}/run`, {
1054
+ method: "POST",
1055
+ body: JSON.stringify({ inputs })
1056
+ });
1057
+ }
1058
+ // ===========================================================================
1059
+ // Workflows API - Organization
1060
+ // ===========================================================================
1061
+ /**
1062
+ * List organization workflows.
1063
+ */
1064
+ async listOrgWorkflows() {
1065
+ return this.request("/workflows/org");
1066
+ }
1067
+ /**
1068
+ * Get organization workflow by name.
1069
+ */
1070
+ async getOrgWorkflow(name) {
1071
+ return this.request(`/workflows/org/${name}`);
1072
+ }
1073
+ /**
1074
+ * Create or update an organization workflow.
1075
+ */
1076
+ async saveOrgWorkflow(workflow) {
1077
+ return this.request("/workflows/org", {
1078
+ method: "POST",
1079
+ body: JSON.stringify(workflow)
1080
+ });
1081
+ }
1082
+ /**
1083
+ * Delete an organization workflow.
1084
+ */
1085
+ async deleteOrgWorkflow(name) {
1086
+ return this.request(`/workflows/org/${name}`, {
1087
+ method: "DELETE"
1088
+ });
1089
+ }
1090
+ /**
1091
+ * Run an organization workflow.
1092
+ */
1093
+ async runOrgWorkflow(name, inputs) {
1094
+ return this.request(`/workflows/org/${name}/run`, {
1095
+ method: "POST",
1096
+ body: JSON.stringify({ inputs })
1097
+ });
1098
+ }
1099
+ // ===========================================================================
1002
1100
  // Utility methods
1003
1101
  // ===========================================================================
1004
1102
  encodeBase64(data) {
@@ -2061,7 +2159,7 @@ async function createRNSSEStream(options) {
2061
2159
  reject(new Error(authError));
2062
2160
  return;
2063
2161
  }
2064
- if (errorMessage.includes("404")) {
2162
+ if (status === 404 || errorMessage.includes("404")) {
2065
2163
  emitter.emit("error", { error: "Session not found or not active" });
2066
2164
  cleanup();
2067
2165
  reject(new Error("Session not found or not active"));
@@ -2441,26 +2539,38 @@ function useJadeSessionCore(options = {}) {
2441
2539
  }
2442
2540
  }, [appState, pauseOnBackground, session.isStreaming]);
2443
2541
  const setupEventHandlers = useCallback(
2444
- (emitter) => {
2542
+ (emitter, streamSessionId) => {
2543
+ const resolvedId = { current: streamSessionId };
2544
+ const guard = (prev) => {
2545
+ if (resolvedId.current && prev.sessionId !== resolvedId.current) return true;
2546
+ return false;
2547
+ };
2445
2548
  emitter.on("session", (data) => {
2549
+ resolvedId.current = data.session_id;
2446
2550
  setSession((prev) => ({ ...prev, sessionId: data.session_id }));
2447
2551
  });
2448
2552
  emitter.on("content_block_start", (data) => {
2449
2553
  const block = data.content_block;
2450
2554
  if (block?.type === "text") {
2451
2555
  accumulatedTextRef.current[data.index] = "";
2452
- setSession((prev) => ({ ...prev, showTinkering: false }));
2556
+ setSession((prev) => {
2557
+ if (guard(prev)) return prev;
2558
+ return { ...prev, showTinkering: false };
2559
+ });
2453
2560
  } else if (block?.type === "tool_use") {
2454
- setSession((prev) => ({
2455
- ...prev,
2456
- streamingToolCall: {
2457
- tool_name: block.name || "",
2458
- tool_use_id: block.id || "",
2459
- block_index: data.index,
2460
- accumulated_input: ""
2461
- },
2462
- showTinkering: false
2463
- }));
2561
+ setSession((prev) => {
2562
+ if (guard(prev)) return prev;
2563
+ return {
2564
+ ...prev,
2565
+ streamingToolCall: {
2566
+ tool_name: block.name || "",
2567
+ tool_use_id: block.id || "",
2568
+ block_index: data.index,
2569
+ accumulated_input: ""
2570
+ },
2571
+ showTinkering: false
2572
+ };
2573
+ });
2464
2574
  }
2465
2575
  });
2466
2576
  emitter.on("content_block_delta", (data) => {
@@ -2468,14 +2578,18 @@ function useJadeSessionCore(options = {}) {
2468
2578
  if (delta?.type === "text_delta" && delta.text) {
2469
2579
  const idx = data.index ?? 0;
2470
2580
  accumulatedTextRef.current[idx] = (accumulatedTextRef.current[idx] || "") + delta.text;
2471
- setSession((prev) => ({
2472
- ...prev,
2473
- streamingText: accumulatedTextRef.current[idx],
2474
- streamingBlockIndex: idx,
2475
- showTinkering: false
2476
- }));
2581
+ setSession((prev) => {
2582
+ if (guard(prev)) return prev;
2583
+ return {
2584
+ ...prev,
2585
+ streamingText: accumulatedTextRef.current[idx],
2586
+ streamingBlockIndex: idx,
2587
+ showTinkering: false
2588
+ };
2589
+ });
2477
2590
  } else if (delta?.type === "input_json_delta" && delta.partial_json) {
2478
2591
  setSession((prev) => {
2592
+ if (guard(prev)) return prev;
2479
2593
  if (!prev.streamingToolCall) return prev;
2480
2594
  return {
2481
2595
  ...prev,
@@ -2491,11 +2605,14 @@ function useJadeSessionCore(options = {}) {
2491
2605
  const idx = data.index ?? 0;
2492
2606
  if (accumulatedTextRef.current[idx] !== void 0) {
2493
2607
  delete accumulatedTextRef.current[idx];
2494
- setSession((prev) => ({
2495
- ...prev,
2496
- streamingText: void 0,
2497
- streamingBlockIndex: void 0
2498
- }));
2608
+ setSession((prev) => {
2609
+ if (guard(prev)) return prev;
2610
+ return {
2611
+ ...prev,
2612
+ streamingText: void 0,
2613
+ streamingBlockIndex: void 0
2614
+ };
2615
+ });
2499
2616
  }
2500
2617
  });
2501
2618
  emitter.on("entry", (data) => {
@@ -2507,54 +2624,69 @@ function useJadeSessionCore(options = {}) {
2507
2624
  }
2508
2625
  }
2509
2626
  if (entry.type === "tool_call") {
2510
- setSession((prev) => ({
2511
- ...prev,
2512
- conversation: [...prev.conversation, entry],
2513
- streamingToolCall: void 0,
2514
- isExecutingTool: true,
2515
- executingToolName: entry.tool_name
2516
- }));
2627
+ setSession((prev) => {
2628
+ if (guard(prev)) return prev;
2629
+ return {
2630
+ ...prev,
2631
+ conversation: [...prev.conversation, entry],
2632
+ streamingToolCall: void 0,
2633
+ isExecutingTool: true,
2634
+ executingToolName: entry.tool_name
2635
+ };
2636
+ });
2517
2637
  } else if (entry.type === "tool_result") {
2518
- setSession((prev) => ({
2519
- ...prev,
2520
- conversation: [...prev.conversation, entry],
2521
- isExecutingTool: false,
2522
- executingToolName: void 0
2523
- }));
2638
+ setSession((prev) => {
2639
+ if (guard(prev)) return prev;
2640
+ return {
2641
+ ...prev,
2642
+ conversation: [...prev.conversation, entry],
2643
+ isExecutingTool: false,
2644
+ executingToolName: void 0
2645
+ };
2646
+ });
2524
2647
  } else {
2525
- setSession((prev) => ({
2526
- ...prev,
2527
- conversation: [...prev.conversation, entry]
2528
- }));
2648
+ setSession((prev) => {
2649
+ if (guard(prev)) return prev;
2650
+ return {
2651
+ ...prev,
2652
+ conversation: [...prev.conversation, entry]
2653
+ };
2654
+ });
2529
2655
  }
2530
2656
  });
2531
2657
  emitter.on("complete", (data) => {
2532
- setSession((prev) => ({
2533
- ...prev,
2534
- sessionId: data.session_id || prev.sessionId,
2535
- isStreaming: false,
2536
- showTinkering: false,
2537
- isExecutingTool: false,
2538
- executingToolName: void 0
2539
- }));
2658
+ setSession((prev) => {
2659
+ if (guard(prev)) return prev;
2660
+ return {
2661
+ ...prev,
2662
+ sessionId: data.session_id || prev.sessionId,
2663
+ isStreaming: false,
2664
+ showTinkering: false,
2665
+ isExecutingTool: false,
2666
+ executingToolName: void 0
2667
+ };
2668
+ });
2540
2669
  abortRef.current = null;
2541
2670
  });
2542
2671
  emitter.on("error", (data) => {
2543
- setSession((prev) => ({
2544
- ...prev,
2545
- conversation: [
2546
- ...prev.conversation,
2547
- {
2548
- type: "error",
2549
- text: data.error,
2550
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2551
- }
2552
- ],
2553
- isStreaming: false,
2554
- showTinkering: false,
2555
- isExecutingTool: false,
2556
- executingToolName: void 0
2557
- }));
2672
+ setSession((prev) => {
2673
+ if (guard(prev)) return prev;
2674
+ return {
2675
+ ...prev,
2676
+ conversation: [
2677
+ ...prev.conversation,
2678
+ {
2679
+ type: "error",
2680
+ text: data.error,
2681
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2682
+ }
2683
+ ],
2684
+ isStreaming: false,
2685
+ showTinkering: false,
2686
+ isExecutingTool: false,
2687
+ executingToolName: void 0
2688
+ };
2689
+ });
2558
2690
  abortRef.current = null;
2559
2691
  });
2560
2692
  },
@@ -2581,7 +2713,7 @@ function useJadeSessionCore(options = {}) {
2581
2713
  skills
2582
2714
  });
2583
2715
  abortRef.current = abort;
2584
- setupEventHandlers(emitter);
2716
+ setupEventHandlers(emitter, session.sessionId);
2585
2717
  },
2586
2718
  [client, session.sessionId, session.isStreaming, setupEventHandlers]
2587
2719
  );
@@ -2618,6 +2750,10 @@ function useJadeSessionCore(options = {}) {
2618
2750
  }
2619
2751
  }, [client, session.sessionId]);
2620
2752
  const clear = useCallback(() => {
2753
+ abortRef.current?.();
2754
+ abortRef.current = null;
2755
+ accumulatedTextRef.current = {};
2756
+ cancelInProgressRef.current = false;
2621
2757
  setSession({
2622
2758
  conversation: [],
2623
2759
  isStreaming: false
@@ -2663,7 +2799,7 @@ function useJadeSessionCore(options = {}) {
2663
2799
  accumulatedTextRef.current = {};
2664
2800
  const { emitter, abort } = client.reconnect(targetSessionId, -1);
2665
2801
  abortRef.current = abort;
2666
- setupEventHandlers(emitter);
2802
+ setupEventHandlers(emitter, targetSessionId);
2667
2803
  },
2668
2804
  [client, setupEventHandlers]
2669
2805
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gr33n-ai/jade-sdk-rn-client",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "description": "React Native client for Jade AI - generate images, videos, and audio on mobile",
5
5
  "license": "MIT",
6
6
  "author": "gr33n.ai",