@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
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-input-request-tool.cjs.js","names":["useState","useMemo","inputRequestTooLarge","AskUserContainer","AskUserLabel","AskUserBody","Notice","_Fragment","Txt","Button","jsonSchemaToZodRuntime","DynamicForm","ExternalLink","CodeEditor"],"sources":["../../../../src/domains/chat/tools/mcp-input-request-tool.tsx"],"sourcesContent":["import { ExternalLink } from 'lucide-react';\nimport { useMemo, useState } from 'react';\nimport type {\n McpInputAction,\n McpInputRequest,\n McpInputRequestPayload,\n McpInputResponse,\n McpInputResumeData,\n} from './mcp-input-request';\nimport { inputRequestTooLarge } from './mcp-input-request';\nimport { AskUserBody, AskUserContainer, AskUserLabel } from '@/ds/components/ai/ask-user';\nimport { Button } from '@/ds/components/Button';\nimport { CodeEditor } from '@/ds/components/CodeEditor';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { DynamicForm } from '@/lib/form/dynamic-form';\nimport { jsonSchemaToZodRuntime } from '@/lib/form/json-schema-to-zod-runtime';\n\nexport interface McpInputRequestToolProps {\n payload: McpInputRequestPayload;\n /** Sends the answers; the request stays open (and answerable again) if the returned promise rejects. */\n onSubmit: (resumeData: McpInputResumeData) => void | Promise<unknown>;\n isSubmitting?: boolean;\n /** Why the last answer didn't go through. */\n error?: string;\n /** History view: show the question without answering it. */\n readOnly?: boolean;\n /** Why the request can't be answered here, shown with the questions. */\n notice?: string;\n}\n\nconst ACTION_LABEL: Record<McpInputAction, string> = {\n accept: 'Accepted',\n decline: 'Declined',\n cancel: 'Cancelled',\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\ntype RequestView =\n | { kind: 'form'; message?: string; schema: Record<string, unknown> }\n | { kind: 'url'; message?: string; url: string }\n | { kind: 'unsupported'; message?: string };\n\nconst httpUrl = (value: unknown): string | undefined => {\n if (typeof value !== 'string') return undefined;\n try {\n const url = new URL(value);\n return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : undefined;\n } catch {\n return undefined;\n }\n};\n\n/** More fields than any real question needs; a bigger form is not one Studio should draw. */\nconst MAX_FORM_FIELDS = 100;\nconst FIELD_TYPES = new Set(['string', 'number', 'integer', 'boolean']);\n// Keywords the MCP spec allows on a requested field. `pattern` isn't one: a server-chosen regex would\n// run against every keystroke.\nconst FIELD_KEYS = [\n 'type',\n 'title',\n 'description',\n 'default',\n 'enum',\n 'enumNames',\n 'format',\n 'minLength',\n 'maxLength',\n 'minimum',\n 'maximum',\n];\n\nconst isStringList = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every(item => typeof item === 'string');\n\n/**\n * The MCP spec limits `requestedSchema` to a flat object of primitive fields. Keep only that shape, so a\n * server can't hand Studio a deep or huge schema to convert; anything else isn't a form Studio draws.\n */\nfunction formSchema(requested: Record<string, unknown>): Record<string, unknown> | undefined {\n const { type, properties, required } = requested;\n if (type !== 'object' || !isRecord(properties)) return undefined;\n const entries = Object.entries(properties);\n if (entries.length > MAX_FORM_FIELDS) return undefined;\n const fields: Record<string, Record<string, unknown>> = {};\n for (const [key, field] of entries) {\n if (!isRecord(field) || typeof field.type !== 'string' || !FIELD_TYPES.has(field.type)) return undefined;\n if (field.enum !== undefined && !isStringList(field.enum)) return undefined;\n fields[key] = Object.fromEntries(\n FIELD_KEYS.filter(name => field[name] !== undefined).map(name => [name, field[name]]),\n );\n }\n return { type: 'object', properties: fields, ...(isStringList(required) ? { required } : {}) };\n}\n\nfunction viewRequest({ method, params }: McpInputRequest): RequestView {\n const message = typeof params?.message === 'string' ? params.message : undefined;\n if (method !== 'elicitation/create') return { kind: 'unsupported', message };\n // Only web links: a server-chosen `javascript:` URL must not become a clickable link.\n const url = params?.mode === 'url' ? httpUrl(params.url) : undefined;\n if (url) return { kind: 'url', message, url };\n const schema =\n params?.mode !== 'url' && isRecord(params?.requestedSchema) ? formSchema(params.requestedSchema) : undefined;\n if (schema) return { kind: 'form', message, schema };\n return { kind: 'unsupported', message };\n}\n\n/**\n * The questions an MCP server asked while a tool call's task waits. Every request key needs an answer\n * before the call can continue, so answers are held until the last one is given, then sent together.\n */\nexport function McpInputRequestTool({\n payload,\n onSubmit,\n isSubmitting,\n error,\n readOnly,\n notice,\n}: McpInputRequestToolProps) {\n const [answers, setAnswers] = useState<Record<string, McpInputResponse>>({});\n const keys = Object.keys(payload.requests);\n const tooLarge = useMemo(() => inputRequestTooLarge(payload), [payload]);\n\n const send = (inputResponses: Record<string, McpInputResponse>) => {\n void Promise.resolve(onSubmit({ inputResponses })).catch(() => setAnswers({}));\n };\n\n const answer = (key: string, response: McpInputResponse) => {\n const next = { ...answers, [key]: response };\n setAnswers(next);\n if (!keys.every(requestKey => next[requestKey])) return;\n send(next);\n };\n\n const cancelAll = () => {\n const next = Object.fromEntries(keys.map(key => [key, { action: 'cancel' as const }]));\n setAnswers(next);\n send(next);\n };\n\n return (\n <AskUserContainer className=\"mb-4\" data-testid=\"mcp-input-request\">\n <AskUserLabel>\n {payload.serverName} needs input to continue {payload.toolName}\n </AskUserLabel>\n <AskUserBody className=\"flex flex-col gap-4\">\n {notice && <Notice variant=\"info\">{notice}</Notice>}\n {tooLarge ? (\n <>\n <Txt tone=\"muted\">{payload.serverName} sent more than Studio can show here.</Txt>\n {!readOnly && (\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n disabled={isSubmitting || Object.keys(answers).length > 0}\n onClick={cancelAll}\n >\n Cancel\n </Button>\n </div>\n )}\n </>\n ) : (\n Object.entries(payload.requests).map(([key, request]) => (\n <InputRequest\n key={key}\n requestKey={key}\n request={request}\n answered={answers[key]?.action}\n disabled={readOnly || isSubmitting || answers[key] !== undefined}\n readOnly={readOnly}\n onAnswer={response => answer(key, response)}\n />\n ))\n )}\n {error && (\n <Notice variant=\"destructive\" role=\"alert\">\n {error}\n </Notice>\n )}\n </AskUserBody>\n </AskUserContainer>\n );\n}\n\nfunction InputRequest({\n requestKey,\n request,\n answered,\n disabled,\n readOnly,\n onAnswer,\n}: {\n requestKey: string;\n request: McpInputRequest;\n answered?: McpInputAction;\n disabled?: boolean;\n readOnly?: boolean;\n onAnswer: (response: McpInputResponse) => void;\n}) {\n const view = useMemo(() => viewRequest(request), [request]);\n const schema = useMemo(() => (view.kind === 'form' ? jsonSchemaToZodRuntime(view.schema) : undefined), [view]);\n\n const decline = (\n <Button type=\"button\" disabled={disabled} onClick={() => onAnswer({ action: 'decline' })}>\n Decline\n </Button>\n );\n const cancel = (\n <Button type=\"button\" variant=\"ghost\" disabled={disabled} onClick={() => onAnswer({ action: 'cancel' })}>\n Cancel\n </Button>\n );\n\n return (\n <section aria-label={`Input request ${requestKey}`} className=\"flex flex-col gap-3\">\n {view.message && (\n <Txt variant=\"subheading\" tone=\"ink\">\n {view.message}\n </Txt>\n )}\n {answered ? (\n <Txt tone=\"muted\">{ACTION_LABEL[answered]}</Txt>\n ) : readOnly ? null : view.kind === 'form' ? (\n <DynamicForm\n schema={schema}\n onSubmit={values => onAnswer({ action: 'accept', content: isRecord(values) ? values : {} })}\n isSubmitLoading={disabled}\n submitButtonLabel=\"Accept\"\n submitButtonVariant=\"primary\"\n submitActions={\n <>\n {decline}\n {cancel}\n </>\n }\n />\n ) : view.kind === 'url' ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button\n variant=\"link\"\n render={<a href={view.url} target=\"_blank\" rel=\"noopener noreferrer\" />}\n iconEnd={<ExternalLink />}\n >\n Open link\n </Button>\n <Button type=\"button\" variant=\"primary\" disabled={disabled} onClick={() => onAnswer({ action: 'accept' })}>\n Done\n </Button>\n {cancel}\n </div>\n ) : (\n <>\n <Txt tone=\"muted\">Studio can't answer a {request.method} request.</Txt>\n <CodeEditor data={{ ...request }} />\n <div className=\"flex gap-2\">{cancel}</div>\n </>\n )}\n </section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mcp-input-request-tool.cjs.js","names":["useState","useMemo","inputRequestTooLarge","AskUserContainer","AskUserLabel","AskUserBody","Notice","_Fragment","Txt","Button","jsonSchemaToZodRuntime","DynamicForm","ExternalLink","CodeEditor"],"sources":["../../../../src/domains/chat/tools/mcp-input-request-tool.tsx"],"sourcesContent":["import { ExternalLink } from 'lucide-react';\nimport { useMemo, useState } from 'react';\nimport type {\n McpInputAction,\n McpInputRequest,\n McpInputRequestPayload,\n McpInputResponse,\n McpInputResumeData,\n} from './mcp-input-request';\nimport { inputRequestTooLarge } from './mcp-input-request';\nimport { AskUserBody, AskUserContainer, AskUserLabel } from '@/ds/components/ai/ask-user';\nimport { Button } from '@/ds/components/Button';\nimport { CodeEditor } from '@/ds/components/CodeEditor';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { DynamicForm } from '@/lib/form/dynamic-form';\nimport { jsonSchemaToZodRuntime } from '@/lib/form/json-schema-to-zod-runtime';\n\nexport interface McpInputRequestToolProps {\n payload: McpInputRequestPayload;\n /** Sends the answers; the request stays open (and answerable again) if the returned promise rejects. */\n onSubmit: (resumeData: McpInputResumeData) => void | Promise<unknown>;\n isSubmitting?: boolean;\n /** Why the last answer didn't go through. */\n error?: string;\n /** History view: show the question without answering it. */\n readOnly?: boolean;\n /** Why the request can't be answered here, shown with the questions. */\n notice?: string;\n}\n\nconst ACTION_LABEL: Record<McpInputAction, string> = {\n accept: 'Accepted',\n decline: 'Declined',\n cancel: 'Cancelled',\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\ntype RequestView =\n | { kind: 'form'; message?: string; schema: Record<string, unknown> }\n | { kind: 'url'; message?: string; url: string }\n | { kind: 'unsupported'; message?: string };\n\nconst httpUrl = (value: unknown): string | undefined => {\n if (typeof value !== 'string') return undefined;\n try {\n const url = new URL(value);\n return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : undefined;\n } catch {\n return undefined;\n }\n};\n\n/** More fields than any real question needs; a bigger form is not one Studio should draw. */\nconst MAX_FORM_FIELDS = 100;\nconst FIELD_TYPES = new Set(['string', 'number', 'integer', 'boolean']);\n// Keywords the MCP spec allows on a requested field. `pattern` isn't one: a server-chosen regex would\n// run against every keystroke.\nconst FIELD_KEYS = [\n 'type',\n 'title',\n 'description',\n 'default',\n 'enum',\n 'enumNames',\n 'format',\n 'minLength',\n 'maxLength',\n 'minimum',\n 'maximum',\n];\n\nconst isStringList = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every(item => typeof item === 'string');\n\n/**\n * The MCP spec limits `requestedSchema` to a flat object of primitive fields. Keep only that shape, so a\n * server can't hand Studio a deep or huge schema to convert; anything else isn't a form Studio draws.\n */\nfunction formSchema(requested: Record<string, unknown>): Record<string, unknown> | undefined {\n const { type, properties, required } = requested;\n if (type !== 'object' || !isRecord(properties)) return undefined;\n const entries = Object.entries(properties);\n if (entries.length > MAX_FORM_FIELDS) return undefined;\n const fields: Record<string, Record<string, unknown>> = {};\n for (const [key, field] of entries) {\n if (!isRecord(field) || typeof field.type !== 'string' || !FIELD_TYPES.has(field.type)) return undefined;\n if (field.enum !== undefined && !isStringList(field.enum)) return undefined;\n fields[key] = Object.fromEntries(\n FIELD_KEYS.filter(name => field[name] !== undefined).map(name => [name, field[name]]),\n );\n }\n return { type: 'object', properties: fields, ...(isStringList(required) ? { required } : {}) };\n}\n\nfunction viewRequest({ method, params }: McpInputRequest): RequestView {\n const message = typeof params?.message === 'string' ? params.message : undefined;\n if (method !== 'elicitation/create') return { kind: 'unsupported', message };\n // Only web links: a server-chosen `javascript:` URL must not become a clickable link.\n const url = params?.mode === 'url' ? httpUrl(params.url) : undefined;\n if (url) return { kind: 'url', message, url };\n const schema =\n params?.mode !== 'url' && isRecord(params?.requestedSchema) ? formSchema(params.requestedSchema) : undefined;\n if (schema) return { kind: 'form', message, schema };\n return { kind: 'unsupported', message };\n}\n\n/**\n * The questions an MCP server asked while a tool call's task waits. Every request key needs an answer\n * before the call can continue, so answers are held until the last one is given, then sent together.\n */\nexport function McpInputRequestTool({\n payload,\n onSubmit,\n isSubmitting,\n error,\n readOnly,\n notice,\n}: McpInputRequestToolProps) {\n // A Map, not an object: request keys come from the server, and one named `constructor` or\n // `__proto__` must not read `Object.prototype` and look answered.\n const [answers, setAnswers] = useState<ReadonlyMap<string, McpInputResponse>>(() => new Map());\n const keys = Object.keys(payload.requests);\n const tooLarge = useMemo(() => inputRequestTooLarge(payload), [payload]);\n\n const send = (next: ReadonlyMap<string, McpInputResponse>) => {\n // `Object.fromEntries` defines own properties, so a `__proto__` key is sent as data.\n const inputResponses = Object.fromEntries(next);\n void Promise.resolve(onSubmit({ inputResponses })).catch(() => setAnswers(new Map()));\n };\n\n const answer = (key: string, response: McpInputResponse) => {\n const next = new Map(answers).set(key, response);\n setAnswers(next);\n if (!keys.every(requestKey => next.has(requestKey))) return;\n send(next);\n };\n\n const cancelAll = () => {\n const next = new Map(keys.map(key => [key, { action: 'cancel' as const }]));\n setAnswers(next);\n send(next);\n };\n\n return (\n <AskUserContainer className=\"mb-4\" data-testid=\"mcp-input-request\">\n <AskUserLabel>\n {payload.serverName} needs input to continue {payload.toolName}\n </AskUserLabel>\n <AskUserBody className=\"flex flex-col gap-4\">\n {notice && <Notice variant=\"info\">{notice}</Notice>}\n {tooLarge ? (\n <>\n <Txt tone=\"muted\">{payload.serverName} sent more than Studio can show here.</Txt>\n {!readOnly && (\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"ghost\" disabled={isSubmitting || answers.size > 0} onClick={cancelAll}>\n Cancel\n </Button>\n </div>\n )}\n </>\n ) : (\n Object.entries(payload.requests).map(([key, request]) => (\n <InputRequest\n key={key}\n requestKey={key}\n request={request}\n answered={answers.get(key)?.action}\n disabled={readOnly || isSubmitting || answers.has(key)}\n readOnly={readOnly}\n onAnswer={response => answer(key, response)}\n />\n ))\n )}\n {error && (\n <Notice variant=\"destructive\" role=\"alert\">\n {error}\n </Notice>\n )}\n </AskUserBody>\n </AskUserContainer>\n );\n}\n\nfunction InputRequest({\n requestKey,\n request,\n answered,\n disabled,\n readOnly,\n onAnswer,\n}: {\n requestKey: string;\n request: McpInputRequest;\n answered?: McpInputAction;\n disabled?: boolean;\n readOnly?: boolean;\n onAnswer: (response: McpInputResponse) => void;\n}) {\n const view = useMemo(() => viewRequest(request), [request]);\n const schema = useMemo(() => (view.kind === 'form' ? jsonSchemaToZodRuntime(view.schema) : undefined), [view]);\n\n const decline = (\n <Button type=\"button\" disabled={disabled} onClick={() => onAnswer({ action: 'decline' })}>\n Decline\n </Button>\n );\n const cancel = (\n <Button type=\"button\" variant=\"ghost\" disabled={disabled} onClick={() => onAnswer({ action: 'cancel' })}>\n Cancel\n </Button>\n );\n\n return (\n <section aria-label={`Input request ${requestKey}`} className=\"flex flex-col gap-3\">\n {view.message && (\n <Txt variant=\"subheading\" tone=\"ink\">\n {view.message}\n </Txt>\n )}\n {answered ? (\n <Txt tone=\"muted\">{ACTION_LABEL[answered]}</Txt>\n ) : readOnly ? null : view.kind === 'form' ? (\n <DynamicForm\n schema={schema}\n onSubmit={values => onAnswer({ action: 'accept', content: isRecord(values) ? values : {} })}\n isSubmitLoading={disabled}\n submitButtonLabel=\"Accept\"\n submitButtonVariant=\"primary\"\n submitActions={\n <>\n {decline}\n {cancel}\n </>\n }\n />\n ) : view.kind === 'url' ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button\n variant=\"link\"\n render={<a href={view.url} target=\"_blank\" rel=\"noopener noreferrer\" />}\n iconEnd={<ExternalLink />}\n >\n Open link\n </Button>\n <Button type=\"button\" variant=\"primary\" disabled={disabled} onClick={() => onAnswer({ action: 'accept' })}>\n Done\n </Button>\n {cancel}\n </div>\n ) : (\n <>\n <Txt tone=\"muted\">Studio can't answer a {request.method} request.</Txt>\n <CodeEditor data={{ ...request }} />\n <div className=\"flex gap-2\">{cancel}</div>\n </>\n )}\n </section>\n );\n}\n"],"mappings":"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"}
@@ -96,23 +96,21 @@ function viewRequest({ method, params }) {
96
96
  * before the call can continue, so answers are held until the last one is given, then sent together.
97
97
  */
98
98
  function McpInputRequestTool({ payload, onSubmit, isSubmitting, error, readOnly, notice }) {
99
- const [answers, setAnswers] = useState({});
99
+ const [answers, setAnswers] = useState(() => /* @__PURE__ */ new Map());
100
100
  const keys = Object.keys(payload.requests);
101
101
  const tooLarge = useMemo(() => inputRequestTooLarge(payload), [payload]);
102
- const send = (inputResponses) => {
103
- Promise.resolve(onSubmit({ inputResponses })).catch(() => setAnswers({}));
102
+ const send = (next) => {
103
+ const inputResponses = Object.fromEntries(next);
104
+ Promise.resolve(onSubmit({ inputResponses })).catch(() => setAnswers(/* @__PURE__ */ new Map()));
104
105
  };
105
106
  const answer = (key, response) => {
106
- const next = {
107
- ...answers,
108
- [key]: response
109
- };
107
+ const next = new Map(answers).set(key, response);
110
108
  setAnswers(next);
111
- if (!keys.every((requestKey) => next[requestKey])) return;
109
+ if (!keys.every((requestKey) => next.has(requestKey))) return;
112
110
  send(next);
113
111
  };
114
112
  const cancelAll = () => {
115
- const next = Object.fromEntries(keys.map((key) => [key, { action: "cancel" }]));
113
+ const next = new Map(keys.map((key) => [key, { action: "cancel" }]));
116
114
  setAnswers(next);
117
115
  send(next);
118
116
  };
@@ -138,15 +136,15 @@ function McpInputRequestTool({ payload, onSubmit, isSubmitting, error, readOnly,
138
136
  children: /* @__PURE__ */ jsx(Button, {
139
137
  type: "button",
140
138
  variant: "ghost",
141
- disabled: isSubmitting || Object.keys(answers).length > 0,
139
+ disabled: isSubmitting || answers.size > 0,
142
140
  onClick: cancelAll,
143
141
  children: "Cancel"
144
142
  })
145
143
  })] }) : Object.entries(payload.requests).map(([key, request]) => /* @__PURE__ */ jsx(InputRequest, {
146
144
  requestKey: key,
147
145
  request,
148
- answered: answers[key]?.action,
149
- disabled: readOnly || isSubmitting || answers[key] !== void 0,
146
+ answered: answers.get(key)?.action,
147
+ disabled: readOnly || isSubmitting || answers.has(key),
150
148
  readOnly,
151
149
  onAnswer: (response) => answer(key, response)
152
150
  }, key)),
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-input-request-tool.es.js","names":["_Fragment"],"sources":["../../../../src/domains/chat/tools/mcp-input-request-tool.tsx"],"sourcesContent":["import { ExternalLink } from 'lucide-react';\nimport { useMemo, useState } from 'react';\nimport type {\n McpInputAction,\n McpInputRequest,\n McpInputRequestPayload,\n McpInputResponse,\n McpInputResumeData,\n} from './mcp-input-request';\nimport { inputRequestTooLarge } from './mcp-input-request';\nimport { AskUserBody, AskUserContainer, AskUserLabel } from '@/ds/components/ai/ask-user';\nimport { Button } from '@/ds/components/Button';\nimport { CodeEditor } from '@/ds/components/CodeEditor';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { DynamicForm } from '@/lib/form/dynamic-form';\nimport { jsonSchemaToZodRuntime } from '@/lib/form/json-schema-to-zod-runtime';\n\nexport interface McpInputRequestToolProps {\n payload: McpInputRequestPayload;\n /** Sends the answers; the request stays open (and answerable again) if the returned promise rejects. */\n onSubmit: (resumeData: McpInputResumeData) => void | Promise<unknown>;\n isSubmitting?: boolean;\n /** Why the last answer didn't go through. */\n error?: string;\n /** History view: show the question without answering it. */\n readOnly?: boolean;\n /** Why the request can't be answered here, shown with the questions. */\n notice?: string;\n}\n\nconst ACTION_LABEL: Record<McpInputAction, string> = {\n accept: 'Accepted',\n decline: 'Declined',\n cancel: 'Cancelled',\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\ntype RequestView =\n | { kind: 'form'; message?: string; schema: Record<string, unknown> }\n | { kind: 'url'; message?: string; url: string }\n | { kind: 'unsupported'; message?: string };\n\nconst httpUrl = (value: unknown): string | undefined => {\n if (typeof value !== 'string') return undefined;\n try {\n const url = new URL(value);\n return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : undefined;\n } catch {\n return undefined;\n }\n};\n\n/** More fields than any real question needs; a bigger form is not one Studio should draw. */\nconst MAX_FORM_FIELDS = 100;\nconst FIELD_TYPES = new Set(['string', 'number', 'integer', 'boolean']);\n// Keywords the MCP spec allows on a requested field. `pattern` isn't one: a server-chosen regex would\n// run against every keystroke.\nconst FIELD_KEYS = [\n 'type',\n 'title',\n 'description',\n 'default',\n 'enum',\n 'enumNames',\n 'format',\n 'minLength',\n 'maxLength',\n 'minimum',\n 'maximum',\n];\n\nconst isStringList = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every(item => typeof item === 'string');\n\n/**\n * The MCP spec limits `requestedSchema` to a flat object of primitive fields. Keep only that shape, so a\n * server can't hand Studio a deep or huge schema to convert; anything else isn't a form Studio draws.\n */\nfunction formSchema(requested: Record<string, unknown>): Record<string, unknown> | undefined {\n const { type, properties, required } = requested;\n if (type !== 'object' || !isRecord(properties)) return undefined;\n const entries = Object.entries(properties);\n if (entries.length > MAX_FORM_FIELDS) return undefined;\n const fields: Record<string, Record<string, unknown>> = {};\n for (const [key, field] of entries) {\n if (!isRecord(field) || typeof field.type !== 'string' || !FIELD_TYPES.has(field.type)) return undefined;\n if (field.enum !== undefined && !isStringList(field.enum)) return undefined;\n fields[key] = Object.fromEntries(\n FIELD_KEYS.filter(name => field[name] !== undefined).map(name => [name, field[name]]),\n );\n }\n return { type: 'object', properties: fields, ...(isStringList(required) ? { required } : {}) };\n}\n\nfunction viewRequest({ method, params }: McpInputRequest): RequestView {\n const message = typeof params?.message === 'string' ? params.message : undefined;\n if (method !== 'elicitation/create') return { kind: 'unsupported', message };\n // Only web links: a server-chosen `javascript:` URL must not become a clickable link.\n const url = params?.mode === 'url' ? httpUrl(params.url) : undefined;\n if (url) return { kind: 'url', message, url };\n const schema =\n params?.mode !== 'url' && isRecord(params?.requestedSchema) ? formSchema(params.requestedSchema) : undefined;\n if (schema) return { kind: 'form', message, schema };\n return { kind: 'unsupported', message };\n}\n\n/**\n * The questions an MCP server asked while a tool call's task waits. Every request key needs an answer\n * before the call can continue, so answers are held until the last one is given, then sent together.\n */\nexport function McpInputRequestTool({\n payload,\n onSubmit,\n isSubmitting,\n error,\n readOnly,\n notice,\n}: McpInputRequestToolProps) {\n const [answers, setAnswers] = useState<Record<string, McpInputResponse>>({});\n const keys = Object.keys(payload.requests);\n const tooLarge = useMemo(() => inputRequestTooLarge(payload), [payload]);\n\n const send = (inputResponses: Record<string, McpInputResponse>) => {\n void Promise.resolve(onSubmit({ inputResponses })).catch(() => setAnswers({}));\n };\n\n const answer = (key: string, response: McpInputResponse) => {\n const next = { ...answers, [key]: response };\n setAnswers(next);\n if (!keys.every(requestKey => next[requestKey])) return;\n send(next);\n };\n\n const cancelAll = () => {\n const next = Object.fromEntries(keys.map(key => [key, { action: 'cancel' as const }]));\n setAnswers(next);\n send(next);\n };\n\n return (\n <AskUserContainer className=\"mb-4\" data-testid=\"mcp-input-request\">\n <AskUserLabel>\n {payload.serverName} needs input to continue {payload.toolName}\n </AskUserLabel>\n <AskUserBody className=\"flex flex-col gap-4\">\n {notice && <Notice variant=\"info\">{notice}</Notice>}\n {tooLarge ? (\n <>\n <Txt tone=\"muted\">{payload.serverName} sent more than Studio can show here.</Txt>\n {!readOnly && (\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n disabled={isSubmitting || Object.keys(answers).length > 0}\n onClick={cancelAll}\n >\n Cancel\n </Button>\n </div>\n )}\n </>\n ) : (\n Object.entries(payload.requests).map(([key, request]) => (\n <InputRequest\n key={key}\n requestKey={key}\n request={request}\n answered={answers[key]?.action}\n disabled={readOnly || isSubmitting || answers[key] !== undefined}\n readOnly={readOnly}\n onAnswer={response => answer(key, response)}\n />\n ))\n )}\n {error && (\n <Notice variant=\"destructive\" role=\"alert\">\n {error}\n </Notice>\n )}\n </AskUserBody>\n </AskUserContainer>\n );\n}\n\nfunction InputRequest({\n requestKey,\n request,\n answered,\n disabled,\n readOnly,\n onAnswer,\n}: {\n requestKey: string;\n request: McpInputRequest;\n answered?: McpInputAction;\n disabled?: boolean;\n readOnly?: boolean;\n onAnswer: (response: McpInputResponse) => void;\n}) {\n const view = useMemo(() => viewRequest(request), [request]);\n const schema = useMemo(() => (view.kind === 'form' ? jsonSchemaToZodRuntime(view.schema) : undefined), [view]);\n\n const decline = (\n <Button type=\"button\" disabled={disabled} onClick={() => onAnswer({ action: 'decline' })}>\n Decline\n </Button>\n );\n const cancel = (\n <Button type=\"button\" variant=\"ghost\" disabled={disabled} onClick={() => onAnswer({ action: 'cancel' })}>\n Cancel\n </Button>\n );\n\n return (\n <section aria-label={`Input request ${requestKey}`} className=\"flex flex-col gap-3\">\n {view.message && (\n <Txt variant=\"subheading\" tone=\"ink\">\n {view.message}\n </Txt>\n )}\n {answered ? (\n <Txt tone=\"muted\">{ACTION_LABEL[answered]}</Txt>\n ) : readOnly ? null : view.kind === 'form' ? (\n <DynamicForm\n schema={schema}\n onSubmit={values => onAnswer({ action: 'accept', content: isRecord(values) ? values : {} })}\n isSubmitLoading={disabled}\n submitButtonLabel=\"Accept\"\n submitButtonVariant=\"primary\"\n submitActions={\n <>\n {decline}\n {cancel}\n </>\n }\n />\n ) : view.kind === 'url' ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button\n variant=\"link\"\n render={<a href={view.url} target=\"_blank\" rel=\"noopener noreferrer\" />}\n iconEnd={<ExternalLink />}\n >\n Open link\n </Button>\n <Button type=\"button\" variant=\"primary\" disabled={disabled} onClick={() => onAnswer({ action: 'accept' })}>\n Done\n </Button>\n {cancel}\n </div>\n ) : (\n <>\n <Txt tone=\"muted\">Studio can't answer a {request.method} request.</Txt>\n <CodeEditor data={{ ...request }} />\n <div className=\"flex gap-2\">{cancel}</div>\n </>\n )}\n </section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"mcp-input-request-tool.es.js","names":["_Fragment"],"sources":["../../../../src/domains/chat/tools/mcp-input-request-tool.tsx"],"sourcesContent":["import { ExternalLink } from 'lucide-react';\nimport { useMemo, useState } from 'react';\nimport type {\n McpInputAction,\n McpInputRequest,\n McpInputRequestPayload,\n McpInputResponse,\n McpInputResumeData,\n} from './mcp-input-request';\nimport { inputRequestTooLarge } from './mcp-input-request';\nimport { AskUserBody, AskUserContainer, AskUserLabel } from '@/ds/components/ai/ask-user';\nimport { Button } from '@/ds/components/Button';\nimport { CodeEditor } from '@/ds/components/CodeEditor';\nimport { Notice } from '@/ds/components/Notice';\nimport { Txt } from '@/ds/components/Txt';\nimport { DynamicForm } from '@/lib/form/dynamic-form';\nimport { jsonSchemaToZodRuntime } from '@/lib/form/json-schema-to-zod-runtime';\n\nexport interface McpInputRequestToolProps {\n payload: McpInputRequestPayload;\n /** Sends the answers; the request stays open (and answerable again) if the returned promise rejects. */\n onSubmit: (resumeData: McpInputResumeData) => void | Promise<unknown>;\n isSubmitting?: boolean;\n /** Why the last answer didn't go through. */\n error?: string;\n /** History view: show the question without answering it. */\n readOnly?: boolean;\n /** Why the request can't be answered here, shown with the questions. */\n notice?: string;\n}\n\nconst ACTION_LABEL: Record<McpInputAction, string> = {\n accept: 'Accepted',\n decline: 'Declined',\n cancel: 'Cancelled',\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\ntype RequestView =\n | { kind: 'form'; message?: string; schema: Record<string, unknown> }\n | { kind: 'url'; message?: string; url: string }\n | { kind: 'unsupported'; message?: string };\n\nconst httpUrl = (value: unknown): string | undefined => {\n if (typeof value !== 'string') return undefined;\n try {\n const url = new URL(value);\n return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : undefined;\n } catch {\n return undefined;\n }\n};\n\n/** More fields than any real question needs; a bigger form is not one Studio should draw. */\nconst MAX_FORM_FIELDS = 100;\nconst FIELD_TYPES = new Set(['string', 'number', 'integer', 'boolean']);\n// Keywords the MCP spec allows on a requested field. `pattern` isn't one: a server-chosen regex would\n// run against every keystroke.\nconst FIELD_KEYS = [\n 'type',\n 'title',\n 'description',\n 'default',\n 'enum',\n 'enumNames',\n 'format',\n 'minLength',\n 'maxLength',\n 'minimum',\n 'maximum',\n];\n\nconst isStringList = (value: unknown): value is string[] =>\n Array.isArray(value) && value.every(item => typeof item === 'string');\n\n/**\n * The MCP spec limits `requestedSchema` to a flat object of primitive fields. Keep only that shape, so a\n * server can't hand Studio a deep or huge schema to convert; anything else isn't a form Studio draws.\n */\nfunction formSchema(requested: Record<string, unknown>): Record<string, unknown> | undefined {\n const { type, properties, required } = requested;\n if (type !== 'object' || !isRecord(properties)) return undefined;\n const entries = Object.entries(properties);\n if (entries.length > MAX_FORM_FIELDS) return undefined;\n const fields: Record<string, Record<string, unknown>> = {};\n for (const [key, field] of entries) {\n if (!isRecord(field) || typeof field.type !== 'string' || !FIELD_TYPES.has(field.type)) return undefined;\n if (field.enum !== undefined && !isStringList(field.enum)) return undefined;\n fields[key] = Object.fromEntries(\n FIELD_KEYS.filter(name => field[name] !== undefined).map(name => [name, field[name]]),\n );\n }\n return { type: 'object', properties: fields, ...(isStringList(required) ? { required } : {}) };\n}\n\nfunction viewRequest({ method, params }: McpInputRequest): RequestView {\n const message = typeof params?.message === 'string' ? params.message : undefined;\n if (method !== 'elicitation/create') return { kind: 'unsupported', message };\n // Only web links: a server-chosen `javascript:` URL must not become a clickable link.\n const url = params?.mode === 'url' ? httpUrl(params.url) : undefined;\n if (url) return { kind: 'url', message, url };\n const schema =\n params?.mode !== 'url' && isRecord(params?.requestedSchema) ? formSchema(params.requestedSchema) : undefined;\n if (schema) return { kind: 'form', message, schema };\n return { kind: 'unsupported', message };\n}\n\n/**\n * The questions an MCP server asked while a tool call's task waits. Every request key needs an answer\n * before the call can continue, so answers are held until the last one is given, then sent together.\n */\nexport function McpInputRequestTool({\n payload,\n onSubmit,\n isSubmitting,\n error,\n readOnly,\n notice,\n}: McpInputRequestToolProps) {\n // A Map, not an object: request keys come from the server, and one named `constructor` or\n // `__proto__` must not read `Object.prototype` and look answered.\n const [answers, setAnswers] = useState<ReadonlyMap<string, McpInputResponse>>(() => new Map());\n const keys = Object.keys(payload.requests);\n const tooLarge = useMemo(() => inputRequestTooLarge(payload), [payload]);\n\n const send = (next: ReadonlyMap<string, McpInputResponse>) => {\n // `Object.fromEntries` defines own properties, so a `__proto__` key is sent as data.\n const inputResponses = Object.fromEntries(next);\n void Promise.resolve(onSubmit({ inputResponses })).catch(() => setAnswers(new Map()));\n };\n\n const answer = (key: string, response: McpInputResponse) => {\n const next = new Map(answers).set(key, response);\n setAnswers(next);\n if (!keys.every(requestKey => next.has(requestKey))) return;\n send(next);\n };\n\n const cancelAll = () => {\n const next = new Map(keys.map(key => [key, { action: 'cancel' as const }]));\n setAnswers(next);\n send(next);\n };\n\n return (\n <AskUserContainer className=\"mb-4\" data-testid=\"mcp-input-request\">\n <AskUserLabel>\n {payload.serverName} needs input to continue {payload.toolName}\n </AskUserLabel>\n <AskUserBody className=\"flex flex-col gap-4\">\n {notice && <Notice variant=\"info\">{notice}</Notice>}\n {tooLarge ? (\n <>\n <Txt tone=\"muted\">{payload.serverName} sent more than Studio can show here.</Txt>\n {!readOnly && (\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"ghost\" disabled={isSubmitting || answers.size > 0} onClick={cancelAll}>\n Cancel\n </Button>\n </div>\n )}\n </>\n ) : (\n Object.entries(payload.requests).map(([key, request]) => (\n <InputRequest\n key={key}\n requestKey={key}\n request={request}\n answered={answers.get(key)?.action}\n disabled={readOnly || isSubmitting || answers.has(key)}\n readOnly={readOnly}\n onAnswer={response => answer(key, response)}\n />\n ))\n )}\n {error && (\n <Notice variant=\"destructive\" role=\"alert\">\n {error}\n </Notice>\n )}\n </AskUserBody>\n </AskUserContainer>\n );\n}\n\nfunction InputRequest({\n requestKey,\n request,\n answered,\n disabled,\n readOnly,\n onAnswer,\n}: {\n requestKey: string;\n request: McpInputRequest;\n answered?: McpInputAction;\n disabled?: boolean;\n readOnly?: boolean;\n onAnswer: (response: McpInputResponse) => void;\n}) {\n const view = useMemo(() => viewRequest(request), [request]);\n const schema = useMemo(() => (view.kind === 'form' ? jsonSchemaToZodRuntime(view.schema) : undefined), [view]);\n\n const decline = (\n <Button type=\"button\" disabled={disabled} onClick={() => onAnswer({ action: 'decline' })}>\n Decline\n </Button>\n );\n const cancel = (\n <Button type=\"button\" variant=\"ghost\" disabled={disabled} onClick={() => onAnswer({ action: 'cancel' })}>\n Cancel\n </Button>\n );\n\n return (\n <section aria-label={`Input request ${requestKey}`} className=\"flex flex-col gap-3\">\n {view.message && (\n <Txt variant=\"subheading\" tone=\"ink\">\n {view.message}\n </Txt>\n )}\n {answered ? (\n <Txt tone=\"muted\">{ACTION_LABEL[answered]}</Txt>\n ) : readOnly ? null : view.kind === 'form' ? (\n <DynamicForm\n schema={schema}\n onSubmit={values => onAnswer({ action: 'accept', content: isRecord(values) ? values : {} })}\n isSubmitLoading={disabled}\n submitButtonLabel=\"Accept\"\n submitButtonVariant=\"primary\"\n submitActions={\n <>\n {decline}\n {cancel}\n </>\n }\n />\n ) : view.kind === 'url' ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button\n variant=\"link\"\n render={<a href={view.url} target=\"_blank\" rel=\"noopener noreferrer\" />}\n iconEnd={<ExternalLink />}\n >\n Open link\n </Button>\n <Button type=\"button\" variant=\"primary\" disabled={disabled} onClick={() => onAnswer({ action: 'accept' })}>\n Done\n </Button>\n {cancel}\n </div>\n ) : (\n <>\n <Txt tone=\"muted\">Studio can't answer a {request.method} request.</Txt>\n <CodeEditor data={{ ...request }} />\n <div className=\"flex gap-2\">{cancel}</div>\n </>\n )}\n </section>\n );\n}\n"],"mappings":"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"}
@@ -46,8 +46,8 @@ function toolCardKind({ toolName, toolCallId, input, output, state, backgroundTa
46
46
  if (toolName === "mastra-memory-om-observation") return "observation";
47
47
  if (isHiddenTool(toolName)) return "hidden";
48
48
  if (toolName === "ask_user") return "ask_user";
49
- if (toolName === "request_connection" && require_domains_connect_types.isConnectPromptPayload(output)) return "connect_prompt";
50
49
  const { suspended } = toolInteraction(metadata, toolName, toolCallId);
50
+ if (toolName === "request_connection" && (require_domains_connect_types.isConnectPromptPayload(output) || require_domains_connect_types.isConnectPromptPayload(suspended?.suspendPayload))) return "connect_prompt";
51
51
  if (toolName === "submit_plan" || hasSubmitPlanToolId(suspended?.suspendPayload) || hasSubmitPlanToolId(output)) return "submit_plan";
52
52
  if (!isDeferredCall(backgroundTaskId, backgroundTaskStatus) && require_domains_chat_tools_mcp_input_request.suspendedMcpInputRequest(metadata, toolCallId)) return "mcp_input_request";
53
53
  if (typeof output === "string" && output.toLowerCase().includes("background task")) return "background";
@@ -1 +1 @@
1
- {"version":3,"file":"tool-card-kind.cjs.js","names":["WORKSPACE_TOOLS","isRecord","getCodeModeCall","isTaskTool","isConnectPromptPayload","suspendedMcpInputRequest","KNOWLEDGE_TOOLS","knowledgeCard"],"sources":["../../../../src/domains/chat/tools/tool-card-kind.ts"],"sourcesContent":["import type { MessageMetadata, SuspendedToolMetadata, ToolApprovalMetadata } from '../messages/message-metadata';\nimport type { ToolPartFields } from '../messages/renderers/tool-part';\nimport { isRecord } from '../messages/signal-data';\nimport { KNOWLEDGE_TOOLS, knowledgeCard } from './badges/knowledge-tool-badge';\nimport { getCodeModeCall } from './code-mode';\nimport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\nimport { suspendedMcpInputRequest } from './mcp-input-request';\nimport { SUBMIT_PLAN_TOOL_ID } from './submit-plan-tool-id';\nimport { WORKSPACE_TOOLS } from './workspace-tool-constants';\nimport { isConnectPromptPayload } from '@/domains/connect/types';\nimport type { ActivityStatus } from '@/ds/components/ai/activity';\nimport { isTaskTool } from '@/ds/components/ai/tool-call';\n\n/** Which card draws a call. Decided once, shared by the dispatcher and the fold. */\nexport type ToolCardKind =\n | 'hidden'\n | 'observation'\n | 'ask_user'\n | 'connect_prompt'\n | 'submit_plan'\n | 'mcp_input_request'\n | 'background'\n | 'agent'\n | 'workflow'\n | 'file_tree'\n | 'sandbox'\n | 'code_mode'\n | 'mcp_app'\n | 'knowledge'\n | 'plain';\n\nexport interface ToolCardContext {\n metadata?: MessageMetadata;\n mcpAppTools?: Record<string, unknown>;\n}\n\nexport interface ToolInteraction {\n approval?: ToolApprovalMetadata;\n suspended?: SuspendedToolMetadata;\n}\n\nconst SANDBOX_TOOLS = new Set<string>([\n WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND,\n WORKSPACE_TOOLS.SANDBOX.GET_PROCESS_OUTPUT,\n WORKSPACE_TOOLS.SANDBOX.KILL_PROCESS,\n]);\n\nconst hasSubmitPlanToolId = (value: unknown): boolean => isRecord(value) && value.toolId === SUBMIT_PLAN_TOOL_ID;\n\nconst isNetworkFrom = (metadata: MessageMetadata | undefined, from: string): boolean =>\n metadata?.mode === 'network' && metadata.from === from;\n\nexport const isAgentCall = (metadata: MessageMetadata | undefined, toolName: string): boolean =>\n isNetworkFrom(metadata, 'AGENT') || toolName.startsWith('agent-');\n\nexport const isWorkflowCall = (metadata: MessageMetadata | undefined, toolName: string): boolean =>\n isNetworkFrom(metadata, 'WORKFLOW') || toolName.startsWith('workflow-');\n\nexport const isSettledState = (state: string | undefined): boolean =>\n state === 'output-available' || state === 'result';\n\n/** A call running as a Mastra background task. An awaited call's part also names its task, but holds the final result. */\nexport const isDeferredCall = (backgroundTaskId: string | undefined, backgroundTaskStatus: string | undefined) =>\n backgroundTaskId !== undefined && backgroundTaskStatus !== 'completed' && backgroundTaskStatus !== 'failed';\n\n/** A call neither settled nor carried by a live run reads as idle, so stale history never shimmers. */\nexport function badgeStatus(state: string | undefined, chatRunning: boolean): ActivityStatus {\n if (state === 'output-error') return 'error';\n if (isSettledState(state) || !chatRunning) return 'idle';\n return 'running';\n}\n\nexport const codeModeCall = (input: unknown, output: unknown) =>\n isRecord(input) || typeof input === 'string' ? getCodeModeCall(input, output) : null;\n\n/** Approval and suspension land keyed by tool name on older runs and by call id on newer ones. */\nexport function toolInteraction(\n metadata: MessageMetadata | undefined,\n toolName: string,\n toolCallId: string,\n): ToolInteraction {\n const approvals = metadata?.requireApprovalMetadata;\n const suspensions = metadata?.suspendedTools;\n const namedApproval = approvals?.[toolName];\n return {\n approval:\n metadata?.mode === 'network'\n ? namedApproval?.toolCallId === toolCallId\n ? namedApproval\n : approvals?.[toolCallId]\n : (approvals?.[toolCallId] ??\n Object.values(approvals ?? {}).find(approval => approval.toolCallId === toolCallId)),\n suspended: suspensions?.[toolName] ?? suspensions?.[toolCallId],\n };\n}\n\n/** Tools the transcript never draws: working memory updates and task tools (shown in the docked task list). */\nexport function isHiddenTool(toolName: string): boolean {\n return toolName === 'updateWorkingMemory' || isTaskTool(toolName);\n}\n\nexport function toolCardKind(\n { toolName, toolCallId, input, output, state, backgroundTaskId, backgroundTaskStatus }: ToolPartFields,\n { metadata, mcpAppTools }: ToolCardContext,\n): ToolCardKind {\n if (toolName === 'mastra-memory-om-observation') return 'observation';\n if (isHiddenTool(toolName)) return 'hidden';\n // A question read back in history draws as a plain badge, but it is still a question: never folded away.\n if (toolName === 'ask_user') return 'ask_user';\n // Built-in `request_connection` tool from `@mastra/connect`: a settled call renders\n // as an interactive connect card; while running it falls back to a plain badge.\n if (toolName === CONNECT_PROMPT_TOOL_NAME && isConnectPromptPayload(output)) return 'connect_prompt';\n const { suspended } = toolInteraction(metadata, toolName, toolCallId);\n if (\n toolName === SUBMIT_PLAN_TOOL_ID ||\n hasSubmitPlanToolId(suspended?.suspendPayload) ||\n hasSubmitPlanToolId(output)\n ) {\n return 'submit_plan';\n }\n // A foreground call parked on an MCP task's question: the run waits for the answer. A deferred\n // call's question lives on its background task instead, which the background card reads.\n if (!isDeferredCall(backgroundTaskId, backgroundTaskStatus) && suspendedMcpInputRequest(metadata, toolCallId)) {\n return 'mcp_input_request';\n }\n if (typeof output === 'string' && output.toLowerCase().includes('background task')) return 'background';\n if (isAgentCall(metadata, toolName)) return 'agent';\n if (isWorkflowCall(metadata, toolName)) return 'workflow';\n if (toolName === WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES) return 'file_tree';\n if (SANDBOX_TOOLS.has(toolName)) return 'sandbox';\n if (codeModeCall(input, output)) return 'code_mode';\n if (mcpAppTools?.[toolName]) return 'mcp_app';\n // A settled or failed Knowledge call draws as a readable card; while running it stays a plain badge.\n if (\n KNOWLEDGE_TOOLS.has(toolName) &&\n (state === 'output-error' || (isSettledState(state) && knowledgeCard(toolName, input, output)))\n ) {\n return 'knowledge';\n }\n return 'plain';\n}\n"],"mappings":";;;;;;;;;;;AAyCA,IAAM,gCAAgB,IAAI,IAAY;CACpCA,oDAAAA,gBAAgB,QAAQ;CACxBA,oDAAAA,gBAAgB,QAAQ;CACxBA,oDAAAA,gBAAgB,QAAQ;AAC1B,CAAC;AAED,IAAM,uBAAuB,UAA4BC,0CAAAA,SAAS,KAAK,KAAK,MAAM,WAAA;AAElF,IAAM,iBAAiB,UAAuC,SAC5D,UAAU,SAAS,aAAa,SAAS,SAAS;AAEpD,IAAa,eAAe,UAAuC,aACjE,cAAc,UAAU,OAAO,KAAK,SAAS,WAAW,QAAQ;AAElE,IAAa,kBAAkB,UAAuC,aACpE,cAAc,UAAU,UAAU,KAAK,SAAS,WAAW,WAAW;AAExE,IAAa,kBAAkB,UAC7B,UAAU,sBAAsB,UAAU;;AAG5C,IAAa,kBAAkB,kBAAsC,yBACnE,qBAAqB,KAAA,KAAa,yBAAyB,eAAe,yBAAyB;;AAGrG,SAAgB,YAAY,OAA2B,aAAsC;CAC3F,IAAI,UAAU,gBAAgB,OAAO;CACrC,IAAI,eAAe,KAAK,KAAK,CAAC,aAAa,OAAO;CAClD,OAAO;AACT;AAEA,IAAa,gBAAgB,OAAgB,WAC3CA,0CAAAA,SAAS,KAAK,KAAK,OAAO,UAAU,WAAWC,qCAAAA,gBAAgB,OAAO,MAAM,IAAI;;AAGlF,SAAgB,gBACd,UACA,UACA,YACiB;CACjB,MAAM,YAAY,UAAU;CAC5B,MAAM,cAAc,UAAU;CAC9B,MAAM,gBAAgB,YAAY;CAClC,OAAO;EACL,UACE,UAAU,SAAS,YACf,eAAe,eAAe,aAC5B,gBACA,YAAY,cACb,YAAY,eACb,OAAO,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,MAAK,aAAY,SAAS,eAAe,UAAU;EACxF,WAAW,cAAc,aAAa,cAAc;CACtD;AACF;;AAGA,SAAgB,aAAa,UAA2B;CACtD,OAAO,aAAa,yBAAyBC,0BAAAA,WAAW,QAAQ;AAClE;AAEA,SAAgB,aACd,EAAE,UAAU,YAAY,OAAO,QAAQ,OAAO,kBAAkB,wBAChE,EAAE,UAAU,eACE;CACd,IAAI,aAAa,gCAAgC,OAAO;CACxD,IAAI,aAAa,QAAQ,GAAG,OAAO;CAEnC,IAAI,aAAa,YAAY,OAAO;CAGpC,IAAI,aAAA,wBAAyCC,8BAAAA,uBAAuB,MAAM,GAAG,OAAO;CACpF,MAAM,EAAE,cAAc,gBAAgB,UAAU,UAAU,UAAU;CACpE,IACE,aAAA,iBACA,oBAAoB,WAAW,cAAc,KAC7C,oBAAoB,MAAM,GAE1B,OAAO;CAIT,IAAI,CAAC,eAAe,kBAAkB,oBAAoB,KAAKC,6CAAAA,yBAAyB,UAAU,UAAU,GAC1G,OAAO;CAET,IAAI,OAAO,WAAW,YAAY,OAAO,YAAY,CAAC,CAAC,SAAS,iBAAiB,GAAG,OAAO;CAC3F,IAAI,YAAY,UAAU,QAAQ,GAAG,OAAO;CAC5C,IAAI,eAAe,UAAU,QAAQ,GAAG,OAAO;CAC/C,IAAI,aAAaL,oDAAAA,gBAAgB,WAAW,YAAY,OAAO;CAC/D,IAAI,cAAc,IAAI,QAAQ,GAAG,OAAO;CACxC,IAAI,aAAa,OAAO,MAAM,GAAG,OAAO;CACxC,IAAI,cAAc,WAAW,OAAO;CAEpC,IACEM,uDAAAA,gBAAgB,IAAI,QAAQ,MAC3B,UAAU,kBAAmB,eAAe,KAAK,KAAKC,uDAAAA,cAAc,UAAU,OAAO,MAAM,IAE5F,OAAO;CAET,OAAO;AACT"}
1
+ {"version":3,"file":"tool-card-kind.cjs.js","names":["WORKSPACE_TOOLS","isRecord","getCodeModeCall","isTaskTool","isConnectPromptPayload","suspendedMcpInputRequest","KNOWLEDGE_TOOLS","knowledgeCard"],"sources":["../../../../src/domains/chat/tools/tool-card-kind.ts"],"sourcesContent":["import type { MessageMetadata, SuspendedToolMetadata, ToolApprovalMetadata } from '../messages/message-metadata';\nimport type { ToolPartFields } from '../messages/renderers/tool-part';\nimport { isRecord } from '../messages/signal-data';\nimport { KNOWLEDGE_TOOLS, knowledgeCard } from './badges/knowledge-tool-badge';\nimport { getCodeModeCall } from './code-mode';\nimport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\nimport { suspendedMcpInputRequest } from './mcp-input-request';\nimport { SUBMIT_PLAN_TOOL_ID } from './submit-plan-tool-id';\nimport { WORKSPACE_TOOLS } from './workspace-tool-constants';\nimport { isConnectPromptPayload } from '@/domains/connect/types';\nimport type { ActivityStatus } from '@/ds/components/ai/activity';\nimport { isTaskTool } from '@/ds/components/ai/tool-call';\n\n/** Which card draws a call. Decided once, shared by the dispatcher and the fold. */\nexport type ToolCardKind =\n | 'hidden'\n | 'observation'\n | 'ask_user'\n | 'connect_prompt'\n | 'submit_plan'\n | 'mcp_input_request'\n | 'background'\n | 'agent'\n | 'workflow'\n | 'file_tree'\n | 'sandbox'\n | 'code_mode'\n | 'mcp_app'\n | 'knowledge'\n | 'plain';\n\nexport interface ToolCardContext {\n metadata?: MessageMetadata;\n mcpAppTools?: Record<string, unknown>;\n}\n\nexport interface ToolInteraction {\n approval?: ToolApprovalMetadata;\n suspended?: SuspendedToolMetadata;\n}\n\nconst SANDBOX_TOOLS = new Set<string>([\n WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND,\n WORKSPACE_TOOLS.SANDBOX.GET_PROCESS_OUTPUT,\n WORKSPACE_TOOLS.SANDBOX.KILL_PROCESS,\n]);\n\nconst hasSubmitPlanToolId = (value: unknown): boolean => isRecord(value) && value.toolId === SUBMIT_PLAN_TOOL_ID;\n\nconst isNetworkFrom = (metadata: MessageMetadata | undefined, from: string): boolean =>\n metadata?.mode === 'network' && metadata.from === from;\n\nexport const isAgentCall = (metadata: MessageMetadata | undefined, toolName: string): boolean =>\n isNetworkFrom(metadata, 'AGENT') || toolName.startsWith('agent-');\n\nexport const isWorkflowCall = (metadata: MessageMetadata | undefined, toolName: string): boolean =>\n isNetworkFrom(metadata, 'WORKFLOW') || toolName.startsWith('workflow-');\n\nexport const isSettledState = (state: string | undefined): boolean =>\n state === 'output-available' || state === 'result';\n\n/** A call running as a Mastra background task. An awaited call's part also names its task, but holds the final result. */\nexport const isDeferredCall = (backgroundTaskId: string | undefined, backgroundTaskStatus: string | undefined) =>\n backgroundTaskId !== undefined && backgroundTaskStatus !== 'completed' && backgroundTaskStatus !== 'failed';\n\n/** A call neither settled nor carried by a live run reads as idle, so stale history never shimmers. */\nexport function badgeStatus(state: string | undefined, chatRunning: boolean): ActivityStatus {\n if (state === 'output-error') return 'error';\n if (isSettledState(state) || !chatRunning) return 'idle';\n return 'running';\n}\n\nexport const codeModeCall = (input: unknown, output: unknown) =>\n isRecord(input) || typeof input === 'string' ? getCodeModeCall(input, output) : null;\n\n/** Approval and suspension land keyed by tool name on older runs and by call id on newer ones. */\nexport function toolInteraction(\n metadata: MessageMetadata | undefined,\n toolName: string,\n toolCallId: string,\n): ToolInteraction {\n const approvals = metadata?.requireApprovalMetadata;\n const suspensions = metadata?.suspendedTools;\n const namedApproval = approvals?.[toolName];\n return {\n approval:\n metadata?.mode === 'network'\n ? namedApproval?.toolCallId === toolCallId\n ? namedApproval\n : approvals?.[toolCallId]\n : (approvals?.[toolCallId] ??\n Object.values(approvals ?? {}).find(approval => approval.toolCallId === toolCallId)),\n suspended: suspensions?.[toolName] ?? suspensions?.[toolCallId],\n };\n}\n\n/** Tools the transcript never draws: working memory updates and task tools (shown in the docked task list). */\nexport function isHiddenTool(toolName: string): boolean {\n return toolName === 'updateWorkingMemory' || isTaskTool(toolName);\n}\n\nexport function toolCardKind(\n { toolName, toolCallId, input, output, state, backgroundTaskId, backgroundTaskStatus }: ToolPartFields,\n { metadata, mcpAppTools }: ToolCardContext,\n): ToolCardKind {\n if (toolName === 'mastra-memory-om-observation') return 'observation';\n if (isHiddenTool(toolName)) return 'hidden';\n // A question read back in history draws as a plain badge, but it is still a question: never folded away.\n if (toolName === 'ask_user') return 'ask_user';\n // Built-in `request_connection` tool from `@mastra/connect`: the run pauses on it with\n // the prompt as its suspend payload, and the settled call keeps it as its result. Both\n // render the connect card; a call with neither falls back to a plain badge.\n const { suspended } = toolInteraction(metadata, toolName, toolCallId);\n if (\n toolName === CONNECT_PROMPT_TOOL_NAME &&\n (isConnectPromptPayload(output) || isConnectPromptPayload(suspended?.suspendPayload))\n ) {\n return 'connect_prompt';\n }\n if (\n toolName === SUBMIT_PLAN_TOOL_ID ||\n hasSubmitPlanToolId(suspended?.suspendPayload) ||\n hasSubmitPlanToolId(output)\n ) {\n return 'submit_plan';\n }\n // A foreground call parked on an MCP task's question: the run waits for the answer. A deferred\n // call's question lives on its background task instead, which the background card reads.\n if (!isDeferredCall(backgroundTaskId, backgroundTaskStatus) && suspendedMcpInputRequest(metadata, toolCallId)) {\n return 'mcp_input_request';\n }\n if (typeof output === 'string' && output.toLowerCase().includes('background task')) return 'background';\n if (isAgentCall(metadata, toolName)) return 'agent';\n if (isWorkflowCall(metadata, toolName)) return 'workflow';\n if (toolName === WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES) return 'file_tree';\n if (SANDBOX_TOOLS.has(toolName)) return 'sandbox';\n if (codeModeCall(input, output)) return 'code_mode';\n if (mcpAppTools?.[toolName]) return 'mcp_app';\n // A settled or failed Knowledge call draws as a readable card; while running it stays a plain badge.\n if (\n KNOWLEDGE_TOOLS.has(toolName) &&\n (state === 'output-error' || (isSettledState(state) && knowledgeCard(toolName, input, output)))\n ) {\n return 'knowledge';\n }\n return 'plain';\n}\n"],"mappings":";;;;;;;;;;;AAyCA,IAAM,gCAAgB,IAAI,IAAY;CACpCA,oDAAAA,gBAAgB,QAAQ;CACxBA,oDAAAA,gBAAgB,QAAQ;CACxBA,oDAAAA,gBAAgB,QAAQ;AAC1B,CAAC;AAED,IAAM,uBAAuB,UAA4BC,0CAAAA,SAAS,KAAK,KAAK,MAAM,WAAA;AAElF,IAAM,iBAAiB,UAAuC,SAC5D,UAAU,SAAS,aAAa,SAAS,SAAS;AAEpD,IAAa,eAAe,UAAuC,aACjE,cAAc,UAAU,OAAO,KAAK,SAAS,WAAW,QAAQ;AAElE,IAAa,kBAAkB,UAAuC,aACpE,cAAc,UAAU,UAAU,KAAK,SAAS,WAAW,WAAW;AAExE,IAAa,kBAAkB,UAC7B,UAAU,sBAAsB,UAAU;;AAG5C,IAAa,kBAAkB,kBAAsC,yBACnE,qBAAqB,KAAA,KAAa,yBAAyB,eAAe,yBAAyB;;AAGrG,SAAgB,YAAY,OAA2B,aAAsC;CAC3F,IAAI,UAAU,gBAAgB,OAAO;CACrC,IAAI,eAAe,KAAK,KAAK,CAAC,aAAa,OAAO;CAClD,OAAO;AACT;AAEA,IAAa,gBAAgB,OAAgB,WAC3CA,0CAAAA,SAAS,KAAK,KAAK,OAAO,UAAU,WAAWC,qCAAAA,gBAAgB,OAAO,MAAM,IAAI;;AAGlF,SAAgB,gBACd,UACA,UACA,YACiB;CACjB,MAAM,YAAY,UAAU;CAC5B,MAAM,cAAc,UAAU;CAC9B,MAAM,gBAAgB,YAAY;CAClC,OAAO;EACL,UACE,UAAU,SAAS,YACf,eAAe,eAAe,aAC5B,gBACA,YAAY,cACb,YAAY,eACb,OAAO,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,MAAK,aAAY,SAAS,eAAe,UAAU;EACxF,WAAW,cAAc,aAAa,cAAc;CACtD;AACF;;AAGA,SAAgB,aAAa,UAA2B;CACtD,OAAO,aAAa,yBAAyBC,0BAAAA,WAAW,QAAQ;AAClE;AAEA,SAAgB,aACd,EAAE,UAAU,YAAY,OAAO,QAAQ,OAAO,kBAAkB,wBAChE,EAAE,UAAU,eACE;CACd,IAAI,aAAa,gCAAgC,OAAO;CACxD,IAAI,aAAa,QAAQ,GAAG,OAAO;CAEnC,IAAI,aAAa,YAAY,OAAO;CAIpC,MAAM,EAAE,cAAc,gBAAgB,UAAU,UAAU,UAAU;CACpE,IACE,aAAA,yBACCC,8BAAAA,uBAAuB,MAAM,KAAKA,8BAAAA,uBAAuB,WAAW,cAAc,IAEnF,OAAO;CAET,IACE,aAAA,iBACA,oBAAoB,WAAW,cAAc,KAC7C,oBAAoB,MAAM,GAE1B,OAAO;CAIT,IAAI,CAAC,eAAe,kBAAkB,oBAAoB,KAAKC,6CAAAA,yBAAyB,UAAU,UAAU,GAC1G,OAAO;CAET,IAAI,OAAO,WAAW,YAAY,OAAO,YAAY,CAAC,CAAC,SAAS,iBAAiB,GAAG,OAAO;CAC3F,IAAI,YAAY,UAAU,QAAQ,GAAG,OAAO;CAC5C,IAAI,eAAe,UAAU,QAAQ,GAAG,OAAO;CAC/C,IAAI,aAAaL,oDAAAA,gBAAgB,WAAW,YAAY,OAAO;CAC/D,IAAI,cAAc,IAAI,QAAQ,GAAG,OAAO;CACxC,IAAI,aAAa,OAAO,MAAM,GAAG,OAAO;CACxC,IAAI,cAAc,WAAW,OAAO;CAEpC,IACEM,uDAAAA,gBAAgB,IAAI,QAAQ,MAC3B,UAAU,kBAAmB,eAAe,KAAK,KAAKC,uDAAAA,cAAc,UAAU,OAAO,MAAM,IAE5F,OAAO;CAET,OAAO;AACT"}
@@ -45,8 +45,8 @@ function toolCardKind({ toolName, toolCallId, input, output, state, backgroundTa
45
45
  if (toolName === "mastra-memory-om-observation") return "observation";
46
46
  if (isHiddenTool(toolName)) return "hidden";
47
47
  if (toolName === "ask_user") return "ask_user";
48
- if (toolName === "request_connection" && isConnectPromptPayload(output)) return "connect_prompt";
49
48
  const { suspended } = toolInteraction(metadata, toolName, toolCallId);
49
+ if (toolName === "request_connection" && (isConnectPromptPayload(output) || isConnectPromptPayload(suspended?.suspendPayload))) return "connect_prompt";
50
50
  if (toolName === "submit_plan" || hasSubmitPlanToolId(suspended?.suspendPayload) || hasSubmitPlanToolId(output)) return "submit_plan";
51
51
  if (!isDeferredCall(backgroundTaskId, backgroundTaskStatus) && suspendedMcpInputRequest(metadata, toolCallId)) return "mcp_input_request";
52
52
  if (typeof output === "string" && output.toLowerCase().includes("background task")) return "background";
@@ -1 +1 @@
1
- {"version":3,"file":"tool-card-kind.es.js","names":[],"sources":["../../../../src/domains/chat/tools/tool-card-kind.ts"],"sourcesContent":["import type { MessageMetadata, SuspendedToolMetadata, ToolApprovalMetadata } from '../messages/message-metadata';\nimport type { ToolPartFields } from '../messages/renderers/tool-part';\nimport { isRecord } from '../messages/signal-data';\nimport { KNOWLEDGE_TOOLS, knowledgeCard } from './badges/knowledge-tool-badge';\nimport { getCodeModeCall } from './code-mode';\nimport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\nimport { suspendedMcpInputRequest } from './mcp-input-request';\nimport { SUBMIT_PLAN_TOOL_ID } from './submit-plan-tool-id';\nimport { WORKSPACE_TOOLS } from './workspace-tool-constants';\nimport { isConnectPromptPayload } from '@/domains/connect/types';\nimport type { ActivityStatus } from '@/ds/components/ai/activity';\nimport { isTaskTool } from '@/ds/components/ai/tool-call';\n\n/** Which card draws a call. Decided once, shared by the dispatcher and the fold. */\nexport type ToolCardKind =\n | 'hidden'\n | 'observation'\n | 'ask_user'\n | 'connect_prompt'\n | 'submit_plan'\n | 'mcp_input_request'\n | 'background'\n | 'agent'\n | 'workflow'\n | 'file_tree'\n | 'sandbox'\n | 'code_mode'\n | 'mcp_app'\n | 'knowledge'\n | 'plain';\n\nexport interface ToolCardContext {\n metadata?: MessageMetadata;\n mcpAppTools?: Record<string, unknown>;\n}\n\nexport interface ToolInteraction {\n approval?: ToolApprovalMetadata;\n suspended?: SuspendedToolMetadata;\n}\n\nconst SANDBOX_TOOLS = new Set<string>([\n WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND,\n WORKSPACE_TOOLS.SANDBOX.GET_PROCESS_OUTPUT,\n WORKSPACE_TOOLS.SANDBOX.KILL_PROCESS,\n]);\n\nconst hasSubmitPlanToolId = (value: unknown): boolean => isRecord(value) && value.toolId === SUBMIT_PLAN_TOOL_ID;\n\nconst isNetworkFrom = (metadata: MessageMetadata | undefined, from: string): boolean =>\n metadata?.mode === 'network' && metadata.from === from;\n\nexport const isAgentCall = (metadata: MessageMetadata | undefined, toolName: string): boolean =>\n isNetworkFrom(metadata, 'AGENT') || toolName.startsWith('agent-');\n\nexport const isWorkflowCall = (metadata: MessageMetadata | undefined, toolName: string): boolean =>\n isNetworkFrom(metadata, 'WORKFLOW') || toolName.startsWith('workflow-');\n\nexport const isSettledState = (state: string | undefined): boolean =>\n state === 'output-available' || state === 'result';\n\n/** A call running as a Mastra background task. An awaited call's part also names its task, but holds the final result. */\nexport const isDeferredCall = (backgroundTaskId: string | undefined, backgroundTaskStatus: string | undefined) =>\n backgroundTaskId !== undefined && backgroundTaskStatus !== 'completed' && backgroundTaskStatus !== 'failed';\n\n/** A call neither settled nor carried by a live run reads as idle, so stale history never shimmers. */\nexport function badgeStatus(state: string | undefined, chatRunning: boolean): ActivityStatus {\n if (state === 'output-error') return 'error';\n if (isSettledState(state) || !chatRunning) return 'idle';\n return 'running';\n}\n\nexport const codeModeCall = (input: unknown, output: unknown) =>\n isRecord(input) || typeof input === 'string' ? getCodeModeCall(input, output) : null;\n\n/** Approval and suspension land keyed by tool name on older runs and by call id on newer ones. */\nexport function toolInteraction(\n metadata: MessageMetadata | undefined,\n toolName: string,\n toolCallId: string,\n): ToolInteraction {\n const approvals = metadata?.requireApprovalMetadata;\n const suspensions = metadata?.suspendedTools;\n const namedApproval = approvals?.[toolName];\n return {\n approval:\n metadata?.mode === 'network'\n ? namedApproval?.toolCallId === toolCallId\n ? namedApproval\n : approvals?.[toolCallId]\n : (approvals?.[toolCallId] ??\n Object.values(approvals ?? {}).find(approval => approval.toolCallId === toolCallId)),\n suspended: suspensions?.[toolName] ?? suspensions?.[toolCallId],\n };\n}\n\n/** Tools the transcript never draws: working memory updates and task tools (shown in the docked task list). */\nexport function isHiddenTool(toolName: string): boolean {\n return toolName === 'updateWorkingMemory' || isTaskTool(toolName);\n}\n\nexport function toolCardKind(\n { toolName, toolCallId, input, output, state, backgroundTaskId, backgroundTaskStatus }: ToolPartFields,\n { metadata, mcpAppTools }: ToolCardContext,\n): ToolCardKind {\n if (toolName === 'mastra-memory-om-observation') return 'observation';\n if (isHiddenTool(toolName)) return 'hidden';\n // A question read back in history draws as a plain badge, but it is still a question: never folded away.\n if (toolName === 'ask_user') return 'ask_user';\n // Built-in `request_connection` tool from `@mastra/connect`: a settled call renders\n // as an interactive connect card; while running it falls back to a plain badge.\n if (toolName === CONNECT_PROMPT_TOOL_NAME && isConnectPromptPayload(output)) return 'connect_prompt';\n const { suspended } = toolInteraction(metadata, toolName, toolCallId);\n if (\n toolName === SUBMIT_PLAN_TOOL_ID ||\n hasSubmitPlanToolId(suspended?.suspendPayload) ||\n hasSubmitPlanToolId(output)\n ) {\n return 'submit_plan';\n }\n // A foreground call parked on an MCP task's question: the run waits for the answer. A deferred\n // call's question lives on its background task instead, which the background card reads.\n if (!isDeferredCall(backgroundTaskId, backgroundTaskStatus) && suspendedMcpInputRequest(metadata, toolCallId)) {\n return 'mcp_input_request';\n }\n if (typeof output === 'string' && output.toLowerCase().includes('background task')) return 'background';\n if (isAgentCall(metadata, toolName)) return 'agent';\n if (isWorkflowCall(metadata, toolName)) return 'workflow';\n if (toolName === WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES) return 'file_tree';\n if (SANDBOX_TOOLS.has(toolName)) return 'sandbox';\n if (codeModeCall(input, output)) return 'code_mode';\n if (mcpAppTools?.[toolName]) return 'mcp_app';\n // A settled or failed Knowledge call draws as a readable card; while running it stays a plain badge.\n if (\n KNOWLEDGE_TOOLS.has(toolName) &&\n (state === 'output-error' || (isSettledState(state) && knowledgeCard(toolName, input, output)))\n ) {\n return 'knowledge';\n }\n return 'plain';\n}\n"],"mappings":";;;;;;;;;;AAyCA,IAAM,gCAAgB,IAAI,IAAY;CACpC,gBAAgB,QAAQ;CACxB,gBAAgB,QAAQ;CACxB,gBAAgB,QAAQ;AAC1B,CAAC;AAED,IAAM,uBAAuB,UAA4B,SAAS,KAAK,KAAK,MAAM,WAAA;AAElF,IAAM,iBAAiB,UAAuC,SAC5D,UAAU,SAAS,aAAa,SAAS,SAAS;AAEpD,IAAa,eAAe,UAAuC,aACjE,cAAc,UAAU,OAAO,KAAK,SAAS,WAAW,QAAQ;AAElE,IAAa,kBAAkB,UAAuC,aACpE,cAAc,UAAU,UAAU,KAAK,SAAS,WAAW,WAAW;AAExE,IAAa,kBAAkB,UAC7B,UAAU,sBAAsB,UAAU;;AAG5C,IAAa,kBAAkB,kBAAsC,yBACnE,qBAAqB,KAAA,KAAa,yBAAyB,eAAe,yBAAyB;;AAGrG,SAAgB,YAAY,OAA2B,aAAsC;CAC3F,IAAI,UAAU,gBAAgB,OAAO;CACrC,IAAI,eAAe,KAAK,KAAK,CAAC,aAAa,OAAO;CAClD,OAAO;AACT;AAEA,IAAa,gBAAgB,OAAgB,WAC3C,SAAS,KAAK,KAAK,OAAO,UAAU,WAAW,gBAAgB,OAAO,MAAM,IAAI;;AAGlF,SAAgB,gBACd,UACA,UACA,YACiB;CACjB,MAAM,YAAY,UAAU;CAC5B,MAAM,cAAc,UAAU;CAC9B,MAAM,gBAAgB,YAAY;CAClC,OAAO;EACL,UACE,UAAU,SAAS,YACf,eAAe,eAAe,aAC5B,gBACA,YAAY,cACb,YAAY,eACb,OAAO,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,MAAK,aAAY,SAAS,eAAe,UAAU;EACxF,WAAW,cAAc,aAAa,cAAc;CACtD;AACF;;AAGA,SAAgB,aAAa,UAA2B;CACtD,OAAO,aAAa,yBAAyB,WAAW,QAAQ;AAClE;AAEA,SAAgB,aACd,EAAE,UAAU,YAAY,OAAO,QAAQ,OAAO,kBAAkB,wBAChE,EAAE,UAAU,eACE;CACd,IAAI,aAAa,gCAAgC,OAAO;CACxD,IAAI,aAAa,QAAQ,GAAG,OAAO;CAEnC,IAAI,aAAa,YAAY,OAAO;CAGpC,IAAI,aAAA,wBAAyC,uBAAuB,MAAM,GAAG,OAAO;CACpF,MAAM,EAAE,cAAc,gBAAgB,UAAU,UAAU,UAAU;CACpE,IACE,aAAA,iBACA,oBAAoB,WAAW,cAAc,KAC7C,oBAAoB,MAAM,GAE1B,OAAO;CAIT,IAAI,CAAC,eAAe,kBAAkB,oBAAoB,KAAK,yBAAyB,UAAU,UAAU,GAC1G,OAAO;CAET,IAAI,OAAO,WAAW,YAAY,OAAO,YAAY,CAAC,CAAC,SAAS,iBAAiB,GAAG,OAAO;CAC3F,IAAI,YAAY,UAAU,QAAQ,GAAG,OAAO;CAC5C,IAAI,eAAe,UAAU,QAAQ,GAAG,OAAO;CAC/C,IAAI,aAAa,gBAAgB,WAAW,YAAY,OAAO;CAC/D,IAAI,cAAc,IAAI,QAAQ,GAAG,OAAO;CACxC,IAAI,aAAa,OAAO,MAAM,GAAG,OAAO;CACxC,IAAI,cAAc,WAAW,OAAO;CAEpC,IACE,gBAAgB,IAAI,QAAQ,MAC3B,UAAU,kBAAmB,eAAe,KAAK,KAAK,cAAc,UAAU,OAAO,MAAM,IAE5F,OAAO;CAET,OAAO;AACT"}
1
+ {"version":3,"file":"tool-card-kind.es.js","names":[],"sources":["../../../../src/domains/chat/tools/tool-card-kind.ts"],"sourcesContent":["import type { MessageMetadata, SuspendedToolMetadata, ToolApprovalMetadata } from '../messages/message-metadata';\nimport type { ToolPartFields } from '../messages/renderers/tool-part';\nimport { isRecord } from '../messages/signal-data';\nimport { KNOWLEDGE_TOOLS, knowledgeCard } from './badges/knowledge-tool-badge';\nimport { getCodeModeCall } from './code-mode';\nimport { CONNECT_PROMPT_TOOL_NAME } from './connect-prompt-tool-id';\nimport { suspendedMcpInputRequest } from './mcp-input-request';\nimport { SUBMIT_PLAN_TOOL_ID } from './submit-plan-tool-id';\nimport { WORKSPACE_TOOLS } from './workspace-tool-constants';\nimport { isConnectPromptPayload } from '@/domains/connect/types';\nimport type { ActivityStatus } from '@/ds/components/ai/activity';\nimport { isTaskTool } from '@/ds/components/ai/tool-call';\n\n/** Which card draws a call. Decided once, shared by the dispatcher and the fold. */\nexport type ToolCardKind =\n | 'hidden'\n | 'observation'\n | 'ask_user'\n | 'connect_prompt'\n | 'submit_plan'\n | 'mcp_input_request'\n | 'background'\n | 'agent'\n | 'workflow'\n | 'file_tree'\n | 'sandbox'\n | 'code_mode'\n | 'mcp_app'\n | 'knowledge'\n | 'plain';\n\nexport interface ToolCardContext {\n metadata?: MessageMetadata;\n mcpAppTools?: Record<string, unknown>;\n}\n\nexport interface ToolInteraction {\n approval?: ToolApprovalMetadata;\n suspended?: SuspendedToolMetadata;\n}\n\nconst SANDBOX_TOOLS = new Set<string>([\n WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND,\n WORKSPACE_TOOLS.SANDBOX.GET_PROCESS_OUTPUT,\n WORKSPACE_TOOLS.SANDBOX.KILL_PROCESS,\n]);\n\nconst hasSubmitPlanToolId = (value: unknown): boolean => isRecord(value) && value.toolId === SUBMIT_PLAN_TOOL_ID;\n\nconst isNetworkFrom = (metadata: MessageMetadata | undefined, from: string): boolean =>\n metadata?.mode === 'network' && metadata.from === from;\n\nexport const isAgentCall = (metadata: MessageMetadata | undefined, toolName: string): boolean =>\n isNetworkFrom(metadata, 'AGENT') || toolName.startsWith('agent-');\n\nexport const isWorkflowCall = (metadata: MessageMetadata | undefined, toolName: string): boolean =>\n isNetworkFrom(metadata, 'WORKFLOW') || toolName.startsWith('workflow-');\n\nexport const isSettledState = (state: string | undefined): boolean =>\n state === 'output-available' || state === 'result';\n\n/** A call running as a Mastra background task. An awaited call's part also names its task, but holds the final result. */\nexport const isDeferredCall = (backgroundTaskId: string | undefined, backgroundTaskStatus: string | undefined) =>\n backgroundTaskId !== undefined && backgroundTaskStatus !== 'completed' && backgroundTaskStatus !== 'failed';\n\n/** A call neither settled nor carried by a live run reads as idle, so stale history never shimmers. */\nexport function badgeStatus(state: string | undefined, chatRunning: boolean): ActivityStatus {\n if (state === 'output-error') return 'error';\n if (isSettledState(state) || !chatRunning) return 'idle';\n return 'running';\n}\n\nexport const codeModeCall = (input: unknown, output: unknown) =>\n isRecord(input) || typeof input === 'string' ? getCodeModeCall(input, output) : null;\n\n/** Approval and suspension land keyed by tool name on older runs and by call id on newer ones. */\nexport function toolInteraction(\n metadata: MessageMetadata | undefined,\n toolName: string,\n toolCallId: string,\n): ToolInteraction {\n const approvals = metadata?.requireApprovalMetadata;\n const suspensions = metadata?.suspendedTools;\n const namedApproval = approvals?.[toolName];\n return {\n approval:\n metadata?.mode === 'network'\n ? namedApproval?.toolCallId === toolCallId\n ? namedApproval\n : approvals?.[toolCallId]\n : (approvals?.[toolCallId] ??\n Object.values(approvals ?? {}).find(approval => approval.toolCallId === toolCallId)),\n suspended: suspensions?.[toolName] ?? suspensions?.[toolCallId],\n };\n}\n\n/** Tools the transcript never draws: working memory updates and task tools (shown in the docked task list). */\nexport function isHiddenTool(toolName: string): boolean {\n return toolName === 'updateWorkingMemory' || isTaskTool(toolName);\n}\n\nexport function toolCardKind(\n { toolName, toolCallId, input, output, state, backgroundTaskId, backgroundTaskStatus }: ToolPartFields,\n { metadata, mcpAppTools }: ToolCardContext,\n): ToolCardKind {\n if (toolName === 'mastra-memory-om-observation') return 'observation';\n if (isHiddenTool(toolName)) return 'hidden';\n // A question read back in history draws as a plain badge, but it is still a question: never folded away.\n if (toolName === 'ask_user') return 'ask_user';\n // Built-in `request_connection` tool from `@mastra/connect`: the run pauses on it with\n // the prompt as its suspend payload, and the settled call keeps it as its result. Both\n // render the connect card; a call with neither falls back to a plain badge.\n const { suspended } = toolInteraction(metadata, toolName, toolCallId);\n if (\n toolName === CONNECT_PROMPT_TOOL_NAME &&\n (isConnectPromptPayload(output) || isConnectPromptPayload(suspended?.suspendPayload))\n ) {\n return 'connect_prompt';\n }\n if (\n toolName === SUBMIT_PLAN_TOOL_ID ||\n hasSubmitPlanToolId(suspended?.suspendPayload) ||\n hasSubmitPlanToolId(output)\n ) {\n return 'submit_plan';\n }\n // A foreground call parked on an MCP task's question: the run waits for the answer. A deferred\n // call's question lives on its background task instead, which the background card reads.\n if (!isDeferredCall(backgroundTaskId, backgroundTaskStatus) && suspendedMcpInputRequest(metadata, toolCallId)) {\n return 'mcp_input_request';\n }\n if (typeof output === 'string' && output.toLowerCase().includes('background task')) return 'background';\n if (isAgentCall(metadata, toolName)) return 'agent';\n if (isWorkflowCall(metadata, toolName)) return 'workflow';\n if (toolName === WORKSPACE_TOOLS.FILESYSTEM.LIST_FILES) return 'file_tree';\n if (SANDBOX_TOOLS.has(toolName)) return 'sandbox';\n if (codeModeCall(input, output)) return 'code_mode';\n if (mcpAppTools?.[toolName]) return 'mcp_app';\n // A settled or failed Knowledge call draws as a readable card; while running it stays a plain badge.\n if (\n KNOWLEDGE_TOOLS.has(toolName) &&\n (state === 'output-error' || (isSettledState(state) && knowledgeCard(toolName, input, output)))\n ) {\n return 'knowledge';\n }\n return 'plain';\n}\n"],"mappings":";;;;;;;;;;AAyCA,IAAM,gCAAgB,IAAI,IAAY;CACpC,gBAAgB,QAAQ;CACxB,gBAAgB,QAAQ;CACxB,gBAAgB,QAAQ;AAC1B,CAAC;AAED,IAAM,uBAAuB,UAA4B,SAAS,KAAK,KAAK,MAAM,WAAA;AAElF,IAAM,iBAAiB,UAAuC,SAC5D,UAAU,SAAS,aAAa,SAAS,SAAS;AAEpD,IAAa,eAAe,UAAuC,aACjE,cAAc,UAAU,OAAO,KAAK,SAAS,WAAW,QAAQ;AAElE,IAAa,kBAAkB,UAAuC,aACpE,cAAc,UAAU,UAAU,KAAK,SAAS,WAAW,WAAW;AAExE,IAAa,kBAAkB,UAC7B,UAAU,sBAAsB,UAAU;;AAG5C,IAAa,kBAAkB,kBAAsC,yBACnE,qBAAqB,KAAA,KAAa,yBAAyB,eAAe,yBAAyB;;AAGrG,SAAgB,YAAY,OAA2B,aAAsC;CAC3F,IAAI,UAAU,gBAAgB,OAAO;CACrC,IAAI,eAAe,KAAK,KAAK,CAAC,aAAa,OAAO;CAClD,OAAO;AACT;AAEA,IAAa,gBAAgB,OAAgB,WAC3C,SAAS,KAAK,KAAK,OAAO,UAAU,WAAW,gBAAgB,OAAO,MAAM,IAAI;;AAGlF,SAAgB,gBACd,UACA,UACA,YACiB;CACjB,MAAM,YAAY,UAAU;CAC5B,MAAM,cAAc,UAAU;CAC9B,MAAM,gBAAgB,YAAY;CAClC,OAAO;EACL,UACE,UAAU,SAAS,YACf,eAAe,eAAe,aAC5B,gBACA,YAAY,cACb,YAAY,eACb,OAAO,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,MAAK,aAAY,SAAS,eAAe,UAAU;EACxF,WAAW,cAAc,aAAa,cAAc;CACtD;AACF;;AAGA,SAAgB,aAAa,UAA2B;CACtD,OAAO,aAAa,yBAAyB,WAAW,QAAQ;AAClE;AAEA,SAAgB,aACd,EAAE,UAAU,YAAY,OAAO,QAAQ,OAAO,kBAAkB,wBAChE,EAAE,UAAU,eACE;CACd,IAAI,aAAa,gCAAgC,OAAO;CACxD,IAAI,aAAa,QAAQ,GAAG,OAAO;CAEnC,IAAI,aAAa,YAAY,OAAO;CAIpC,MAAM,EAAE,cAAc,gBAAgB,UAAU,UAAU,UAAU;CACpE,IACE,aAAA,yBACC,uBAAuB,MAAM,KAAK,uBAAuB,WAAW,cAAc,IAEnF,OAAO;CAET,IACE,aAAA,iBACA,oBAAoB,WAAW,cAAc,KAC7C,oBAAoB,MAAM,GAE1B,OAAO;CAIT,IAAI,CAAC,eAAe,kBAAkB,oBAAoB,KAAK,yBAAyB,UAAU,UAAU,GAC1G,OAAO;CAET,IAAI,OAAO,WAAW,YAAY,OAAO,YAAY,CAAC,CAAC,SAAS,iBAAiB,GAAG,OAAO;CAC3F,IAAI,YAAY,UAAU,QAAQ,GAAG,OAAO;CAC5C,IAAI,eAAe,UAAU,QAAQ,GAAG,OAAO;CAC/C,IAAI,aAAa,gBAAgB,WAAW,YAAY,OAAO;CAC/D,IAAI,cAAc,IAAI,QAAQ,GAAG,OAAO;CACxC,IAAI,aAAa,OAAO,MAAM,GAAG,OAAO;CACxC,IAAI,cAAc,WAAW,OAAO;CAEpC,IACE,gBAAgB,IAAI,QAAQ,MAC3B,UAAU,kBAAmB,eAAe,KAAK,KAAK,cAAc,UAAU,OAAO,MAAM,IAE5F,OAAO;CAET,OAAO;AACT"}
@@ -1,4 +1,4 @@
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.ToolCard = require_tool_card.ToolCard;
4
4
  exports.ToolCardInner = require_tool_card.ToolCardInner;
@@ -1 +1 @@
1
- export { t as ToolCard, n as ToolCardInner } from "../../../tool-card-toDgoZzJ.js";
1
+ export { t as ToolCard, n as ToolCardInner } from "../../../tool-card-HZ5bm7ue.js";
@@ -1 +1 @@
1
- {"version":3,"file":"types.cjs.js","names":[],"sources":["../../../src/domains/connect/types.ts"],"sourcesContent":["/**\n * Wire contract of the end-user connect surface that `userConnectRoutes()`\n * from `@mastra/connect` mounts on the dev server (default base path\n * `/connect`). These routes are custom `apiRoutes`, so they are not part of\n * the `@mastra/client-js` SDK — the shapes below mirror\n * `packages/connect/src/user-connect-routes.ts` and must stay in sync with it.\n */\n\nexport interface UserConnectAuthField {\n name: string;\n /**\n * Where the value goes in a headless auth call: `credentials` are\n * exchanged with the provider, `params` configure the flow (e.g. an MCP\n * server URL). Missing (older servers) is treated as `credentials`.\n */\n target?: 'credentials' | 'params' | null;\n label?: string | null;\n description?: string | null;\n placeholder?: string | null;\n secret?: boolean | null;\n required?: boolean | null;\n order?: number | null;\n}\n\n/** JSON redemption of the launch gate (`Accept: application/json`). */\nexport interface UserConnectLaunchTokenResponse {\n /**\n * Nango connect session token for headless authorization, or null when the\n * server could not extract one — the client then falls back to the hosted\n * popup through the same gate's 302 flow.\n */\n sessionToken: string | null;\n expiresAt: string;\n}\n\nexport interface UserConnectProvider {\n id: string;\n displayName: string | null;\n logoUrl: string | null;\n authType: string | null;\n authFields: UserConnectAuthField[] | null;\n}\n\nexport interface UserConnectProvidersResponse {\n providers: UserConnectProvider[];\n}\n\nexport type UserConnectionScope = 'global' | 'user';\n\nexport interface UserConnection {\n id: string;\n integrationId: string;\n /**\n * Display metadata resolved server-side against the FULL platform catalog\n * (not the policy-filtered provider list), so rows for de-allowlisted\n * providers still render properly. Null when the catalog has no entry or\n * the lookup failed.\n */\n displayName: string | null;\n logoUrl: string | null;\n status: string;\n accountLabel: string | null;\n scope: UserConnectionScope;\n}\n\nexport interface UserConnectionsResponse {\n connections: UserConnection[];\n}\n\nexport interface UserConnectSessionResponse {\n connectionId: string;\n integrationId: string;\n /**\n * Server-relative launch-gate path (`{basePath}/connections/:id/launch`).\n * The popup navigates here — same-origin with the Mastra server and\n * authenticated — and the server verifies this browser owns the pending\n * session before 302ing into the vendor OAuth flow. The raw vendor connect\n * URL is never exposed to the browser.\n */\n launchUrl: string;\n expiresAt: string | null;\n}\n\nexport interface UserConnectionStatusResponse {\n id: string;\n status: string;\n scope: UserConnectionScope;\n}\n\nexport interface UserConnectErrorBody {\n error: { code: string; message: string };\n}\n\n/**\n * One provider in the structured output of the built-in `request_connection`\n * tool (`@mastra/connect`'s `ConnectPromptProvider`). Chat renders these as a\n * connect card.\n */\nexport interface ConnectPromptProvider {\n id: string;\n displayName: string | null;\n logoUrl: string | null;\n authType: string | null;\n /** Fields the UI must collect before starting headless authorization. */\n authFields: UserConnectAuthField[] | null;\n connected: boolean;\n scope: UserConnectionScope | null;\n}\n\n/** Structured output of the built-in `request_connection` tool. */\nexport interface ConnectPromptPayload {\n type: 'connect-prompt';\n requestedProvider?: string;\n providers: ConnectPromptProvider[];\n}\n\n/** Narrows an arbitrary tool output to a connect-prompt payload. */\nexport function isConnectPromptPayload(output: unknown): output is ConnectPromptPayload {\n return (\n typeof output === 'object' &&\n output !== null &&\n (output as { type?: unknown }).type === 'connect-prompt' &&\n Array.isArray((output as { providers?: unknown }).providers)\n );\n}\n"],"mappings":";;;AAqHA,SAAgB,uBAAuB,QAAiD;CACtF,OACE,OAAO,WAAW,YAClB,WAAW,QACV,OAA8B,SAAS,oBACxC,MAAM,QAAS,OAAmC,SAAS;AAE/D"}
1
+ {"version":3,"file":"types.cjs.js","names":[],"sources":["../../../src/domains/connect/types.ts"],"sourcesContent":["/**\n * Wire contract of the end-user connect surface that `userConnectRoutes()`\n * from `@mastra/connect` mounts on the dev server (default base path\n * `/connect`). These routes are custom `apiRoutes`, so they are not part of\n * the `@mastra/client-js` SDK — the shapes below mirror\n * `packages/connect/src/user-connect-routes.ts` and must stay in sync with it.\n */\n\nexport interface UserConnectAuthField {\n name: string;\n /**\n * Where the value goes in a headless auth call: `credentials` are\n * exchanged with the provider, `params` configure the flow (e.g. an MCP\n * server URL). Missing (older servers) is treated as `credentials`.\n */\n target?: 'credentials' | 'params' | null;\n label?: string | null;\n description?: string | null;\n placeholder?: string | null;\n secret?: boolean | null;\n required?: boolean | null;\n order?: number | null;\n}\n\n/** JSON redemption of the launch gate (`Accept: application/json`). */\nexport interface UserConnectLaunchTokenResponse {\n /**\n * Nango connect session token for headless authorization, or null when the\n * server could not extract one — the client then falls back to the hosted\n * popup through the same gate's 302 flow.\n */\n sessionToken: string | null;\n expiresAt: string;\n}\n\nexport interface UserConnectProvider {\n id: string;\n displayName: string | null;\n logoUrl: string | null;\n authType: string | null;\n authFields: UserConnectAuthField[] | null;\n}\n\nexport interface UserConnectProvidersResponse {\n providers: UserConnectProvider[];\n}\n\nexport type UserConnectionScope = 'global' | 'user';\n\nexport interface UserConnection {\n id: string;\n integrationId: string;\n /**\n * Display metadata resolved server-side against the FULL platform catalog\n * (not the policy-filtered provider list), so rows for de-allowlisted\n * providers still render properly. Null when the catalog has no entry or\n * the lookup failed.\n */\n displayName: string | null;\n logoUrl: string | null;\n status: string;\n accountLabel: string | null;\n scope: UserConnectionScope;\n}\n\nexport interface UserConnectionsResponse {\n connections: UserConnection[];\n}\n\nexport interface UserConnectSessionResponse {\n connectionId: string;\n integrationId: string;\n /**\n * Server-relative launch-gate path (`{basePath}/connections/:id/launch`).\n * The popup navigates here — same-origin with the Mastra server and\n * authenticated — and the server verifies this browser owns the pending\n * session before 302ing into the vendor OAuth flow. The raw vendor connect\n * URL is never exposed to the browser.\n */\n launchUrl: string;\n expiresAt: string | null;\n}\n\nexport interface UserConnectionStatusResponse {\n id: string;\n status: string;\n scope: UserConnectionScope;\n}\n\nexport interface UserConnectErrorBody {\n error: { code: string; message: string };\n}\n\n/**\n * One provider in the structured output of the built-in `request_connection`\n * tool (`@mastra/connect`'s `ConnectPromptProvider`). Chat renders these as a\n * connect card.\n */\nexport interface ConnectPromptProvider {\n id: string;\n displayName: string | null;\n logoUrl: string | null;\n authType: string | null;\n /** Fields the UI must collect before starting headless authorization. */\n authFields: UserConnectAuthField[] | null;\n connected: boolean;\n scope: UserConnectionScope | null;\n}\n\n/** Structured output of the built-in `request_connection` tool. */\nexport interface ConnectPromptPayload {\n type: 'connect-prompt';\n requestedProvider?: string;\n providers: ConnectPromptProvider[];\n /** Outcome for the model once a paused prompt resumes, e.g. \"Linear connected successfully.\" */\n message?: string;\n /** Why the connection failed, when a paused prompt resumed without one. */\n error?: string;\n}\n\n/**\n * What the card resumes a paused `request_connection` call with once the\n * connection settles: `active`, or `failed` with the reason the card showed.\n */\nexport type ConnectPromptResume =\n | { provider: string; status: 'active' }\n | { provider: string; status: 'failed'; error: string };\n\n/** Narrows an arbitrary tool output to a connect-prompt payload. */\nexport function isConnectPromptPayload(output: unknown): output is ConnectPromptPayload {\n return (\n typeof output === 'object' &&\n output !== null &&\n (output as { type?: unknown }).type === 'connect-prompt' &&\n Array.isArray((output as { providers?: unknown }).providers)\n );\n}\n"],"mappings":";;;AAiIA,SAAgB,uBAAuB,QAAiD;CACtF,OACE,OAAO,WAAW,YAClB,WAAW,QACV,OAA8B,SAAS,oBACxC,MAAM,QAAS,OAAmC,SAAS;AAE/D"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.es.js","names":[],"sources":["../../../src/domains/connect/types.ts"],"sourcesContent":["/**\n * Wire contract of the end-user connect surface that `userConnectRoutes()`\n * from `@mastra/connect` mounts on the dev server (default base path\n * `/connect`). These routes are custom `apiRoutes`, so they are not part of\n * the `@mastra/client-js` SDK — the shapes below mirror\n * `packages/connect/src/user-connect-routes.ts` and must stay in sync with it.\n */\n\nexport interface UserConnectAuthField {\n name: string;\n /**\n * Where the value goes in a headless auth call: `credentials` are\n * exchanged with the provider, `params` configure the flow (e.g. an MCP\n * server URL). Missing (older servers) is treated as `credentials`.\n */\n target?: 'credentials' | 'params' | null;\n label?: string | null;\n description?: string | null;\n placeholder?: string | null;\n secret?: boolean | null;\n required?: boolean | null;\n order?: number | null;\n}\n\n/** JSON redemption of the launch gate (`Accept: application/json`). */\nexport interface UserConnectLaunchTokenResponse {\n /**\n * Nango connect session token for headless authorization, or null when the\n * server could not extract one — the client then falls back to the hosted\n * popup through the same gate's 302 flow.\n */\n sessionToken: string | null;\n expiresAt: string;\n}\n\nexport interface UserConnectProvider {\n id: string;\n displayName: string | null;\n logoUrl: string | null;\n authType: string | null;\n authFields: UserConnectAuthField[] | null;\n}\n\nexport interface UserConnectProvidersResponse {\n providers: UserConnectProvider[];\n}\n\nexport type UserConnectionScope = 'global' | 'user';\n\nexport interface UserConnection {\n id: string;\n integrationId: string;\n /**\n * Display metadata resolved server-side against the FULL platform catalog\n * (not the policy-filtered provider list), so rows for de-allowlisted\n * providers still render properly. Null when the catalog has no entry or\n * the lookup failed.\n */\n displayName: string | null;\n logoUrl: string | null;\n status: string;\n accountLabel: string | null;\n scope: UserConnectionScope;\n}\n\nexport interface UserConnectionsResponse {\n connections: UserConnection[];\n}\n\nexport interface UserConnectSessionResponse {\n connectionId: string;\n integrationId: string;\n /**\n * Server-relative launch-gate path (`{basePath}/connections/:id/launch`).\n * The popup navigates here — same-origin with the Mastra server and\n * authenticated — and the server verifies this browser owns the pending\n * session before 302ing into the vendor OAuth flow. The raw vendor connect\n * URL is never exposed to the browser.\n */\n launchUrl: string;\n expiresAt: string | null;\n}\n\nexport interface UserConnectionStatusResponse {\n id: string;\n status: string;\n scope: UserConnectionScope;\n}\n\nexport interface UserConnectErrorBody {\n error: { code: string; message: string };\n}\n\n/**\n * One provider in the structured output of the built-in `request_connection`\n * tool (`@mastra/connect`'s `ConnectPromptProvider`). Chat renders these as a\n * connect card.\n */\nexport interface ConnectPromptProvider {\n id: string;\n displayName: string | null;\n logoUrl: string | null;\n authType: string | null;\n /** Fields the UI must collect before starting headless authorization. */\n authFields: UserConnectAuthField[] | null;\n connected: boolean;\n scope: UserConnectionScope | null;\n}\n\n/** Structured output of the built-in `request_connection` tool. */\nexport interface ConnectPromptPayload {\n type: 'connect-prompt';\n requestedProvider?: string;\n providers: ConnectPromptProvider[];\n}\n\n/** Narrows an arbitrary tool output to a connect-prompt payload. */\nexport function isConnectPromptPayload(output: unknown): output is ConnectPromptPayload {\n return (\n typeof output === 'object' &&\n output !== null &&\n (output as { type?: unknown }).type === 'connect-prompt' &&\n Array.isArray((output as { providers?: unknown }).providers)\n );\n}\n"],"mappings":";;AAqHA,SAAgB,uBAAuB,QAAiD;CACtF,OACE,OAAO,WAAW,YAClB,WAAW,QACV,OAA8B,SAAS,oBACxC,MAAM,QAAS,OAAmC,SAAS;AAE/D"}
1
+ {"version":3,"file":"types.es.js","names":[],"sources":["../../../src/domains/connect/types.ts"],"sourcesContent":["/**\n * Wire contract of the end-user connect surface that `userConnectRoutes()`\n * from `@mastra/connect` mounts on the dev server (default base path\n * `/connect`). These routes are custom `apiRoutes`, so they are not part of\n * the `@mastra/client-js` SDK — the shapes below mirror\n * `packages/connect/src/user-connect-routes.ts` and must stay in sync with it.\n */\n\nexport interface UserConnectAuthField {\n name: string;\n /**\n * Where the value goes in a headless auth call: `credentials` are\n * exchanged with the provider, `params` configure the flow (e.g. an MCP\n * server URL). Missing (older servers) is treated as `credentials`.\n */\n target?: 'credentials' | 'params' | null;\n label?: string | null;\n description?: string | null;\n placeholder?: string | null;\n secret?: boolean | null;\n required?: boolean | null;\n order?: number | null;\n}\n\n/** JSON redemption of the launch gate (`Accept: application/json`). */\nexport interface UserConnectLaunchTokenResponse {\n /**\n * Nango connect session token for headless authorization, or null when the\n * server could not extract one — the client then falls back to the hosted\n * popup through the same gate's 302 flow.\n */\n sessionToken: string | null;\n expiresAt: string;\n}\n\nexport interface UserConnectProvider {\n id: string;\n displayName: string | null;\n logoUrl: string | null;\n authType: string | null;\n authFields: UserConnectAuthField[] | null;\n}\n\nexport interface UserConnectProvidersResponse {\n providers: UserConnectProvider[];\n}\n\nexport type UserConnectionScope = 'global' | 'user';\n\nexport interface UserConnection {\n id: string;\n integrationId: string;\n /**\n * Display metadata resolved server-side against the FULL platform catalog\n * (not the policy-filtered provider list), so rows for de-allowlisted\n * providers still render properly. Null when the catalog has no entry or\n * the lookup failed.\n */\n displayName: string | null;\n logoUrl: string | null;\n status: string;\n accountLabel: string | null;\n scope: UserConnectionScope;\n}\n\nexport interface UserConnectionsResponse {\n connections: UserConnection[];\n}\n\nexport interface UserConnectSessionResponse {\n connectionId: string;\n integrationId: string;\n /**\n * Server-relative launch-gate path (`{basePath}/connections/:id/launch`).\n * The popup navigates here — same-origin with the Mastra server and\n * authenticated — and the server verifies this browser owns the pending\n * session before 302ing into the vendor OAuth flow. The raw vendor connect\n * URL is never exposed to the browser.\n */\n launchUrl: string;\n expiresAt: string | null;\n}\n\nexport interface UserConnectionStatusResponse {\n id: string;\n status: string;\n scope: UserConnectionScope;\n}\n\nexport interface UserConnectErrorBody {\n error: { code: string; message: string };\n}\n\n/**\n * One provider in the structured output of the built-in `request_connection`\n * tool (`@mastra/connect`'s `ConnectPromptProvider`). Chat renders these as a\n * connect card.\n */\nexport interface ConnectPromptProvider {\n id: string;\n displayName: string | null;\n logoUrl: string | null;\n authType: string | null;\n /** Fields the UI must collect before starting headless authorization. */\n authFields: UserConnectAuthField[] | null;\n connected: boolean;\n scope: UserConnectionScope | null;\n}\n\n/** Structured output of the built-in `request_connection` tool. */\nexport interface ConnectPromptPayload {\n type: 'connect-prompt';\n requestedProvider?: string;\n providers: ConnectPromptProvider[];\n /** Outcome for the model once a paused prompt resumes, e.g. \"Linear connected successfully.\" */\n message?: string;\n /** Why the connection failed, when a paused prompt resumed without one. */\n error?: string;\n}\n\n/**\n * What the card resumes a paused `request_connection` call with once the\n * connection settles: `active`, or `failed` with the reason the card showed.\n */\nexport type ConnectPromptResume =\n | { provider: string; status: 'active' }\n | { provider: string; status: 'failed'; error: string };\n\n/** Narrows an arbitrary tool output to a connect-prompt payload. */\nexport function isConnectPromptPayload(output: unknown): output is ConnectPromptPayload {\n return (\n typeof output === 'object' &&\n output !== null &&\n (output as { type?: unknown }).type === 'connect-prompt' &&\n Array.isArray((output as { providers?: unknown }).providers)\n );\n}\n"],"mappings":";;AAiIA,SAAgB,uBAAuB,QAAiD;CACtF,OACE,OAAO,WAAW,YAClB,WAAW,QACV,OAA8B,SAAS,oBACxC,MAAM,QAAS,OAAmC,SAAS;AAE/D"}
@@ -116,6 +116,23 @@ function useConnectSurface() {
116
116
  });
117
117
  }
118
118
  var connectionsQueryKey = ["user-connect", "connections"];
119
+ /**
120
+ * A connection that activates or goes away changes which tools resolve for
121
+ * this user, so the connection list and every agent / tool listing are
122
+ * refetched. The server drops its cached tool snapshot when it reports the
123
+ * change, so the refetch already sees the new tools.
124
+ */
125
+ function invalidateAfterConnectionChange(queryClient) {
126
+ return Promise.all([
127
+ connectionsQueryKey,
128
+ ["agent"],
129
+ ["agents"],
130
+ ["tools"]
131
+ ].map(async (queryKey) => {
132
+ await queryClient.cancelQueries({ queryKey });
133
+ await queryClient.invalidateQueries({ queryKey });
134
+ }));
135
+ }
119
136
  /** Lists the caller's visible connections (global + their own). */
120
137
  function useUserConnections({ enabled }) {
121
138
  const connectFetch = useConnectFetch();
@@ -171,7 +188,7 @@ function useConnectionStatus({ connectionId }) {
171
188
  };
172
189
  throw error;
173
190
  }
174
- if (status.status !== "pending") queryClient.invalidateQueries({ queryKey: connectionsQueryKey });
191
+ if (status.status !== "pending") invalidateAfterConnectionChange(queryClient);
175
192
  return status;
176
193
  }
177
194
  });
@@ -375,7 +392,7 @@ function useDisconnectUserConnection() {
375
392
  const queryClient = (0, _tanstack_react_query.useQueryClient)();
376
393
  return (0, _tanstack_react_query.useMutation)({
377
394
  mutationFn: ({ connectionId }) => connectFetch(`/connections/${encodeURIComponent(connectionId)}`, { method: "DELETE" }),
378
- onSuccess: () => queryClient.invalidateQueries({ queryKey: connectionsQueryKey })
395
+ onSuccess: () => invalidateAfterConnectionChange(queryClient)
379
396
  });
380
397
  }
381
398
  exports.CONNECT_BASE_PATH = CONNECT_BASE_PATH;
@@ -1 +1 @@
1
- {"version":3,"file":"use-user-connect.cjs.js","names":["useMastraClient","useCallback","useQuery","useMutation","useQueryClient","useState","useRef","splitHeadlessAuthValues","headlessAuthFields","startHeadlessAuth","HeadlessAuthError","supportsHeadlessAuth","headlessAuthRequiresForm"],"sources":["../../../src/domains/connect/use-user-connect.ts"],"sourcesContent":["import { useMastraClient } from '@mastra/react';\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport {\n HeadlessAuthError,\n headlessAuthFields,\n headlessAuthRequiresForm,\n splitHeadlessAuthValues,\n startHeadlessAuth,\n supportsHeadlessAuth,\n} from './headless-connect';\nimport type { HeadlessAuthHandle } from './headless-connect';\nimport type {\n UserConnectErrorBody,\n UserConnectLaunchTokenResponse,\n UserConnectProvider,\n UserConnectProvidersResponse,\n UserConnectSessionResponse,\n UserConnectionStatusResponse,\n UserConnectionsResponse,\n} from './types';\n\n/** Default mount prefix of `userConnectRoutes()` on the dev server. */\nexport const CONNECT_BASE_PATH = '/connect';\n\nexport class UserConnectRequestError extends Error {\n status: number;\n code: string | null;\n\n constructor(status: number, code: string | null, message: string) {\n super(message);\n this.name = 'UserConnectRequestError';\n this.status = status;\n this.code = code;\n }\n}\n\ntype ConnectFetch = <T>(path: string, init?: RequestInit) => Promise<T>;\n\n/**\n * Fetches against the dev server's mounted connect routes. Custom `apiRoutes`\n * live outside the `/api` prefix, so this goes around the SDK while reusing\n * its base URL, auth headers, credentials mode, and refresh-aware fetch.\n */\nfunction useConnectFetch(): ConnectFetch {\n const client = useMastraClient();\n const { baseUrl, headers, credentials, fetch: customFetch } = client.options;\n\n return useCallback<ConnectFetch>(\n async (path, init) => {\n const fetchFn = customFetch ?? fetch;\n const response = await fetchFn(`${baseUrl}${CONNECT_BASE_PATH}${path}`, {\n ...init,\n headers: { ...headers, ...init?.headers },\n credentials,\n });\n if (!response.ok) {\n let code: string | null = null;\n let message = `Request failed with status ${response.status}`;\n try {\n const body = (await response.json()) as Partial<UserConnectErrorBody>;\n if (body?.error) {\n code = body.error.code ?? null;\n message = body.error.message ?? message;\n }\n } catch {\n // Non-JSON error body (e.g. a plain 404 for unmounted routes).\n }\n throw new UserConnectRequestError(response.status, code, message);\n }\n if (response.status === 204) return undefined as never;\n return (await response.json()) as never;\n },\n [baseUrl, headers, credentials, customFetch],\n );\n}\n\n/**\n * Resolves a server-relative launch-gate path (returned by the session route\n * as `launchUrl`) against the SDK's base URL for top-level popup navigation.\n * The launch route is authenticated by the browser's session cookie — the\n * server re-verifies that this browser owns the pending session before\n * redirecting into the vendor OAuth flow.\n */\nexport function useConnectLaunchHref(): (launchUrl: string) => string {\n const client = useMastraClient();\n const { baseUrl } = client.options;\n return useCallback((launchUrl: string) => (baseUrl ? `${baseUrl}${launchUrl}` : launchUrl), [baseUrl]);\n}\n\n/**\n * Redeems the launch gate for a headless session token (`Accept:\n * application/json`) instead of following its hosted 302. The gate runs the\n * same ownership assertion in both modes; a `null` token means the server\n * could not extract one and the caller falls back to the hosted popup.\n */\nfunction useRedeemLaunchToken(): (launchUrl: string) => Promise<UserConnectLaunchTokenResponse> {\n const client = useMastraClient();\n const { baseUrl, headers, credentials, fetch: customFetch } = client.options;\n\n return useCallback(\n async launchUrl => {\n const fetchFn = customFetch ?? fetch;\n // `launchUrl` is the full server-relative gate path (it already carries\n // the connect mount prefix), so it resolves against the bare base URL.\n const response = await fetchFn(`${baseUrl ?? ''}${launchUrl}`, {\n headers: { ...headers, accept: 'application/json' },\n credentials,\n });\n if (!response.ok) {\n throw new UserConnectRequestError(response.status, null, `Request failed with status ${response.status}`);\n }\n return (await response.json()) as UserConnectLaunchTokenResponse;\n },\n [baseUrl, headers, credentials, customFetch],\n );\n}\n\nexport type ConnectSurface =\n | { status: 'ready'; providers: UserConnectProvidersResponse['providers'] }\n | { status: 'not-mounted' }\n | { status: 'unauthenticated' };\n\n/**\n * Capability detection for the end-user connect surface: probes\n * `GET /connect/providers` and distinguishes a working surface from the two\n * setup gaps (routes not mounted → 404, no end-user identity → 401).\n */\nexport function useConnectSurface() {\n const connectFetch = useConnectFetch();\n\n return useQuery<ConnectSurface, UserConnectRequestError>({\n queryKey: ['user-connect', 'surface'],\n retry: false,\n queryFn: async () => {\n try {\n const body = await connectFetch<UserConnectProvidersResponse>('/providers');\n return { status: 'ready', providers: body.providers };\n } catch (error) {\n if (error instanceof UserConnectRequestError) {\n if (error.status === 404) return { status: 'not-mounted' };\n if (error.status === 401) return { status: 'unauthenticated' };\n }\n throw error;\n }\n },\n });\n}\n\nconst connectionsQueryKey = ['user-connect', 'connections'] as const;\n\n/** Lists the caller's visible connections (global + their own). */\nexport function useUserConnections({ enabled }: { enabled: boolean }) {\n const connectFetch = useConnectFetch();\n\n return useQuery<UserConnectionsResponse['connections'], UserConnectRequestError>({\n queryKey: connectionsQueryKey,\n enabled,\n retry: false,\n queryFn: async () => {\n const body = await connectFetch<UserConnectionsResponse>('/connections');\n return body.connections;\n },\n });\n}\n\n/** Creates a connect session for a provider; the caller drives the popup. */\nexport function useCreateConnectSession() {\n const connectFetch = useConnectFetch();\n\n return useMutation<UserConnectSessionResponse, UserConnectRequestError, { provider: string }>({\n mutationFn: ({ provider }) =>\n connectFetch<UserConnectSessionResponse>(`/${encodeURIComponent(provider)}/sessions`, { method: 'POST' }),\n });\n}\n\n/** 2s interval with a 5 minute cap, mirroring the hosted connect page. */\nconst STATUS_POLL_INTERVAL_MS = 2_000;\nconst STATUS_MAX_POLLS = 150;\n\n/**\n * Polls a pending connection until it leaves `pending`. A 404 counts as still\n * pending — the platform row may not be visible until the provider handshake\n * lands. Polling stops once the status settles (or after the 5 minute cap);\n * the connections list is refreshed when it does.\n */\nexport function useConnectionStatus({ connectionId }: { connectionId: string | null }) {\n const connectFetch = useConnectFetch();\n const queryClient = useQueryClient();\n\n return useQuery<UserConnectionStatusResponse, UserConnectRequestError>({\n queryKey: ['user-connect', 'connection-status', connectionId],\n enabled: connectionId !== null,\n retry: false,\n refetchInterval: query => {\n const status = query.state.data?.status;\n if (status !== undefined && status !== 'pending') return false;\n if (query.state.dataUpdateCount >= STATUS_MAX_POLLS) return false;\n return STATUS_POLL_INTERVAL_MS;\n },\n queryFn: async () => {\n let status: UserConnectionStatusResponse;\n try {\n status = await connectFetch<UserConnectionStatusResponse>(\n `/connections/${encodeURIComponent(connectionId ?? '')}/status`,\n );\n } catch (error) {\n if (error instanceof UserConnectRequestError && error.status === 404) {\n return { id: connectionId ?? '', status: 'pending', scope: 'user' };\n }\n throw error;\n }\n if (status.status !== 'pending') {\n void queryClient.invalidateQueries({ queryKey: connectionsQueryKey });\n }\n return status;\n },\n });\n}\n\nexport interface PendingConnect {\n provider: string;\n connectionId: string;\n /**\n * `headless` — the Nango SDK drives the provider's own consent popup (or no\n * popup at all for collected credentials). `hosted` — a popup navigated\n * through the launch gate's 302 into the hosted connect page.\n */\n mode: 'headless' | 'hosted';\n /** Shown when the popup was blocked so the user can finish the flow manually. */\n fallbackUrl: string | null;\n}\n\n/**\n * What a caller passes to `connect()`: the provider's id plus the auth shape\n * when known. Callers that only know the id (older payloads) always get the\n * hosted flow.\n */\nexport type ConnectTarget = Pick<UserConnectProvider, 'id'> &\n Partial<Pick<UserConnectProvider, 'authType' | 'authFields'>>;\n\n/**\n * Grace period after the vendor popup closes before an unsettled flow is\n * abandoned — the activation webhook often lands a few seconds after the\n * vendor window closes itself.\n */\nconst POPUP_CLOSE_GRACE_MS = 15_000;\n/** Wall-clock cap matching the status poll's 5 minute budget. */\nconst PENDING_TIMEOUT_MS = STATUS_POLL_INTERVAL_MS * STATUS_MAX_POLLS;\n\nexport interface UsePendingConnectOptions {\n /** Overrides for the abandonment timings (primarily for tests). */\n popupCloseGraceMs?: number;\n pendingTimeoutMs?: number;\n}\n\n/**\n * Outcome of the most recently settled flow: `active` when the connection\n * activated, `failed` for everything else (non-active settle, poll failure,\n * abandoned popup, poll budget). Cleared when a new flow starts.\n */\nexport interface SettledConnect {\n provider: string;\n status: 'active' | 'failed';\n /** Human-readable failure detail (blocked popup, closed window, rejected credentials). */\n message?: string;\n}\n\n/** Window features for the hosted connect popup. */\nconst POPUP_FEATURES = 'popup,width=600,height=720';\n\n/**\n * Drives one add-connection flow end to end. Providers whose auth the browser\n * can complete headlessly go through the launch gate's JSON redemption: the\n * released session token keys the Nango SDK, which opens the provider's own\n * consent popup (or submits collected credentials with no popup). Everything\n * else gets the hosted flow — a popup opened synchronously in the click\n * gesture (so popup blockers allow it) and navigated through the launch\n * gate's 302. Either way the connection status is polled until it settles.\n * The pending state always resolves — on settle, on a poll or auth failure,\n * when the popup closes without settling (after a grace period), or at the\n * poll budget — so the Connect action is never stuck disabled.\n */\nexport function usePendingConnect(options: UsePendingConnectOptions = {}) {\n const { popupCloseGraceMs = POPUP_CLOSE_GRACE_MS, pendingTimeoutMs = PENDING_TIMEOUT_MS } = options;\n const session = useCreateConnectSession();\n const resolveLaunchHref = useConnectLaunchHref();\n const redeemLaunchToken = useRedeemLaunchToken();\n const [pending, setPending] = useState<PendingConnect | null>(null);\n const [settled, setSettled] = useState<SettledConnect | null>(null);\n const [connectedProviders, setConnectedProviders] = useState<ReadonlySet<string>>(new Set());\n const popupRef = useRef<Pick<Window, 'closed'> | null>(null);\n const headlessRef = useRef<HeadlessAuthHandle | null>(null);\n // Mirrors `pending` for async guards (the SDK promise settles outside the\n // render cycle); written directly at every transition so a cancel is never\n // raced by a trailing rejection.\n const pendingRef = useRef<PendingConnect | null>(null);\n const status = useConnectionStatus({ connectionId: pending?.connectionId ?? null });\n\n useEffect(() => {\n pendingRef.current = pending;\n }, [pending]);\n\n // Close the provider popup if the surface unmounts mid-flow.\n useEffect(\n () => () => {\n headlessRef.current?.close();\n headlessRef.current = null;\n },\n [],\n );\n\n // Settle only on a status response for the connection currently pending —\n // a stale result from a previous flow must not clear or mark a newer one.\n const statusData = status.data;\n useEffect(() => {\n if (!pending || statusData === undefined) return;\n if (statusData.id !== pending.connectionId || statusData.status === 'pending') return;\n const { provider } = pending;\n if (statusData.status === 'active') {\n setConnectedProviders(previous => (previous.has(provider) ? previous : new Set([...previous, provider])));\n }\n // The provider window closes itself on active settles; drop the handle so\n // a trailing SDK rejection cannot re-settle this flow.\n headlessRef.current = null;\n pendingRef.current = null;\n setSettled({ provider, status: statusData.status === 'active' ? 'active' : 'failed' });\n setPending(null);\n }, [pending, statusData]);\n\n // A failed poll hands the Connect action back instead of leaving it stuck.\n useEffect(() => {\n if (!status.isError || !pending) return;\n // The ref is cleared before close() so the rejection it triggers no-ops.\n pendingRef.current = null;\n headlessRef.current?.close();\n headlessRef.current = null;\n setSettled({ provider: pending.provider, status: 'failed' });\n setPending(null);\n }, [status.isError, pending]);\n\n // Abandonment: the popup closed without the flow settling (grace period for\n // the trailing webhook) or the poll budget ran out.\n useEffect(() => {\n if (!pending) return;\n const startedAt = Date.now();\n let closedAt: number | null = null;\n const tickMs = Math.min(1_000, popupCloseGraceMs);\n const timer = window.setInterval(() => {\n if (popupRef.current?.closed) closedAt ??= Date.now();\n const abandoned = closedAt !== null && Date.now() - closedAt >= popupCloseGraceMs;\n const timedOut = Date.now() - startedAt >= pendingTimeoutMs;\n if (abandoned || timedOut) {\n // The ref is cleared before close() so the rejection it triggers no-ops.\n pendingRef.current = null;\n headlessRef.current?.close();\n headlessRef.current = null;\n setSettled({ provider: pending.provider, status: 'failed' });\n setPending(null);\n }\n }, tickMs);\n return () => window.clearInterval(timer);\n }, [pending, popupCloseGraceMs, pendingTimeoutMs]);\n\n const beginHeadless = useCallback(\n async (provider: ConnectTarget, created: UserConnectSessionResponse, values?: Record<string, string>) => {\n let sessionToken: string | null = null;\n try {\n sessionToken = (await redeemLaunchToken(created.launchUrl)).sessionToken;\n } catch {\n // Redemption failure (older platform, transient error) is not fatal —\n // the hosted flow below still works through the gate's 302.\n }\n if (!sessionToken) {\n // Opened after an await, so a blocker may stop it; the fallback link\n // in the pending state covers that.\n const launchHref = resolveLaunchHref(created.launchUrl);\n const popup = window.open(launchHref, 'mastra-connect', POPUP_FEATURES);\n popupRef.current = popup;\n const next: PendingConnect = {\n provider: provider.id,\n connectionId: created.connectionId,\n mode: 'hosted',\n fallbackUrl: popup ? null : launchHref,\n };\n pendingRef.current = next;\n setPending(next);\n return;\n }\n\n const split = values ? splitHeadlessAuthValues(headlessAuthFields(provider), values) : {};\n const handle = startHeadlessAuth({\n sessionToken,\n integrationId: created.integrationId,\n authType: provider.authType,\n credentials: split.credentials,\n params: split.params,\n });\n headlessRef.current = handle;\n const next: PendingConnect = {\n provider: provider.id,\n connectionId: created.connectionId,\n mode: 'headless',\n fallbackUrl: null,\n };\n pendingRef.current = next;\n setPending(next);\n // Success needs no action here — the activation webhook lands on the\n // platform and the status poll settles the flow. Failures settle it now.\n handle.result.catch((error: unknown) => {\n if (pendingRef.current?.connectionId !== created.connectionId) return;\n headlessRef.current = null;\n pendingRef.current = null;\n const message = error instanceof HeadlessAuthError ? error.message : 'Could not authorize this connection.';\n setSettled({ provider: provider.id, status: 'failed', message });\n setPending(null);\n });\n },\n [redeemLaunchToken, resolveLaunchHref],\n );\n\n const connect = useCallback(\n (provider: ConnectTarget, values?: Record<string, string>) => {\n setSettled(null);\n headlessRef.current?.close();\n headlessRef.current = null;\n\n // Headless needs both the capability and the inputs: a caller without a\n // form (no `values`) can only run flows whose fields are all optional —\n // anything else falls back to the hosted page, which renders the form.\n const headless =\n supportsHeadlessAuth(provider.authType, provider.authFields) &&\n (values !== undefined || !headlessAuthRequiresForm(provider));\n // Hosted flows open the popup synchronously in the click gesture so\n // popup blockers allow it; headless flows never navigate to a hosted\n // page, so no window opens here.\n const popup = headless ? null : window.open('', 'mastra-connect', POPUP_FEATURES);\n popupRef.current = popup;\n session.mutate(\n { provider: provider.id },\n {\n onSuccess: created => {\n if (headless) {\n void beginHeadless(provider, created, values);\n return;\n }\n // The popup enters OAuth through the server's launch gate, which\n // verifies this browser owns the pending session before\n // redirecting — the raw vendor URL never reaches the page.\n const launchHref = resolveLaunchHref(created.launchUrl);\n if (popup) {\n popup.location.href = launchHref;\n }\n const next: PendingConnect = {\n provider: provider.id,\n connectionId: created.connectionId,\n mode: 'hosted',\n fallbackUrl: popup ? null : launchHref,\n };\n pendingRef.current = next;\n setPending(next);\n },\n onError: () => {\n popup?.close();\n popupRef.current = null;\n },\n },\n );\n },\n [session, resolveLaunchHref, beginHeadless],\n );\n\n /** Abandons the in-flight flow: closes the provider window, hands the Connect action back. */\n const cancel = useCallback(() => {\n headlessRef.current?.close();\n headlessRef.current = null;\n popupRef.current = null;\n pendingRef.current = null;\n setPending(null);\n setSettled(null);\n }, []);\n\n return {\n pending,\n connect,\n cancel,\n /** Outcome of the most recently settled flow; null while none settled or a flow is in progress. */\n settled,\n /** Providers whose connection settled `active` during this hook's lifetime. */\n connectedProviders,\n isCreating: session.isPending,\n createError: session.isError ? session.error : null,\n };\n}\n\n/** Disconnects one of the caller's own connections. */\nexport function useDisconnectUserConnection() {\n const connectFetch = useConnectFetch();\n const queryClient = useQueryClient();\n\n return useMutation<void, UserConnectRequestError, { connectionId: string }>({\n mutationFn: ({ connectionId }) =>\n connectFetch<void>(`/connections/${encodeURIComponent(connectionId)}`, { method: 'DELETE' }),\n onSuccess: () => queryClient.invalidateQueries({ queryKey: connectionsQueryKey }),\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-user-connect.cjs.js","names":["useMastraClient","useCallback","useQuery","useMutation","useQueryClient","useState","useRef","splitHeadlessAuthValues","headlessAuthFields","startHeadlessAuth","HeadlessAuthError","supportsHeadlessAuth","headlessAuthRequiresForm"],"sources":["../../../src/domains/connect/use-user-connect.ts"],"sourcesContent":["import { useMastraClient } from '@mastra/react';\nimport type { QueryClient } from '@tanstack/react-query';\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport {\n HeadlessAuthError,\n headlessAuthFields,\n headlessAuthRequiresForm,\n splitHeadlessAuthValues,\n startHeadlessAuth,\n supportsHeadlessAuth,\n} from './headless-connect';\nimport type { HeadlessAuthHandle } from './headless-connect';\nimport type {\n UserConnectErrorBody,\n UserConnectLaunchTokenResponse,\n UserConnectProvider,\n UserConnectProvidersResponse,\n UserConnectSessionResponse,\n UserConnectionStatusResponse,\n UserConnectionsResponse,\n} from './types';\n\n/** Default mount prefix of `userConnectRoutes()` on the dev server. */\nexport const CONNECT_BASE_PATH = '/connect';\n\nexport class UserConnectRequestError extends Error {\n status: number;\n code: string | null;\n\n constructor(status: number, code: string | null, message: string) {\n super(message);\n this.name = 'UserConnectRequestError';\n this.status = status;\n this.code = code;\n }\n}\n\ntype ConnectFetch = <T>(path: string, init?: RequestInit) => Promise<T>;\n\n/**\n * Fetches against the dev server's mounted connect routes. Custom `apiRoutes`\n * live outside the `/api` prefix, so this goes around the SDK while reusing\n * its base URL, auth headers, credentials mode, and refresh-aware fetch.\n */\nfunction useConnectFetch(): ConnectFetch {\n const client = useMastraClient();\n const { baseUrl, headers, credentials, fetch: customFetch } = client.options;\n\n return useCallback<ConnectFetch>(\n async (path, init) => {\n const fetchFn = customFetch ?? fetch;\n const response = await fetchFn(`${baseUrl}${CONNECT_BASE_PATH}${path}`, {\n ...init,\n headers: { ...headers, ...init?.headers },\n credentials,\n });\n if (!response.ok) {\n let code: string | null = null;\n let message = `Request failed with status ${response.status}`;\n try {\n const body = (await response.json()) as Partial<UserConnectErrorBody>;\n if (body?.error) {\n code = body.error.code ?? null;\n message = body.error.message ?? message;\n }\n } catch {\n // Non-JSON error body (e.g. a plain 404 for unmounted routes).\n }\n throw new UserConnectRequestError(response.status, code, message);\n }\n if (response.status === 204) return undefined as never;\n return (await response.json()) as never;\n },\n [baseUrl, headers, credentials, customFetch],\n );\n}\n\n/**\n * Resolves a server-relative launch-gate path (returned by the session route\n * as `launchUrl`) against the SDK's base URL for top-level popup navigation.\n * The launch route is authenticated by the browser's session cookie — the\n * server re-verifies that this browser owns the pending session before\n * redirecting into the vendor OAuth flow.\n */\nexport function useConnectLaunchHref(): (launchUrl: string) => string {\n const client = useMastraClient();\n const { baseUrl } = client.options;\n return useCallback((launchUrl: string) => (baseUrl ? `${baseUrl}${launchUrl}` : launchUrl), [baseUrl]);\n}\n\n/**\n * Redeems the launch gate for a headless session token (`Accept:\n * application/json`) instead of following its hosted 302. The gate runs the\n * same ownership assertion in both modes; a `null` token means the server\n * could not extract one and the caller falls back to the hosted popup.\n */\nfunction useRedeemLaunchToken(): (launchUrl: string) => Promise<UserConnectLaunchTokenResponse> {\n const client = useMastraClient();\n const { baseUrl, headers, credentials, fetch: customFetch } = client.options;\n\n return useCallback(\n async launchUrl => {\n const fetchFn = customFetch ?? fetch;\n // `launchUrl` is the full server-relative gate path (it already carries\n // the connect mount prefix), so it resolves against the bare base URL.\n const response = await fetchFn(`${baseUrl ?? ''}${launchUrl}`, {\n headers: { ...headers, accept: 'application/json' },\n credentials,\n });\n if (!response.ok) {\n throw new UserConnectRequestError(response.status, null, `Request failed with status ${response.status}`);\n }\n return (await response.json()) as UserConnectLaunchTokenResponse;\n },\n [baseUrl, headers, credentials, customFetch],\n );\n}\n\nexport type ConnectSurface =\n | { status: 'ready'; providers: UserConnectProvidersResponse['providers'] }\n | { status: 'not-mounted' }\n | { status: 'unauthenticated' };\n\n/**\n * Capability detection for the end-user connect surface: probes\n * `GET /connect/providers` and distinguishes a working surface from the two\n * setup gaps (routes not mounted → 404, no end-user identity → 401).\n */\nexport function useConnectSurface() {\n const connectFetch = useConnectFetch();\n\n return useQuery<ConnectSurface, UserConnectRequestError>({\n queryKey: ['user-connect', 'surface'],\n retry: false,\n queryFn: async () => {\n try {\n const body = await connectFetch<UserConnectProvidersResponse>('/providers');\n return { status: 'ready', providers: body.providers };\n } catch (error) {\n if (error instanceof UserConnectRequestError) {\n if (error.status === 404) return { status: 'not-mounted' };\n if (error.status === 401) return { status: 'unauthenticated' };\n }\n throw error;\n }\n },\n });\n}\n\nconst connectionsQueryKey = ['user-connect', 'connections'] as const;\n\n/**\n * A connection that activates or goes away changes which tools resolve for\n * this user, so the connection list and every agent / tool listing are\n * refetched. The server drops its cached tool snapshot when it reports the\n * change, so the refetch already sees the new tools.\n */\nfunction invalidateAfterConnectionChange(queryClient: QueryClient) {\n return Promise.all(\n [connectionsQueryKey, ['agent'], ['agents'], ['tools']].map(async queryKey => {\n // A first fetch still in flight would otherwise be reused by the\n // invalidation, and its pre-change answer would land as fresh data.\n await queryClient.cancelQueries({ queryKey });\n await queryClient.invalidateQueries({ queryKey });\n }),\n );\n}\n\n/** Lists the caller's visible connections (global + their own). */\nexport function useUserConnections({ enabled }: { enabled: boolean }) {\n const connectFetch = useConnectFetch();\n\n return useQuery<UserConnectionsResponse['connections'], UserConnectRequestError>({\n queryKey: connectionsQueryKey,\n enabled,\n retry: false,\n queryFn: async () => {\n const body = await connectFetch<UserConnectionsResponse>('/connections');\n return body.connections;\n },\n });\n}\n\n/** Creates a connect session for a provider; the caller drives the popup. */\nexport function useCreateConnectSession() {\n const connectFetch = useConnectFetch();\n\n return useMutation<UserConnectSessionResponse, UserConnectRequestError, { provider: string }>({\n mutationFn: ({ provider }) =>\n connectFetch<UserConnectSessionResponse>(`/${encodeURIComponent(provider)}/sessions`, { method: 'POST' }),\n });\n}\n\n/** 2s interval with a 5 minute cap, mirroring the hosted connect page. */\nconst STATUS_POLL_INTERVAL_MS = 2_000;\nconst STATUS_MAX_POLLS = 150;\n\n/**\n * Polls a pending connection until it leaves `pending`. A 404 counts as still\n * pending — the platform row may not be visible until the provider handshake\n * lands. Polling stops once the status settles (or after the 5 minute cap);\n * the connections list is refreshed when it does.\n */\nexport function useConnectionStatus({ connectionId }: { connectionId: string | null }) {\n const connectFetch = useConnectFetch();\n const queryClient = useQueryClient();\n\n return useQuery<UserConnectionStatusResponse, UserConnectRequestError>({\n queryKey: ['user-connect', 'connection-status', connectionId],\n enabled: connectionId !== null,\n retry: false,\n refetchInterval: query => {\n const status = query.state.data?.status;\n if (status !== undefined && status !== 'pending') return false;\n if (query.state.dataUpdateCount >= STATUS_MAX_POLLS) return false;\n return STATUS_POLL_INTERVAL_MS;\n },\n queryFn: async () => {\n let status: UserConnectionStatusResponse;\n try {\n status = await connectFetch<UserConnectionStatusResponse>(\n `/connections/${encodeURIComponent(connectionId ?? '')}/status`,\n );\n } catch (error) {\n if (error instanceof UserConnectRequestError && error.status === 404) {\n return { id: connectionId ?? '', status: 'pending', scope: 'user' };\n }\n throw error;\n }\n if (status.status !== 'pending') {\n void invalidateAfterConnectionChange(queryClient);\n }\n return status;\n },\n });\n}\n\nexport interface PendingConnect {\n provider: string;\n connectionId: string;\n /**\n * `headless` — the Nango SDK drives the provider's own consent popup (or no\n * popup at all for collected credentials). `hosted` — a popup navigated\n * through the launch gate's 302 into the hosted connect page.\n */\n mode: 'headless' | 'hosted';\n /** Shown when the popup was blocked so the user can finish the flow manually. */\n fallbackUrl: string | null;\n}\n\n/**\n * What a caller passes to `connect()`: the provider's id plus the auth shape\n * when known. Callers that only know the id (older payloads) always get the\n * hosted flow.\n */\nexport type ConnectTarget = Pick<UserConnectProvider, 'id'> &\n Partial<Pick<UserConnectProvider, 'authType' | 'authFields'>>;\n\n/**\n * Grace period after the vendor popup closes before an unsettled flow is\n * abandoned — the activation webhook often lands a few seconds after the\n * vendor window closes itself.\n */\nconst POPUP_CLOSE_GRACE_MS = 15_000;\n/** Wall-clock cap matching the status poll's 5 minute budget. */\nconst PENDING_TIMEOUT_MS = STATUS_POLL_INTERVAL_MS * STATUS_MAX_POLLS;\n\nexport interface UsePendingConnectOptions {\n /** Overrides for the abandonment timings (primarily for tests). */\n popupCloseGraceMs?: number;\n pendingTimeoutMs?: number;\n}\n\n/**\n * Outcome of the most recently settled flow: `active` when the connection\n * activated, `failed` for everything else (non-active settle, poll failure,\n * abandoned popup, poll budget). Cleared when a new flow starts.\n */\nexport interface SettledConnect {\n provider: string;\n status: 'active' | 'failed';\n /** Human-readable failure detail (blocked popup, closed window, rejected credentials). */\n message?: string;\n}\n\n/** Window features for the hosted connect popup. */\nconst POPUP_FEATURES = 'popup,width=600,height=720';\n\n/**\n * Drives one add-connection flow end to end. Providers whose auth the browser\n * can complete headlessly go through the launch gate's JSON redemption: the\n * released session token keys the Nango SDK, which opens the provider's own\n * consent popup (or submits collected credentials with no popup). Everything\n * else gets the hosted flow — a popup opened synchronously in the click\n * gesture (so popup blockers allow it) and navigated through the launch\n * gate's 302. Either way the connection status is polled until it settles.\n * The pending state always resolves — on settle, on a poll or auth failure,\n * when the popup closes without settling (after a grace period), or at the\n * poll budget — so the Connect action is never stuck disabled.\n */\nexport function usePendingConnect(options: UsePendingConnectOptions = {}) {\n const { popupCloseGraceMs = POPUP_CLOSE_GRACE_MS, pendingTimeoutMs = PENDING_TIMEOUT_MS } = options;\n const session = useCreateConnectSession();\n const resolveLaunchHref = useConnectLaunchHref();\n const redeemLaunchToken = useRedeemLaunchToken();\n const [pending, setPending] = useState<PendingConnect | null>(null);\n const [settled, setSettled] = useState<SettledConnect | null>(null);\n const [connectedProviders, setConnectedProviders] = useState<ReadonlySet<string>>(new Set());\n const popupRef = useRef<Pick<Window, 'closed'> | null>(null);\n const headlessRef = useRef<HeadlessAuthHandle | null>(null);\n // Mirrors `pending` for async guards (the SDK promise settles outside the\n // render cycle); written directly at every transition so a cancel is never\n // raced by a trailing rejection.\n const pendingRef = useRef<PendingConnect | null>(null);\n const status = useConnectionStatus({ connectionId: pending?.connectionId ?? null });\n\n useEffect(() => {\n pendingRef.current = pending;\n }, [pending]);\n\n // Close the provider popup if the surface unmounts mid-flow.\n useEffect(\n () => () => {\n headlessRef.current?.close();\n headlessRef.current = null;\n },\n [],\n );\n\n // Settle only on a status response for the connection currently pending —\n // a stale result from a previous flow must not clear or mark a newer one.\n const statusData = status.data;\n useEffect(() => {\n if (!pending || statusData === undefined) return;\n if (statusData.id !== pending.connectionId || statusData.status === 'pending') return;\n const { provider } = pending;\n if (statusData.status === 'active') {\n setConnectedProviders(previous => (previous.has(provider) ? previous : new Set([...previous, provider])));\n }\n // The provider window closes itself on active settles; drop the handle so\n // a trailing SDK rejection cannot re-settle this flow.\n headlessRef.current = null;\n pendingRef.current = null;\n setSettled({ provider, status: statusData.status === 'active' ? 'active' : 'failed' });\n setPending(null);\n }, [pending, statusData]);\n\n // A failed poll hands the Connect action back instead of leaving it stuck.\n useEffect(() => {\n if (!status.isError || !pending) return;\n // The ref is cleared before close() so the rejection it triggers no-ops.\n pendingRef.current = null;\n headlessRef.current?.close();\n headlessRef.current = null;\n setSettled({ provider: pending.provider, status: 'failed' });\n setPending(null);\n }, [status.isError, pending]);\n\n // Abandonment: the popup closed without the flow settling (grace period for\n // the trailing webhook) or the poll budget ran out.\n useEffect(() => {\n if (!pending) return;\n const startedAt = Date.now();\n let closedAt: number | null = null;\n const tickMs = Math.min(1_000, popupCloseGraceMs);\n const timer = window.setInterval(() => {\n if (popupRef.current?.closed) closedAt ??= Date.now();\n const abandoned = closedAt !== null && Date.now() - closedAt >= popupCloseGraceMs;\n const timedOut = Date.now() - startedAt >= pendingTimeoutMs;\n if (abandoned || timedOut) {\n // The ref is cleared before close() so the rejection it triggers no-ops.\n pendingRef.current = null;\n headlessRef.current?.close();\n headlessRef.current = null;\n setSettled({ provider: pending.provider, status: 'failed' });\n setPending(null);\n }\n }, tickMs);\n return () => window.clearInterval(timer);\n }, [pending, popupCloseGraceMs, pendingTimeoutMs]);\n\n const beginHeadless = useCallback(\n async (provider: ConnectTarget, created: UserConnectSessionResponse, values?: Record<string, string>) => {\n let sessionToken: string | null = null;\n try {\n sessionToken = (await redeemLaunchToken(created.launchUrl)).sessionToken;\n } catch {\n // Redemption failure (older platform, transient error) is not fatal —\n // the hosted flow below still works through the gate's 302.\n }\n if (!sessionToken) {\n // Opened after an await, so a blocker may stop it; the fallback link\n // in the pending state covers that.\n const launchHref = resolveLaunchHref(created.launchUrl);\n const popup = window.open(launchHref, 'mastra-connect', POPUP_FEATURES);\n popupRef.current = popup;\n const next: PendingConnect = {\n provider: provider.id,\n connectionId: created.connectionId,\n mode: 'hosted',\n fallbackUrl: popup ? null : launchHref,\n };\n pendingRef.current = next;\n setPending(next);\n return;\n }\n\n const split = values ? splitHeadlessAuthValues(headlessAuthFields(provider), values) : {};\n const handle = startHeadlessAuth({\n sessionToken,\n integrationId: created.integrationId,\n authType: provider.authType,\n credentials: split.credentials,\n params: split.params,\n });\n headlessRef.current = handle;\n const next: PendingConnect = {\n provider: provider.id,\n connectionId: created.connectionId,\n mode: 'headless',\n fallbackUrl: null,\n };\n pendingRef.current = next;\n setPending(next);\n // Success needs no action here — the activation webhook lands on the\n // platform and the status poll settles the flow. Failures settle it now.\n handle.result.catch((error: unknown) => {\n if (pendingRef.current?.connectionId !== created.connectionId) return;\n headlessRef.current = null;\n pendingRef.current = null;\n const message = error instanceof HeadlessAuthError ? error.message : 'Could not authorize this connection.';\n setSettled({ provider: provider.id, status: 'failed', message });\n setPending(null);\n });\n },\n [redeemLaunchToken, resolveLaunchHref],\n );\n\n const connect = useCallback(\n (provider: ConnectTarget, values?: Record<string, string>) => {\n setSettled(null);\n headlessRef.current?.close();\n headlessRef.current = null;\n\n // Headless needs both the capability and the inputs: a caller without a\n // form (no `values`) can only run flows whose fields are all optional —\n // anything else falls back to the hosted page, which renders the form.\n const headless =\n supportsHeadlessAuth(provider.authType, provider.authFields) &&\n (values !== undefined || !headlessAuthRequiresForm(provider));\n // Hosted flows open the popup synchronously in the click gesture so\n // popup blockers allow it; headless flows never navigate to a hosted\n // page, so no window opens here.\n const popup = headless ? null : window.open('', 'mastra-connect', POPUP_FEATURES);\n popupRef.current = popup;\n session.mutate(\n { provider: provider.id },\n {\n onSuccess: created => {\n if (headless) {\n void beginHeadless(provider, created, values);\n return;\n }\n // The popup enters OAuth through the server's launch gate, which\n // verifies this browser owns the pending session before\n // redirecting — the raw vendor URL never reaches the page.\n const launchHref = resolveLaunchHref(created.launchUrl);\n if (popup) {\n popup.location.href = launchHref;\n }\n const next: PendingConnect = {\n provider: provider.id,\n connectionId: created.connectionId,\n mode: 'hosted',\n fallbackUrl: popup ? null : launchHref,\n };\n pendingRef.current = next;\n setPending(next);\n },\n onError: () => {\n popup?.close();\n popupRef.current = null;\n },\n },\n );\n },\n [session, resolveLaunchHref, beginHeadless],\n );\n\n /** Abandons the in-flight flow: closes the provider window, hands the Connect action back. */\n const cancel = useCallback(() => {\n headlessRef.current?.close();\n headlessRef.current = null;\n popupRef.current = null;\n pendingRef.current = null;\n setPending(null);\n setSettled(null);\n }, []);\n\n return {\n pending,\n connect,\n cancel,\n /** Outcome of the most recently settled flow; null while none settled or a flow is in progress. */\n settled,\n /** Providers whose connection settled `active` during this hook's lifetime. */\n connectedProviders,\n isCreating: session.isPending,\n createError: session.isError ? session.error : null,\n };\n}\n\n/** Disconnects one of the caller's own connections. */\nexport function useDisconnectUserConnection() {\n const connectFetch = useConnectFetch();\n const queryClient = useQueryClient();\n\n return useMutation<void, UserConnectRequestError, { connectionId: string }>({\n mutationFn: ({ connectionId }) =>\n connectFetch<void>(`/connections/${encodeURIComponent(connectionId)}`, { method: 'DELETE' }),\n onSuccess: () => invalidateAfterConnectionChange(queryClient),\n });\n}\n"],"mappings":"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"}