@8wave/ai-elements 0.111.0 → 0.112.0-beta.3

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 (40) hide show
  1. package/dist/_chunks/{PkToolRequestGeolocation-BP5yJQIW.js → PkToolRequestGeolocation-65jeiJs8.js} +5 -5
  2. package/dist/_chunks/{PkToolRequestGeolocation-BP5yJQIW.js.map → PkToolRequestGeolocation-65jeiJs8.js.map} +1 -1
  3. package/dist/_chunks/{PkToolRequestOAuthConnection-4SqtPTCS.js → PkToolRequestOAuthConnection-bHpDAtzu.js} +5 -5
  4. package/dist/_chunks/{PkToolRequestOAuthConnection-4SqtPTCS.js.map → PkToolRequestOAuthConnection-bHpDAtzu.js.map} +1 -1
  5. package/dist/_chunks/{PkToolShowForm-C45JD6jS.js → PkToolShowForm-KuTsIy5P.js} +21 -21
  6. package/dist/_chunks/{PkToolShowForm-C45JD6jS.js.map → PkToolShowForm-KuTsIy5P.js.map} +1 -1
  7. package/dist/_chunks/{PkToolShowSources-DlBfHwkg.js → PkToolShowSources-CY_q7ANb.js} +4 -4
  8. package/dist/_chunks/{PkToolShowSources-DlBfHwkg.js.map → PkToolShowSources-CY_q7ANb.js.map} +1 -1
  9. package/dist/_chunks/{PkToolShowSuggestedReply-DzoRQESi.js → PkToolShowSuggestedReply-D98Atidw.js} +5 -5
  10. package/dist/_chunks/{PkToolShowSuggestedReply-DzoRQESi.js.map → PkToolShowSuggestedReply-D98Atidw.js.map} +1 -1
  11. package/dist/_chunks/{Document-Bmx6774B.js → markdown-BRD-hDrH.js} +70 -72
  12. package/dist/_chunks/markdown-BRD-hDrH.js.map +1 -0
  13. package/dist/ai-elements.es.js +602 -590
  14. package/dist/ai-elements.es.js.map +1 -1
  15. package/dist-vue/PkChatbot.js +1 -1
  16. package/dist-vue/PkChatbotMessages.js +1 -1
  17. package/dist-vue/PkChatbotViewChat.js +1 -1
  18. package/dist-vue/PkChatbotViewFiles.js +1 -1
  19. package/dist-vue/_chunks/{Document-8umjeTVS.js → Document-9SUVPzht.js} +70 -72
  20. package/dist-vue/_chunks/Document-9SUVPzht.js.map +1 -0
  21. package/dist-vue/_chunks/{PkChatbot-CloRPTIX.js → PkChatbot-BBR2t9R9.js} +3 -3
  22. package/dist-vue/_chunks/{PkChatbot-CloRPTIX.js.map → PkChatbot-BBR2t9R9.js.map} +1 -1
  23. package/dist-vue/_chunks/{PkChatbotMessages-r9D-kFvX.js → PkChatbotMessages-9NluDSgJ.js} +3 -3
  24. package/dist-vue/_chunks/{PkChatbotMessages-r9D-kFvX.js.map → PkChatbotMessages-9NluDSgJ.js.map} +1 -1
  25. package/dist-vue/_chunks/{PkChatbotViewChat-DP0EUTDu.js → PkChatbotViewChat-C0fxsO-G.js} +3 -3
  26. package/dist-vue/_chunks/{PkChatbotViewChat-DP0EUTDu.js.map → PkChatbotViewChat-C0fxsO-G.js.map} +1 -1
  27. package/dist-vue/_chunks/{PkChatbotViewFiles-hzlJUf0f.js → PkChatbotViewFiles-DPpZHHvK.js} +2 -2
  28. package/dist-vue/_chunks/{PkChatbotViewFiles-hzlJUf0f.js.map → PkChatbotViewFiles-DPpZHHvK.js.map} +1 -1
  29. package/dist-vue/_chunks/{PkToolShowDiagram-BAdJMmt7.js → PkToolShowDiagram-CVHYhfwH.js} +2 -2
  30. package/dist-vue/_chunks/{PkToolShowDiagram-BAdJMmt7.js.map → PkToolShowDiagram-CVHYhfwH.js.map} +1 -1
  31. package/dist-vue/_chunks/{PkToolShowSources-Dlaz2Y77.js → PkToolShowSources-Qq3c8_ZQ.js} +2 -2
  32. package/dist-vue/_chunks/{PkToolShowSources-Dlaz2Y77.js.map → PkToolShowSources-Qq3c8_ZQ.js.map} +1 -1
  33. package/dist-vue/index.js +3136 -3116
  34. package/dist-vue/index.js.map +1 -1
  35. package/dist-vue/packages/components/src/chat/PkLanguageModelCombobox.d.ts +7 -0
  36. package/dist-vue/packages/models/src/schema/ModelProvider.d.ts +121 -8
  37. package/dist-vue/packages/models/src/schema/constants.d.ts +2 -1
  38. package/package.json +1 -1
  39. package/dist/_chunks/Document-Bmx6774B.js.map +0 -1
  40. package/dist-vue/_chunks/Document-8umjeTVS.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { C as e, F as t, L as n, O as r, Tt as i, _ as a, b as o, bt as s, d as c, h as l, it as u, m as d, v as f, x as p, xt as m } from "./vue.runtime.esm-bundler-CIT_Sbqw.js";
2
2
  import { r as h } from "./vue-i18n-BdMHu4Kd.js";
3
- import { t as g } from "./index.es-C2U5wcOx.js";
4
- import { u as _ } from "./Agent-tE7ZJPxG.js";
3
+ import { u as g } from "./Agent-tE7ZJPxG.js";
4
+ import { t as _ } from "./index.es-C2U5wcOx.js";
5
5
  r(), u(), m();
6
6
  var v = { class: "flex items-center gap-6 text-12 text-word-3 border border-surface-3 rounded-xl px-sm py-10" }, y = /* @__PURE__ */ e({
7
7
  __name: "PkToolRequestGeolocation",
@@ -12,7 +12,7 @@ var v = { class: "flex items-center gap-6 text-12 text-word-3 border border-surf
12
12
  },
13
13
  emits: ["result"],
14
14
  setup(e, { emit: r }) {
15
- let u = e, m = r, { t: y } = h({ useScope: "global" }), b = d(() => u.part), x = d(() => b.value.state ?? "unknown"), S = d(() => _(b.value.output)), C = d(() => x.value === "output-available" && !S.value), w = d(() => {
15
+ let u = e, m = r, { t: y } = h({ useScope: "global" }), b = d(() => u.part), x = d(() => b.value.state ?? "unknown"), S = d(() => g(b.value.output)), C = d(() => x.value === "output-available" && !S.value), w = d(() => {
16
16
  let e = b.value.output;
17
17
  if (!(!e || typeof e == "string" || "error" in e)) return e;
18
18
  }), T = d(() => {
@@ -41,7 +41,7 @@ var v = { class: "flex items-center gap-6 text-12 text-word-3 border border-surf
41
41
  }, { timeout: 15e3 });
42
42
  }
43
43
  }), (e, t) => {
44
- let r = g;
44
+ let r = _;
45
45
  return n(), f("div", v, [C.value ? w.value ? (n(), f(c, { key: 1 }, [p(r, {
46
46
  name: "ri:map-pin-line",
47
47
  class: "text-16"
@@ -66,4 +66,4 @@ var v = { class: "flex items-center gap-6 text-12 text-word-3 border border-surf
66
66
  //#endregion
67
67
  export { y as default };
68
68
 
69
- //# sourceMappingURL=PkToolRequestGeolocation-BP5yJQIW.js.map
69
+ //# sourceMappingURL=PkToolRequestGeolocation-65jeiJs8.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkToolRequestGeolocation-BP5yJQIW.js","names":["$n"],"sources":["../../../../packages/components/src/chat/PkToolRequestGeolocation.vue","../../../../packages/components/src/chat/PkToolRequestGeolocation.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, onMounted } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n import type { ReverseGeocodeFn } from 'utils/src/device-context'\n\n type GeolocationOutput =\n | {\n latitude: number\n longitude: number\n accuracy?: number\n city?: string\n }\n | { error: 'permission_denied' | 'position_unavailable' | 'timeout' }\n\n const props = defineProps<{\n part: unknown\n reverseGeocode?: ReverseGeocodeFn\n /**\n * Whether a voice call is on the line right now. A request drawn\n * during a call arrives already resolved with the render ack, and the\n * browser prompt must fire only while the caller who was asked is\n * still there: reopening the conversation later must not ask again.\n */\n callLive?: boolean\n }>()\n\n const emit = defineEmits<{\n result: [output: GeolocationOutput]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n input?: { reason?: string }\n /** The position, or the voice render ack (a string). */\n output?: GeolocationOutput | string\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n\n /**\n * The ack is not a position: it only says the card was drawn during a\n * call (see `isVoiceRenderAck`, same rule as the forms). Besides gating\n * the prompt below, it must be kept away from the output parsing: `in`\n * on a string throws, which broke this card on every spoken turn.\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n const isOutputAvailable = computed(\n () => state.value === 'output-available' && !isRenderAck.value,\n )\n\n const successOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || 'error' in out) {\n return undefined\n }\n return out\n })\n\n const errorOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || !('error' in out)) {\n return undefined\n }\n return out\n })\n\n /** Maps GeolocationPositionError.code to a typed string */\n function mapGeolocationError(\n code: number,\n ): 'permission_denied' | 'position_unavailable' | 'timeout' {\n if (code === 1) {\n return 'permission_denied'\n }\n if (code === 3) {\n return 'timeout'\n }\n return 'position_unavailable'\n }\n\n onMounted(() => {\n const isAwaitingPosition =\n state.value === 'input-available' ||\n (isRenderAck.value && props.callLive === true)\n if (!isAwaitingPosition) {\n return\n }\n\n if (!navigator.geolocation) {\n emit('result', { error: 'position_unavailable' })\n return\n }\n\n navigator.geolocation.getCurrentPosition(\n async (position) => {\n const { latitude, longitude, accuracy } = position.coords\n const city = props.reverseGeocode\n ? await props\n .reverseGeocode(latitude, longitude)\n .catch(() => undefined)\n : undefined\n emit('result', { latitude, longitude, accuracy, city })\n },\n (error) => {\n emit('result', { error: mapGeolocationError(error.code) })\n },\n { timeout: 15000 },\n )\n })\n</script>\n\n<template>\n <div\n class=\"flex items-center gap-6 text-12 text-word-3 border border-surface-3 rounded-xl px-sm py-10\">\n <!-- Loading state -->\n <template v-if=\"!isOutputAvailable\">\n <VvIcon name=\"loading-loop\" class=\"text-16\" />\n <span>{{ $t('messagePart.requestGeolocation') }}</span>\n </template>\n\n <!-- Success state -->\n <template v-else-if=\"successOutput\">\n <VvIcon name=\"ri:map-pin-line\" class=\"text-16\" />\n <span>\n {{\n $n(successOutput.latitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }},\n {{\n $n(successOutput.longitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }}\n <template v-if=\"successOutput.accuracy !== undefined\">\n (±{{\n $n(successOutput.accuracy, {\n style: 'unit',\n unit: 'meter',\n })\n }})\n </template>\n </span>\n </template>\n\n <!-- Error state -->\n <template v-else-if=\"errorOutput\">\n <span class=\"i-vv-alert-circle text-16 shrink-0 text-danger\" />\n <span>\n {{\n errorOutput.error === 'permission_denied'\n ? $t('message.geolocationPermissionDenied')\n : $t('message.geolocationUnavailable')\n }}\n </span>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, onMounted } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n import type { ReverseGeocodeFn } from 'utils/src/device-context'\n\n type GeolocationOutput =\n | {\n latitude: number\n longitude: number\n accuracy?: number\n city?: string\n }\n | { error: 'permission_denied' | 'position_unavailable' | 'timeout' }\n\n const props = defineProps<{\n part: unknown\n reverseGeocode?: ReverseGeocodeFn\n /**\n * Whether a voice call is on the line right now. A request drawn\n * during a call arrives already resolved with the render ack, and the\n * browser prompt must fire only while the caller who was asked is\n * still there: reopening the conversation later must not ask again.\n */\n callLive?: boolean\n }>()\n\n const emit = defineEmits<{\n result: [output: GeolocationOutput]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n input?: { reason?: string }\n /** The position, or the voice render ack (a string). */\n output?: GeolocationOutput | string\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n\n /**\n * The ack is not a position: it only says the card was drawn during a\n * call (see `isVoiceRenderAck`, same rule as the forms). Besides gating\n * the prompt below, it must be kept away from the output parsing: `in`\n * on a string throws, which broke this card on every spoken turn.\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n const isOutputAvailable = computed(\n () => state.value === 'output-available' && !isRenderAck.value,\n )\n\n const successOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || 'error' in out) {\n return undefined\n }\n return out\n })\n\n const errorOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || !('error' in out)) {\n return undefined\n }\n return out\n })\n\n /** Maps GeolocationPositionError.code to a typed string */\n function mapGeolocationError(\n code: number,\n ): 'permission_denied' | 'position_unavailable' | 'timeout' {\n if (code === 1) {\n return 'permission_denied'\n }\n if (code === 3) {\n return 'timeout'\n }\n return 'position_unavailable'\n }\n\n onMounted(() => {\n const isAwaitingPosition =\n state.value === 'input-available' ||\n (isRenderAck.value && props.callLive === true)\n if (!isAwaitingPosition) {\n return\n }\n\n if (!navigator.geolocation) {\n emit('result', { error: 'position_unavailable' })\n return\n }\n\n navigator.geolocation.getCurrentPosition(\n async (position) => {\n const { latitude, longitude, accuracy } = position.coords\n const city = props.reverseGeocode\n ? await props\n .reverseGeocode(latitude, longitude)\n .catch(() => undefined)\n : undefined\n emit('result', { latitude, longitude, accuracy, city })\n },\n (error) => {\n emit('result', { error: mapGeolocationError(error.code) })\n },\n { timeout: 15000 },\n )\n })\n</script>\n\n<template>\n <div\n class=\"flex items-center gap-6 text-12 text-word-3 border border-surface-3 rounded-xl px-sm py-10\">\n <!-- Loading state -->\n <template v-if=\"!isOutputAvailable\">\n <VvIcon name=\"loading-loop\" class=\"text-16\" />\n <span>{{ $t('messagePart.requestGeolocation') }}</span>\n </template>\n\n <!-- Success state -->\n <template v-else-if=\"successOutput\">\n <VvIcon name=\"ri:map-pin-line\" class=\"text-16\" />\n <span>\n {{\n $n(successOutput.latitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }},\n {{\n $n(successOutput.longitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }}\n <template v-if=\"successOutput.accuracy !== undefined\">\n (±{{\n $n(successOutput.accuracy, {\n style: 'unit',\n unit: 'meter',\n })\n }})\n </template>\n </span>\n </template>\n\n <!-- Error state -->\n <template v-else-if=\"errorOutput\">\n <span class=\"i-vv-alert-circle text-16 shrink-0 text-danger\" />\n <span>\n {{\n errorOutput.error === 'permission_denied'\n ? $t('message.geolocationPermissionDenied')\n : $t('message.geolocationUnavailable')\n }}\n </span>\n </template>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;EAgBI,IAAM,IAAQ,GAYR,IAAO,GAIP,EAAE,GAAG,MAAO,EAAQ,EAAE,UAAU,SAAS,CAAC,GAE1C,IAAW,QACN,EAAM,IAMhB,GAEK,IAAQ,QAAe,EAAS,MAAM,SAAS,SAAS,GAQxD,IAAc,QAAe,EAAiB,EAAS,MAAM,MAAM,CAAC,GAEpE,IAAoB,QAChB,EAAM,UAAU,sBAAsB,CAAC,EAAY,KAC7D,GAEM,IAAgB,QAAe;GACjC,IAAM,IAAM,EAAS,MAAM;GAC3B,IAAI,GAAC,KAAO,OAAO,KAAQ,YAAY,WAAW,IAGlD,OAAO;EACX,CAAC,GAEK,IAAc,QAAe;GAC/B,IAAM,IAAM,EAAS,MAAM;GAC3B,IAAK,KAAO,OAAO,KAAQ,YAAc,WAAW,GAGpD,OAAO;EACX,CAAC;EAGD,SAAS,EACL,GACwD;GAOxD,OANI,MAAS,IACF,sBAEP,MAAS,IACF,YAEJ;EACX;SAEA,QAAgB;GAIZ,IAFI,EAAM,UAAU,qBACf,EAAY,SAAS,EAAM,aAAa,IAK7C;QAAI,CAAC,UAAU,aAAa;KACxB,EAAK,UAAU,EAAE,OAAO,uBAAuB,CAAC;KAChD;IACJ;IAEA,UAAU,YAAY,mBAClB,OAAO,MAAa;KAChB,IAAM,EAAE,aAAU,cAAW,gBAAa,EAAS,QAC7C,IAAO,EAAM,iBACb,MAAM,EACD,eAAe,GAAU,CAAS,CAAA,CAClC,YAAY,KAAA,CAAS,IAC1B,KAAA;KACN,EAAK,UAAU;MAAE;MAAU;MAAW;MAAU;KAAK,CAAC;IAC1D,IACC,MAAU;KACP,EAAK,UAAU,EAAE,OAAO,EAAoB,EAAM,IAAI,EAAE,CAAC;IAC7D,GACA,EAAE,SAAS,KAAM,CACrB;GAhBA;EAiBJ,CAAC;;GAID,OAAA,EAAA,GAAA,EAgDM,OAhDN,GAgDM,CA7Ce,EAAA,QAMI,EAAA,SAArB,EAAA,GAAA,EA0BW,GAAA,EAAA,KAAA,EAAA,GAAA,CAzBP,EAAiD,GAAA;IAAzC,MAAK;IAAkB,OAAM;GACrC,CAAA,GAAA,EAuBO,QAAA,MAAA,CArBCA,EAAAA,EAAAA,EAAAA,GAAG,EAAA,MAAc,UAAQ;;;;GAK3B,CAAA,CAAA,IAAA,OACF,EACIA,EAAAA,GAAG,EAAA,MAAc,WAAS;;;;GAK5B,CAAA,CAAA,IAAA,KACF,CAAA,GAAgB,EAAA,MAAc,aAAa,KAAA,IAO3C,EAAA,IAAA,EAAA,KAPA,EAAA,GAAA,EAOW,GAAA,EAAA,KAAA,EAAA,GAAA,CAP2C,EAAA,QAChD,EACEA,EAAAA,GAAG,EAAA,MAAc,UAAQ;;;GAI3B,CAAA,CAAA,IAAA,MACN,CAAA,CAAA,GAAA,EAAA,EAAA,CAAA,CAAA,GAAA,EAAA,KAKa,EAAA,SAArB,EAAA,GAAA,EASW,GAAA,EAAA,KAAA,EAAA,GAAA,CARP,AAAA,EAAA,OAAA,EAA+D,QAAA,EAAzD,OAAM,iDAAgD,GAAA,MAAA,EAAA,GAC5D,EAMO,QAAA,MAAA,EAJC,EAAA,MAAY,UAAK,sBAAmD,EAAA,CAAA,CAAE,CAAA,qCAAA,IAAkE,EAAA,CAAA,CAAE,CAAA,gCAAA,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA,KAAA,EAAA,IAAA,EAAA,KAvCtJ,EAAA,GAAA,EAGW,GAAA,EAAA,KAAA,EAAA,GAAA,CAFP,EAA8C,GAAA;IAAtC,MAAK;IAAe,OAAM;GAClC,CAAA,GAAA,EAAuD,QAAA,MAAA,EAA9C,EAAA,CAAA,CAAE,CAAA,gCAAA,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA,EAqCuI,CAAA"}
1
+ {"version":3,"file":"PkToolRequestGeolocation-65jeiJs8.js","names":["$n"],"sources":["../../../../packages/components/src/chat/PkToolRequestGeolocation.vue","../../../../packages/components/src/chat/PkToolRequestGeolocation.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, onMounted } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n import type { ReverseGeocodeFn } from 'utils/src/device-context'\n\n type GeolocationOutput =\n | {\n latitude: number\n longitude: number\n accuracy?: number\n city?: string\n }\n | { error: 'permission_denied' | 'position_unavailable' | 'timeout' }\n\n const props = defineProps<{\n part: unknown\n reverseGeocode?: ReverseGeocodeFn\n /**\n * Whether a voice call is on the line right now. A request drawn\n * during a call arrives already resolved with the render ack, and the\n * browser prompt must fire only while the caller who was asked is\n * still there: reopening the conversation later must not ask again.\n */\n callLive?: boolean\n }>()\n\n const emit = defineEmits<{\n result: [output: GeolocationOutput]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n input?: { reason?: string }\n /** The position, or the voice render ack (a string). */\n output?: GeolocationOutput | string\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n\n /**\n * The ack is not a position: it only says the card was drawn during a\n * call (see `isVoiceRenderAck`, same rule as the forms). Besides gating\n * the prompt below, it must be kept away from the output parsing: `in`\n * on a string throws, which broke this card on every spoken turn.\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n const isOutputAvailable = computed(\n () => state.value === 'output-available' && !isRenderAck.value,\n )\n\n const successOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || 'error' in out) {\n return undefined\n }\n return out\n })\n\n const errorOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || !('error' in out)) {\n return undefined\n }\n return out\n })\n\n /** Maps GeolocationPositionError.code to a typed string */\n function mapGeolocationError(\n code: number,\n ): 'permission_denied' | 'position_unavailable' | 'timeout' {\n if (code === 1) {\n return 'permission_denied'\n }\n if (code === 3) {\n return 'timeout'\n }\n return 'position_unavailable'\n }\n\n onMounted(() => {\n const isAwaitingPosition =\n state.value === 'input-available' ||\n (isRenderAck.value && props.callLive === true)\n if (!isAwaitingPosition) {\n return\n }\n\n if (!navigator.geolocation) {\n emit('result', { error: 'position_unavailable' })\n return\n }\n\n navigator.geolocation.getCurrentPosition(\n async (position) => {\n const { latitude, longitude, accuracy } = position.coords\n const city = props.reverseGeocode\n ? await props\n .reverseGeocode(latitude, longitude)\n .catch(() => undefined)\n : undefined\n emit('result', { latitude, longitude, accuracy, city })\n },\n (error) => {\n emit('result', { error: mapGeolocationError(error.code) })\n },\n { timeout: 15000 },\n )\n })\n</script>\n\n<template>\n <div\n class=\"flex items-center gap-6 text-12 text-word-3 border border-surface-3 rounded-xl px-sm py-10\">\n <!-- Loading state -->\n <template v-if=\"!isOutputAvailable\">\n <VvIcon name=\"loading-loop\" class=\"text-16\" />\n <span>{{ $t('messagePart.requestGeolocation') }}</span>\n </template>\n\n <!-- Success state -->\n <template v-else-if=\"successOutput\">\n <VvIcon name=\"ri:map-pin-line\" class=\"text-16\" />\n <span>\n {{\n $n(successOutput.latitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }},\n {{\n $n(successOutput.longitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }}\n <template v-if=\"successOutput.accuracy !== undefined\">\n (±{{\n $n(successOutput.accuracy, {\n style: 'unit',\n unit: 'meter',\n })\n }})\n </template>\n </span>\n </template>\n\n <!-- Error state -->\n <template v-else-if=\"errorOutput\">\n <span class=\"i-vv-alert-circle text-16 shrink-0 text-danger\" />\n <span>\n {{\n errorOutput.error === 'permission_denied'\n ? $t('message.geolocationPermissionDenied')\n : $t('message.geolocationUnavailable')\n }}\n </span>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, onMounted } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n import type { ReverseGeocodeFn } from 'utils/src/device-context'\n\n type GeolocationOutput =\n | {\n latitude: number\n longitude: number\n accuracy?: number\n city?: string\n }\n | { error: 'permission_denied' | 'position_unavailable' | 'timeout' }\n\n const props = defineProps<{\n part: unknown\n reverseGeocode?: ReverseGeocodeFn\n /**\n * Whether a voice call is on the line right now. A request drawn\n * during a call arrives already resolved with the render ack, and the\n * browser prompt must fire only while the caller who was asked is\n * still there: reopening the conversation later must not ask again.\n */\n callLive?: boolean\n }>()\n\n const emit = defineEmits<{\n result: [output: GeolocationOutput]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n input?: { reason?: string }\n /** The position, or the voice render ack (a string). */\n output?: GeolocationOutput | string\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n\n /**\n * The ack is not a position: it only says the card was drawn during a\n * call (see `isVoiceRenderAck`, same rule as the forms). Besides gating\n * the prompt below, it must be kept away from the output parsing: `in`\n * on a string throws, which broke this card on every spoken turn.\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n const isOutputAvailable = computed(\n () => state.value === 'output-available' && !isRenderAck.value,\n )\n\n const successOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || 'error' in out) {\n return undefined\n }\n return out\n })\n\n const errorOutput = computed(() => {\n const out = toolPart.value.output\n if (!out || typeof out === 'string' || !('error' in out)) {\n return undefined\n }\n return out\n })\n\n /** Maps GeolocationPositionError.code to a typed string */\n function mapGeolocationError(\n code: number,\n ): 'permission_denied' | 'position_unavailable' | 'timeout' {\n if (code === 1) {\n return 'permission_denied'\n }\n if (code === 3) {\n return 'timeout'\n }\n return 'position_unavailable'\n }\n\n onMounted(() => {\n const isAwaitingPosition =\n state.value === 'input-available' ||\n (isRenderAck.value && props.callLive === true)\n if (!isAwaitingPosition) {\n return\n }\n\n if (!navigator.geolocation) {\n emit('result', { error: 'position_unavailable' })\n return\n }\n\n navigator.geolocation.getCurrentPosition(\n async (position) => {\n const { latitude, longitude, accuracy } = position.coords\n const city = props.reverseGeocode\n ? await props\n .reverseGeocode(latitude, longitude)\n .catch(() => undefined)\n : undefined\n emit('result', { latitude, longitude, accuracy, city })\n },\n (error) => {\n emit('result', { error: mapGeolocationError(error.code) })\n },\n { timeout: 15000 },\n )\n })\n</script>\n\n<template>\n <div\n class=\"flex items-center gap-6 text-12 text-word-3 border border-surface-3 rounded-xl px-sm py-10\">\n <!-- Loading state -->\n <template v-if=\"!isOutputAvailable\">\n <VvIcon name=\"loading-loop\" class=\"text-16\" />\n <span>{{ $t('messagePart.requestGeolocation') }}</span>\n </template>\n\n <!-- Success state -->\n <template v-else-if=\"successOutput\">\n <VvIcon name=\"ri:map-pin-line\" class=\"text-16\" />\n <span>\n {{\n $n(successOutput.latitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }},\n {{\n $n(successOutput.longitude, {\n style: 'decimal',\n minimumFractionDigits: 5,\n maximumFractionDigits: 5,\n })\n }}\n <template v-if=\"successOutput.accuracy !== undefined\">\n (±{{\n $n(successOutput.accuracy, {\n style: 'unit',\n unit: 'meter',\n })\n }})\n </template>\n </span>\n </template>\n\n <!-- Error state -->\n <template v-else-if=\"errorOutput\">\n <span class=\"i-vv-alert-circle text-16 shrink-0 text-danger\" />\n <span>\n {{\n errorOutput.error === 'permission_denied'\n ? $t('message.geolocationPermissionDenied')\n : $t('message.geolocationUnavailable')\n }}\n </span>\n </template>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;EAgBI,IAAM,IAAQ,GAYR,IAAO,GAIP,EAAE,GAAG,MAAO,EAAQ,EAAE,UAAU,SAAS,CAAC,GAE1C,IAAW,QACN,EAAM,IAMhB,GAEK,IAAQ,QAAe,EAAS,MAAM,SAAS,SAAS,GAQxD,IAAc,QAAe,EAAiB,EAAS,MAAM,MAAM,CAAC,GAEpE,IAAoB,QAChB,EAAM,UAAU,sBAAsB,CAAC,EAAY,KAC7D,GAEM,IAAgB,QAAe;GACjC,IAAM,IAAM,EAAS,MAAM;GAC3B,IAAI,GAAC,KAAO,OAAO,KAAQ,YAAY,WAAW,IAGlD,OAAO;EACX,CAAC,GAEK,IAAc,QAAe;GAC/B,IAAM,IAAM,EAAS,MAAM;GAC3B,IAAK,KAAO,OAAO,KAAQ,YAAc,WAAW,GAGpD,OAAO;EACX,CAAC;EAGD,SAAS,EACL,GACwD;GAOxD,OANI,MAAS,IACF,sBAEP,MAAS,IACF,YAEJ;EACX;SAEA,QAAgB;GAIZ,IAFI,EAAM,UAAU,qBACf,EAAY,SAAS,EAAM,aAAa,IAK7C;QAAI,CAAC,UAAU,aAAa;KACxB,EAAK,UAAU,EAAE,OAAO,uBAAuB,CAAC;KAChD;IACJ;IAEA,UAAU,YAAY,mBAClB,OAAO,MAAa;KAChB,IAAM,EAAE,aAAU,cAAW,gBAAa,EAAS,QAC7C,IAAO,EAAM,iBACb,MAAM,EACD,eAAe,GAAU,CAAS,CAAA,CAClC,YAAY,KAAA,CAAS,IAC1B,KAAA;KACN,EAAK,UAAU;MAAE;MAAU;MAAW;MAAU;KAAK,CAAC;IAC1D,IACC,MAAU;KACP,EAAK,UAAU,EAAE,OAAO,EAAoB,EAAM,IAAI,EAAE,CAAC;IAC7D,GACA,EAAE,SAAS,KAAM,CACrB;GAhBA;EAiBJ,CAAC;;GAID,OAAA,EAAA,GAAA,EAgDM,OAhDN,GAgDM,CA7Ce,EAAA,QAMI,EAAA,SAArB,EAAA,GAAA,EA0BW,GAAA,EAAA,KAAA,EAAA,GAAA,CAzBP,EAAiD,GAAA;IAAzC,MAAK;IAAkB,OAAM;GACrC,CAAA,GAAA,EAuBO,QAAA,MAAA,CArBCA,EAAAA,EAAAA,EAAAA,GAAG,EAAA,MAAc,UAAQ;;;;GAK3B,CAAA,CAAA,IAAA,OACF,EACIA,EAAAA,GAAG,EAAA,MAAc,WAAS;;;;GAK5B,CAAA,CAAA,IAAA,KACF,CAAA,GAAgB,EAAA,MAAc,aAAa,KAAA,IAO3C,EAAA,IAAA,EAAA,KAPA,EAAA,GAAA,EAOW,GAAA,EAAA,KAAA,EAAA,GAAA,CAP2C,EAAA,QAChD,EACEA,EAAAA,GAAG,EAAA,MAAc,UAAQ;;;GAI3B,CAAA,CAAA,IAAA,MACN,CAAA,CAAA,GAAA,EAAA,EAAA,CAAA,CAAA,GAAA,EAAA,KAKa,EAAA,SAArB,EAAA,GAAA,EASW,GAAA,EAAA,KAAA,EAAA,GAAA,CARP,AAAA,EAAA,OAAA,EAA+D,QAAA,EAAzD,OAAM,iDAAgD,GAAA,MAAA,EAAA,GAC5D,EAMO,QAAA,MAAA,EAJC,EAAA,MAAY,UAAK,sBAAmD,EAAA,CAAA,CAAE,CAAA,qCAAA,IAAkE,EAAA,CAAA,CAAE,CAAA,gCAAA,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA,KAAA,EAAA,IAAA,EAAA,KAvCtJ,EAAA,GAAA,EAGW,GAAA,EAAA,KAAA,EAAA,GAAA,CAFP,EAA8C,GAAA;IAAtC,MAAK;IAAe,OAAM;GAClC,CAAA,GAAA,EAAuD,QAAA,MAAA,EAA9C,EAAA,CAAA,CAAE,CAAA,gCAAA,CAAA,GAAA,CAAA,CAAA,GAAA,EAAA,EAqCuI,CAAA"}
@@ -1,7 +1,7 @@
1
1
  import { C as e, F as t, I as n, L as r, O as i, Tt as a, _ as o, bt as s, d as c, g as l, h as u, it as d, m as f, pt as p, v as m, x as h, xt as g } from "./vue.runtime.esm-bundler-CIT_Sbqw.js";
2
2
  import { r as _ } from "./vue-i18n-BdMHu4Kd.js";
3
- import { r as v, t as y } from "./index.es-C2U5wcOx.js";
4
- import { u as b } from "./Agent-tE7ZJPxG.js";
3
+ import { u as v } from "./Agent-tE7ZJPxG.js";
4
+ import { r as y, t as b } from "./index.es-C2U5wcOx.js";
5
5
  i(), g(), d();
6
6
  var x = { class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12" }, S = { class: "px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8" }, C = { class: "truncate" }, ee = { class: "p-sm" }, w = {
7
7
  key: 0,
@@ -24,7 +24,7 @@ var x = { class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-s
24
24
  },
25
25
  emits: ["connected"],
26
26
  setup(e, { emit: i }) {
27
- let d = e, g = i, { t: N } = _({ useScope: "global" }), P = f(() => d.part), F = f(() => P.value.state ?? "unknown"), I = f(() => P.value.input?.serverName), L = f(() => b(P.value.output)), R = f(() => P.value.output?.connected === !0), z = f(() => F.value === "input-available" || L.value), B = p(null), V = p(!1), H = p(!1), U = p(null), W = f(() => V.value || R.value), G = f(() => !!I.value && !!d.resolveConnection);
27
+ let d = e, g = i, { t: N } = _({ useScope: "global" }), P = f(() => d.part), F = f(() => P.value.state ?? "unknown"), I = f(() => P.value.input?.serverName), L = f(() => v(P.value.output)), R = f(() => P.value.output?.connected === !0), z = f(() => F.value === "input-available" || L.value), B = p(null), V = p(!1), H = p(!1), U = p(null), W = f(() => V.value || R.value), G = f(() => !!I.value && !!d.resolveConnection);
28
28
  async function K() {
29
29
  if (!I.value || !d.checkConnection) return !1;
30
30
  try {
@@ -91,7 +91,7 @@ var x = { class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-s
91
91
  return t(() => window.addEventListener("message", Q)), n(() => {
92
92
  window.removeEventListener("message", Q), X();
93
93
  }), (e, t) => {
94
- let n = y, i = v;
94
+ let n = b, i = y;
95
95
  return r(), m("div", x, [u("div", S, [h(n, {
96
96
  name: "ri:link-m",
97
97
  class: "text-16 shrink-0"
@@ -119,4 +119,4 @@ var x = { class: "border border-surface-3 rounded-xl w-full overflow-hidden bg-s
119
119
  //#endregion
120
120
  export { N as default };
121
121
 
122
- //# sourceMappingURL=PkToolRequestOAuthConnection-4SqtPTCS.js.map
122
+ //# sourceMappingURL=PkToolRequestOAuthConnection-bHpDAtzu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PkToolRequestOAuthConnection-4SqtPTCS.js","names":[],"sources":["../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue","../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link-m\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link-m\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link-m\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link-m\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PkToolRequestOAuthConnection-bHpDAtzu.js","names":[],"sources":["../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue","../../../../packages/components/src/chat/PkToolRequestOAuthConnection.vue"],"sourcesContent":["<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link-m\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link-m\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n import { computed, onMounted, onUnmounted, ref } from 'vue'\n import { useI18n } from 'vue-i18n'\n import { isVoiceRenderAck } from 'models'\n\n const props = defineProps<{\n part: unknown\n resolveConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; authorizeUrl: string }>\n checkConnection?: (\n serverName: string,\n ) => Promise<{ mcpServerId: string; connected: boolean }>\n }>()\n\n const emit = defineEmits<{\n connected: [mcpServerId: string]\n }>()\n\n const { t: $t } = useI18n({ useScope: 'global' })\n\n /** How often the connection is re-read while the sign-in window is out. */\n const POLL_INTERVAL_MS = 2000\n /**\n * How long we keep asking. The only end to a sign-in nobody finishes:\n * `popup.closed` cannot be that signal, because the browser disowns the\n * handle when the provider severs the opener (that is what\n * `Cross-Origin-Opener-Policy` does), so it reads as closed from the first\n * check of the very flow this watch exists for. Long enough for a password\n * manager and a second factor.\n */\n const POLL_TIMEOUT_MS = 5 * 60 * 1000\n\n const toolPart = computed(() => {\n return props.part as {\n state?: string\n toolCallId?: string\n input?: { serverName: string }\n output?: unknown\n }\n })\n\n const state = computed(() => toolPart.value.state ?? 'unknown')\n const serverName = computed(() => toolPart.value.input?.serverName)\n\n /**\n * A card drawn during a CALL arrives already \"answered\": the turn could\n * not wait for the connection, so the backend closed the tool with a note\n * saying the card was drawn (see `isVoiceRenderAck`, same rule as the\n * forms). That note is not a connection, and without this the card\n * rendered its inert fallback: the caller was told to connect the account\n * and given nothing to click (measured 2026-08-07).\n */\n const isRenderAck = computed(() => isVoiceRenderAck(toolPart.value.output))\n\n /** The output this card wrote itself once the connection went through. */\n const isConnectedByOutput = computed(\n () =>\n (toolPart.value.output as { connected?: unknown } | undefined)\n ?.connected === true,\n )\n\n const isAwaitingConnection = computed(\n () => state.value === 'input-available' || isRenderAck.value,\n )\n\n const mcpServerId = ref<string | null>(null)\n /**\n * Set once THIS card's sign-in went through, and never from a plain read of\n * the connection: a card still asking is the turn saying it could not use\n * that integration, and a token row can outlive its usefulness (a refresh\n * that failed transiently keeps the row, so the turn sees no usable token\n * while a status read still sees one). Trusting the read there would have\n * left the card claiming success, with no button, on a turn still waiting.\n * It doubles as the guard that resumes the turn exactly once, whichever\n * channel got there first.\n */\n const hasConnected = ref(false)\n /** A sign-in window we opened is still out there. */\n const isSigningIn = ref(false)\n const oauthError = ref<string | null>(null)\n\n const isDone = computed(\n () => hasConnected.value || isConnectedByOutput.value,\n )\n const canConnect = computed(\n () => Boolean(serverName.value) && Boolean(props.resolveConnection),\n )\n\n /**\n * Whether the server says this user has connected, asked without minting\n * anything: no OAuth `state` is burned by looking.\n */\n async function isConnectedNow(): Promise<boolean> {\n if (!serverName.value || !props.checkConnection) {\n return false\n }\n try {\n const status = await props.checkConnection(serverName.value)\n mcpServerId.value = status.mcpServerId\n return status.connected\n } catch {\n // Not knowing is not an error to show: the sign-in window is still\n // out there and the next read may well answer.\n return false\n }\n }\n\n let pollTimer: ReturnType<typeof setTimeout> | undefined\n /**\n * Which sign-in a running tick belongs to. A tick suspended on its read\n * outlives the attempt that scheduled it: without this it woke up inside\n * the NEXT attempt, restarted its own chain over that one's timer and then\n * stopped it on its own older deadline.\n */\n let signInAttempt = 0\n /** The window this attempt opened, the only sender we accept a report from. */\n let signInWindow: Window | null = null\n\n function stopWatching() {\n if (pollTimer !== undefined) {\n clearTimeout(pollTimer)\n pollTimer = undefined\n }\n signInAttempt += 1\n signInWindow = null\n isSigningIn.value = false\n }\n\n function settleConnected(connectedServerId: string) {\n stopWatching()\n if (hasConnected.value) {\n return\n }\n hasConnected.value = true\n mcpServerId.value = connectedServerId\n oauthError.value = null\n emit('connected', connectedServerId)\n }\n\n /**\n * Watches the connection from THIS window while the sign-in window is away.\n *\n * The callback page reports through `window.opener`, and a provider that\n * sends `Cross-Origin-Opener-Policy` anywhere in its redirect chain takes\n * that opener away: the connection then succeeds with nobody listening, the\n * card stays on its button and the suspended turn is never resumed\n * (reported 2026-08-27). This channel never goes through that window, so it\n * survives whatever the provider does to it. Chained rather than periodic:\n * a read slower than the interval would otherwise overlap the next one.\n */\n function watchForConnection(popup: Window) {\n stopWatching()\n const attempt = signInAttempt\n signInWindow = popup\n isSigningIn.value = true\n const deadline = Date.now() + POLL_TIMEOUT_MS\n const tick = async () => {\n if (attempt !== signInAttempt || Date.now() > deadline) {\n if (attempt === signInAttempt) {\n stopWatching()\n }\n return\n }\n const connected = await isConnectedNow()\n if (attempt !== signInAttempt) {\n return\n }\n if (connected && mcpServerId.value) {\n settleConnected(mcpServerId.value)\n return\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n pollTimer = setTimeout(tick, POLL_INTERVAL_MS)\n }\n\n /**\n * The callback page's own report, when the browser let it through: faster\n * than the poll, and the only channel carrying the failure reason.\n *\n * Accepted only from the window this card opened, and only while that\n * sign-in is out: the listener stays up for as long as the card is on\n * screen, and nothing else with a handle on this page may resume a turn by\n * posting a message at it.\n */\n function onMessage(event: MessageEvent) {\n if (\n event.data?.type !== 'mcp-oauth-complete' ||\n !isSigningIn.value ||\n event.source !== signInWindow\n ) {\n return\n }\n const reportedServerId = event.data.mcpServerId as string | undefined\n if (\n reportedServerId &&\n mcpServerId.value &&\n reportedServerId !== mcpServerId.value\n ) {\n return\n }\n if (!event.data.success) {\n // A failure carries no server id: the callback never got that far.\n stopWatching()\n oauthError.value = event.data.error ?? 'Unknown error'\n return\n }\n const connectedServerId = reportedServerId ?? mcpServerId.value\n if (connectedServerId) {\n settleConnected(connectedServerId)\n }\n }\n\n /**\n * Opens the provider's sign-in, on an authorize URL minted NOW.\n *\n * The URL carries an OAuth `state` that lives ten minutes server-side.\n * Minted when the card rendered, it was routinely expired by the time\n * anyone clicked, and the callback then failed on a sign-in the user had\n * just completed. The window is opened blank first, synchronously, or the\n * popup blocker kills it while we wait for the URL.\n */\n async function openAuthWindow() {\n if (\n !serverName.value ||\n !props.resolveConnection ||\n isSigningIn.value\n ) {\n return\n }\n oauthError.value = null\n const popup = window.open(\n '',\n 'mcp_oauth',\n 'width=600,height=700,menubar=no,toolbar=no',\n )\n if (!popup) {\n oauthError.value = $t('auth.popupBlocked')\n return\n }\n watchForConnection(popup)\n try {\n const connection = await props.resolveConnection(serverName.value)\n mcpServerId.value = connection.mcpServerId\n popup.location.href = connection.authorizeUrl\n } catch (err) {\n stopWatching()\n popup.close()\n oauthError.value =\n err instanceof Error ? err.message : 'Failed to resolve OAuth'\n }\n }\n\n // For the card's whole life, not just between the click and the answer: a\n // card that re-renders while the sign-in window is away used to lose the\n // listener with it, and the completion landed on nobody.\n onMounted(() => window.addEventListener('message', onMessage))\n onUnmounted(() => {\n window.removeEventListener('message', onMessage)\n stopWatching()\n })\n</script>\n\n<template>\n <!-- The same frame and header as the other tool cards: this was the only\n one drawing itself as a tinted box (DESIGN.md). -->\n <div\n class=\"border border-surface-3 rounded-xl w-full overflow-hidden bg-surface text-12\">\n <div\n class=\"px-sm py-6 bg-surface-1 min-h-40 text-12 border-b border-surface-3 text-word-3 flex items-center gap-8\">\n <VvIcon name=\"ri:link-m\" class=\"text-16 shrink-0\" />\n <strong class=\"truncate\">{{ serverName }}</strong>\n </div>\n <div class=\"p-sm\">\n <!-- Connected: just now, or by this card on an earlier visit -->\n <template v-if=\"isDone\">\n <div class=\"flex items-center gap-8\">\n <VvIcon name=\"ri:check-line\" class=\"text-16 text-success\" />\n <strong class=\"font-medium text-word-1\">\n {{\n $t('messagePart.requestOAuthConnectionSuccess', {\n name: serverName,\n })\n }}\n </strong>\n </div>\n </template>\n\n <!-- Signing in: the window is out and we are watching for it -->\n <template v-else-if=\"isSigningIn\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"loading-loop\" />\n <span class=\"text-10\">{{\n $t('messagePart.requestOAuthConnection')\n }}</span>\n </div>\n </template>\n\n <!-- OAuth error -->\n <template v-else-if=\"oauthError\">\n <div class=\"flex flex-col gap-xs\">\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:close-line\" class=\"text-danger\" />\n <strong class=\"text-10\">\n {{ oauthError }}\n </strong>\n </div>\n <VvButton\n v-if=\"canConnect\"\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </div>\n </template>\n\n <!-- Waiting on the user: show the connect button -->\n <template v-else-if=\"isAwaitingConnection && canConnect\">\n <div class=\"flex items-center gap-6 text-word-3 mb-xs\">\n <span>\n {{\n $t('messagePart.requestOAuthConnectionPrompt', {\n name: serverName,\n })\n }}\n </span>\n </div>\n <VvButton\n modifiers=\"primary-small\"\n icon=\"ri:external-link-line\"\n :label=\"\n $t('messagePart.requestOAuthConnectionButton', {\n name: serverName,\n })\n \"\n @click=\"openAuthWindow\" />\n </template>\n <!-- Fallback for unknown state -->\n <template v-else>\n <div class=\"flex items-center gap-8 text-word-3\">\n <VvIcon name=\"ri:link-m\" />\n <span class=\"text-10\">\n {{\n $t('messagePart.requestOAuthConnectionInfo', {\n name: serverName,\n })\n }}\n </span>\n </div>\n </template>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { $ as e, B as t, C as n, G as r, K as i, L as a, M as o, O as s, St as c, Tt as l, X as u, _ as d, b as f, bt as p, d as m, dt as ee, g as h, h as g, it as _, j as v, l as te, m as y, pt as b, r as x, u as S, v as C, wt as ne, x as w, xt as T, y as E, z as D } from "./vue.runtime.esm-bundler-CIT_Sbqw.js";
2
2
  import { r as re } from "./vue-i18n-BdMHu4Kd.js";
3
3
  import { o as ie, s as ae, t as oe } from "./PkStreamingMarkdown-CNI8aV8t.js";
4
- import { c as O, f as se, o as k, p as A, r as ce, s as j, t as le } from "./index.es-C2U5wcOx.js";
5
- import { u as M } from "./Agent-tE7ZJPxG.js";
4
+ import { u as O } from "./Agent-tE7ZJPxG.js";
5
+ import { c as k, f as se, o as A, p as j, r as ce, s as M, t as le } from "./index.es-C2U5wcOx.js";
6
6
  s(), T();
7
7
  //#endregion
8
8
  //#region ../../packages/components/src/PkEditorRadioButtons.vue
@@ -37,7 +37,7 @@ var ue = /* @__PURE__ */ n({
37
37
  setup(n) {
38
38
  let s = n, c = i(n, "modelValue"), u = r(), d = y(() => `${s.name}-${u}`), p = y(() => [...s.modifiers ? Array.isArray(s.modifiers) ? s.modifiers : [s.modifiers] : [], s.segmented ? "segment" : "button"]);
39
39
  return (r, i) => {
40
- let s = j, u = O;
40
+ let s = M, u = k;
41
41
  return a(), h(u, {
42
42
  modelValue: c.value,
43
43
  "onUpdate:modelValue": i[0] ||= (e) => c.value = e,
@@ -99,7 +99,7 @@ var de = /* @__PURE__ */ n({
99
99
  setup(n) {
100
100
  let r = i(n, "modelValue");
101
101
  return (i, s) => {
102
- let c = k, u = A;
102
+ let c = A, u = j;
103
103
  return a(), h(u, {
104
104
  modelValue: r.value,
105
105
  "onUpdate:modelValue": s[0] ||= (e) => r.value = e,
@@ -166,17 +166,17 @@ var fe = {
166
166
  },
167
167
  emits: ["select"],
168
168
  setup(t, { emit: n }) {
169
- let r = t, i = n, { t: o, d: s } = re({ useScope: "global" }), f = y(() => JSON.parse(JSON.stringify(r.part))), _ = y(() => f.value.toolName === "showMultipleChoice" || f.value.type === "tool-showMultipleChoice"), v = y(() => M(f.value.output)), x = y(() => !r.answered && (f.value.state === "input-available" || v.value)), T = y(() => {
169
+ let r = t, i = n, { t: o, d: s } = re({ useScope: "global" }), f = y(() => JSON.parse(JSON.stringify(r.part))), _ = y(() => f.value.toolName === "showMultipleChoice" || f.value.type === "tool-showMultipleChoice"), v = y(() => O(f.value.output)), x = y(() => !r.answered && (f.value.state === "input-available" || v.value)), T = y(() => {
170
170
  let e = H.value;
171
- return e ? A.value.length === 1 && !e.allowFreeformInput && !r.allowCustomAnswer : !1;
172
- }), O = y(() => _.value && A.value.length === 1 && !H.value?.multiSelect), k = y(() => !!H.value?.options && !H.value?.multiSelect && (T.value || O.value)), A = y(() => {
171
+ return e ? j.value.length === 1 && !e.allowFreeformInput && !r.allowCustomAnswer : !1;
172
+ }), k = y(() => _.value && j.value.length === 1 && !H.value?.multiSelect), A = y(() => !!H.value?.options && !H.value?.multiSelect && (T.value || k.value)), j = y(() => {
173
173
  let e = f.value.input;
174
174
  return e?.steps?.length ? e.steps : e?.question && e?.options ? [{
175
175
  id: "legacy",
176
176
  question: e.question,
177
177
  options: e.options.map((e) => typeof e == "string" ? { label: e } : e)
178
178
  }] : [];
179
- }), j = y(() => f.value.state === "output-error" && !A.value.length), V = b(0), H = y(() => A.value[V.value]), U = y(() => V.value > 0), W = y(() => V.value < A.value.length - 1), Ae = y(() => A.value.length > 1), G = y(() => Ae.value ? Math.round((V.value + 1) / A.value.length * 100) : null), K = ee({}), q = b("");
179
+ }), M = y(() => f.value.state === "output-error" && !j.value.length), V = b(0), H = y(() => j.value[V.value]), U = y(() => V.value > 0), W = y(() => V.value < j.value.length - 1), Ae = y(() => j.value.length > 1), G = y(() => Ae.value ? Math.round((V.value + 1) / j.value.length * 100) : null), K = ee({}), q = b("");
180
180
  u(V, () => {
181
181
  q.value = "";
182
182
  });
@@ -203,11 +203,11 @@ var fe = {
203
203
  return Array.isArray(t) ? t : void 0;
204
204
  }
205
205
  u(() => f.value.output, (e) => {
206
- e && !M(e) && Object.assign(K, ie(A.value, e));
206
+ e && !O(e) && Object.assign(K, ie(j.value, e));
207
207
  }, { immediate: !0 });
208
208
  let Me = y(() => {
209
209
  let e = f.value.output;
210
- if (!e || M(e)) return;
210
+ if (!e || O(e)) return;
211
211
  if (typeof e == "string") return e;
212
212
  let t = je(e);
213
213
  if (t?.length === 1) {
@@ -231,21 +231,21 @@ var fe = {
231
231
  }
232
232
  function Q() {
233
233
  if (!x.value) return;
234
- let e = A.value.map((e, t) => ({
234
+ let e = j.value.map((e, t) => ({
235
235
  id: e.id,
236
236
  value: K[t] ?? null
237
237
  }));
238
- if (O.value && !H.value?.allowFreeformInput) {
238
+ if (k.value && !H.value?.allowFreeformInput) {
239
239
  let t = e[0].value;
240
240
  i("select", Array.isArray(t) ? t[0] : t);
241
241
  } else i("select", { answers: e });
242
242
  }
243
243
  function $() {
244
244
  let e = q.value.trim();
245
- e && (K[V.value] = e, q.value = "", A.value.length === 1 ? Q() : Z());
245
+ e && (K[V.value] = e, q.value = "", j.value.length === 1 ? Q() : Z());
246
246
  }
247
247
  function Pe() {
248
- x.value && (T.value || O.value) && Q();
248
+ x.value && (T.value || k.value) && Q();
249
249
  }
250
250
  function Fe(e, t) {
251
251
  let n = K[t];
@@ -253,12 +253,12 @@ var fe = {
253
253
  }
254
254
  return (t, n) => {
255
255
  let r = se, i = ce, s = le;
256
- return j.value ? (a(), C("div", fe, [n[4] ||= g("span", { class: "i-vv-alert-circle text-16 shrink-0" }, null, -1), g("span", null, l(p(o)("message.formRenderError")), 1)])) : A.value.length ? (a(), C("div", pe, [f.value.input?.title || x.value && A.value.length > 1 ? (a(), C("div", me, [f.value.input?.title ? (a(), C("div", {
256
+ return M.value ? (a(), C("div", fe, [n[4] ||= g("span", { class: "i-vv-alert-circle text-16 shrink-0" }, null, -1), g("span", null, l(p(o)("message.formRenderError")), 1)])) : j.value.length ? (a(), C("div", pe, [f.value.input?.title || x.value && j.value.length > 1 ? (a(), C("div", me, [f.value.input?.title ? (a(), C("div", {
257
257
  key: 0,
258
- class: c(["px-sm py-10 font-bold", { "border-b border-surface-3": x.value && A.value.length > 1 }])
259
- }, l(f.value.input?.title), 3)) : d("", !0), x.value && A.value.length > 1 ? (a(), C("div", N, [g("div", P, [g("span", null, l(p(o)("showForm.step", {
258
+ class: c(["px-sm py-10 font-bold", { "border-b border-surface-3": x.value && j.value.length > 1 }])
259
+ }, l(f.value.input?.title), 3)) : d("", !0), x.value && j.value.length > 1 ? (a(), C("div", N, [g("div", P, [g("span", null, l(p(o)("showForm.step", {
260
260
  current: V.value + 1,
261
- total: A.value.length
261
+ total: j.value.length
262
262
  })), 1), g("span", null, l(G.value) + "%", 1)]), g("div", F, [g("div", {
263
263
  class: "h-full bg-word-4 transition-all duration-300",
264
264
  style: ne({ width: `${G.value}%` })
@@ -337,7 +337,7 @@ var fe = {
337
337
  "disabled",
338
338
  "placeholder"
339
339
  ]))], 32)])) : d("", !0),
340
- k.value ? d("", !0) : (a(), C("div", xe, [U.value ? (a(), h(i, {
340
+ A.value ? d("", !0) : (a(), C("div", xe, [U.value ? (a(), h(i, {
341
341
  key: 0,
342
342
  modifiers: "action-quiet",
343
343
  icon: "ri:arrow-left-s-line",
@@ -354,7 +354,7 @@ var fe = {
354
354
  "icon",
355
355
  "label"
356
356
  ])]))
357
- ], 64)) : (a(), C(m, { key: 2 }, [g("div", Ce, [g("ul", we, [(a(!0), C(m, null, D(A.value, (e, t) => (a(), C("li", {
357
+ ], 64)) : (a(), C(m, { key: 2 }, [g("div", Ce, [g("ul", we, [(a(!0), C(m, null, D(j.value, (e, t) => (a(), C("li", {
358
358
  key: t,
359
359
  class: "leading-normal"
360
360
  }, [g("div", Te, l(e.question), 1), g("span", Ee, l(Fe(e, t)), 1)]))), 128))])]), g("div", De, [w(s, {
@@ -367,4 +367,4 @@ var fe = {
367
367
  //#endregion
368
368
  export { V as default };
369
369
 
370
- //# sourceMappingURL=PkToolShowForm-C45JD6jS.js.map
370
+ //# sourceMappingURL=PkToolShowForm-KuTsIy5P.js.map