@usecrow/ui 0.1.50 → 0.1.51

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.cjs CHANGED
@@ -70,6 +70,7 @@ function useChat({
70
70
  persistAnonymousConversations,
71
71
  welcomeMessage,
72
72
  selectedModel: initialSelectedModel,
73
+ toolConsentSettings,
73
74
  onVerificationStatus,
74
75
  onConversationId,
75
76
  onWorkflowEvent,
@@ -94,6 +95,8 @@ function useChat({
94
95
  const abortControllerRef = React3.useRef(null);
95
96
  const hasCheckedPersistRef = React3.useRef(false);
96
97
  const streamingToolCallsRef = React3.useRef([]);
98
+ const toolConsentSettingsRef = React3.useRef(toolConsentSettings);
99
+ toolConsentSettingsRef.current = toolConsentSettings;
97
100
  React3.useEffect(() => {
98
101
  if (initialSelectedModel) {
99
102
  setSelectedModel((prev) => prev !== initialSelectedModel ? initialSelectedModel : prev);
@@ -345,26 +348,43 @@ function useChat({
345
348
  }
346
349
  break;
347
350
  case "client_tool_call":
348
- onToolCall?.({
349
- type: "start",
350
- toolName: parsed.tool_name,
351
- arguments: parsed.arguments
352
- });
353
- const clientToolCall = {
354
- id: parsed.tool_call_id || `tool-${Date.now()}`,
355
- name: parsed.tool_name,
356
- displayName: parsed.display_name || void 0,
357
- arguments: parsed.arguments || {},
358
- status: "executing",
359
- timestamp: /* @__PURE__ */ new Date()
360
- };
361
- streamingToolCallsRef.current = [...streamingToolCallsRef.current, clientToolCall];
362
- setActiveToolCalls((prev) => [...prev, clientToolCall]);
363
- pendingClientTools.push({
364
- toolName: parsed.tool_name,
365
- toolCallId: parsed.tool_call_id,
366
- arguments: parsed.arguments
367
- });
351
+ {
352
+ const needsConsent = toolConsentSettingsRef.current?.[parsed.tool_name]?.requires_consent === true;
353
+ onToolCall?.({
354
+ type: "start",
355
+ toolName: parsed.tool_name,
356
+ arguments: parsed.arguments
357
+ });
358
+ if (needsConsent) {
359
+ const consentClientTc = {
360
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
361
+ name: parsed.tool_name,
362
+ displayName: parsed.display_name || void 0,
363
+ arguments: parsed.arguments || {},
364
+ status: "awaiting_consent",
365
+ requiresConsent: true,
366
+ timestamp: /* @__PURE__ */ new Date()
367
+ };
368
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, consentClientTc];
369
+ setActiveToolCalls((prev) => [...prev, consentClientTc]);
370
+ } else {
371
+ const clientToolCall = {
372
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
373
+ name: parsed.tool_name,
374
+ displayName: parsed.display_name || void 0,
375
+ arguments: parsed.arguments || {},
376
+ status: "executing",
377
+ timestamp: /* @__PURE__ */ new Date()
378
+ };
379
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, clientToolCall];
380
+ setActiveToolCalls((prev) => [...prev, clientToolCall]);
381
+ pendingClientTools.push({
382
+ toolName: parsed.tool_name,
383
+ toolCallId: parsed.tool_call_id,
384
+ arguments: parsed.arguments
385
+ });
386
+ }
387
+ }
368
388
  break;
369
389
  case "tool_consent_required":
370
390
  onToolCall?.({
@@ -746,21 +766,36 @@ function useChat({
746
766
  break;
747
767
  case "client_tool_call":
748
768
  {
749
- const toolCallEntry = {
750
- id: parsed.tool_call_id || `tool-${Date.now()}`,
751
- name: parsed.tool_name,
752
- displayName: parsed.display_name || void 0,
753
- arguments: parsed.arguments || {},
754
- status: "executing",
755
- timestamp: /* @__PURE__ */ new Date()
756
- };
757
- streamingToolCallsRef.current = [...streamingToolCallsRef.current, toolCallEntry];
758
- setActiveToolCalls((prev) => [...prev, toolCallEntry]);
759
- pendingClientTools.push({
760
- toolName: parsed.tool_name,
761
- toolCallId: parsed.tool_call_id,
762
- arguments: parsed.arguments
763
- });
769
+ const needsConsent2 = toolConsentSettingsRef.current?.[parsed.tool_name]?.requires_consent === true;
770
+ if (needsConsent2) {
771
+ const consentEntry2 = {
772
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
773
+ name: parsed.tool_name,
774
+ displayName: parsed.display_name || void 0,
775
+ arguments: parsed.arguments || {},
776
+ status: "awaiting_consent",
777
+ requiresConsent: true,
778
+ timestamp: /* @__PURE__ */ new Date()
779
+ };
780
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, consentEntry2];
781
+ setActiveToolCalls((prev) => [...prev, consentEntry2]);
782
+ } else {
783
+ const toolCallEntry = {
784
+ id: parsed.tool_call_id || `tool-${Date.now()}`,
785
+ name: parsed.tool_name,
786
+ displayName: parsed.display_name || void 0,
787
+ arguments: parsed.arguments || {},
788
+ status: "executing",
789
+ timestamp: /* @__PURE__ */ new Date()
790
+ };
791
+ streamingToolCallsRef.current = [...streamingToolCallsRef.current, toolCallEntry];
792
+ setActiveToolCalls((prev) => [...prev, toolCallEntry]);
793
+ pendingClientTools.push({
794
+ toolName: parsed.tool_name,
795
+ toolCallId: parsed.tool_call_id,
796
+ arguments: parsed.arguments
797
+ });
798
+ }
764
799
  }
765
800
  break;
766
801
  case "tool_consent_required":
@@ -1527,6 +1562,9 @@ function useWidgetStyles({
1527
1562
  const [initialSuggestions, setInitialSuggestions] = React3.useState(
1528
1563
  styleCache.get(key)?.initialSuggestions || []
1529
1564
  );
1565
+ const [toolConsentSettings, setToolConsentSettings] = React3.useState(
1566
+ styleCache.get(key)?.toolConsentSettings || {}
1567
+ );
1530
1568
  const [agentName, setAgentName] = React3.useState(
1531
1569
  styleCache.get(key)?.agentName || "Assistant"
1532
1570
  );
@@ -1567,6 +1605,7 @@ function useWidgetStyles({
1567
1605
  setWelcomeMessage(config.welcomeMessage ?? void 0);
1568
1606
  setSelectedModel(config.model ?? void 0);
1569
1607
  setInitialSuggestions(config.initialSuggestions || []);
1608
+ setToolConsentSettings(config.toolConsentSettings || {});
1570
1609
  } catch (err) {
1571
1610
  console.error("[CrowWidget] Failed to fetch styles:", err);
1572
1611
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -1605,6 +1644,7 @@ function useWidgetStyles({
1605
1644
  welcomeMessage,
1606
1645
  selectedModel,
1607
1646
  initialSuggestions,
1647
+ toolConsentSettings,
1608
1648
  refetch: fetchStyles
1609
1649
  };
1610
1650
  }
@@ -1640,6 +1680,9 @@ function useCopilotStyles({
1640
1680
  const [selectedModel, setSelectedModel] = React3.useState(
1641
1681
  styleCache.get(key)?.model ?? void 0
1642
1682
  );
1683
+ const [toolConsentSettings, setToolConsentSettings] = React3.useState(
1684
+ styleCache.get(key)?.toolConsentSettings || {}
1685
+ );
1643
1686
  const hasFetchedRef = React3.useRef(false);
1644
1687
  const fetchStyles = async () => {
1645
1688
  if (skip) return;
@@ -1656,6 +1699,7 @@ function useCopilotStyles({
1656
1699
  setPersistAnonymousConversations(config.persistAnonymousConversations ?? true);
1657
1700
  setWelcomeMessage(config.welcomeMessage ?? void 0);
1658
1701
  setSelectedModel(config.model ?? void 0);
1702
+ setToolConsentSettings(config.toolConsentSettings || {});
1659
1703
  } catch (err) {
1660
1704
  console.error("[CrowCopilot] Failed to fetch styles:", err);
1661
1705
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -1693,6 +1737,7 @@ function useCopilotStyles({
1693
1737
  persistAnonymousConversations,
1694
1738
  welcomeMessage,
1695
1739
  selectedModel,
1740
+ toolConsentSettings,
1696
1741
  refetch: fetchStyles
1697
1742
  };
1698
1743
  }
@@ -3348,7 +3393,8 @@ function CrowWidget({
3348
3393
  persistAnonymousConversations,
3349
3394
  welcomeMessage: welcomeMessageFromAPI,
3350
3395
  selectedModel: selectedModelFromAPI,
3351
- initialSuggestions
3396
+ initialSuggestions,
3397
+ toolConsentSettings
3352
3398
  } = useWidgetStyles({
3353
3399
  productId,
3354
3400
  apiUrl,
@@ -3391,6 +3437,7 @@ function CrowWidget({
3391
3437
  persistAnonymousConversations,
3392
3438
  welcomeMessage,
3393
3439
  selectedModel,
3440
+ toolConsentSettings,
3394
3441
  onVerificationStatus: (isVerified) => {
3395
3442
  setIsVerifiedUser(isVerified);
3396
3443
  },
@@ -3693,14 +3740,47 @@ function CrowWidget({
3693
3740
  const handleToolConsent = async (toolCallId, approved) => {
3694
3741
  const toolCall = chat.activeToolCalls.find((tc) => tc.id === toolCallId) || chat.messages.flatMap((m) => m.toolCalls || []).find((tc) => tc.id === toolCallId);
3695
3742
  if (!toolCall) return;
3743
+ const isClientSide = !toolCall.serverSideExecution;
3696
3744
  if (approved) {
3697
3745
  chat.updateToolCallStatus(toolCallId, "executing");
3698
- if (submitToolResultRef.current) {
3699
- await submitToolResultRef.current(
3700
- toolCallId,
3701
- toolCall.name,
3702
- { consent_approved: true, tool_arguments: toolCall.arguments || {} }
3703
- );
3746
+ if (isClientSide) {
3747
+ try {
3748
+ const result = await executeClientToolRef.current?.(
3749
+ toolCall.name,
3750
+ toolCall.arguments || {}
3751
+ );
3752
+ const resultObj = result;
3753
+ const dataObj = resultObj?.data;
3754
+ const wasUserCancelled = dataObj?.declined === true || typeof resultObj?.error === "string" && resultObj.error.includes("cancelled by user") || typeof resultObj?.error === "string" && resultObj.error.includes("declined");
3755
+ if (wasUserCancelled) {
3756
+ console.log("[Crow Widget] Tool was cancelled by user after consent");
3757
+ return;
3758
+ }
3759
+ if (result && submitToolResultRef.current) {
3760
+ await submitToolResultRef.current(
3761
+ toolCallId,
3762
+ toolCall.name,
3763
+ result
3764
+ );
3765
+ }
3766
+ } catch (e) {
3767
+ console.error("[Crow Widget] Tool error after consent:", e);
3768
+ if (submitToolResultRef.current) {
3769
+ await submitToolResultRef.current(
3770
+ toolCallId,
3771
+ toolCall.name,
3772
+ { success: false, error: String(e) }
3773
+ );
3774
+ }
3775
+ }
3776
+ } else {
3777
+ if (submitToolResultRef.current) {
3778
+ await submitToolResultRef.current(
3779
+ toolCallId,
3780
+ toolCall.name,
3781
+ { consent_approved: true, tool_arguments: toolCall.arguments || {} }
3782
+ );
3783
+ }
3704
3784
  }
3705
3785
  } else {
3706
3786
  chat.updateToolCallStatus(toolCallId, "denied");
@@ -4129,7 +4209,8 @@ function CrowCopilot({
4129
4209
  pageNavigationRoutes,
4130
4210
  persistAnonymousConversations,
4131
4211
  welcomeMessage: welcomeMessageFromAPI,
4132
- selectedModel
4212
+ selectedModel,
4213
+ toolConsentSettings
4133
4214
  } = useCopilotStyles({
4134
4215
  productId,
4135
4216
  apiUrl,
@@ -4320,6 +4401,7 @@ function CrowCopilot({
4320
4401
  persistAnonymousConversations,
4321
4402
  welcomeMessage,
4322
4403
  selectedModel,
4404
+ toolConsentSettings,
4323
4405
  onVerificationStatus: (isVerified) => {
4324
4406
  setIsVerifiedUser(isVerified);
4325
4407
  },
@@ -4518,14 +4600,54 @@ function CrowCopilot({
4518
4600
  const handleToolConsent = async (toolCallId, approved) => {
4519
4601
  const toolCall = chat.activeToolCalls.find((tc) => tc.id === toolCallId) || chat.messages.flatMap((m) => m.toolCalls || []).find((tc) => tc.id === toolCallId);
4520
4602
  if (!toolCall) return;
4603
+ const isClientSide = !toolCall.serverSideExecution;
4521
4604
  if (approved) {
4522
4605
  chat.updateToolCallStatus(toolCallId, "executing");
4523
- if (submitToolResultRef.current) {
4524
- await submitToolResultRef.current(
4525
- toolCallId,
4526
- toolCall.name,
4527
- { consent_approved: true, tool_arguments: toolCall.arguments || {} }
4528
- );
4606
+ if (isClientSide) {
4607
+ try {
4608
+ const result = await executeClientToolRef.current?.(
4609
+ toolCall.name,
4610
+ toolCall.arguments || {}
4611
+ );
4612
+ const resultObj = result;
4613
+ const dataObj = resultObj?.data;
4614
+ const wasUserCancelled = dataObj?.declined === true || typeof resultObj?.error === "string" && resultObj.error.includes("cancelled by user") || typeof resultObj?.error === "string" && resultObj.error.includes("declined");
4615
+ if (wasUserCancelled) {
4616
+ console.log("[Crow Copilot] Tool was cancelled by user after consent");
4617
+ if (submitToolResultRef.current) {
4618
+ await submitToolResultRef.current(
4619
+ toolCallId,
4620
+ toolCall.name,
4621
+ { success: false, cancelled: true, error: "Action was cancelled by the user." }
4622
+ );
4623
+ }
4624
+ return;
4625
+ }
4626
+ if (result && submitToolResultRef.current) {
4627
+ await submitToolResultRef.current(
4628
+ toolCallId,
4629
+ toolCall.name,
4630
+ result
4631
+ );
4632
+ }
4633
+ } catch (e) {
4634
+ console.error("[Crow Copilot] Tool error after consent:", e);
4635
+ if (submitToolResultRef.current) {
4636
+ await submitToolResultRef.current(
4637
+ toolCallId,
4638
+ toolCall.name,
4639
+ { success: false, error: String(e) }
4640
+ );
4641
+ }
4642
+ }
4643
+ } else {
4644
+ if (submitToolResultRef.current) {
4645
+ await submitToolResultRef.current(
4646
+ toolCallId,
4647
+ toolCall.name,
4648
+ { consent_approved: true, tool_arguments: toolCall.arguments || {} }
4649
+ );
4650
+ }
4529
4651
  }
4530
4652
  } else {
4531
4653
  chat.updateToolCallStatus(toolCallId, "denied");