@mastra/playground-ui 68.0.0-alpha.3 → 68.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.
Files changed (163) hide show
  1. package/dist/{FilterBar-CQl04O6t.cjs → FilterBar-BSP2UTjc.cjs} +2 -2
  2. package/dist/{FilterBar-CQl04O6t.cjs.map → FilterBar-BSP2UTjc.cjs.map} +1 -1
  3. package/dist/{FilterBar-BgW2dqRQ.js → FilterBar-PSQ1OwqH.js} +2 -2
  4. package/dist/{FilterBar-BgW2dqRQ.js.map → FilterBar-PSQ1OwqH.js.map} +1 -1
  5. package/dist/{MetricsCard-CEB-f2sv.cjs → MetricsCard-BEwiS7Pe.cjs} +8 -8
  6. package/dist/MetricsCard-BEwiS7Pe.cjs.map +1 -0
  7. package/dist/{MetricsCard-M8awJ2B_.js → MetricsCard-ua6C6b8a.js} +10 -10
  8. package/dist/MetricsCard-ua6C6b8a.js.map +1 -0
  9. package/dist/{MetricsLineChart-BhHoduO0.js → MetricsLineChart-4SwxBbQx.js} +14 -3
  10. package/dist/MetricsLineChart-4SwxBbQx.js.map +1 -0
  11. package/dist/{MetricsLineChart-CjBm4IXu.cjs → MetricsLineChart-tZxsauau.cjs} +13 -2
  12. package/dist/MetricsLineChart-tZxsauau.cjs.map +1 -0
  13. package/dist/{MetricsStackedBarChart-BdMlKv6G.cjs → MetricsStackedBarChart-CDLE76IB.cjs} +2 -2
  14. package/dist/{MetricsStackedBarChart-BdMlKv6G.cjs.map → MetricsStackedBarChart-CDLE76IB.cjs.map} +1 -1
  15. package/dist/{MetricsStackedBarChart-BRuA6MTE.js → MetricsStackedBarChart-D2QyS4-z.js} +2 -2
  16. package/dist/{MetricsStackedBarChart-BRuA6MTE.js.map → MetricsStackedBarChart-D2QyS4-z.js.map} +1 -1
  17. package/dist/components/DateTimeRangePicker.cjs.js +1 -1
  18. package/dist/components/DateTimeRangePicker.es.js +1 -1
  19. package/dist/components/FilterBar.cjs.js +2 -2
  20. package/dist/components/FilterBar.es.js +2 -2
  21. package/dist/components/MetricsCard.cjs.js +1 -1
  22. package/dist/components/MetricsCard.es.js +1 -1
  23. package/dist/components/MetricsLineChart.cjs.js +1 -1
  24. package/dist/components/MetricsLineChart.es.js +1 -1
  25. package/dist/components/MetricsStackedBarChart.cjs.js +1 -1
  26. package/dist/components/MetricsStackedBarChart.es.js +1 -1
  27. package/dist/{date-time-range-picker-CTt7Od0P.cjs → date-time-range-picker-CWGjoR3j.cjs} +40 -13
  28. package/dist/date-time-range-picker-CWGjoR3j.cjs.map +1 -0
  29. package/dist/{date-time-range-picker-DKh1K9Iy.js → date-time-range-picker-CdSiXwB4.js} +40 -13
  30. package/dist/date-time-range-picker-CdSiXwB4.js.map +1 -0
  31. package/dist/domains/chat/messages/message-row.cjs.js +1 -1
  32. package/dist/domains/chat/messages/message-row.es.js +1 -1
  33. package/dist/domains/chat/tools/badges/agent-badge-wrapper.cjs.js +1 -1
  34. package/dist/domains/chat/tools/badges/agent-badge-wrapper.es.js +1 -1
  35. package/dist/domains/chat/tools/badges/agent-badge.cjs.js +1 -1
  36. package/dist/domains/chat/tools/badges/agent-badge.es.js +1 -1
  37. package/dist/domains/chat/tools/connect-prompt-tool.cjs.js +79 -4
  38. package/dist/domains/chat/tools/connect-prompt-tool.cjs.js.map +1 -1
  39. package/dist/domains/chat/tools/connect-prompt-tool.es.js +79 -4
  40. package/dist/domains/chat/tools/connect-prompt-tool.es.js.map +1 -1
  41. package/dist/domains/chat/tools/mcp-input-request-tool.cjs.js +10 -12
  42. package/dist/domains/chat/tools/mcp-input-request-tool.cjs.js.map +1 -1
  43. package/dist/domains/chat/tools/mcp-input-request-tool.es.js +10 -12
  44. package/dist/domains/chat/tools/mcp-input-request-tool.es.js.map +1 -1
  45. package/dist/domains/chat/tools/tool-card-kind.cjs.js +1 -1
  46. package/dist/domains/chat/tools/tool-card-kind.cjs.js.map +1 -1
  47. package/dist/domains/chat/tools/tool-card-kind.es.js +1 -1
  48. package/dist/domains/chat/tools/tool-card-kind.es.js.map +1 -1
  49. package/dist/domains/chat/tools/tool-card.cjs.js +1 -1
  50. package/dist/domains/chat/tools/tool-card.es.js +1 -1
  51. package/dist/domains/connect/types.cjs.js.map +1 -1
  52. package/dist/domains/connect/types.es.js.map +1 -1
  53. package/dist/domains/connect/use-user-connect.cjs.js +19 -2
  54. package/dist/domains/connect/use-user-connect.cjs.js.map +1 -1
  55. package/dist/domains/connect/use-user-connect.es.js +19 -2
  56. package/dist/domains/connect/use-user-connect.es.js.map +1 -1
  57. package/dist/domains/dashboards/components/dashboard-renderer.cjs.js +4 -3
  58. package/dist/domains/dashboards/components/dashboard-renderer.cjs.js.map +1 -1
  59. package/dist/domains/dashboards/components/dashboard-renderer.es.js +4 -3
  60. package/dist/domains/dashboards/components/dashboard-renderer.es.js.map +1 -1
  61. package/dist/domains/dashboards/components/widget-chrome.cjs.js +1 -1
  62. package/dist/domains/dashboards/components/widget-chrome.es.js +1 -1
  63. package/dist/domains/dashboards/components/widget-error.cjs.js +1 -1
  64. package/dist/domains/dashboards/components/widget-error.es.js +1 -1
  65. package/dist/domains/dashboards/hooks/use-widget-views.cjs.js +4 -3
  66. package/dist/domains/dashboards/hooks/use-widget-views.cjs.js.map +1 -1
  67. package/dist/domains/dashboards/hooks/use-widget-views.es.js +4 -3
  68. package/dist/domains/dashboards/hooks/use-widget-views.es.js.map +1 -1
  69. package/dist/domains/dashboards/kinds/scores/scores-widget.cjs.js +9 -6
  70. package/dist/domains/dashboards/kinds/scores/scores-widget.cjs.js.map +1 -1
  71. package/dist/domains/dashboards/kinds/scores/scores-widget.es.js +9 -6
  72. package/dist/domains/dashboards/kinds/scores/scores-widget.es.js.map +1 -1
  73. package/dist/domains/dashboards/kinds/table/table-tabs.cjs.js +2 -2
  74. package/dist/domains/dashboards/kinds/table/table-tabs.es.js +2 -2
  75. package/dist/domains/dashboards/kinds/table/table-widget.cjs.js +1 -1
  76. package/dist/domains/dashboards/kinds/table/table-widget.es.js +1 -1
  77. package/dist/domains/dashboards/kinds/timeseries/timeseries-widget.cjs.js +6 -5
  78. package/dist/domains/dashboards/kinds/timeseries/timeseries-widget.cjs.js.map +1 -1
  79. package/dist/domains/dashboards/kinds/timeseries/timeseries-widget.es.js +6 -5
  80. package/dist/domains/dashboards/kinds/timeseries/timeseries-widget.es.js.map +1 -1
  81. package/dist/domains/dashboards/lib/chrome.cjs.js +9 -2
  82. package/dist/domains/dashboards/lib/chrome.cjs.js.map +1 -1
  83. package/dist/domains/dashboards/lib/chrome.es.js +9 -2
  84. package/dist/domains/dashboards/lib/chrome.es.js.map +1 -1
  85. package/dist/domains/dashboards/widget-kind.cjs.js.map +1 -1
  86. package/dist/domains/dashboards/widget-kind.es.js.map +1 -1
  87. package/dist/domains/metrics/components/agent-runs-card.cjs.js +3 -3
  88. package/dist/domains/metrics/components/agent-runs-card.es.js +3 -3
  89. package/dist/domains/metrics/components/breakdown/breakdown-tab-body.cjs.js +1 -1
  90. package/dist/domains/metrics/components/breakdown/breakdown-tab-body.es.js +1 -1
  91. package/dist/domains/metrics/components/breakdown-card.cjs.js +1 -1
  92. package/dist/domains/metrics/components/breakdown-card.es.js +1 -1
  93. package/dist/domains/metrics/components/chart-area.cjs.js +1 -1
  94. package/dist/domains/metrics/components/chart-area.es.js +1 -1
  95. package/dist/domains/metrics/components/chart-card-error.cjs.js +1 -1
  96. package/dist/domains/metrics/components/chart-card-error.es.js +1 -1
  97. package/dist/domains/metrics/components/chart-card.cjs.js +1 -1
  98. package/dist/domains/metrics/components/chart-card.es.js +1 -1
  99. package/dist/domains/metrics/components/date-range-selector.cjs.js +1 -1
  100. package/dist/domains/metrics/components/date-range-selector.es.js +1 -1
  101. package/dist/domains/metrics/components/distinct-ids/distinct-ids-chart.cjs.js +3 -2
  102. package/dist/domains/metrics/components/distinct-ids/distinct-ids-chart.cjs.js.map +1 -1
  103. package/dist/domains/metrics/components/distinct-ids/distinct-ids-chart.es.js +3 -2
  104. package/dist/domains/metrics/components/distinct-ids/distinct-ids-chart.es.js.map +1 -1
  105. package/dist/domains/metrics/components/failure-rate-card.cjs.js +3 -2
  106. package/dist/domains/metrics/components/failure-rate-card.cjs.js.map +1 -1
  107. package/dist/domains/metrics/components/failure-rate-card.es.js +3 -2
  108. package/dist/domains/metrics/components/failure-rate-card.es.js.map +1 -1
  109. package/dist/domains/metrics/components/latency-card.cjs.js +3 -2
  110. package/dist/domains/metrics/components/latency-card.cjs.js.map +1 -1
  111. package/dist/domains/metrics/components/latency-card.es.js +3 -2
  112. package/dist/domains/metrics/components/latency-card.es.js.map +1 -1
  113. package/dist/domains/metrics/components/metrics-time-range-chip.cjs.js +1 -1
  114. package/dist/domains/metrics/components/metrics-time-range-chip.es.js +1 -1
  115. package/dist/domains/metrics/components/scores-card.cjs.js +2 -1
  116. package/dist/domains/metrics/components/scores-card.cjs.js.map +1 -1
  117. package/dist/domains/metrics/components/scores-card.es.js +2 -1
  118. package/dist/domains/metrics/components/scores-card.es.js.map +1 -1
  119. package/dist/domains/metrics/components/token-usage-card.cjs.js +3 -3
  120. package/dist/domains/metrics/components/token-usage-card.es.js +3 -3
  121. package/dist/domains/metrics/components/trace-aggregate-card.cjs.js +1 -1
  122. package/dist/domains/metrics/components/trace-aggregate-card.es.js +1 -1
  123. package/dist/domains/observability/components/observability-histogram.cjs.js +1 -1
  124. package/dist/domains/observability/components/observability-histogram.es.js +1 -1
  125. package/dist/domains/spans/components/spans-filter-bar.cjs.js +2 -2
  126. package/dist/domains/spans/components/spans-filter-bar.es.js +2 -2
  127. package/dist/domains/spans/hooks/use-spans-filter-bar.cjs.js +1 -1
  128. package/dist/domains/spans/hooks/use-spans-filter-bar.es.js +1 -1
  129. package/dist/domains/threads/components/threads-filter-bar.cjs.js +2 -2
  130. package/dist/domains/threads/components/threads-filter-bar.es.js +2 -2
  131. package/dist/domains/threads/hooks/use-threads-filter-bar.cjs.js +1 -1
  132. package/dist/domains/threads/hooks/use-threads-filter-bar.es.js +1 -1
  133. package/dist/domains/traces/components/traces-explorer/traces-explorer-filter-bar.cjs.js +2 -2
  134. package/dist/domains/traces/components/traces-explorer/traces-explorer-filter-bar.es.js +2 -2
  135. package/dist/domains/traces/components/traces-explorer/use-traces-filter-bar.cjs.js +1 -1
  136. package/dist/domains/traces/components/traces-explorer/use-traces-filter-bar.es.js +1 -1
  137. package/dist/{filter-bar-time-range-mg9JeYue.js → filter-bar-time-range-qlV8p7hp.js} +2 -2
  138. package/dist/{filter-bar-time-range-mg9JeYue.js.map → filter-bar-time-range-qlV8p7hp.js.map} +1 -1
  139. package/dist/{filter-bar-time-range-BA-pXMlo.cjs → filter-bar-time-range-zdM024JD.cjs} +2 -2
  140. package/dist/{filter-bar-time-range-BA-pXMlo.cjs.map → filter-bar-time-range-zdM024JD.cjs.map} +1 -1
  141. package/dist/src/domains/chat/tools/connect-prompt-tool.d.ts +10 -1
  142. package/dist/src/domains/connect/types.d.ts +16 -0
  143. package/dist/src/domains/dashboards/components/dashboard-renderer.d.ts +4 -2
  144. package/dist/src/domains/dashboards/hooks/use-widget-views.d.ts +4 -3
  145. package/dist/src/domains/dashboards/index.d.ts +1 -1
  146. package/dist/src/domains/dashboards/kinds/scores/scores-widget.d.ts +1 -1
  147. package/dist/src/domains/dashboards/lib/chrome.d.ts +6 -4
  148. package/dist/src/domains/dashboards/widget-kind.d.ts +12 -0
  149. package/dist/src/ds/components/DateTimeRangePicker/date-time-range-picker.d.ts +12 -1
  150. package/dist/src/ds/components/MetricsCard/metrics-card-headline.d.ts +1 -1
  151. package/dist/src/ds/components/MetricsLineChart/metrics-line-chart.d.ts +8 -0
  152. package/dist/style.css +4 -0
  153. package/dist/{tool-card-rxhTDpk_.cjs → tool-card-BOLXeKIp.cjs} +4 -1
  154. package/dist/{tool-card-rxhTDpk_.cjs.map → tool-card-BOLXeKIp.cjs.map} +1 -1
  155. package/dist/{tool-card-toDgoZzJ.js → tool-card-HZ5bm7ue.js} +4 -1
  156. package/dist/{tool-card-toDgoZzJ.js.map → tool-card-HZ5bm7ue.js.map} +1 -1
  157. package/package.json +9 -9
  158. package/dist/MetricsCard-CEB-f2sv.cjs.map +0 -1
  159. package/dist/MetricsCard-M8awJ2B_.js.map +0 -1
  160. package/dist/MetricsLineChart-BhHoduO0.js.map +0 -1
  161. package/dist/MetricsLineChart-CjBm4IXu.cjs.map +0 -1
  162. package/dist/date-time-range-picker-CTt7Od0P.cjs.map +0 -1
  163. package/dist/date-time-range-picker-DKh1K9Iy.js.map +0 -1
@@ -176,6 +176,16 @@ var DATE_PRESETS = [
176
176
  label: "Custom range..."
177
177
  }
178
178
  ];
179
+ var sameTime = (a, b) => a?.getTime() === b?.getTime();
180
+ /**
181
+ * Whether a named range breaks the limits the custom editor enforces: it starts before
182
+ * `minDate`'s day, or spans more than `maxRangeMs` (an open end counts up to now).
183
+ */
184
+ function outsideLimits(range, minDate, maxRangeMs) {
185
+ const tooEarly = minDate !== void 0 && range.from < startOfDay(minDate);
186
+ const span = (range.to ?? /* @__PURE__ */ new Date()).getTime() - range.from.getTime();
187
+ return tooEarly || maxRangeMs !== void 0 && span > maxRangeMs;
188
+ }
179
189
  var fmtClock = (d) => d.toLocaleTimeString("en-US", d.getMinutes() === 0 ? { hour: "numeric" } : {
180
190
  hour: "numeric",
181
191
  minute: "2-digit"
@@ -197,7 +207,7 @@ function customRangeLabel(dateFrom, dateTo) {
197
207
  if (!dateTo) return `${start} \u2013 End`;
198
208
  return `${start} \u2013 ${dateFrom !== void 0 && fmtDay(dateFrom) === fmtDay(dateTo) ? fmtClock(dateTo) : fmtMoment(dateTo)}`;
199
209
  }
200
- function DateTimeRangePicker({ preset = "all", onPresetChange, dateFrom, dateTo, onDateChange, onDateRangeChange, disabled, maxRangeMs, minDate, presets, size = "sm", defaultPreset, onReset, ref }) {
210
+ function DateTimeRangePicker({ preset = "all", onPresetChange, dateFrom, dateTo, onDateChange, onDateRangeChange, disabled, maxRangeMs, minDate, presets, namedRanges = [], size = "sm", defaultPreset, onReset, ref }) {
201
211
  const visiblePresets = presets ? DATE_PRESETS.filter((p) => presets.includes(p.value)) : DATE_PRESETS;
202
212
  const [customRangeOpen, setCustomRangeOpen] = useState(false);
203
213
  const [presetMenuOpen, setPresetMenuOpen] = useState(false);
@@ -240,9 +250,16 @@ function DateTimeRangePicker({ preset = "all", onPresetChange, dateFrom, dateTo,
240
250
  if (presetBeforeCustom) applyPreset(presetBeforeCustom);
241
251
  setPresetMenuOpen(true);
242
252
  };
243
- const canReset = defaultPreset !== void 0 && shownPreset === "custom";
244
- const editingCustomRange = shownPreset === "custom" && !presetMenuOpen;
245
- const label = shownPreset === "custom" ? customRangeLabel(dateFrom, dateTo) : datePresetLabel;
253
+ const namedIndex = shownPreset === "custom" ? namedRanges.findIndex((r) => sameTime(r.from, dateFrom) && sameTime(r.to, dateTo)) : -1;
254
+ const named = namedRanges[namedIndex];
255
+ const canReset = defaultPreset !== void 0 && shownPreset === "custom" && !named;
256
+ const editingCustomRange = shownPreset === "custom" && !presetMenuOpen && (!named || customRangeOpen);
257
+ const label = named ? named.label : shownPreset === "custom" ? customRangeLabel(dateFrom, dateTo) : datePresetLabel;
258
+ const applyNamedRange = (range) => {
259
+ setPresetMenuOpen(false);
260
+ onPresetChange?.("custom");
261
+ applyCustomRange(range.from, range.to);
262
+ };
246
263
  const trigger = /* @__PURE__ */ jsx(Button, {
247
264
  ref,
248
265
  size,
@@ -279,14 +296,24 @@ function DateTimeRangePicker({ preset = "all", onPresetChange, dateFrom, dateTo,
279
296
  onOpenChange: setPresetMenuOpen,
280
297
  children: [/* @__PURE__ */ jsx(DropdownMenu.Trigger, { render: trigger }), /* @__PURE__ */ jsx(DropdownMenu.Content, {
281
298
  align: "start",
282
- children: /* @__PURE__ */ jsx(DropdownMenu.RadioGroup, {
283
- value: shownPreset,
284
- children: visiblePresets.map((p) => /* @__PURE__ */ jsx(DropdownMenu.RadioItem, {
285
- value: p.value,
286
- closeOnClick: true,
287
- onClick: () => handlePresetSelect(p.value),
288
- children: p.label
289
- }, p.value))
299
+ children: /* @__PURE__ */ jsxs(DropdownMenu.RadioGroup, {
300
+ value: named ? `named-${namedIndex}` : shownPreset,
301
+ children: [
302
+ namedRanges.map((r, i) => /* @__PURE__ */ jsx(DropdownMenu.RadioItem, {
303
+ value: `named-${i}`,
304
+ disabled: outsideLimits(r, minDate, maxRangeMs),
305
+ closeOnClick: true,
306
+ onClick: () => applyNamedRange(r),
307
+ children: r.label
308
+ }, `named-${i}`)),
309
+ namedRanges.length > 0 && /* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
310
+ visiblePresets.map((p) => /* @__PURE__ */ jsx(DropdownMenu.RadioItem, {
311
+ value: p.value,
312
+ closeOnClick: true,
313
+ onClick: () => handlePresetSelect(p.value),
314
+ children: p.label
315
+ }, p.value))
316
+ ]
290
317
  })
291
318
  })]
292
319
  })]
@@ -313,4 +340,4 @@ function JoinReset({ reset, size, children }) {
313
340
  }
314
341
  export { DateTimeRangePicker as t };
315
342
 
316
- //# sourceMappingURL=date-time-range-picker-DKh1K9Iy.js.map
343
+ //# sourceMappingURL=date-time-range-picker-CdSiXwB4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date-time-range-picker-CdSiXwB4.js","names":["_Fragment"],"sources":["../src/ds/components/DateTimeRangePicker/custom-range-editor.tsx","../src/ds/components/DateTimeRangePicker/date-time-range-picker.tsx"],"sourcesContent":["import { format, isValid, parse, startOfDay } from 'date-fns';\nimport { Check } from 'lucide-react';\nimport { useId, useState } from 'react';\nimport { Button } from '@/ds/components/Button/Button';\nimport { DatePicker, TimePicker } from '@/ds/components/DateTimePicker';\nimport { Field, FieldError } from '@/ds/components/Field';\nimport { Txt } from '@/ds/components/Txt';\nimport { cn } from '@/lib/utils';\n\nfunction formatDays(ms: number): string {\n const days = Math.round(ms / (24 * 60 * 60 * 1000));\n return `${days} day${days === 1 ? '' : 's'}`;\n}\n\n/** The draft day at the draft time, or the bare day when the time doesn't parse. */\nfunction combineDateAndTime(date: Date | undefined, timeStr: string): Date | undefined {\n if (!date) return undefined;\n const dateOnly = new Date(date.getFullYear(), date.getMonth(), date.getDate());\n const combined = parse(timeStr, 'h:mm a', dateOnly);\n return isValid(combined) ? combined : date;\n}\n\nexport interface CustomRangeEditorProps {\n dateFrom?: Date;\n dateTo?: Date;\n disabled?: boolean;\n /** Widest range that can be applied, in milliseconds. */\n maxRangeMs?: number;\n /** Earliest day a range can start on. Days before it are disabled. */\n minDate?: Date;\n onApply: (from: Date | undefined, to: Date | undefined) => void;\n onShowPresets: () => void;\n}\n\n/**\n * The custom range popover body. It mounts each time the popover opens, so its drafts\n * start from the current range without the picker resetting them.\n */\nexport function CustomRangeEditor({\n dateFrom,\n dateTo,\n disabled,\n maxRangeMs,\n minDate,\n onApply,\n onShowPresets,\n}: CustomRangeEditorProps) {\n const [draftDateFrom, setDraftDateFrom] = useState<Date | undefined>(dateFrom);\n const [draftDateTo, setDraftDateTo] = useState<Date | undefined>(dateTo);\n const [draftTimeFrom, setDraftTimeFrom] = useState('12:00 AM');\n const [draftTimeTo, setDraftTimeTo] = useState('11:59 PM');\n const [applyAttempted, setApplyAttempted] = useState(false);\n const errorId = useId();\n\n const fromDate = combineDateAndTime(draftDateFrom, draftTimeFrom);\n const toDate = combineDateAndTime(draftDateTo, draftTimeTo);\n const rangeReversed = fromDate !== undefined && toDate !== undefined && fromDate.getTime() > toDate.getTime();\n const rangeTooWide =\n fromDate !== undefined && toDate !== undefined && toDate.getTime() - fromDate.getTime() > (maxRangeMs ?? Infinity);\n const firstDay = minDate ? startOfDay(minDate) : undefined;\n const endFirstDay = draftDateFrom && (!firstDay || draftDateFrom > firstDay) ? draftDateFrom : firstDay;\n const rangeTooEarly = fromDate !== undefined && firstDay !== undefined && fromDate < firstDay;\n const rangeInvalid = rangeReversed || rangeTooWide || rangeTooEarly;\n // Shown after a failed Apply, and gone as soon as the drafts form a valid range again.\n let error: string | undefined;\n if (applyAttempted && rangeReversed) error = 'Start date/time must be before end date/time';\n else if (applyAttempted && rangeTooWide) error = `Range cannot be longer than ${formatDays(maxRangeMs ?? 0)}`;\n else if (applyAttempted && firstDay && rangeTooEarly)\n error = `Range cannot start before ${format(firstDay, 'MMM d, yyyy')}`;\n\n const apply = () => {\n if (rangeInvalid) {\n setApplyAttempted(true);\n return;\n }\n onApply(fromDate, toDate);\n };\n\n return (\n <>\n <div\n role=\"group\"\n aria-label=\"Custom date range\"\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n className=\"flex\"\n >\n <div className={cn('border-r border-border')}>\n <Txt as=\"span\" variant=\"column\" tone=\"muted\" className=\"block px-4 pt-3\">\n Start\n </Txt>\n <DatePicker\n mode=\"single\"\n selected={draftDateFrom}\n month={draftDateFrom}\n onSelect={setDraftDateFrom}\n disabled={disabled}\n fromDate={firstDay}\n toDate={draftDateTo}\n />\n <TimePicker\n label=\"Start time\"\n className=\"mx-4 mb-3 w-auto\"\n defaultValue={draftTimeFrom}\n onValueChange={v => {\n if (!disabled) setDraftTimeFrom(v);\n }}\n />\n </div>\n <div>\n <Txt as=\"span\" variant=\"column\" tone=\"muted\" className=\"block px-4 pt-3\">\n End\n </Txt>\n <DatePicker\n mode=\"single\"\n selected={draftDateTo}\n month={draftDateTo}\n onSelect={setDraftDateTo}\n disabled={disabled}\n fromDate={endFirstDay}\n />\n <TimePicker\n label=\"End time\"\n className=\"mx-4 mb-3 w-auto\"\n defaultValue={draftTimeTo}\n onValueChange={v => {\n if (!disabled) setDraftTimeTo(v);\n }}\n />\n </div>\n </div>\n <Field invalid={Boolean(error)}>\n <FieldError id={errorId} className=\"px-4 pb-1\">\n {error}\n </FieldError>\n </Field>\n <div className={cn('flex items-center justify-between px-4 pb-3')}>\n <Button variant=\"ghost\" size=\"sm\" disabled={disabled} onClick={onShowPresets}>\n &larr; Presets\n </Button>\n <Button icon={<Check />} variant=\"primary\" size=\"sm\" onClick={apply} disabled={disabled}>\n Apply\n </Button>\n </div>\n </>\n );\n}\n","import { startOfDay } from 'date-fns';\nimport { CalendarIcon, XIcon } from 'lucide-react';\nimport { useState } from 'react';\nimport type { ReactNode, Ref } from 'react';\nimport { CustomRangeEditor } from './custom-range-editor';\nimport { Button, TriggerChevron } from '@/ds/components/Button/Button';\nimport type { TextButtonSize } from '@/ds/components/Button/Button';\nimport { ButtonsGroup } from '@/ds/components/ButtonsGroup/buttons-group';\nimport { DropdownMenu } from '@/ds/components/DropdownMenu/dropdown-menu';\nimport { Popover, PopoverTrigger, PopoverContent } from '@/ds/components/Popover/popover';\nimport { cn } from '@/lib/utils';\n\nexport type DateRangePreset = 'all' | 'last-24h' | 'last-3d' | 'last-7d' | 'last-14d' | 'last-30d' | 'custom';\n\nconst DATE_PRESETS: { value: DateRangePreset; label: string; ms?: number }[] = [\n { value: 'all', label: 'All' },\n { value: 'last-24h', label: 'Last 24 hours', ms: 24 * 60 * 60 * 1000 },\n { value: 'last-3d', label: 'Last 3 days', ms: 3 * 24 * 60 * 60 * 1000 },\n { value: 'last-7d', label: 'Last 7 days', ms: 7 * 24 * 60 * 60 * 1000 },\n { value: 'last-14d', label: 'Last 14 days', ms: 14 * 24 * 60 * 60 * 1000 },\n { value: 'last-30d', label: 'Last 30 days', ms: 30 * 24 * 60 * 60 * 1000 },\n { value: 'custom', label: 'Custom range...' },\n];\n\nconst sameTime = (a: Date | undefined, b: Date | undefined) => a?.getTime() === b?.getTime();\n\n/**\n * Whether a named range breaks the limits the custom editor enforces: it starts before\n * `minDate`'s day, or spans more than `maxRangeMs` (an open end counts up to now).\n */\nfunction outsideLimits(range: DateTimeRangePickerNamedRange, minDate?: Date, maxRangeMs?: number) {\n const tooEarly = minDate !== undefined && range.from < startOfDay(minDate);\n const span = (range.to ?? new Date()).getTime() - range.from.getTime();\n const tooWide = maxRangeMs !== undefined && span > maxRangeMs;\n return tooEarly || tooWide;\n}\n\nconst fmtClock = (d: Date) =>\n d.toLocaleTimeString('en-US', d.getMinutes() === 0 ? { hour: 'numeric' } : { hour: 'numeric', minute: '2-digit' });\n\n/**\n * A custom range as day and time, kept short: `Oct 21, 2:05 PM – 4 PM` within one day,\n * `Oct 20, 12 AM – Oct 27, 9:30 AM` across days, with years only outside the current one.\n */\nfunction customRangeLabel(dateFrom: Date | undefined, dateTo: Date | undefined) {\n const thisYear = new Date().getFullYear();\n const withYear = [dateFrom, dateTo].some(d => d && d.getFullYear() !== thisYear);\n const fmtDay = (d: Date) =>\n d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', ...(withYear && { year: 'numeric' }) });\n const fmtMoment = (d: Date) => `${fmtDay(d)}, ${fmtClock(d)}`;\n\n const start = dateFrom ? fmtMoment(dateFrom) : 'Start';\n if (!dateTo) return `${start} \\u2013 End`;\n const sameDay = dateFrom !== undefined && fmtDay(dateFrom) === fmtDay(dateTo);\n return `${start} \\u2013 ${sameDay ? fmtClock(dateTo) : fmtMoment(dateTo)}`;\n}\n\n/** A fixed range offered beside the presets, e.g. the current billing period. */\nexport type DateTimeRangePickerNamedRange = { label: string; from: Date; to?: Date };\n\nexport interface DateTimeRangePickerProps {\n preset?: DateRangePreset;\n onPresetChange?: (preset: DateRangePreset) => void;\n dateFrom?: Date;\n dateTo?: Date;\n onDateChange?: (value: Date | undefined, type: 'from' | 'to') => void;\n /**\n * Called once with both ends when a custom range is applied. When provided it replaces\n * the two `onDateChange` calls for that step, so consumers can write the range atomically.\n */\n onDateRangeChange?: (from: Date | undefined, to: Date | undefined) => void;\n disabled?: boolean;\n /** Widest custom range that can be applied, in milliseconds. Presets are unaffected. */\n maxRangeMs?: number;\n /** Earliest day a custom range can start on, e.g. the oldest data still kept. Earlier days are disabled. */\n minDate?: Date;\n /** Subset of presets to show. If omitted, all presets are shown. */\n presets?: readonly DateRangePreset[];\n /**\n * Fixed ranges listed above the presets, e.g. `{ label: 'This period', from: periodStart }`.\n * Picking one applies it as a custom range; while it's applied the trigger shows its label.\n */\n namedRanges?: readonly DateTimeRangePickerNamedRange[];\n /** Size of the trigger Button. Defaults to 'sm', the toolbar rung. */\n size?: TextButtonSize;\n /**\n * The preset a custom range resets to. While a custom range is applied, a × joined to the\n * trigger takes the chevron's place and switches back to it.\n */\n defaultPreset?: DateRangePreset;\n /** Replaces what the reset × does, e.g. to undo a histogram zoom before falling back to `defaultPreset`. */\n onReset?: () => void;\n /** The trigger Button. */\n ref?: Ref<HTMLButtonElement>;\n}\n\nexport function DateTimeRangePicker({\n preset = 'all',\n onPresetChange,\n dateFrom,\n dateTo,\n onDateChange,\n onDateRangeChange,\n disabled,\n maxRangeMs,\n minDate,\n presets,\n namedRanges = [],\n size = 'sm',\n defaultPreset,\n onReset,\n ref,\n}: DateTimeRangePickerProps) {\n const visiblePresets = presets ? DATE_PRESETS.filter(p => presets.includes(p.value)) : DATE_PRESETS;\n const [customRangeOpen, setCustomRangeOpen] = useState(false);\n const [presetMenuOpen, setPresetMenuOpen] = useState(false);\n // The preset active before \"Custom range...\" was picked, until a range is applied.\n // \"← Presets\" restores it instead of jumping to the first preset in the list.\n const [presetBeforeCustom, setPresetBeforeCustom] = useState<DateRangePreset | undefined>();\n\n // \"← Presets\" restores the previous preset through the parent, which may apply it a\n // render later (e.g. a router URL update). Show it right away instead of \"custom\".\n const restoringPreset = presetMenuOpen && preset === 'custom' ? presetBeforeCustom : undefined;\n const shownPreset = restoringPreset ?? preset;\n const datePresetLabel = DATE_PRESETS.find(p => p.value === shownPreset)?.label ?? 'All';\n\n const applyPreset = (value: DateRangePreset) => {\n onPresetChange?.(value);\n const entry = DATE_PRESETS.find(p => p.value === value);\n if (entry?.ms) {\n onDateChange?.(new Date(Date.now() - entry.ms), 'from');\n onDateChange?.(undefined, 'to');\n } else {\n onDateChange?.(undefined, 'from');\n onDateChange?.(undefined, 'to');\n }\n };\n\n const handlePresetSelect = (value: DateRangePreset) => {\n setPresetMenuOpen(false);\n if (value === 'custom') {\n if (preset !== 'custom') setPresetBeforeCustom(preset);\n onPresetChange?.(value);\n setCustomRangeOpen(true);\n return;\n }\n setPresetBeforeCustom(undefined);\n applyPreset(value);\n };\n\n const applyCustomRange = (fromDate: Date | undefined, toDate: Date | undefined) => {\n if (onDateRangeChange) {\n onDateRangeChange(fromDate, toDate);\n } else {\n onDateChange?.(fromDate, 'from');\n onDateChange?.(toDate, 'to');\n }\n setPresetBeforeCustom(undefined);\n setCustomRangeOpen(false);\n };\n\n // Back to the preset list. Restores the preset the user came from; an applied custom\n // range matches no preset, so it stays and the list shows \"Custom range...\" as current.\n const showPresets = () => {\n setCustomRangeOpen(false);\n if (presetBeforeCustom) applyPreset(presetBeforeCustom);\n setPresetMenuOpen(true);\n };\n\n // The applied custom range, when it is one of the named ranges.\n const namedIndex =\n shownPreset === 'custom' ? namedRanges.findIndex(r => sameTime(r.from, dateFrom) && sameTime(r.to, dateTo)) : -1;\n const named = namedRanges[namedIndex];\n const canReset = defaultPreset !== undefined && shownPreset === 'custom' && !named;\n // A named range shows the preset list, unless \"Custom range...\" was picked to edit it.\n const editingCustomRange = shownPreset === 'custom' && !presetMenuOpen && (!named || customRangeOpen);\n const label = named ? named.label : shownPreset === 'custom' ? customRangeLabel(dateFrom, dateTo) : datePresetLabel;\n\n const applyNamedRange = (range: DateTimeRangePickerNamedRange) => {\n setPresetMenuOpen(false);\n onPresetChange?.('custom');\n applyCustomRange(range.from, range.to);\n };\n const trigger = (\n <Button\n ref={ref}\n size={size}\n disabled={disabled}\n icon={<CalendarIcon />}\n iconEnd={canReset ? undefined : <TriggerChevron />}\n >\n {label}\n </Button>\n );\n\n return (\n <JoinReset\n size={size}\n reset={\n canReset && (\n <ResetRangeButton size={size} disabled={disabled} onReset={onReset ?? (() => applyPreset(defaultPreset))} />\n )\n }\n >\n {editingCustomRange && (\n <Popover open={customRangeOpen} onOpenChange={setCustomRangeOpen}>\n <PopoverTrigger render={trigger} />\n <PopoverContent align=\"start\" className={cn('w-auto p-0')}>\n <CustomRangeEditor\n dateFrom={dateFrom}\n dateTo={dateTo}\n disabled={disabled}\n maxRangeMs={maxRangeMs}\n minDate={minDate}\n onApply={applyCustomRange}\n onShowPresets={showPresets}\n />\n </PopoverContent>\n </Popover>\n )}\n {!editingCustomRange && (\n <DropdownMenu open={presetMenuOpen} onOpenChange={setPresetMenuOpen}>\n <DropdownMenu.Trigger render={trigger} />\n <DropdownMenu.Content align=\"start\">\n {/* Items select on click (not onValueChange) so re-picking the checked\n \"Custom range...\" still reopens the range editor. */}\n <DropdownMenu.RadioGroup value={named ? `named-${namedIndex}` : shownPreset}>\n {namedRanges.map((r, i) => (\n <DropdownMenu.RadioItem\n key={`named-${i}`}\n value={`named-${i}`}\n disabled={outsideLimits(r, minDate, maxRangeMs)}\n closeOnClick\n onClick={() => applyNamedRange(r)}\n >\n {r.label}\n </DropdownMenu.RadioItem>\n ))}\n {namedRanges.length > 0 && <DropdownMenu.Separator />}\n {visiblePresets.map(p => (\n <DropdownMenu.RadioItem\n key={p.value}\n value={p.value}\n closeOnClick\n onClick={() => handlePresetSelect(p.value)}\n >\n {p.label}\n </DropdownMenu.RadioItem>\n ))}\n </DropdownMenu.RadioGroup>\n </DropdownMenu.Content>\n </DropdownMenu>\n )}\n </JoinReset>\n );\n}\n\n/** Switches a custom range back to the picker's default preset. */\nfunction ResetRangeButton({\n size,\n disabled,\n onReset,\n}: {\n size: TextButtonSize;\n disabled?: boolean;\n onReset: () => void;\n}) {\n return (\n <Button\n type=\"button\"\n size={`icon-${size}` as const}\n tooltip=\"Reset time range\"\n disabled={disabled}\n onClick={onReset}\n >\n <XIcon />\n </Button>\n );\n}\n\n/** The trigger alone, or joined with the reset × as one pill. */\nfunction JoinReset({ reset, size, children }: { reset: ReactNode; size: TextButtonSize; children: ReactNode }) {\n if (!reset) return children;\n return (\n <ButtonsGroup size={size}>\n {children}\n {reset}\n </ButtonsGroup>\n );\n}\n"],"mappings":"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"}
@@ -22,7 +22,7 @@ const require_domains_chat_messages_renderers_reasoning_part_renderer = require(
22
22
  const require_domains_chat_messages_renderers_status_renderers = require("./renderers/status-renderers.cjs.js");
23
23
  const require_domains_chat_tools_tool_card_kind = require("../tools/tool-card-kind.cjs.js");
24
24
  const require_domains_chat_tools_tool_call_effects = require("../tools/tool-call-effects.cjs.js");
25
- const require_tool_card = require("../../../tool-card-rxhTDpk_.cjs");
25
+ const require_tool_card = require("../../../tool-card-BOLXeKIp.cjs");
26
26
  const require_domains_chat_tools_tool_groups = require("../tools/tool-groups.cjs.js");
27
27
  const require_components_ai_message_reveal = require("../../../components/ai/message-reveal.cjs.js");
28
28
  let react = require("react");
@@ -21,7 +21,7 @@ import { ReasoningPartRenderer } from "./renderers/reasoning-part-renderer.es.js
21
21
  import { messageStatusRenderers } from "./renderers/status-renderers.es.js";
22
22
  import { badgeStatus, isSettledState } from "../tools/tool-card-kind.es.js";
23
23
  import { ToolCallEffects } from "../tools/tool-call-effects.es.js";
24
- import { t as ToolCard } from "../../../tool-card-toDgoZzJ.js";
24
+ import { t as ToolCard } from "../../../tool-card-HZ5bm7ue.js";
25
25
  import { collectToolGroups } from "../tools/tool-groups.es.js";
26
26
  import { useRevealedParts } from "../../../components/ai/message-reveal.es.js";
27
27
  import { memo, useMemo } from "react";
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_tool_card = require("../../../../tool-card-rxhTDpk_.cjs");
2
+ const require_tool_card = require("../../../../tool-card-BOLXeKIp.cjs");
3
3
  exports.AgentBadgeWrapper = require_tool_card.AgentBadgeWrapper;
@@ -1 +1 @@
1
- export { r as AgentBadgeWrapper } from "../../../../tool-card-toDgoZzJ.js";
1
+ export { r as AgentBadgeWrapper } from "../../../../tool-card-HZ5bm7ue.js";
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_tool_card = require("../../../../tool-card-rxhTDpk_.cjs");
2
+ const require_tool_card = require("../../../../tool-card-BOLXeKIp.cjs");
3
3
  exports.AgentBadge = require_tool_card.AgentBadge;
@@ -1 +1 @@
1
- export { i as AgentBadge } from "../../../../tool-card-toDgoZzJ.js";
1
+ export { i as AgentBadge } from "../../../../tool-card-HZ5bm7ue.js";
@@ -3,32 +3,107 @@ const require_icons_Icon = require("../../../icons/Icon.cjs.js");
3
3
  const require_Button = require("../../../Button-DLcMcuUL.cjs");
4
4
  const require_Txt = require("../../../Txt-BnoSqxKE.cjs");
5
5
  const require_Notice = require("../../../Notice-JZ7SW7q6.cjs");
6
+ const require_domains_chat_context_tool_call_context = require("../context/tool-call-context.cjs.js");
6
7
  const require_domains_chat_tools_connect_prompt_tool_id = require("./connect-prompt-tool-id.cjs.js");
7
8
  const require_domains_connect_types = require("../../connect/types.cjs.js");
9
+ const require_domains_chat_tools_tool_card_kind = require("./tool-card-kind.cjs.js");
8
10
  const require_data_list = require("../../../data-list-C4Vmq5HA.cjs");
9
11
  const require_domains_connect_use_user_connect = require("../../connect/use-user-connect.cjs.js");
10
12
  const require_domains_connect_provider_logo = require("../../connect/provider-logo.cjs.js");
13
+ let react = require("react");
11
14
  let react_jsx_runtime = require("react/jsx-runtime");
12
15
  let lucide_react = require("lucide-react");
13
16
  // src/domains/chat/tools/connect-prompt-tool.tsx
14
17
  /**
18
+ * The prompt this card renders: the finished call's result, or the payload
19
+ * the run paused with while it waits for the user to connect.
20
+ */
21
+ function connectPromptFor({ toolName, toolCallId, output, metadata }) {
22
+ if (require_domains_connect_types.isConnectPromptPayload(output)) return {
23
+ payload: output,
24
+ paused: false
25
+ };
26
+ const suspendPayload = require_domains_chat_tools_tool_card_kind.toolInteraction(metadata, toolName, toolCallId).suspended?.suspendPayload;
27
+ if (require_domains_connect_types.isConnectPromptPayload(suspendPayload)) return {
28
+ payload: suspendPayload,
29
+ paused: true
30
+ };
31
+ }
32
+ /** The resume tool's schema accepts; longer failure reasons are cut to fit. */
33
+ var MAX_RESUME_ERROR_LENGTH = 500;
34
+ /**
35
+ * What to resume the paused call with: the settled flow, or a failure when the
36
+ * connect session couldn't even be created (e.g. the connection limit).
37
+ */
38
+ function connectPromptResumeFor(payload, settled, createError) {
39
+ const failed = (provider, error) => ({
40
+ provider,
41
+ status: "failed",
42
+ error: error.slice(0, MAX_RESUME_ERROR_LENGTH)
43
+ });
44
+ if (createError) {
45
+ const provider = payload.requestedProvider ?? payload.providers[0]?.id;
46
+ return provider ? failed(provider, createError.message) : void 0;
47
+ }
48
+ if (!settled) return void 0;
49
+ if (settled.status === "active") return {
50
+ provider: settled.provider,
51
+ status: "active"
52
+ };
53
+ return failed(settled.provider, settled.message ?? "The connection was not completed.");
54
+ }
55
+ /**
15
56
  * Chat card for the built-in `request_connection` tool: the agent surfaces it
16
57
  * when provider tools are unavailable because the caller has no connection.
17
58
  * Each provider row offers a Connect action that creates a connect session on
18
59
  * the mounted `/connect` routes, drives the OAuth flow in a popup through the
19
60
  * server's launch gate (which verifies this browser owns the pending session
20
61
  * before redirecting to the vendor), and polls until the connection settles.
62
+ *
63
+ * The tool pauses the agent run while the card is up. Once the connection
64
+ * settles, the card resumes the paused call with the outcome — connected, or
65
+ * the reason it failed — so the agent continues from the tool's result rather
66
+ * than a chat message.
21
67
  */
22
- var ConnectPromptTool = ({ output, readOnly = false }) => {
23
- if (!require_domains_connect_types.isConnectPromptPayload(output)) return null;
68
+ var ConnectPromptTool = ({ toolName, toolCallId, output, metadata, readOnly = false }) => {
69
+ const prompt = connectPromptFor({
70
+ toolName,
71
+ toolCallId,
72
+ output,
73
+ metadata
74
+ });
75
+ if (!prompt) return null;
24
76
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConnectPromptCard, {
25
- payload: output,
77
+ toolCallId,
78
+ payload: prompt.payload,
79
+ paused: prompt.paused,
26
80
  readOnly
27
81
  });
28
82
  };
29
- var ConnectPromptCard = ({ payload, readOnly }) => {
83
+ var ConnectPromptCard = ({ toolCallId, payload, paused, readOnly }) => {
30
84
  const { pending, connect, connectedProviders, isCreating, createError, settled } = require_domains_connect_use_user_connect.usePendingConnect();
85
+ const { approveToolcall, isRunning, toolCallApprovals } = require_domains_chat_context_tool_call_context.useToolCall();
86
+ const resumedRef = (0, react.useRef)(null);
87
+ const alreadyResumed = toolCallApprovals[toolCallId] !== void 0;
31
88
  const errorMessage = createError?.message ?? (settled?.status === "failed" ? settled.message ?? "The connection was not completed. Try connecting again." : null);
89
+ (0, react.useEffect)(() => {
90
+ if (readOnly || !paused || alreadyResumed || isRunning) return;
91
+ const outcome = createError ?? settled;
92
+ if (!outcome || resumedRef.current === outcome) return;
93
+ resumedRef.current = outcome;
94
+ const resume = connectPromptResumeFor(payload, settled, createError);
95
+ if (resume) approveToolcall(toolCallId, resume);
96
+ }, [
97
+ readOnly,
98
+ paused,
99
+ alreadyResumed,
100
+ isRunning,
101
+ settled,
102
+ createError,
103
+ payload,
104
+ approveToolcall,
105
+ toolCallId
106
+ ]);
32
107
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
33
108
  "aria-label": "Connect an account",
34
109
  className: "my-2 space-y-3",
@@ -1 +1 @@
1
- {"version":3,"file":"connect-prompt-tool.cjs.js","names":["isConnectPromptPayload","usePendingConnect","DataList","Txt","ProviderLogo","Icon","CheckIcon","Button","Notice"],"sources":["../../../../src/domains/chat/tools/connect-prompt-tool.tsx"],"sourcesContent":["import { CheckIcon } from 'lucide-react';\nimport type { ConnectPromptPayload } from '@/domains/connect';\nimport { isConnectPromptPayload, ProviderLogo, usePendingConnect } from '@/domains/connect';\nimport { Button } from '@/ds/components/Button';\nimport { DataList } from '@/ds/components/DataList';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { Icon } from '@/ds/icons/Icon';\n\nexport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\n\nexport interface ConnectPromptToolProps {\n output: unknown;\n /** Historical rendering: show the card but suppress the connect action. */\n readOnly?: boolean;\n}\n\n/**\n * Chat card for the built-in `request_connection` tool: the agent surfaces it\n * when provider tools are unavailable because the caller has no connection.\n * Each provider row offers a Connect action that creates a connect session on\n * the mounted `/connect` routes, drives the OAuth flow in a popup through the\n * server's launch gate (which verifies this browser owns the pending session\n * before redirecting to the vendor), and polls until the connection settles.\n */\nexport const ConnectPromptTool = ({ output, readOnly = false }: ConnectPromptToolProps) => {\n if (!isConnectPromptPayload(output)) return null;\n return <ConnectPromptCard payload={output} readOnly={readOnly} />;\n};\n\nconst ConnectPromptCard = ({ payload, readOnly }: { payload: ConnectPromptPayload; readOnly: boolean }) => {\n const { pending, connect, connectedProviders, isCreating, createError, settled } = usePendingConnect();\n\n const errorMessage =\n createError?.message ??\n (settled?.status === 'failed'\n ? (settled.message ?? 'The connection was not completed. Try connecting again.')\n : null);\n\n return (\n <section aria-label=\"Connect an account\" className=\"my-2 space-y-3\">\n <DataList columns=\"minmax(0, 1fr)\" fit=\"container\" scrollable={false}>\n <DataList.Subheader>\n <Txt as=\"h4\" variant=\"subheading\" tone=\"ink\">\n Connect an account\n </Txt>\n </DataList.Subheader>\n {payload.providers.map(provider => {\n const isConnected = provider.connected || connectedProviders.has(provider.id);\n const isPending = pending?.provider === provider.id;\n return (\n <DataList.RowStatic key={provider.id}>\n <DataList.Cell className=\"col-span-full py-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <ProviderLogo logoUrl={provider.logoUrl} />\n <Txt as=\"span\" variant=\"body-sm\" tone=\"ink\" className=\"truncate\">\n {provider.displayName ?? provider.id}\n </Txt>\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n {isPending ? (\n <Txt as=\"span\" variant=\"body-sm\" tone=\"muted\" role=\"status\">\n Waiting for authorization…\n </Txt>\n ) : null}\n {isPending && pending?.fallbackUrl ? (\n <a\n href={pending.fallbackUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-ui-md underline underline-offset-2\"\n >\n Open the connect window\n </a>\n ) : null}\n {isConnected ? (\n <Txt\n as=\"span\"\n variant=\"body-sm\"\n tone=\"muted\"\n className=\"flex min-w-24 items-center justify-center gap-1\"\n >\n <Icon size=\"sm\">\n <CheckIcon aria-hidden />\n </Icon>\n Connected\n </Txt>\n ) : (\n <Button\n variant=\"primary\"\n size=\"sm\"\n className=\"min-w-24\"\n disabled={readOnly || isCreating || isPending}\n onClick={() => connect(provider)}\n >\n Connect\n </Button>\n )}\n </div>\n </div>\n </DataList.Cell>\n </DataList.RowStatic>\n );\n })}\n </DataList>\n {errorMessage ? (\n <div role=\"alert\">\n <Notice variant=\"destructive\" title=\"Couldn't connect\">\n <Notice.Message>{errorMessage}</Notice.Message>\n </Notice>\n </div>\n ) : null}\n </section>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"connect-prompt-tool.cjs.js","names":["isConnectPromptPayload","toolInteraction","usePendingConnect","useToolCall","useRef","DataList","Txt","ProviderLogo","Icon","CheckIcon","Button","Notice"],"sources":["../../../../src/domains/chat/tools/connect-prompt-tool.tsx"],"sourcesContent":["import { CheckIcon } from 'lucide-react';\nimport { useEffect, useRef } from 'react';\nimport type { MessageMetadata } from '../messages/message-metadata';\nimport { toolInteraction } from './tool-card-kind';\nimport { useToolCall } from '@/domains/chat/context/tool-call-context';\nimport type { ConnectPromptPayload, ConnectPromptResume, SettledConnect } from '@/domains/connect';\nimport { isConnectPromptPayload, ProviderLogo, usePendingConnect } from '@/domains/connect';\nimport { Button } from '@/ds/components/Button';\nimport { DataList } from '@/ds/components/DataList';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { Icon } from '@/ds/icons/Icon';\n\nexport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\n\nexport interface ConnectPromptToolProps {\n toolName: string;\n toolCallId: string;\n output: unknown;\n metadata?: MessageMetadata;\n /** Historical rendering: show the card but suppress the connect action. */\n readOnly?: boolean;\n}\n\n/**\n * The prompt this card renders: the finished call's result, or the payload\n * the run paused with while it waits for the user to connect.\n */\nfunction connectPromptFor({\n toolName,\n toolCallId,\n output,\n metadata,\n}: Pick<ConnectPromptToolProps, 'toolName' | 'toolCallId' | 'output' | 'metadata'>):\n | { payload: ConnectPromptPayload; paused: boolean }\n | undefined {\n if (isConnectPromptPayload(output)) return { payload: output, paused: false };\n const suspendPayload = toolInteraction(metadata, toolName, toolCallId).suspended?.suspendPayload;\n if (isConnectPromptPayload(suspendPayload)) return { payload: suspendPayload, paused: true };\n return undefined;\n}\n\n/** The resume tool's schema accepts; longer failure reasons are cut to fit. */\nconst MAX_RESUME_ERROR_LENGTH = 500;\n\n/**\n * What to resume the paused call with: the settled flow, or a failure when the\n * connect session couldn't even be created (e.g. the connection limit).\n */\nfunction connectPromptResumeFor(\n payload: ConnectPromptPayload,\n settled: SettledConnect | null,\n createError: Error | null,\n): ConnectPromptResume | undefined {\n const failed = (provider: string, error: string): ConnectPromptResume => ({\n provider,\n status: 'failed',\n error: error.slice(0, MAX_RESUME_ERROR_LENGTH),\n });\n if (createError) {\n const provider = payload.requestedProvider ?? payload.providers[0]?.id;\n return provider ? failed(provider, createError.message) : undefined;\n }\n if (!settled) return undefined;\n if (settled.status === 'active') return { provider: settled.provider, status: 'active' };\n return failed(settled.provider, settled.message ?? 'The connection was not completed.');\n}\n\n/**\n * Chat card for the built-in `request_connection` tool: the agent surfaces it\n * when provider tools are unavailable because the caller has no connection.\n * Each provider row offers a Connect action that creates a connect session on\n * the mounted `/connect` routes, drives the OAuth flow in a popup through the\n * server's launch gate (which verifies this browser owns the pending session\n * before redirecting to the vendor), and polls until the connection settles.\n *\n * The tool pauses the agent run while the card is up. Once the connection\n * settles, the card resumes the paused call with the outcome — connected, or\n * the reason it failed — so the agent continues from the tool's result rather\n * than a chat message.\n */\nexport const ConnectPromptTool = ({\n toolName,\n toolCallId,\n output,\n metadata,\n readOnly = false,\n}: ConnectPromptToolProps) => {\n const prompt = connectPromptFor({ toolName, toolCallId, output, metadata });\n if (!prompt) return null;\n return (\n <ConnectPromptCard toolCallId={toolCallId} payload={prompt.payload} paused={prompt.paused} readOnly={readOnly} />\n );\n};\n\ninterface ConnectPromptCardProps {\n toolCallId: string;\n payload: ConnectPromptPayload;\n /** The run is waiting on this call, so an activation resumes it. */\n paused: boolean;\n readOnly: boolean;\n}\n\nconst ConnectPromptCard = ({ toolCallId, payload, paused, readOnly }: ConnectPromptCardProps) => {\n const { pending, connect, connectedProviders, isCreating, createError, settled } = usePendingConnect();\n const { approveToolcall, isRunning, toolCallApprovals } = useToolCall();\n // The outcome (settled flow or session-creation error) this call was resumed with.\n const resumedRef = useRef<SettledConnect | Error | null>(null);\n const alreadyResumed = toolCallApprovals[toolCallId] !== undefined;\n\n const errorMessage =\n createError?.message ??\n (settled?.status === 'failed'\n ? (settled.message ?? 'The connection was not completed. Try connecting again.')\n : null);\n\n // Resume once per outcome, success or failure, so the agent sees what\n // happened in the tool result. A run that is still streaming can't be\n // resumed yet, so an outcome that lands mid-run waits for it to stop.\n useEffect(() => {\n if (readOnly || !paused || alreadyResumed || isRunning) return;\n const outcome = createError ?? settled;\n if (!outcome || resumedRef.current === outcome) return;\n resumedRef.current = outcome;\n const resume = connectPromptResumeFor(payload, settled, createError);\n if (resume) approveToolcall(toolCallId, resume);\n }, [readOnly, paused, alreadyResumed, isRunning, settled, createError, payload, approveToolcall, toolCallId]);\n\n return (\n <section aria-label=\"Connect an account\" className=\"my-2 space-y-3\">\n <DataList columns=\"minmax(0, 1fr)\" fit=\"container\" scrollable={false}>\n <DataList.Subheader>\n <Txt as=\"h4\" variant=\"subheading\" tone=\"ink\">\n Connect an account\n </Txt>\n </DataList.Subheader>\n {payload.providers.map(provider => {\n const isConnected = provider.connected || connectedProviders.has(provider.id);\n const isPending = pending?.provider === provider.id;\n return (\n <DataList.RowStatic key={provider.id}>\n <DataList.Cell className=\"col-span-full py-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <ProviderLogo logoUrl={provider.logoUrl} />\n <Txt as=\"span\" variant=\"body-sm\" tone=\"ink\" className=\"truncate\">\n {provider.displayName ?? provider.id}\n </Txt>\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n {isPending ? (\n <Txt as=\"span\" variant=\"body-sm\" tone=\"muted\" role=\"status\">\n Waiting for authorization…\n </Txt>\n ) : null}\n {isPending && pending?.fallbackUrl ? (\n <a\n href={pending.fallbackUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-ui-md underline underline-offset-2\"\n >\n Open the connect window\n </a>\n ) : null}\n {isConnected ? (\n <Txt\n as=\"span\"\n variant=\"body-sm\"\n tone=\"muted\"\n className=\"flex min-w-24 items-center justify-center gap-1\"\n >\n <Icon size=\"sm\">\n <CheckIcon aria-hidden />\n </Icon>\n Connected\n </Txt>\n ) : (\n <Button\n variant=\"primary\"\n size=\"sm\"\n className=\"min-w-24\"\n disabled={readOnly || isCreating || isPending}\n onClick={() => connect(provider)}\n >\n Connect\n </Button>\n )}\n </div>\n </div>\n </DataList.Cell>\n </DataList.RowStatic>\n );\n })}\n </DataList>\n {errorMessage ? (\n <div role=\"alert\">\n <Notice variant=\"destructive\" title=\"Couldn't connect\">\n <Notice.Message>{errorMessage}</Notice.Message>\n </Notice>\n </div>\n ) : null}\n </section>\n );\n};\n"],"mappings":"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"}
@@ -2,32 +2,107 @@ import { Icon } from "../../../icons/Icon.es.js";
2
2
  import { t as Button } from "../../../Button-CLXTbAY8.js";
3
3
  import { t as Txt } from "../../../Txt-BM6TVV_c.js";
4
4
  import { t as Notice } from "../../../Notice-C-8oOZZF.js";
5
+ import { useToolCall } from "../context/tool-call-context.es.js";
5
6
  import { CONNECT_PROMPT_TOOL_NAME } from "./connect-prompt-tool-id.es.js";
6
7
  import { isConnectPromptPayload } from "../../connect/types.es.js";
8
+ import { toolInteraction } from "./tool-card-kind.es.js";
7
9
  import { t as DataList } from "../../../data-list-DHCOoLmj.js";
8
10
  import { usePendingConnect } from "../../connect/use-user-connect.es.js";
9
11
  import { ProviderLogo } from "../../connect/provider-logo.es.js";
12
+ import { useEffect, useRef } from "react";
10
13
  import { jsx, jsxs } from "react/jsx-runtime";
11
14
  import { CheckIcon } from "lucide-react";
12
15
  // src/domains/chat/tools/connect-prompt-tool.tsx
13
16
  /**
17
+ * The prompt this card renders: the finished call's result, or the payload
18
+ * the run paused with while it waits for the user to connect.
19
+ */
20
+ function connectPromptFor({ toolName, toolCallId, output, metadata }) {
21
+ if (isConnectPromptPayload(output)) return {
22
+ payload: output,
23
+ paused: false
24
+ };
25
+ const suspendPayload = toolInteraction(metadata, toolName, toolCallId).suspended?.suspendPayload;
26
+ if (isConnectPromptPayload(suspendPayload)) return {
27
+ payload: suspendPayload,
28
+ paused: true
29
+ };
30
+ }
31
+ /** The resume tool's schema accepts; longer failure reasons are cut to fit. */
32
+ var MAX_RESUME_ERROR_LENGTH = 500;
33
+ /**
34
+ * What to resume the paused call with: the settled flow, or a failure when the
35
+ * connect session couldn't even be created (e.g. the connection limit).
36
+ */
37
+ function connectPromptResumeFor(payload, settled, createError) {
38
+ const failed = (provider, error) => ({
39
+ provider,
40
+ status: "failed",
41
+ error: error.slice(0, MAX_RESUME_ERROR_LENGTH)
42
+ });
43
+ if (createError) {
44
+ const provider = payload.requestedProvider ?? payload.providers[0]?.id;
45
+ return provider ? failed(provider, createError.message) : void 0;
46
+ }
47
+ if (!settled) return void 0;
48
+ if (settled.status === "active") return {
49
+ provider: settled.provider,
50
+ status: "active"
51
+ };
52
+ return failed(settled.provider, settled.message ?? "The connection was not completed.");
53
+ }
54
+ /**
14
55
  * Chat card for the built-in `request_connection` tool: the agent surfaces it
15
56
  * when provider tools are unavailable because the caller has no connection.
16
57
  * Each provider row offers a Connect action that creates a connect session on
17
58
  * the mounted `/connect` routes, drives the OAuth flow in a popup through the
18
59
  * server's launch gate (which verifies this browser owns the pending session
19
60
  * before redirecting to the vendor), and polls until the connection settles.
61
+ *
62
+ * The tool pauses the agent run while the card is up. Once the connection
63
+ * settles, the card resumes the paused call with the outcome — connected, or
64
+ * the reason it failed — so the agent continues from the tool's result rather
65
+ * than a chat message.
20
66
  */
21
- var ConnectPromptTool = ({ output, readOnly = false }) => {
22
- if (!isConnectPromptPayload(output)) return null;
67
+ var ConnectPromptTool = ({ toolName, toolCallId, output, metadata, readOnly = false }) => {
68
+ const prompt = connectPromptFor({
69
+ toolName,
70
+ toolCallId,
71
+ output,
72
+ metadata
73
+ });
74
+ if (!prompt) return null;
23
75
  return /* @__PURE__ */ jsx(ConnectPromptCard, {
24
- payload: output,
76
+ toolCallId,
77
+ payload: prompt.payload,
78
+ paused: prompt.paused,
25
79
  readOnly
26
80
  });
27
81
  };
28
- var ConnectPromptCard = ({ payload, readOnly }) => {
82
+ var ConnectPromptCard = ({ toolCallId, payload, paused, readOnly }) => {
29
83
  const { pending, connect, connectedProviders, isCreating, createError, settled } = usePendingConnect();
84
+ const { approveToolcall, isRunning, toolCallApprovals } = useToolCall();
85
+ const resumedRef = useRef(null);
86
+ const alreadyResumed = toolCallApprovals[toolCallId] !== void 0;
30
87
  const errorMessage = createError?.message ?? (settled?.status === "failed" ? settled.message ?? "The connection was not completed. Try connecting again." : null);
88
+ useEffect(() => {
89
+ if (readOnly || !paused || alreadyResumed || isRunning) return;
90
+ const outcome = createError ?? settled;
91
+ if (!outcome || resumedRef.current === outcome) return;
92
+ resumedRef.current = outcome;
93
+ const resume = connectPromptResumeFor(payload, settled, createError);
94
+ if (resume) approveToolcall(toolCallId, resume);
95
+ }, [
96
+ readOnly,
97
+ paused,
98
+ alreadyResumed,
99
+ isRunning,
100
+ settled,
101
+ createError,
102
+ payload,
103
+ approveToolcall,
104
+ toolCallId
105
+ ]);
31
106
  return /* @__PURE__ */ jsxs("section", {
32
107
  "aria-label": "Connect an account",
33
108
  className: "my-2 space-y-3",
@@ -1 +1 @@
1
- {"version":3,"file":"connect-prompt-tool.es.js","names":[],"sources":["../../../../src/domains/chat/tools/connect-prompt-tool.tsx"],"sourcesContent":["import { CheckIcon } from 'lucide-react';\nimport type { ConnectPromptPayload } from '@/domains/connect';\nimport { isConnectPromptPayload, ProviderLogo, usePendingConnect } from '@/domains/connect';\nimport { Button } from '@/ds/components/Button';\nimport { DataList } from '@/ds/components/DataList';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { Icon } from '@/ds/icons/Icon';\n\nexport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\n\nexport interface ConnectPromptToolProps {\n output: unknown;\n /** Historical rendering: show the card but suppress the connect action. */\n readOnly?: boolean;\n}\n\n/**\n * Chat card for the built-in `request_connection` tool: the agent surfaces it\n * when provider tools are unavailable because the caller has no connection.\n * Each provider row offers a Connect action that creates a connect session on\n * the mounted `/connect` routes, drives the OAuth flow in a popup through the\n * server's launch gate (which verifies this browser owns the pending session\n * before redirecting to the vendor), and polls until the connection settles.\n */\nexport const ConnectPromptTool = ({ output, readOnly = false }: ConnectPromptToolProps) => {\n if (!isConnectPromptPayload(output)) return null;\n return <ConnectPromptCard payload={output} readOnly={readOnly} />;\n};\n\nconst ConnectPromptCard = ({ payload, readOnly }: { payload: ConnectPromptPayload; readOnly: boolean }) => {\n const { pending, connect, connectedProviders, isCreating, createError, settled } = usePendingConnect();\n\n const errorMessage =\n createError?.message ??\n (settled?.status === 'failed'\n ? (settled.message ?? 'The connection was not completed. Try connecting again.')\n : null);\n\n return (\n <section aria-label=\"Connect an account\" className=\"my-2 space-y-3\">\n <DataList columns=\"minmax(0, 1fr)\" fit=\"container\" scrollable={false}>\n <DataList.Subheader>\n <Txt as=\"h4\" variant=\"subheading\" tone=\"ink\">\n Connect an account\n </Txt>\n </DataList.Subheader>\n {payload.providers.map(provider => {\n const isConnected = provider.connected || connectedProviders.has(provider.id);\n const isPending = pending?.provider === provider.id;\n return (\n <DataList.RowStatic key={provider.id}>\n <DataList.Cell className=\"col-span-full py-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <ProviderLogo logoUrl={provider.logoUrl} />\n <Txt as=\"span\" variant=\"body-sm\" tone=\"ink\" className=\"truncate\">\n {provider.displayName ?? provider.id}\n </Txt>\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n {isPending ? (\n <Txt as=\"span\" variant=\"body-sm\" tone=\"muted\" role=\"status\">\n Waiting for authorization…\n </Txt>\n ) : null}\n {isPending && pending?.fallbackUrl ? (\n <a\n href={pending.fallbackUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-ui-md underline underline-offset-2\"\n >\n Open the connect window\n </a>\n ) : null}\n {isConnected ? (\n <Txt\n as=\"span\"\n variant=\"body-sm\"\n tone=\"muted\"\n className=\"flex min-w-24 items-center justify-center gap-1\"\n >\n <Icon size=\"sm\">\n <CheckIcon aria-hidden />\n </Icon>\n Connected\n </Txt>\n ) : (\n <Button\n variant=\"primary\"\n size=\"sm\"\n className=\"min-w-24\"\n disabled={readOnly || isCreating || isPending}\n onClick={() => connect(provider)}\n >\n Connect\n </Button>\n )}\n </div>\n </div>\n </DataList.Cell>\n </DataList.RowStatic>\n );\n })}\n </DataList>\n {errorMessage ? (\n <div role=\"alert\">\n <Notice variant=\"destructive\" title=\"Couldn't connect\">\n <Notice.Message>{errorMessage}</Notice.Message>\n </Notice>\n </div>\n ) : null}\n </section>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,qBAAqB,EAAE,QAAQ,WAAW,YAAoC;CACzF,IAAI,CAAC,uBAAuB,MAAM,GAAG,OAAO;CAC5C,OAAO,oBAAC,mBAAD;EAAmB,SAAS;EAAkB;CAAW,CAAA;AAClE;AAEA,IAAM,qBAAqB,EAAE,SAAS,eAAqE;CACzG,MAAM,EAAE,SAAS,SAAS,oBAAoB,YAAY,aAAa,YAAY,kBAAkB;CAErG,MAAM,eACJ,aAAa,YACZ,SAAS,WAAW,WAChB,QAAQ,WAAW,4DACpB;CAEN,OACE,qBAAC,WAAD;EAAS,cAAW;EAAqB,WAAU;EAAnD,UAAA,CACE,qBAAC,UAAD;GAAU,SAAQ;GAAiB,KAAI;GAAY,YAAY;GAA/D,UAAA,CACE,oBAAC,SAAS,WAAV,EAAA,UACE,oBAAC,KAAD;IAAK,IAAG;IAAK,SAAQ;IAAa,MAAK;IAAM,UAAA;GAExC,CAAA,EACa,CAAA,GACnB,QAAQ,UAAU,KAAI,aAAY;IACjC,MAAM,cAAc,SAAS,aAAa,mBAAmB,IAAI,SAAS,EAAE;IAC5E,MAAM,YAAY,SAAS,aAAa,SAAS;IACjD,OACE,oBAAC,SAAS,WAAV,EAAA,UACE,oBAAC,SAAS,MAAV;KAAe,WAAU;KACvB,UAAA,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,oBAAC,cAAD,EAAc,SAAS,SAAS,QAAU,CAAA,GAC1C,oBAAC,KAAD;QAAK,IAAG;QAAO,SAAQ;QAAU,MAAK;QAAM,WAAU;QACnD,UAAA,SAAS,eAAe,SAAS;OAC/B,CAAA,CACF;MACL,CAAA,GAAA,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA;QACG,YACC,oBAAC,KAAD;SAAK,IAAG;SAAO,SAAQ;SAAU,MAAK;SAAQ,MAAK;SAAS,UAAA;QAEvD,CAAA,IACH;QACH,aAAa,SAAS,cACrB,oBAAC,KAAD;SACE,MAAM,QAAQ;SACd,QAAO;SACP,KAAI;SACJ,WAAU;SACX,UAAA;QAEE,CAAA,IACD;QACH,cACC,qBAAC,KAAD;SACE,IAAG;SACH,SAAQ;SACR,MAAK;SACL,WAAU;SAJZ,UAAA,CAME,oBAAC,MAAD;UAAM,MAAK;UACT,UAAA,oBAAC,WAAD,EAAW,eAAA,KAAa,CAAA;SACpB,CAAA,GAAC,WAEJ;QAEL,CAAA,IAAA,oBAAC,QAAD;SACE,SAAQ;SACR,MAAK;SACL,WAAU;SACV,UAAU,YAAY,cAAc;SACpC,eAAe,QAAQ,QAAQ;SAChC,UAAA;QAEO,CAAA;OAEP;MACF,CAAA,CAAA;;IACQ,CAAA,EACG,GAnDK,SAAS,EAmDd;GAExB,CAAC,CACO;EACT,CAAA,GAAA,eACC,oBAAC,OAAD;GAAK,MAAK;GACR,UAAA,oBAAC,QAAD;IAAQ,SAAQ;IAAc,OAAM;IAClC,UAAA,oBAAC,OAAO,SAAR,EAAA,UAAiB,aAA6B,CAAA;GACxC,CAAA;EACL,CAAA,IACH,IACG;;AAEb"}
1
+ {"version":3,"file":"connect-prompt-tool.es.js","names":[],"sources":["../../../../src/domains/chat/tools/connect-prompt-tool.tsx"],"sourcesContent":["import { CheckIcon } from 'lucide-react';\nimport { useEffect, useRef } from 'react';\nimport type { MessageMetadata } from '../messages/message-metadata';\nimport { toolInteraction } from './tool-card-kind';\nimport { useToolCall } from '@/domains/chat/context/tool-call-context';\nimport type { ConnectPromptPayload, ConnectPromptResume, SettledConnect } from '@/domains/connect';\nimport { isConnectPromptPayload, ProviderLogo, usePendingConnect } from '@/domains/connect';\nimport { Button } from '@/ds/components/Button';\nimport { DataList } from '@/ds/components/DataList';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { Icon } from '@/ds/icons/Icon';\n\nexport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\n\nexport interface ConnectPromptToolProps {\n toolName: string;\n toolCallId: string;\n output: unknown;\n metadata?: MessageMetadata;\n /** Historical rendering: show the card but suppress the connect action. */\n readOnly?: boolean;\n}\n\n/**\n * The prompt this card renders: the finished call's result, or the payload\n * the run paused with while it waits for the user to connect.\n */\nfunction connectPromptFor({\n toolName,\n toolCallId,\n output,\n metadata,\n}: Pick<ConnectPromptToolProps, 'toolName' | 'toolCallId' | 'output' | 'metadata'>):\n | { payload: ConnectPromptPayload; paused: boolean }\n | undefined {\n if (isConnectPromptPayload(output)) return { payload: output, paused: false };\n const suspendPayload = toolInteraction(metadata, toolName, toolCallId).suspended?.suspendPayload;\n if (isConnectPromptPayload(suspendPayload)) return { payload: suspendPayload, paused: true };\n return undefined;\n}\n\n/** The resume tool's schema accepts; longer failure reasons are cut to fit. */\nconst MAX_RESUME_ERROR_LENGTH = 500;\n\n/**\n * What to resume the paused call with: the settled flow, or a failure when the\n * connect session couldn't even be created (e.g. the connection limit).\n */\nfunction connectPromptResumeFor(\n payload: ConnectPromptPayload,\n settled: SettledConnect | null,\n createError: Error | null,\n): ConnectPromptResume | undefined {\n const failed = (provider: string, error: string): ConnectPromptResume => ({\n provider,\n status: 'failed',\n error: error.slice(0, MAX_RESUME_ERROR_LENGTH),\n });\n if (createError) {\n const provider = payload.requestedProvider ?? payload.providers[0]?.id;\n return provider ? failed(provider, createError.message) : undefined;\n }\n if (!settled) return undefined;\n if (settled.status === 'active') return { provider: settled.provider, status: 'active' };\n return failed(settled.provider, settled.message ?? 'The connection was not completed.');\n}\n\n/**\n * Chat card for the built-in `request_connection` tool: the agent surfaces it\n * when provider tools are unavailable because the caller has no connection.\n * Each provider row offers a Connect action that creates a connect session on\n * the mounted `/connect` routes, drives the OAuth flow in a popup through the\n * server's launch gate (which verifies this browser owns the pending session\n * before redirecting to the vendor), and polls until the connection settles.\n *\n * The tool pauses the agent run while the card is up. Once the connection\n * settles, the card resumes the paused call with the outcome — connected, or\n * the reason it failed — so the agent continues from the tool's result rather\n * than a chat message.\n */\nexport const ConnectPromptTool = ({\n toolName,\n toolCallId,\n output,\n metadata,\n readOnly = false,\n}: ConnectPromptToolProps) => {\n const prompt = connectPromptFor({ toolName, toolCallId, output, metadata });\n if (!prompt) return null;\n return (\n <ConnectPromptCard toolCallId={toolCallId} payload={prompt.payload} paused={prompt.paused} readOnly={readOnly} />\n );\n};\n\ninterface ConnectPromptCardProps {\n toolCallId: string;\n payload: ConnectPromptPayload;\n /** The run is waiting on this call, so an activation resumes it. */\n paused: boolean;\n readOnly: boolean;\n}\n\nconst ConnectPromptCard = ({ toolCallId, payload, paused, readOnly }: ConnectPromptCardProps) => {\n const { pending, connect, connectedProviders, isCreating, createError, settled } = usePendingConnect();\n const { approveToolcall, isRunning, toolCallApprovals } = useToolCall();\n // The outcome (settled flow or session-creation error) this call was resumed with.\n const resumedRef = useRef<SettledConnect | Error | null>(null);\n const alreadyResumed = toolCallApprovals[toolCallId] !== undefined;\n\n const errorMessage =\n createError?.message ??\n (settled?.status === 'failed'\n ? (settled.message ?? 'The connection was not completed. Try connecting again.')\n : null);\n\n // Resume once per outcome, success or failure, so the agent sees what\n // happened in the tool result. A run that is still streaming can't be\n // resumed yet, so an outcome that lands mid-run waits for it to stop.\n useEffect(() => {\n if (readOnly || !paused || alreadyResumed || isRunning) return;\n const outcome = createError ?? settled;\n if (!outcome || resumedRef.current === outcome) return;\n resumedRef.current = outcome;\n const resume = connectPromptResumeFor(payload, settled, createError);\n if (resume) approveToolcall(toolCallId, resume);\n }, [readOnly, paused, alreadyResumed, isRunning, settled, createError, payload, approveToolcall, toolCallId]);\n\n return (\n <section aria-label=\"Connect an account\" className=\"my-2 space-y-3\">\n <DataList columns=\"minmax(0, 1fr)\" fit=\"container\" scrollable={false}>\n <DataList.Subheader>\n <Txt as=\"h4\" variant=\"subheading\" tone=\"ink\">\n Connect an account\n </Txt>\n </DataList.Subheader>\n {payload.providers.map(provider => {\n const isConnected = provider.connected || connectedProviders.has(provider.id);\n const isPending = pending?.provider === provider.id;\n return (\n <DataList.RowStatic key={provider.id}>\n <DataList.Cell className=\"col-span-full py-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <ProviderLogo logoUrl={provider.logoUrl} />\n <Txt as=\"span\" variant=\"body-sm\" tone=\"ink\" className=\"truncate\">\n {provider.displayName ?? provider.id}\n </Txt>\n </div>\n <div className=\"flex flex-wrap items-center gap-3\">\n {isPending ? (\n <Txt as=\"span\" variant=\"body-sm\" tone=\"muted\" role=\"status\">\n Waiting for authorization…\n </Txt>\n ) : null}\n {isPending && pending?.fallbackUrl ? (\n <a\n href={pending.fallbackUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-ui-md underline underline-offset-2\"\n >\n Open the connect window\n </a>\n ) : null}\n {isConnected ? (\n <Txt\n as=\"span\"\n variant=\"body-sm\"\n tone=\"muted\"\n className=\"flex min-w-24 items-center justify-center gap-1\"\n >\n <Icon size=\"sm\">\n <CheckIcon aria-hidden />\n </Icon>\n Connected\n </Txt>\n ) : (\n <Button\n variant=\"primary\"\n size=\"sm\"\n className=\"min-w-24\"\n disabled={readOnly || isCreating || isPending}\n onClick={() => connect(provider)}\n >\n Connect\n </Button>\n )}\n </div>\n </div>\n </DataList.Cell>\n </DataList.RowStatic>\n );\n })}\n </DataList>\n {errorMessage ? (\n <div role=\"alert\">\n <Notice variant=\"destructive\" title=\"Couldn't connect\">\n <Notice.Message>{errorMessage}</Notice.Message>\n </Notice>\n </div>\n ) : null}\n </section>\n );\n};\n"],"mappings":"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"}
@@ -97,23 +97,21 @@ function viewRequest({ method, params }) {
97
97
  * before the call can continue, so answers are held until the last one is given, then sent together.
98
98
  */
99
99
  function McpInputRequestTool({ payload, onSubmit, isSubmitting, error, readOnly, notice }) {
100
- const [answers, setAnswers] = (0, react.useState)({});
100
+ const [answers, setAnswers] = (0, react.useState)(() => /* @__PURE__ */ new Map());
101
101
  const keys = Object.keys(payload.requests);
102
102
  const tooLarge = (0, react.useMemo)(() => require_domains_chat_tools_mcp_input_request.inputRequestTooLarge(payload), [payload]);
103
- const send = (inputResponses) => {
104
- Promise.resolve(onSubmit({ inputResponses })).catch(() => setAnswers({}));
103
+ const send = (next) => {
104
+ const inputResponses = Object.fromEntries(next);
105
+ Promise.resolve(onSubmit({ inputResponses })).catch(() => setAnswers(/* @__PURE__ */ new Map()));
105
106
  };
106
107
  const answer = (key, response) => {
107
- const next = {
108
- ...answers,
109
- [key]: response
110
- };
108
+ const next = new Map(answers).set(key, response);
111
109
  setAnswers(next);
112
- if (!keys.every((requestKey) => next[requestKey])) return;
110
+ if (!keys.every((requestKey) => next.has(requestKey))) return;
113
111
  send(next);
114
112
  };
115
113
  const cancelAll = () => {
116
- const next = Object.fromEntries(keys.map((key) => [key, { action: "cancel" }]));
114
+ const next = new Map(keys.map((key) => [key, { action: "cancel" }]));
117
115
  setAnswers(next);
118
116
  send(next);
119
117
  };
@@ -139,15 +137,15 @@ function McpInputRequestTool({ payload, onSubmit, isSubmitting, error, readOnly,
139
137
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Button.Button, {
140
138
  type: "button",
141
139
  variant: "ghost",
142
- disabled: isSubmitting || Object.keys(answers).length > 0,
140
+ disabled: isSubmitting || answers.size > 0,
143
141
  onClick: cancelAll,
144
142
  children: "Cancel"
145
143
  })
146
144
  })] }) : Object.entries(payload.requests).map(([key, request]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InputRequest, {
147
145
  requestKey: key,
148
146
  request,
149
- answered: answers[key]?.action,
150
- disabled: readOnly || isSubmitting || answers[key] !== void 0,
147
+ answered: answers.get(key)?.action,
148
+ disabled: readOnly || isSubmitting || answers.has(key),
151
149
  readOnly,
152
150
  onAnswer: (response) => answer(key, response)
153
151
  }, key)),