@midscene/core 1.10.2-beta-20260706032158.0 → 1.10.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 (161) hide show
  1. package/dist/es/agent/agent.mjs +40 -1
  2. package/dist/es/agent/agent.mjs.map +1 -1
  3. package/dist/es/agent/metrics.mjs +81 -0
  4. package/dist/es/agent/metrics.mjs.map +1 -0
  5. package/dist/es/agent/task-builder.mjs +1 -1
  6. package/dist/es/agent/task-builder.mjs.map +1 -1
  7. package/dist/es/agent/utils.mjs +1 -1
  8. package/dist/es/ai-model/connectivity/run-connectivity-test.mjs +9 -3
  9. package/dist/es/ai-model/connectivity/run-connectivity-test.mjs.map +1 -1
  10. package/dist/es/ai-model/inspect.mjs +3 -3
  11. package/dist/es/ai-model/inspect.mjs.map +1 -1
  12. package/dist/es/ai-model/llm-planning.mjs +1 -1
  13. package/dist/es/ai-model/llm-planning.mjs.map +1 -1
  14. package/dist/es/ai-model/model-adapter/resolve.mjs +2 -2
  15. package/dist/es/ai-model/model-adapter/resolve.mjs.map +1 -1
  16. package/dist/es/ai-model/models/default.mjs +2 -1
  17. package/dist/es/ai-model/models/default.mjs.map +1 -1
  18. package/dist/es/ai-model/models/doubao.mjs +38 -60
  19. package/dist/es/ai-model/models/doubao.mjs.map +1 -1
  20. package/dist/es/ai-model/models/gemini.mjs +4 -15
  21. package/dist/es/ai-model/models/gemini.mjs.map +1 -1
  22. package/dist/es/ai-model/models/glm.mjs +4 -0
  23. package/dist/es/ai-model/models/glm.mjs.map +1 -1
  24. package/dist/es/ai-model/models/gpt.mjs +5 -1
  25. package/dist/es/ai-model/models/gpt.mjs.map +1 -1
  26. package/dist/es/ai-model/models/kimi.mjs +4 -0
  27. package/dist/es/ai-model/models/kimi.mjs.map +1 -1
  28. package/dist/es/ai-model/models/mimo.mjs +3 -2
  29. package/dist/es/ai-model/models/mimo.mjs.map +1 -1
  30. package/dist/es/ai-model/models/qwen.mjs.map +1 -1
  31. package/dist/es/ai-model/models/ui-tars/adapter.mjs +2 -41
  32. package/dist/es/ai-model/models/ui-tars/adapter.mjs.map +1 -1
  33. package/dist/es/ai-model/models/utils/intent.mjs +6 -0
  34. package/dist/es/ai-model/models/utils/intent.mjs.map +1 -0
  35. package/dist/es/ai-model/prompt/describe.mjs +31 -34
  36. package/dist/es/ai-model/prompt/describe.mjs.map +1 -1
  37. package/dist/es/ai-model/prompt/extraction.mjs +15 -9
  38. package/dist/es/ai-model/prompt/extraction.mjs.map +1 -1
  39. package/dist/es/ai-model/prompt/llm-planning.mjs +19 -19
  40. package/dist/es/ai-model/prompt/llm-planning.mjs.map +1 -1
  41. package/dist/es/ai-model/prompt/locate-grounding-rules.mjs +7 -3
  42. package/dist/es/ai-model/prompt/locate-grounding-rules.mjs.map +1 -1
  43. package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs +2 -1
  44. package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs.map +1 -1
  45. package/dist/es/ai-model/service-caller/index.mjs +40 -19
  46. package/dist/es/ai-model/service-caller/index.mjs.map +1 -1
  47. package/dist/es/ai-model/service-caller/json.mjs +31 -28
  48. package/dist/es/ai-model/service-caller/json.mjs.map +1 -1
  49. package/dist/es/device/index.mjs +2 -4
  50. package/dist/es/device/index.mjs.map +1 -1
  51. package/dist/es/dump/html-utils.mjs +63 -1
  52. package/dist/es/dump/html-utils.mjs.map +1 -1
  53. package/dist/es/dump/screenshot-restoration.mjs +6 -0
  54. package/dist/es/dump/screenshot-restoration.mjs.map +1 -1
  55. package/dist/es/element-describer.mjs +36 -18
  56. package/dist/es/element-describer.mjs.map +1 -1
  57. package/dist/es/index.mjs +2 -2
  58. package/dist/es/index.mjs.map +1 -1
  59. package/dist/es/report-generator.mjs +28 -1
  60. package/dist/es/report-generator.mjs.map +1 -1
  61. package/dist/es/report-markdown.mjs +300 -35
  62. package/dist/es/report-markdown.mjs.map +1 -1
  63. package/dist/es/report.mjs +29 -1
  64. package/dist/es/report.mjs.map +1 -1
  65. package/dist/es/service/index.mjs +101 -25
  66. package/dist/es/service/index.mjs.map +1 -1
  67. package/dist/es/service/utils.mjs +65 -1
  68. package/dist/es/service/utils.mjs.map +1 -1
  69. package/dist/es/types.mjs.map +1 -1
  70. package/dist/es/utils.mjs +2 -2
  71. package/dist/lib/agent/agent.js +40 -1
  72. package/dist/lib/agent/agent.js.map +1 -1
  73. package/dist/lib/agent/metrics.js +115 -0
  74. package/dist/lib/agent/metrics.js.map +1 -0
  75. package/dist/lib/agent/task-builder.js +1 -1
  76. package/dist/lib/agent/task-builder.js.map +1 -1
  77. package/dist/lib/agent/utils.js +1 -1
  78. package/dist/lib/ai-model/connectivity/run-connectivity-test.js +9 -3
  79. package/dist/lib/ai-model/connectivity/run-connectivity-test.js.map +1 -1
  80. package/dist/lib/ai-model/inspect.js +3 -3
  81. package/dist/lib/ai-model/inspect.js.map +1 -1
  82. package/dist/lib/ai-model/llm-planning.js +1 -1
  83. package/dist/lib/ai-model/llm-planning.js.map +1 -1
  84. package/dist/lib/ai-model/model-adapter/resolve.js +1 -1
  85. package/dist/lib/ai-model/model-adapter/resolve.js.map +1 -1
  86. package/dist/lib/ai-model/models/default.js +2 -1
  87. package/dist/lib/ai-model/models/default.js.map +1 -1
  88. package/dist/lib/ai-model/models/doubao.js +38 -69
  89. package/dist/lib/ai-model/models/doubao.js.map +1 -1
  90. package/dist/lib/ai-model/models/gemini.js +4 -15
  91. package/dist/lib/ai-model/models/gemini.js.map +1 -1
  92. package/dist/lib/ai-model/models/glm.js +4 -0
  93. package/dist/lib/ai-model/models/glm.js.map +1 -1
  94. package/dist/lib/ai-model/models/gpt.js +5 -1
  95. package/dist/lib/ai-model/models/gpt.js.map +1 -1
  96. package/dist/lib/ai-model/models/kimi.js +4 -0
  97. package/dist/lib/ai-model/models/kimi.js.map +1 -1
  98. package/dist/lib/ai-model/models/mimo.js +3 -2
  99. package/dist/lib/ai-model/models/mimo.js.map +1 -1
  100. package/dist/lib/ai-model/models/qwen.js.map +1 -1
  101. package/dist/lib/ai-model/models/ui-tars/adapter.js +1 -40
  102. package/dist/lib/ai-model/models/ui-tars/adapter.js.map +1 -1
  103. package/dist/lib/ai-model/models/utils/intent.js +40 -0
  104. package/dist/lib/ai-model/models/utils/intent.js.map +1 -0
  105. package/dist/lib/ai-model/prompt/describe.js +31 -34
  106. package/dist/lib/ai-model/prompt/describe.js.map +1 -1
  107. package/dist/lib/ai-model/prompt/extraction.js +14 -8
  108. package/dist/lib/ai-model/prompt/extraction.js.map +1 -1
  109. package/dist/lib/ai-model/prompt/llm-planning.js +19 -19
  110. package/dist/lib/ai-model/prompt/llm-planning.js.map +1 -1
  111. package/dist/lib/ai-model/prompt/locate-grounding-rules.js +7 -3
  112. package/dist/lib/ai-model/prompt/locate-grounding-rules.js.map +1 -1
  113. package/dist/lib/ai-model/prompt/recorder-metadata-generator.js +4 -3
  114. package/dist/lib/ai-model/prompt/recorder-metadata-generator.js.map +1 -1
  115. package/dist/lib/ai-model/service-caller/index.js +44 -23
  116. package/dist/lib/ai-model/service-caller/index.js.map +1 -1
  117. package/dist/lib/ai-model/service-caller/json.js +33 -33
  118. package/dist/lib/ai-model/service-caller/json.js.map +1 -1
  119. package/dist/lib/device/index.js +2 -4
  120. package/dist/lib/device/index.js.map +1 -1
  121. package/dist/lib/dump/html-utils.js +65 -0
  122. package/dist/lib/dump/html-utils.js.map +1 -1
  123. package/dist/lib/dump/screenshot-restoration.js +6 -0
  124. package/dist/lib/dump/screenshot-restoration.js.map +1 -1
  125. package/dist/lib/element-describer.js +37 -22
  126. package/dist/lib/element-describer.js.map +1 -1
  127. package/dist/lib/index.js +1 -4
  128. package/dist/lib/index.js.map +1 -1
  129. package/dist/lib/report-generator.js +27 -0
  130. package/dist/lib/report-generator.js.map +1 -1
  131. package/dist/lib/report-markdown.js +300 -35
  132. package/dist/lib/report-markdown.js.map +1 -1
  133. package/dist/lib/report.js +28 -0
  134. package/dist/lib/report.js.map +1 -1
  135. package/dist/lib/service/index.js +99 -23
  136. package/dist/lib/service/index.js.map +1 -1
  137. package/dist/lib/service/utils.js +98 -1
  138. package/dist/lib/service/utils.js.map +1 -1
  139. package/dist/lib/types.js.map +1 -1
  140. package/dist/lib/utils.js +2 -2
  141. package/dist/types/agent/agent.d.ts +15 -2
  142. package/dist/types/agent/index.d.ts +1 -0
  143. package/dist/types/agent/metrics.d.ts +48 -0
  144. package/dist/types/ai-model/model-adapter/types.d.ts +8 -0
  145. package/dist/types/ai-model/models/doubao.d.ts +1 -4
  146. package/dist/types/ai-model/models/gemini.d.ts +1 -1
  147. package/dist/types/ai-model/models/registry.d.ts +1 -1
  148. package/dist/types/ai-model/models/utils/intent.d.ts +2 -0
  149. package/dist/types/ai-model/service-caller/index.d.ts +8 -3
  150. package/dist/types/ai-model/service-caller/json.d.ts +19 -2
  151. package/dist/types/device/device-options.d.ts +0 -28
  152. package/dist/types/device/index.d.ts +0 -5
  153. package/dist/types/dump/html-utils.d.ts +2 -0
  154. package/dist/types/dump/screenshot-restoration.d.ts +1 -1
  155. package/dist/types/element-describer.d.ts +3 -6
  156. package/dist/types/index.d.ts +2 -2
  157. package/dist/types/report-generator.d.ts +6 -0
  158. package/dist/types/service/index.d.ts +1 -1
  159. package/dist/types/service/utils.d.ts +21 -2
  160. package/dist/types/types.d.ts +12 -2
  161. package/package.json +3 -3
@@ -91,6 +91,29 @@ function peekReportSdkVersion(reportFilePath) {
91
91
  }
92
92
  }
93
93
  const warnedMismatchedVersions = new Set();
94
+ function tryParseAgentReportDump(dumpString) {
95
+ const trimmed = dumpString.trimStart();
96
+ if (!trimmed.startsWith('{') || !trimmed.includes('"executions"')) return null;
97
+ try {
98
+ return external_types_js_namespaceObject.ReportActionDump.fromSerializedString(trimmed);
99
+ } catch {
100
+ return null;
101
+ }
102
+ }
103
+ function mergedAgentReportComment(reports) {
104
+ if (0 === reports.length) return '';
105
+ if (1 === reports.length) return (0, html_utils_js_namespaceObject.generateAgentReportComment)(reports[0]);
106
+ const deviceTypes = Array.from(new Set(reports.map((report)=>report.deviceType).filter(Boolean)));
107
+ const mergedReport = new external_types_js_namespaceObject.ReportActionDump({
108
+ sdkVersion: reports[0].sdkVersion || (0, external_utils_js_namespaceObject.getVersion)(),
109
+ groupName: 'Merged Midscene Report',
110
+ groupDescription: 'Agent-readable summary for merged report HTML',
111
+ modelBriefs: reports.flatMap((report)=>report.modelBriefs ?? []),
112
+ deviceType: 1 === deviceTypes.length ? deviceTypes[0] : 'mixed',
113
+ executions: reports.flatMap((report)=>report.executions ?? [])
114
+ });
115
+ return (0, html_utils_js_namespaceObject.generateAgentReportComment)(mergedReport);
116
+ }
94
117
  class ReportMergingTool {
95
118
  createEmptyDumpString(groupName, groupDescription) {
96
119
  return new external_types_js_namespaceObject.ReportActionDump({
@@ -163,6 +186,7 @@ class ReportMergingTool {
163
186
  const tplWithoutClose = -1 !== htmlEndIdx ? tpl.slice(0, htmlEndIdx) : tpl;
164
187
  (0, external_node_fs_namespaceObject.appendFileSync)(outputFilePath, tplWithoutClose);
165
188
  if (hasDirectoryModeReport) (0, external_node_fs_namespaceObject.appendFileSync)(outputFilePath, (0, html_utils_js_namespaceObject.getBaseUrlFixScript)());
189
+ const agentReports = [];
166
190
  for(let i = 0; i < this.reportInfos.length; i++){
167
191
  const reportInfo = this.reportInfos[i];
168
192
  (0, utils_namespaceObject.logMsg)(`Processing report ${i + 1}/${this.reportInfos.length}`);
@@ -191,6 +215,8 @@ class ReportMergingTool {
191
215
  const extractedDumpString = allDumps.length > 0 ? this.mergeDumpScripts(allDumps.map((d)=>d.content)) : (0, html_utils_js_namespaceObject.extractLastDumpScriptSync)(reportInfo.reportFilePath);
192
216
  if (extractedDumpString) dumpString = extractedDumpString;
193
217
  }
218
+ const agentReport = tryParseAgentReportDump(dumpString);
219
+ if (agentReport) agentReports.push(agentReport);
194
220
  const reportHtmlStr = `${(0, external_utils_js_namespaceObject.reportHTMLContent)({
195
221
  dumpString,
196
222
  attributes: {
@@ -206,6 +232,8 @@ class ReportMergingTool {
206
232
  }, void 0, void 0, false)}\n`;
207
233
  (0, external_node_fs_namespaceObject.appendFileSync)(outputFilePath, reportHtmlStr);
208
234
  }
235
+ const agentComment = mergedAgentReportComment(agentReports);
236
+ if (agentComment) (0, external_node_fs_namespaceObject.appendFileSync)(outputFilePath, agentComment);
209
237
  (0, external_node_fs_namespaceObject.appendFileSync)(outputFilePath, `${htmlEndTag}\n`);
210
238
  (0, utils_namespaceObject.logMsg)(`Successfully merged new report: ${outputFilePath}`);
211
239
  if (rmOriginalReports) {
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../src/report.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import {\n appendFileSync,\n copyFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n rmSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport * as path from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport { antiEscapeScriptTag, logMsg } from '@midscene/shared/utils';\nimport { getReportFileName } from './agent';\nimport {\n DATA_SCREENSHOT_MODE_ATTR,\n extractAllDumpScriptsSync,\n extractLastDumpScriptSync,\n getBaseUrlFixScript,\n streamDumpScriptsSync,\n streamImageScriptsToFile,\n} from './dump/html-utils';\nimport {\n normalizeScreenshotRef,\n resolveScreenshotSource,\n} from './dump/screenshot-store';\nimport {\n type ExecutionDump,\n type IExecutionDump,\n ReportActionDump,\n type ScreenshotMode,\n} from './types';\nimport type { ReportFileWithAttributes } from './types';\nimport { getReportTpl, getVersion, reportHTMLContent } from './utils';\n\n/**\n * Read the screenshot storage mode a report declared at generation time.\n *\n * Reports written by current versions stamp `data-screenshot-mode` onto every\n * dump script tag, so we only need to peek at the first real dump script (the\n * template's bundled JS also references the dump type string, hence the\n * `data-group-id` filter) and can stop streaming immediately.\n *\n * Returns undefined for legacy reports that predate the attribute or for\n * unreadable files, letting the caller fall back to a filesystem heuristic.\n */\nconst screenshotModeAttrRegExp = new RegExp(\n `${DATA_SCREENSHOT_MODE_ATTR}=\"(inline|directory)\"`,\n);\n\nfunction readDeclaredScreenshotMode(\n reportFilePath: string,\n): ScreenshotMode | undefined {\n let mode: ScreenshotMode | undefined;\n try {\n streamDumpScriptsSync(reportFilePath, ({ openTag }) => {\n // Skip false matches from the template's bundled JS code.\n if (!openTag.includes('data-group-id')) return false;\n const match = openTag.match(screenshotModeAttrRegExp);\n mode = match?.[1] as ScreenshotMode | undefined;\n return true; // the first real dump script decides the mode\n });\n } catch {\n // Unreadable / non-existent file — let the caller fall back.\n }\n return mode;\n}\n\n/**\n * Check if a report is in directory mode (html-and-external-assets).\n * Directory mode reports: {name}/index.html + {name}/screenshots/\n *\n * The mode is read from the report's own `data-screenshot-mode` metadata, which\n * is authoritative regardless of whether the run happened to capture any\n * screenshots. For legacy reports without the attribute we fall back to the old\n * filesystem heuristic (an `index.html` that has a sibling `screenshots/` dir).\n */\nexport function isDirectoryModeReport(reportFilePath: string): boolean {\n // Directory-mode reports are always written as `{name}/index.html`, so any\n // other filename is inline. Short-circuit before touching the file so the\n // common inline case (`{name}.html`) costs nothing.\n if (path.basename(reportFilePath) !== 'index.html') return false;\n\n const declared = readDeclaredScreenshotMode(reportFilePath);\n if (declared) return declared === 'directory';\n\n // Legacy fallback for reports generated before screenshotMode was embedded.\n return existsSync(path.join(path.dirname(reportFilePath), 'screenshots'));\n}\n\n/**\n * Whether a report lives in its own dedicated directory (`{name}/index.html`)\n * rather than being a single standalone file (`{name}.html`).\n *\n * This is a structural fact about *where the report file sits*, independent of\n * how it stores screenshots: a directory report can keep screenshots external\n * (a `screenshots/` sibling) OR inline them into `index.html`. Deletion needs\n * this — not the screenshot mode — to decide whether to remove the whole\n * directory or just unlink a file, so that an inline-screenshot report nested in\n * its own folder still has the folder removed instead of being orphaned.\n */\nfunction isDirectoryBasedReport(reportFilePath: string): boolean {\n return path.basename(reportFilePath) === 'index.html';\n}\n\n/**\n * Deduplicate executions by stable id, keeping only the last occurrence.\n * Old-format executions without id are always preserved.\n */\nexport function dedupeExecutionsKeepLatest<T extends Pick<ExecutionDump, 'id'>>(\n executions: T[],\n): T[] {\n let noIdCounter = 0;\n const deduped = new Map<string, T>();\n for (const exec of executions) {\n const key = exec.id || `__no_id_${noIdCounter++}`;\n deduped.set(key, exec);\n }\n return Array.from(deduped.values());\n}\n/**\n * Peek at the first `sdkVersion` field embedded in a midscene_web_dump\n * script tag inside the given report file. Returns undefined if no\n * recognizable tag or sdkVersion is present.\n */\nfunction peekReportSdkVersion(reportFilePath: string): string | undefined {\n try {\n const dump = extractLastDumpScriptSync(reportFilePath);\n if (!dump) return undefined;\n const match = dump.match(/\"sdkVersion\"\\s*:\\s*\"([^\"]+)\"/);\n return match?.[1];\n } catch {\n return undefined;\n }\n}\n\nconst warnedMismatchedVersions = new Set<string>();\n\nexport class ReportMergingTool {\n private reportInfos: ReportFileWithAttributes[] = [];\n\n private createEmptyDumpString(groupName: string, groupDescription?: string) {\n return new ReportActionDump({\n sdkVersion: '',\n groupName,\n groupDescription,\n modelBriefs: [],\n executions: [],\n }).serialize();\n }\n\n public append(reportInfo: ReportFileWithAttributes) {\n if (reportInfo.reportFilePath) {\n const sourceVersion = peekReportSdkVersion(reportInfo.reportFilePath);\n const currentVersion = getVersion();\n if (\n sourceVersion &&\n currentVersion &&\n sourceVersion !== currentVersion &&\n !warnedMismatchedVersions.has(sourceVersion)\n ) {\n warnedMismatchedVersions.add(sourceVersion);\n logMsg(\n `[@midscene/core] ReportMergingTool version mismatch: source report was written by @midscene/core@${sourceVersion} but the merger is @midscene/core@${currentVersion}. This commonly means @midscene/core and the device package (e.g. @midscene/android) resolve to different versions in node_modules. Merged output may silently drop intermediate steps. Align the versions and reinstall (rm -rf node_modules package-lock.json && npm install).`,\n );\n }\n }\n this.reportInfos.push(reportInfo);\n }\n public clear() {\n this.reportInfos = [];\n }\n\n /**\n * Merge multiple dump script contents (from the same source report)\n * into a single serialized ReportActionDump string.\n * If there's only one dump, return it as-is. If multiple, merge\n * all executions into the first dump's group structure.\n */\n private mergeDumpScripts(contents: string[]): string {\n const unescaped = contents\n .map((c) => antiEscapeScriptTag(c))\n .filter((c) => c.length > 0);\n if (unescaped.length === 0) return '';\n if (unescaped.length === 1) return unescaped[0];\n\n // Parse all dumps and collect executions, deduplicating by id (keep last).\n // Only executions with a stable id are deduped; old-format entries without\n // id are always kept (they may be distinct despite sharing the same name).\n const base = ReportActionDump.fromSerializedString(unescaped[0]);\n const allExecutions = [...base.executions];\n for (let i = 1; i < unescaped.length; i++) {\n const other = ReportActionDump.fromSerializedString(unescaped[i]);\n allExecutions.push(...other.executions);\n }\n base.executions = dedupeExecutionsKeepLatest(allExecutions);\n return base.serialize();\n }\n\n public mergeReports(\n reportFileName: 'AUTO' | string = 'AUTO',\n opts?: {\n rmOriginalReports?: boolean;\n overwrite?: boolean;\n outputDir?: string;\n },\n ): string | null {\n const {\n rmOriginalReports = false,\n overwrite = false,\n outputDir,\n } = opts ?? {};\n\n if (this.reportInfos.length === 0) {\n logMsg('No reports to merge');\n return null;\n }\n\n const targetDir = outputDir\n ? path.resolve(outputDir)\n : getMidsceneRunSubDir('report');\n if (outputDir) {\n mkdirSync(targetDir, { recursive: true });\n }\n\n // Resolve each report's screenshot mode exactly once. isDirectoryModeReport\n // reads the file to find the authoritative metadata, so recomputing it for\n // both the output-path decision and the per-report merge loop would re-scan\n // every report twice.\n const isDirModeByIndex = this.reportInfos.map((info) =>\n Boolean(\n info.reportFilePath && isDirectoryModeReport(info.reportFilePath),\n ),\n );\n const hasDirectoryModeReport = isDirModeByIndex.some(Boolean);\n\n const resolvedName =\n reportFileName === 'AUTO'\n ? getReportFileName('merged-report')\n : reportFileName;\n\n // Directory mode: output as {name}/index.html to keep relative paths working\n // Inline mode: output as {name}.html (single file)\n const outputFilePath = hasDirectoryModeReport\n ? path.resolve(targetDir, resolvedName, 'index.html')\n : path.resolve(targetDir, `${resolvedName}.html`);\n\n if (reportFileName !== 'AUTO' && existsSync(outputFilePath)) {\n if (!overwrite) {\n throw new Error(\n `Report file already exists: ${outputFilePath}\\nSet overwrite to true to overwrite this file.`,\n );\n }\n if (hasDirectoryModeReport) {\n rmSync(path.dirname(outputFilePath), { recursive: true, force: true });\n } else {\n unlinkSync(outputFilePath);\n }\n }\n\n if (hasDirectoryModeReport) {\n mkdirSync(path.dirname(outputFilePath), { recursive: true });\n }\n\n logMsg(\n `Start merging ${this.reportInfos.length} reports...\\nCreating template file...`,\n );\n\n try {\n // Write template without closing </html> tag so we can append\n // dump scripts before it. The closing tag is added at the end.\n const htmlEndTag = '</html>';\n const tpl = getReportTpl();\n const htmlEndIdx = tpl.lastIndexOf(htmlEndTag);\n const tplWithoutClose =\n htmlEndIdx !== -1 ? tpl.slice(0, htmlEndIdx) : tpl;\n appendFileSync(outputFilePath, tplWithoutClose);\n\n // For directory-mode output, inject base URL fix script\n if (hasDirectoryModeReport) {\n appendFileSync(outputFilePath, getBaseUrlFixScript());\n }\n\n // Process all reports one by one\n for (let i = 0; i < this.reportInfos.length; i++) {\n const reportInfo = this.reportInfos[i];\n logMsg(`Processing report ${i + 1}/${this.reportInfos.length}`);\n\n const { reportAttributes } = reportInfo;\n let dumpString = this.createEmptyDumpString(\n reportAttributes.testTitle,\n reportAttributes.testDescription,\n );\n let mergedGroupId = `merged-group-${i}`;\n\n if (reportInfo.reportFilePath) {\n if (isDirModeByIndex[i]) {\n // Directory mode: copy external screenshot files. A directory-mode\n // report can legitimately have no screenshots/ dir (a run that\n // captured nothing), so only copy when the source dir exists.\n const reportDir = path.dirname(reportInfo.reportFilePath);\n const screenshotsDir = path.join(reportDir, 'screenshots');\n if (existsSync(screenshotsDir)) {\n const mergedScreenshotsDir = path.join(\n path.dirname(outputFilePath),\n 'screenshots',\n );\n mkdirSync(mergedScreenshotsDir, { recursive: true });\n for (const file of readdirSync(screenshotsDir)) {\n const src = path.join(screenshotsDir, file);\n const dest = path.join(mergedScreenshotsDir, file);\n copyFileSync(src, dest);\n }\n }\n } else {\n // Inline mode: stream image scripts to output file\n streamImageScriptsToFile(reportInfo.reportFilePath, outputFilePath);\n }\n\n // Extract all dump scripts from the source report.\n // After the per-execution append refactor, a single source report\n // may contain multiple <script type=\"midscene_web_dump\"> tags\n // (one per execution). We merge them into a single ReportActionDump.\n // Filter by data-group-id to exclude false matches from the template's\n // bundled JS code, which also references the midscene_web_dump type string.\n const allDumps = extractAllDumpScriptsSync(\n reportInfo.reportFilePath,\n ).filter((d) => d.openTag.includes('data-group-id'));\n const groupIdMatch = allDumps[0]?.openTag.match(\n /data-group-id=\"([^\"]+)\"/,\n );\n if (groupIdMatch) {\n mergedGroupId = decodeURIComponent(groupIdMatch[1]);\n }\n const extractedDumpString =\n allDumps.length > 0\n ? this.mergeDumpScripts(allDumps.map((d) => d.content))\n : extractLastDumpScriptSync(reportInfo.reportFilePath);\n if (extractedDumpString) {\n dumpString = extractedDumpString;\n }\n }\n\n const reportHtmlStr = `${reportHTMLContent(\n {\n dumpString,\n attributes: {\n 'data-group-id': mergedGroupId,\n [DATA_SCREENSHOT_MODE_ATTR]: hasDirectoryModeReport\n ? 'directory'\n : 'inline',\n playwright_test_duration: reportAttributes.testDuration,\n playwright_test_status: reportAttributes.testStatus,\n playwright_test_title: reportAttributes.testTitle,\n playwright_test_id: reportAttributes.testId,\n playwright_test_description: reportAttributes.testDescription,\n is_merged: true,\n },\n },\n undefined,\n undefined,\n false,\n )}\\n`;\n\n appendFileSync(outputFilePath, reportHtmlStr);\n }\n\n // Close the HTML document\n appendFileSync(outputFilePath, `${htmlEndTag}\\n`);\n\n logMsg(`Successfully merged new report: ${outputFilePath}`);\n\n // Remove original reports if needed\n if (rmOriginalReports) {\n for (const info of this.reportInfos) {\n if (!info.reportFilePath) continue;\n try {\n if (isDirectoryBasedReport(info.reportFilePath)) {\n // The report owns its directory (`{name}/index.html`) — remove the\n // whole folder, whether screenshots are external or inlined.\n const reportDir = path.dirname(info.reportFilePath);\n rmSync(reportDir, { recursive: true, force: true });\n } else {\n unlinkSync(info.reportFilePath);\n }\n } catch (error) {\n logMsg(`Error deleting report ${info.reportFilePath}: ${error}`);\n }\n }\n logMsg(`Removed ${this.reportInfos.length} original reports`);\n }\n return outputFilePath;\n } catch (error) {\n logMsg(`Error in mergeReports: ${error}`);\n throw error;\n }\n }\n}\n\nexport interface SplitReportHtmlOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport interface SplitReportHtmlResult {\n executionJsonFiles: string[];\n screenshotFiles: string[];\n}\n\nexport interface CollectedReportExecutions {\n baseDump: ReportActionDump;\n executions: IExecutionDump[];\n}\n\n/**\n * Collect executions from a report HTML, deduplicating by stable id while\n * keeping only the latest occurrence. Old-format executions without id are\n * always preserved.\n */\nexport function collectDedupedExecutions(\n htmlPath: string,\n): CollectedReportExecutions {\n let baseDump: ReportActionDump | null = null;\n let executionSerial = 0;\n const latestSerialByExecutionId = new Map<string, number>();\n\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (execution.id) {\n latestSerialByExecutionId.set(execution.id, executionSerial);\n }\n }\n return false;\n });\n\n const executions: IExecutionDump[] = [];\n executionSerial = 0;\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n if (!baseDump) {\n baseDump = groupedDump;\n }\n\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (\n execution.id &&\n latestSerialByExecutionId.get(execution.id) !== executionSerial\n ) {\n continue;\n }\n executions.push(execution);\n }\n\n return false;\n });\n\n if (!baseDump) {\n throw new Error(`No report dump scripts found in ${htmlPath}`);\n }\n\n return {\n baseDump,\n executions,\n };\n}\n\nfunction extensionByMimeType(mimeType: string): 'png' | 'jpeg' {\n if (mimeType === 'image/png') return 'png';\n if (mimeType === 'image/jpeg') return 'jpeg';\n throw new Error(`Unsupported screenshot mime type: ${mimeType}`);\n}\n\nfunction externalizeScreenshotsInExecution(\n execution: IExecutionDump,\n opts: {\n htmlPath: string;\n screenshotsDir: string;\n writtenFiles: Set<string>;\n },\n): void {\n const visit = (node: unknown): void => {\n if (Array.isArray(node)) {\n for (const item of node) {\n visit(item);\n }\n return;\n }\n\n if (typeof node !== 'object' || node === null) return;\n\n const ref = normalizeScreenshotRef(node);\n if (ref) {\n const ext = extensionByMimeType(ref.mimeType);\n const fileName = `${ref.id}.${ext}`;\n const relativePath = `./screenshots/${fileName}`;\n const absolutePath = path.join(opts.screenshotsDir, fileName);\n\n if (!opts.writtenFiles.has(fileName)) {\n const resolved = resolveScreenshotSource(ref, {\n reportPath: opts.htmlPath,\n });\n if (resolved.type === 'data-uri') {\n const rawBase64 = resolved.dataUri.replace(\n /^data:image\\/[a-zA-Z+]+;base64,/,\n '',\n );\n writeFileSync(absolutePath, Buffer.from(rawBase64, 'base64'));\n } else {\n copyFileSync(resolved.filePath, absolutePath);\n }\n opts.writtenFiles.add(fileName);\n }\n\n ref.storage = 'file';\n ref.path = relativePath;\n return;\n }\n\n for (const value of Object.values(node)) {\n visit(value);\n }\n };\n\n visit(execution);\n}\n\n/**\n * Reverse parse a Midscene report HTML into per-execution JSON files and\n * externalized screenshots.\n */\nexport function splitReportHtmlByExecution(\n options: SplitReportHtmlOptions,\n): SplitReportHtmlResult {\n const { htmlPath, outputDir } = options;\n const screenshotsDir = path.join(outputDir, 'screenshots');\n\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(screenshotsDir, { recursive: true });\n\n const executionJsonFiles: string[] = [];\n const writtenScreenshotFiles = new Set<string>();\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n\n let fileIndex = 0;\n for (const execution of executions) {\n fileIndex += 1;\n externalizeScreenshotsInExecution(execution, {\n htmlPath,\n screenshotsDir,\n writtenFiles: writtenScreenshotFiles,\n });\n const singleExecutionDump = new ReportActionDump({\n sdkVersion: baseDump.sdkVersion,\n groupName: baseDump.groupName,\n groupDescription: baseDump.groupDescription,\n modelBriefs: baseDump.modelBriefs,\n deviceType: baseDump.deviceType,\n executions: [execution],\n });\n\n const jsonFilePath = path.join(outputDir, `${fileIndex}.execution.json`);\n writeFileSync(jsonFilePath, singleExecutionDump.serialize(2), 'utf-8');\n executionJsonFiles.push(jsonFilePath);\n }\n\n return {\n executionJsonFiles,\n screenshotFiles: Array.from(writtenScreenshotFiles)\n .sort()\n .map((fileName) => path.join(screenshotsDir, fileName)),\n };\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","screenshotModeAttrRegExp","RegExp","DATA_SCREENSHOT_MODE_ATTR","readDeclaredScreenshotMode","reportFilePath","mode","streamDumpScriptsSync","openTag","match","isDirectoryModeReport","path","declared","existsSync","isDirectoryBasedReport","dedupeExecutionsKeepLatest","executions","noIdCounter","deduped","Map","exec","Array","peekReportSdkVersion","dump","extractLastDumpScriptSync","warnedMismatchedVersions","Set","ReportMergingTool","groupName","groupDescription","ReportActionDump","reportInfo","sourceVersion","currentVersion","getVersion","logMsg","contents","unescaped","c","antiEscapeScriptTag","base","allExecutions","i","other","reportFileName","opts","rmOriginalReports","overwrite","outputDir","targetDir","getMidsceneRunSubDir","mkdirSync","isDirModeByIndex","info","Boolean","hasDirectoryModeReport","resolvedName","getReportFileName","outputFilePath","Error","rmSync","unlinkSync","htmlEndTag","tpl","getReportTpl","htmlEndIdx","tplWithoutClose","appendFileSync","getBaseUrlFixScript","reportAttributes","dumpString","mergedGroupId","reportDir","screenshotsDir","mergedScreenshotsDir","file","readdirSync","src","dest","copyFileSync","streamImageScriptsToFile","allDumps","extractAllDumpScriptsSync","d","groupIdMatch","decodeURIComponent","extractedDumpString","reportHtmlStr","reportHTMLContent","undefined","error","collectDedupedExecutions","htmlPath","baseDump","executionSerial","latestSerialByExecutionId","dumpScript","groupedDump","execution","extensionByMimeType","mimeType","externalizeScreenshotsInExecution","visit","node","item","ref","normalizeScreenshotRef","ext","fileName","relativePath","absolutePath","resolved","resolveScreenshotSource","rawBase64","writeFileSync","Buffer","value","splitReportHtmlByExecution","options","executionJsonFiles","writtenScreenshotFiles","fileIndex","singleExecutionDump","jsonFilePath"],"mappings":"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"}
1
+ {"version":3,"file":"report.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../src/report.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import {\n appendFileSync,\n copyFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n rmSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport * as path from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport { antiEscapeScriptTag, logMsg } from '@midscene/shared/utils';\nimport { getReportFileName } from './agent';\nimport {\n DATA_SCREENSHOT_MODE_ATTR,\n extractAllDumpScriptsSync,\n extractLastDumpScriptSync,\n generateAgentReportComment,\n getBaseUrlFixScript,\n streamDumpScriptsSync,\n streamImageScriptsToFile,\n} from './dump/html-utils';\nimport {\n normalizeScreenshotRef,\n resolveScreenshotSource,\n} from './dump/screenshot-store';\nimport {\n type ExecutionDump,\n type IExecutionDump,\n ReportActionDump,\n type ScreenshotMode,\n} from './types';\nimport type { ReportFileWithAttributes } from './types';\nimport { getReportTpl, getVersion, reportHTMLContent } from './utils';\n\n/**\n * Read the screenshot storage mode a report declared at generation time.\n *\n * Reports written by current versions stamp `data-screenshot-mode` onto every\n * dump script tag, so we only need to peek at the first real dump script (the\n * template's bundled JS also references the dump type string, hence the\n * `data-group-id` filter) and can stop streaming immediately.\n *\n * Returns undefined for legacy reports that predate the attribute or for\n * unreadable files, letting the caller fall back to a filesystem heuristic.\n */\nconst screenshotModeAttrRegExp = new RegExp(\n `${DATA_SCREENSHOT_MODE_ATTR}=\"(inline|directory)\"`,\n);\n\nfunction readDeclaredScreenshotMode(\n reportFilePath: string,\n): ScreenshotMode | undefined {\n let mode: ScreenshotMode | undefined;\n try {\n streamDumpScriptsSync(reportFilePath, ({ openTag }) => {\n // Skip false matches from the template's bundled JS code.\n if (!openTag.includes('data-group-id')) return false;\n const match = openTag.match(screenshotModeAttrRegExp);\n mode = match?.[1] as ScreenshotMode | undefined;\n return true; // the first real dump script decides the mode\n });\n } catch {\n // Unreadable / non-existent file — let the caller fall back.\n }\n return mode;\n}\n\n/**\n * Check if a report is in directory mode (html-and-external-assets).\n * Directory mode reports: {name}/index.html + {name}/screenshots/\n *\n * The mode is read from the report's own `data-screenshot-mode` metadata, which\n * is authoritative regardless of whether the run happened to capture any\n * screenshots. For legacy reports without the attribute we fall back to the old\n * filesystem heuristic (an `index.html` that has a sibling `screenshots/` dir).\n */\nexport function isDirectoryModeReport(reportFilePath: string): boolean {\n // Directory-mode reports are always written as `{name}/index.html`, so any\n // other filename is inline. Short-circuit before touching the file so the\n // common inline case (`{name}.html`) costs nothing.\n if (path.basename(reportFilePath) !== 'index.html') return false;\n\n const declared = readDeclaredScreenshotMode(reportFilePath);\n if (declared) return declared === 'directory';\n\n // Legacy fallback for reports generated before screenshotMode was embedded.\n return existsSync(path.join(path.dirname(reportFilePath), 'screenshots'));\n}\n\n/**\n * Whether a report lives in its own dedicated directory (`{name}/index.html`)\n * rather than being a single standalone file (`{name}.html`).\n *\n * This is a structural fact about *where the report file sits*, independent of\n * how it stores screenshots: a directory report can keep screenshots external\n * (a `screenshots/` sibling) OR inline them into `index.html`. Deletion needs\n * this — not the screenshot mode — to decide whether to remove the whole\n * directory or just unlink a file, so that an inline-screenshot report nested in\n * its own folder still has the folder removed instead of being orphaned.\n */\nfunction isDirectoryBasedReport(reportFilePath: string): boolean {\n return path.basename(reportFilePath) === 'index.html';\n}\n\n/**\n * Deduplicate executions by stable id, keeping only the last occurrence.\n * Old-format executions without id are always preserved.\n */\nexport function dedupeExecutionsKeepLatest<T extends Pick<ExecutionDump, 'id'>>(\n executions: T[],\n): T[] {\n let noIdCounter = 0;\n const deduped = new Map<string, T>();\n for (const exec of executions) {\n const key = exec.id || `__no_id_${noIdCounter++}`;\n deduped.set(key, exec);\n }\n return Array.from(deduped.values());\n}\n/**\n * Peek at the first `sdkVersion` field embedded in a midscene_web_dump\n * script tag inside the given report file. Returns undefined if no\n * recognizable tag or sdkVersion is present.\n */\nfunction peekReportSdkVersion(reportFilePath: string): string | undefined {\n try {\n const dump = extractLastDumpScriptSync(reportFilePath);\n if (!dump) return undefined;\n const match = dump.match(/\"sdkVersion\"\\s*:\\s*\"([^\"]+)\"/);\n return match?.[1];\n } catch {\n return undefined;\n }\n}\n\nconst warnedMismatchedVersions = new Set<string>();\n\nfunction tryParseAgentReportDump(dumpString: string): ReportActionDump | null {\n const trimmed = dumpString.trimStart();\n if (!trimmed.startsWith('{') || !trimmed.includes('\"executions\"')) {\n return null;\n }\n\n try {\n return ReportActionDump.fromSerializedString(trimmed);\n } catch {\n return null;\n }\n}\n\nfunction mergedAgentReportComment(reports: ReportActionDump[]): string {\n if (reports.length === 0) {\n return '';\n }\n if (reports.length === 1) {\n return generateAgentReportComment(reports[0]);\n }\n\n const deviceTypes = Array.from(\n new Set(reports.map((report) => report.deviceType).filter(Boolean)),\n );\n const mergedReport = new ReportActionDump({\n sdkVersion: reports[0].sdkVersion || getVersion(),\n groupName: 'Merged Midscene Report',\n groupDescription: 'Agent-readable summary for merged report HTML',\n modelBriefs: reports.flatMap((report) => report.modelBriefs ?? []),\n deviceType: deviceTypes.length === 1 ? deviceTypes[0] : 'mixed',\n executions: reports.flatMap((report) => report.executions ?? []),\n });\n return generateAgentReportComment(mergedReport);\n}\n\nexport class ReportMergingTool {\n private reportInfos: ReportFileWithAttributes[] = [];\n\n private createEmptyDumpString(groupName: string, groupDescription?: string) {\n return new ReportActionDump({\n sdkVersion: '',\n groupName,\n groupDescription,\n modelBriefs: [],\n executions: [],\n }).serialize();\n }\n\n public append(reportInfo: ReportFileWithAttributes) {\n if (reportInfo.reportFilePath) {\n const sourceVersion = peekReportSdkVersion(reportInfo.reportFilePath);\n const currentVersion = getVersion();\n if (\n sourceVersion &&\n currentVersion &&\n sourceVersion !== currentVersion &&\n !warnedMismatchedVersions.has(sourceVersion)\n ) {\n warnedMismatchedVersions.add(sourceVersion);\n logMsg(\n `[@midscene/core] ReportMergingTool version mismatch: source report was written by @midscene/core@${sourceVersion} but the merger is @midscene/core@${currentVersion}. This commonly means @midscene/core and the device package (e.g. @midscene/android) resolve to different versions in node_modules. Merged output may silently drop intermediate steps. Align the versions and reinstall (rm -rf node_modules package-lock.json && npm install).`,\n );\n }\n }\n this.reportInfos.push(reportInfo);\n }\n public clear() {\n this.reportInfos = [];\n }\n\n /**\n * Merge multiple dump script contents (from the same source report)\n * into a single serialized ReportActionDump string.\n * If there's only one dump, return it as-is. If multiple, merge\n * all executions into the first dump's group structure.\n */\n private mergeDumpScripts(contents: string[]): string {\n const unescaped = contents\n .map((c) => antiEscapeScriptTag(c))\n .filter((c) => c.length > 0);\n if (unescaped.length === 0) return '';\n if (unescaped.length === 1) return unescaped[0];\n\n // Parse all dumps and collect executions, deduplicating by id (keep last).\n // Only executions with a stable id are deduped; old-format entries without\n // id are always kept (they may be distinct despite sharing the same name).\n const base = ReportActionDump.fromSerializedString(unescaped[0]);\n const allExecutions = [...base.executions];\n for (let i = 1; i < unescaped.length; i++) {\n const other = ReportActionDump.fromSerializedString(unescaped[i]);\n allExecutions.push(...other.executions);\n }\n base.executions = dedupeExecutionsKeepLatest(allExecutions);\n return base.serialize();\n }\n\n public mergeReports(\n reportFileName: 'AUTO' | string = 'AUTO',\n opts?: {\n rmOriginalReports?: boolean;\n overwrite?: boolean;\n outputDir?: string;\n },\n ): string | null {\n const {\n rmOriginalReports = false,\n overwrite = false,\n outputDir,\n } = opts ?? {};\n\n if (this.reportInfos.length === 0) {\n logMsg('No reports to merge');\n return null;\n }\n\n const targetDir = outputDir\n ? path.resolve(outputDir)\n : getMidsceneRunSubDir('report');\n if (outputDir) {\n mkdirSync(targetDir, { recursive: true });\n }\n\n // Resolve each report's screenshot mode exactly once. isDirectoryModeReport\n // reads the file to find the authoritative metadata, so recomputing it for\n // both the output-path decision and the per-report merge loop would re-scan\n // every report twice.\n const isDirModeByIndex = this.reportInfos.map((info) =>\n Boolean(\n info.reportFilePath && isDirectoryModeReport(info.reportFilePath),\n ),\n );\n const hasDirectoryModeReport = isDirModeByIndex.some(Boolean);\n\n const resolvedName =\n reportFileName === 'AUTO'\n ? getReportFileName('merged-report')\n : reportFileName;\n\n // Directory mode: output as {name}/index.html to keep relative paths working\n // Inline mode: output as {name}.html (single file)\n const outputFilePath = hasDirectoryModeReport\n ? path.resolve(targetDir, resolvedName, 'index.html')\n : path.resolve(targetDir, `${resolvedName}.html`);\n\n if (reportFileName !== 'AUTO' && existsSync(outputFilePath)) {\n if (!overwrite) {\n throw new Error(\n `Report file already exists: ${outputFilePath}\\nSet overwrite to true to overwrite this file.`,\n );\n }\n if (hasDirectoryModeReport) {\n rmSync(path.dirname(outputFilePath), { recursive: true, force: true });\n } else {\n unlinkSync(outputFilePath);\n }\n }\n\n if (hasDirectoryModeReport) {\n mkdirSync(path.dirname(outputFilePath), { recursive: true });\n }\n\n logMsg(\n `Start merging ${this.reportInfos.length} reports...\\nCreating template file...`,\n );\n\n try {\n // Write template without closing </html> tag so we can append\n // dump scripts before it. The closing tag is added at the end.\n const htmlEndTag = '</html>';\n const tpl = getReportTpl();\n const htmlEndIdx = tpl.lastIndexOf(htmlEndTag);\n const tplWithoutClose =\n htmlEndIdx !== -1 ? tpl.slice(0, htmlEndIdx) : tpl;\n appendFileSync(outputFilePath, tplWithoutClose);\n\n // For directory-mode output, inject base URL fix script\n if (hasDirectoryModeReport) {\n appendFileSync(outputFilePath, getBaseUrlFixScript());\n }\n\n const agentReports: ReportActionDump[] = [];\n\n // Process all reports one by one\n for (let i = 0; i < this.reportInfos.length; i++) {\n const reportInfo = this.reportInfos[i];\n logMsg(`Processing report ${i + 1}/${this.reportInfos.length}`);\n\n const { reportAttributes } = reportInfo;\n let dumpString = this.createEmptyDumpString(\n reportAttributes.testTitle,\n reportAttributes.testDescription,\n );\n let mergedGroupId = `merged-group-${i}`;\n\n if (reportInfo.reportFilePath) {\n if (isDirModeByIndex[i]) {\n // Directory mode: copy external screenshot files. A directory-mode\n // report can legitimately have no screenshots/ dir (a run that\n // captured nothing), so only copy when the source dir exists.\n const reportDir = path.dirname(reportInfo.reportFilePath);\n const screenshotsDir = path.join(reportDir, 'screenshots');\n if (existsSync(screenshotsDir)) {\n const mergedScreenshotsDir = path.join(\n path.dirname(outputFilePath),\n 'screenshots',\n );\n mkdirSync(mergedScreenshotsDir, { recursive: true });\n for (const file of readdirSync(screenshotsDir)) {\n const src = path.join(screenshotsDir, file);\n const dest = path.join(mergedScreenshotsDir, file);\n copyFileSync(src, dest);\n }\n }\n } else {\n // Inline mode: stream image scripts to output file\n streamImageScriptsToFile(reportInfo.reportFilePath, outputFilePath);\n }\n\n // Extract all dump scripts from the source report.\n // After the per-execution append refactor, a single source report\n // may contain multiple <script type=\"midscene_web_dump\"> tags\n // (one per execution). We merge them into a single ReportActionDump.\n // Filter by data-group-id to exclude false matches from the template's\n // bundled JS code, which also references the midscene_web_dump type string.\n const allDumps = extractAllDumpScriptsSync(\n reportInfo.reportFilePath,\n ).filter((d) => d.openTag.includes('data-group-id'));\n const groupIdMatch = allDumps[0]?.openTag.match(\n /data-group-id=\"([^\"]+)\"/,\n );\n if (groupIdMatch) {\n mergedGroupId = decodeURIComponent(groupIdMatch[1]);\n }\n const extractedDumpString =\n allDumps.length > 0\n ? this.mergeDumpScripts(allDumps.map((d) => d.content))\n : extractLastDumpScriptSync(reportInfo.reportFilePath);\n if (extractedDumpString) {\n dumpString = extractedDumpString;\n }\n }\n\n const agentReport = tryParseAgentReportDump(dumpString);\n if (agentReport) {\n agentReports.push(agentReport);\n }\n\n const reportHtmlStr = `${reportHTMLContent(\n {\n dumpString,\n attributes: {\n 'data-group-id': mergedGroupId,\n [DATA_SCREENSHOT_MODE_ATTR]: hasDirectoryModeReport\n ? 'directory'\n : 'inline',\n playwright_test_duration: reportAttributes.testDuration,\n playwright_test_status: reportAttributes.testStatus,\n playwright_test_title: reportAttributes.testTitle,\n playwright_test_id: reportAttributes.testId,\n playwright_test_description: reportAttributes.testDescription,\n is_merged: true,\n },\n },\n undefined,\n undefined,\n false,\n )}\\n`;\n\n appendFileSync(outputFilePath, reportHtmlStr);\n }\n\n const agentComment = mergedAgentReportComment(agentReports);\n if (agentComment) {\n appendFileSync(outputFilePath, agentComment);\n }\n\n // Close the HTML document\n appendFileSync(outputFilePath, `${htmlEndTag}\\n`);\n\n logMsg(`Successfully merged new report: ${outputFilePath}`);\n\n // Remove original reports if needed\n if (rmOriginalReports) {\n for (const info of this.reportInfos) {\n if (!info.reportFilePath) continue;\n try {\n if (isDirectoryBasedReport(info.reportFilePath)) {\n // The report owns its directory (`{name}/index.html`) — remove the\n // whole folder, whether screenshots are external or inlined.\n const reportDir = path.dirname(info.reportFilePath);\n rmSync(reportDir, { recursive: true, force: true });\n } else {\n unlinkSync(info.reportFilePath);\n }\n } catch (error) {\n logMsg(`Error deleting report ${info.reportFilePath}: ${error}`);\n }\n }\n logMsg(`Removed ${this.reportInfos.length} original reports`);\n }\n return outputFilePath;\n } catch (error) {\n logMsg(`Error in mergeReports: ${error}`);\n throw error;\n }\n }\n}\n\nexport interface SplitReportHtmlOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport interface SplitReportHtmlResult {\n executionJsonFiles: string[];\n screenshotFiles: string[];\n}\n\nexport interface CollectedReportExecutions {\n baseDump: ReportActionDump;\n executions: IExecutionDump[];\n}\n\n/**\n * Collect executions from a report HTML, deduplicating by stable id while\n * keeping only the latest occurrence. Old-format executions without id are\n * always preserved.\n */\nexport function collectDedupedExecutions(\n htmlPath: string,\n): CollectedReportExecutions {\n let baseDump: ReportActionDump | null = null;\n let executionSerial = 0;\n const latestSerialByExecutionId = new Map<string, number>();\n\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (execution.id) {\n latestSerialByExecutionId.set(execution.id, executionSerial);\n }\n }\n return false;\n });\n\n const executions: IExecutionDump[] = [];\n executionSerial = 0;\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n if (!baseDump) {\n baseDump = groupedDump;\n }\n\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (\n execution.id &&\n latestSerialByExecutionId.get(execution.id) !== executionSerial\n ) {\n continue;\n }\n executions.push(execution);\n }\n\n return false;\n });\n\n if (!baseDump) {\n throw new Error(`No report dump scripts found in ${htmlPath}`);\n }\n\n return {\n baseDump,\n executions,\n };\n}\n\nfunction extensionByMimeType(mimeType: string): 'png' | 'jpeg' {\n if (mimeType === 'image/png') return 'png';\n if (mimeType === 'image/jpeg') return 'jpeg';\n throw new Error(`Unsupported screenshot mime type: ${mimeType}`);\n}\n\nfunction externalizeScreenshotsInExecution(\n execution: IExecutionDump,\n opts: {\n htmlPath: string;\n screenshotsDir: string;\n writtenFiles: Set<string>;\n },\n): void {\n const visit = (node: unknown): void => {\n if (Array.isArray(node)) {\n for (const item of node) {\n visit(item);\n }\n return;\n }\n\n if (typeof node !== 'object' || node === null) return;\n\n const ref = normalizeScreenshotRef(node);\n if (ref) {\n const ext = extensionByMimeType(ref.mimeType);\n const fileName = `${ref.id}.${ext}`;\n const relativePath = `./screenshots/${fileName}`;\n const absolutePath = path.join(opts.screenshotsDir, fileName);\n\n if (!opts.writtenFiles.has(fileName)) {\n const resolved = resolveScreenshotSource(ref, {\n reportPath: opts.htmlPath,\n });\n if (resolved.type === 'data-uri') {\n const rawBase64 = resolved.dataUri.replace(\n /^data:image\\/[a-zA-Z+]+;base64,/,\n '',\n );\n writeFileSync(absolutePath, Buffer.from(rawBase64, 'base64'));\n } else {\n copyFileSync(resolved.filePath, absolutePath);\n }\n opts.writtenFiles.add(fileName);\n }\n\n ref.storage = 'file';\n ref.path = relativePath;\n return;\n }\n\n for (const value of Object.values(node)) {\n visit(value);\n }\n };\n\n visit(execution);\n}\n\n/**\n * Reverse parse a Midscene report HTML into per-execution JSON files and\n * externalized screenshots.\n */\nexport function splitReportHtmlByExecution(\n options: SplitReportHtmlOptions,\n): SplitReportHtmlResult {\n const { htmlPath, outputDir } = options;\n const screenshotsDir = path.join(outputDir, 'screenshots');\n\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(screenshotsDir, { recursive: true });\n\n const executionJsonFiles: string[] = [];\n const writtenScreenshotFiles = new Set<string>();\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n\n let fileIndex = 0;\n for (const execution of executions) {\n fileIndex += 1;\n externalizeScreenshotsInExecution(execution, {\n htmlPath,\n screenshotsDir,\n writtenFiles: writtenScreenshotFiles,\n });\n const singleExecutionDump = new ReportActionDump({\n sdkVersion: baseDump.sdkVersion,\n groupName: baseDump.groupName,\n groupDescription: baseDump.groupDescription,\n modelBriefs: baseDump.modelBriefs,\n deviceType: baseDump.deviceType,\n executions: [execution],\n });\n\n const jsonFilePath = path.join(outputDir, `${fileIndex}.execution.json`);\n writeFileSync(jsonFilePath, singleExecutionDump.serialize(2), 'utf-8');\n executionJsonFiles.push(jsonFilePath);\n }\n\n return {\n executionJsonFiles,\n screenshotFiles: Array.from(writtenScreenshotFiles)\n .sort()\n .map((fileName) => path.join(screenshotsDir, fileName)),\n };\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","screenshotModeAttrRegExp","RegExp","DATA_SCREENSHOT_MODE_ATTR","readDeclaredScreenshotMode","reportFilePath","mode","streamDumpScriptsSync","openTag","match","isDirectoryModeReport","path","declared","existsSync","isDirectoryBasedReport","dedupeExecutionsKeepLatest","executions","noIdCounter","deduped","Map","exec","Array","peekReportSdkVersion","dump","extractLastDumpScriptSync","warnedMismatchedVersions","Set","tryParseAgentReportDump","dumpString","trimmed","ReportActionDump","mergedAgentReportComment","reports","generateAgentReportComment","deviceTypes","report","Boolean","mergedReport","getVersion","ReportMergingTool","groupName","groupDescription","reportInfo","sourceVersion","currentVersion","logMsg","contents","unescaped","c","antiEscapeScriptTag","base","allExecutions","i","other","reportFileName","opts","rmOriginalReports","overwrite","outputDir","targetDir","getMidsceneRunSubDir","mkdirSync","isDirModeByIndex","info","hasDirectoryModeReport","resolvedName","getReportFileName","outputFilePath","Error","rmSync","unlinkSync","htmlEndTag","tpl","getReportTpl","htmlEndIdx","tplWithoutClose","appendFileSync","getBaseUrlFixScript","agentReports","reportAttributes","mergedGroupId","reportDir","screenshotsDir","mergedScreenshotsDir","file","readdirSync","src","dest","copyFileSync","streamImageScriptsToFile","allDumps","extractAllDumpScriptsSync","d","groupIdMatch","decodeURIComponent","extractedDumpString","agentReport","reportHtmlStr","reportHTMLContent","undefined","agentComment","error","collectDedupedExecutions","htmlPath","baseDump","executionSerial","latestSerialByExecutionId","dumpScript","groupedDump","execution","extensionByMimeType","mimeType","externalizeScreenshotsInExecution","visit","node","item","ref","normalizeScreenshotRef","ext","fileName","relativePath","absolutePath","resolved","resolveScreenshotSource","rawBase64","writeFileSync","Buffer","value","splitReportHtmlByExecution","options","executionJsonFiles","writtenScreenshotFiles","fileIndex","singleExecutionDump","jsonFilePath"],"mappings":";;;IAAAA,oBAAoB,CAAC,GAAG,CAAC,UAASC;QACjC,IAAI,IAAIC,OAAOD,WACR,IAAGD,oBAAoB,CAAC,CAACC,YAAYC,QAAQ,CAACF,oBAAoB,CAAC,CAAC,UAASE,MACzEC,OAAO,cAAc,CAAC,UAASD,KAAK;YAAE,YAAY;YAAM,KAAKD,UAAU,CAACC,IAAI;QAAC;IAGzF;;;ICNAF,oBAAoB,CAAC,GAAG,CAACI,KAAKC,OAAUF,OAAO,SAAS,CAAC,cAAc,CAAC,IAAI,CAACC,KAAKC;;;ICClFL,oBAAoB,CAAC,GAAG,CAAC;QACxB,IAAG,AAAkB,eAAlB,OAAOM,UAA0BA,OAAO,WAAW,EACrDH,OAAO,cAAc,CAAC,UAASG,OAAO,WAAW,EAAE;YAAE,OAAO;QAAS;QAEtEH,OAAO,cAAc,CAAC,UAAS,cAAc;YAAE,OAAO;QAAK;IAC5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACyCA,MAAMI,2BAA2B,IAAIC,OACnC,GAAGC,8BAAAA,yBAAyBA,CAAC,qBAAqB,CAAC;AAGrD,SAASC,2BACPC,cAAsB;IAEtB,IAAIC;IACJ,IAAI;QACFC,IAAAA,8BAAAA,qBAAAA,AAAAA,EAAsBF,gBAAgB,CAAC,EAAEG,OAAO,EAAE;YAEhD,IAAI,CAACA,QAAQ,QAAQ,CAAC,kBAAkB,OAAO;YAC/C,MAAMC,QAAQD,QAAQ,KAAK,CAACP;YAC5BK,OAAOG,OAAO,CAAC,EAAE;YACjB,OAAO;QACT;IACF,EAAE,OAAM,CAER;IACA,OAAOH;AACT;AAWO,SAASI,sBAAsBL,cAAsB;IAI1D,IAAIM,AAAkC,iBAAlCA,mCAAAA,QAAa,CAACN,iBAAkC,OAAO;IAE3D,MAAMO,WAAWR,2BAA2BC;IAC5C,IAAIO,UAAU,OAAOA,AAAa,gBAAbA;IAGrB,OAAOC,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWF,mCAAAA,IAAS,CAACA,mCAAAA,OAAY,CAACN,iBAAiB;AAC5D;AAaA,SAASS,uBAAuBT,cAAsB;IACpD,OAAOM,AAAkC,iBAAlCA,mCAAAA,QAAa,CAACN;AACvB;AAMO,SAASU,2BACdC,UAAe;IAEf,IAAIC,cAAc;IAClB,MAAMC,UAAU,IAAIC;IACpB,KAAK,MAAMC,QAAQJ,WAAY;QAC7B,MAAMpB,MAAMwB,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAEH,eAAe;QACjDC,QAAQ,GAAG,CAACtB,KAAKwB;IACnB;IACA,OAAOC,MAAM,IAAI,CAACH,QAAQ,MAAM;AAClC;AAMA,SAASI,qBAAqBjB,cAAsB;IAClD,IAAI;QACF,MAAMkB,OAAOC,AAAAA,IAAAA,8BAAAA,yBAAAA,AAAAA,EAA0BnB;QACvC,IAAI,CAACkB,MAAM;QACX,MAAMd,QAAQc,KAAK,KAAK,CAAC;QACzB,OAAOd,OAAO,CAAC,EAAE;IACnB,EAAE,OAAM;QACN;IACF;AACF;AAEA,MAAMgB,2BAA2B,IAAIC;AAErC,SAASC,wBAAwBC,UAAkB;IACjD,MAAMC,UAAUD,WAAW,SAAS;IACpC,IAAI,CAACC,QAAQ,UAAU,CAAC,QAAQ,CAACA,QAAQ,QAAQ,CAAC,iBAChD,OAAO;IAGT,IAAI;QACF,OAAOC,kCAAAA,gBAAAA,CAAAA,oBAAqC,CAACD;IAC/C,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,SAASE,yBAAyBC,OAA2B;IAC3D,IAAIA,AAAmB,MAAnBA,QAAQ,MAAM,EAChB,OAAO;IAET,IAAIA,AAAmB,MAAnBA,QAAQ,MAAM,EAChB,OAAOC,AAAAA,IAAAA,8BAAAA,0BAAAA,AAAAA,EAA2BD,OAAO,CAAC,EAAE;IAG9C,MAAME,cAAcb,MAAM,IAAI,CAC5B,IAAIK,IAAIM,QAAQ,GAAG,CAAC,CAACG,SAAWA,OAAO,UAAU,EAAE,MAAM,CAACC;IAE5D,MAAMC,eAAe,IAAIP,kCAAAA,gBAAgBA,CAAC;QACxC,YAAYE,OAAO,CAAC,EAAE,CAAC,UAAU,IAAIM,AAAAA,IAAAA,kCAAAA,UAAAA,AAAAA;QACrC,WAAW;QACX,kBAAkB;QAClB,aAAaN,QAAQ,OAAO,CAAC,CAACG,SAAWA,OAAO,WAAW,IAAI,EAAE;QACjE,YAAYD,AAAuB,MAAvBA,YAAY,MAAM,GAASA,WAAW,CAAC,EAAE,GAAG;QACxD,YAAYF,QAAQ,OAAO,CAAC,CAACG,SAAWA,OAAO,UAAU,IAAI,EAAE;IACjE;IACA,OAAOF,AAAAA,IAAAA,8BAAAA,0BAAAA,AAAAA,EAA2BI;AACpC;AAEO,MAAME;IAGH,sBAAsBC,SAAiB,EAAEC,gBAAyB,EAAE;QAC1E,OAAO,IAAIX,kCAAAA,gBAAgBA,CAAC;YAC1B,YAAY;YACZU;YACAC;YACA,aAAa,EAAE;YACf,YAAY,EAAE;QAChB,GAAG,SAAS;IACd;IAEO,OAAOC,UAAoC,EAAE;QAClD,IAAIA,WAAW,cAAc,EAAE;YAC7B,MAAMC,gBAAgBrB,qBAAqBoB,WAAW,cAAc;YACpE,MAAME,iBAAiBN,AAAAA,IAAAA,kCAAAA,UAAAA,AAAAA;YACvB,IACEK,iBACAC,kBACAD,kBAAkBC,kBAClB,CAACnB,yBAAyB,GAAG,CAACkB,gBAC9B;gBACAlB,yBAAyB,GAAG,CAACkB;gBAC7BE,IAAAA,sBAAAA,MAAAA,AAAAA,EACE,CAAC,iGAAiG,EAAEF,cAAc,kCAAkC,EAAEC,eAAe,gRAAgR,CAAC;YAE1b;QACF;QACA,IAAI,CAAC,WAAW,CAAC,IAAI,CAACF;IACxB;IACO,QAAQ;QACb,IAAI,CAAC,WAAW,GAAG,EAAE;IACvB;IAQQ,iBAAiBI,QAAkB,EAAU;QACnD,MAAMC,YAAYD,SACf,GAAG,CAAC,CAACE,IAAMC,AAAAA,IAAAA,sBAAAA,mBAAAA,AAAAA,EAAoBD,IAC/B,MAAM,CAAC,CAACA,IAAMA,EAAE,MAAM,GAAG;QAC5B,IAAID,AAAqB,MAArBA,UAAU,MAAM,EAAQ,OAAO;QACnC,IAAIA,AAAqB,MAArBA,UAAU,MAAM,EAAQ,OAAOA,SAAS,CAAC,EAAE;QAK/C,MAAMG,OAAOpB,kCAAAA,gBAAAA,CAAAA,oBAAqC,CAACiB,SAAS,CAAC,EAAE;QAC/D,MAAMI,gBAAgB;eAAID,KAAK,UAAU;SAAC;QAC1C,IAAK,IAAIE,IAAI,GAAGA,IAAIL,UAAU,MAAM,EAAEK,IAAK;YACzC,MAAMC,QAAQvB,kCAAAA,gBAAAA,CAAAA,oBAAqC,CAACiB,SAAS,CAACK,EAAE;YAChED,cAAc,IAAI,IAAIE,MAAM,UAAU;QACxC;QACAH,KAAK,UAAU,GAAGnC,2BAA2BoC;QAC7C,OAAOD,KAAK,SAAS;IACvB;IAEO,aACLI,iBAAkC,MAAM,EACxCC,IAIC,EACc;QACf,MAAM,EACJC,oBAAoB,KAAK,EACzBC,YAAY,KAAK,EACjBC,SAAS,EACV,GAAGH,QAAQ,CAAC;QAEb,IAAI,AAA4B,MAA5B,IAAI,CAAC,WAAW,CAAC,MAAM,EAAQ;YACjCV,IAAAA,sBAAAA,MAAAA,AAAAA,EAAO;YACP,OAAO;QACT;QAEA,MAAMc,YAAYD,YACd/C,mCAAAA,OAAY,CAAC+C,aACbE,AAAAA,IAAAA,uBAAAA,oBAAAA,AAAAA,EAAqB;QACzB,IAAIF,WACFG,AAAAA,IAAAA,iCAAAA,SAAAA,AAAAA,EAAUF,WAAW;YAAE,WAAW;QAAK;QAOzC,MAAMG,mBAAmB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAACC,OAC7C3B,QACE2B,KAAK,cAAc,IAAIrD,sBAAsBqD,KAAK,cAAc;QAGpE,MAAMC,yBAAyBF,iBAAiB,IAAI,CAAC1B;QAErD,MAAM6B,eACJX,AAAmB,WAAnBA,iBACIY,AAAAA,IAAAA,yBAAAA,iBAAAA,AAAAA,EAAkB,mBAClBZ;QAIN,MAAMa,iBAAiBH,yBACnBrD,mCAAAA,OAAY,CAACgD,WAAWM,cAAc,gBACtCtD,mCAAAA,OAAY,CAACgD,WAAW,GAAGM,aAAa,KAAK,CAAC;QAElD,IAAIX,AAAmB,WAAnBA,kBAA6BzC,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWsD,iBAAiB;YAC3D,IAAI,CAACV,WACH,MAAM,IAAIW,MACR,CAAC,4BAA4B,EAAED,eAAe,+CAA+C,CAAC;YAGlG,IAAIH,wBACFK,AAAAA,IAAAA,iCAAAA,MAAAA,AAAAA,EAAO1D,mCAAAA,OAAY,CAACwD,iBAAiB;gBAAE,WAAW;gBAAM,OAAO;YAAK;iBAEpEG,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWH;QAEf;QAEA,IAAIH,wBACFH,AAAAA,IAAAA,iCAAAA,SAAAA,AAAAA,EAAUlD,mCAAAA,OAAY,CAACwD,iBAAiB;YAAE,WAAW;QAAK;QAG5DtB,IAAAA,sBAAAA,MAAAA,AAAAA,EACE,CAAC,cAAc,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,sCAAsC,CAAC;QAGlF,IAAI;YAGF,MAAM0B,aAAa;YACnB,MAAMC,MAAMC,AAAAA,IAAAA,kCAAAA,YAAAA,AAAAA;YACZ,MAAMC,aAAaF,IAAI,WAAW,CAACD;YACnC,MAAMI,kBACJD,AAAe,OAAfA,aAAoBF,IAAI,KAAK,CAAC,GAAGE,cAAcF;YACjDI,IAAAA,iCAAAA,cAAAA,AAAAA,EAAeT,gBAAgBQ;YAG/B,IAAIX,wBACFY,AAAAA,IAAAA,iCAAAA,cAAAA,AAAAA,EAAeT,gBAAgBU,AAAAA,IAAAA,8BAAAA,mBAAAA,AAAAA;YAGjC,MAAMC,eAAmC,EAAE;YAG3C,IAAK,IAAI1B,IAAI,GAAGA,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAEA,IAAK;gBAChD,MAAMV,aAAa,IAAI,CAAC,WAAW,CAACU,EAAE;gBACtCP,IAAAA,sBAAAA,MAAAA,AAAAA,EAAO,CAAC,kBAAkB,EAAEO,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;gBAE9D,MAAM,EAAE2B,gBAAgB,EAAE,GAAGrC;gBAC7B,IAAId,aAAa,IAAI,CAAC,qBAAqB,CACzCmD,iBAAiB,SAAS,EAC1BA,iBAAiB,eAAe;gBAElC,IAAIC,gBAAgB,CAAC,aAAa,EAAE5B,GAAG;gBAEvC,IAAIV,WAAW,cAAc,EAAE;oBAC7B,IAAIoB,gBAAgB,CAACV,EAAE,EAAE;wBAIvB,MAAM6B,YAAYtE,mCAAAA,OAAY,CAAC+B,WAAW,cAAc;wBACxD,MAAMwC,iBAAiBvE,mCAAAA,IAAS,CAACsE,WAAW;wBAC5C,IAAIpE,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWqE,iBAAiB;4BAC9B,MAAMC,uBAAuBxE,mCAAAA,IAAS,CACpCA,mCAAAA,OAAY,CAACwD,iBACb;4BAEFN,IAAAA,iCAAAA,SAAAA,AAAAA,EAAUsB,sBAAsB;gCAAE,WAAW;4BAAK;4BAClD,KAAK,MAAMC,QAAQC,AAAAA,IAAAA,iCAAAA,WAAAA,AAAAA,EAAYH,gBAAiB;gCAC9C,MAAMI,MAAM3E,mCAAAA,IAAS,CAACuE,gBAAgBE;gCACtC,MAAMG,OAAO5E,mCAAAA,IAAS,CAACwE,sBAAsBC;gCAC7CI,IAAAA,iCAAAA,YAAAA,AAAAA,EAAaF,KAAKC;4BACpB;wBACF;oBACF,OAEEE,AAAAA,IAAAA,8BAAAA,wBAAAA,AAAAA,EAAyB/C,WAAW,cAAc,EAAEyB;oBAStD,MAAMuB,WAAWC,AAAAA,IAAAA,8BAAAA,yBAAAA,AAAAA,EACfjD,WAAW,cAAc,EACzB,MAAM,CAAC,CAACkD,IAAMA,EAAE,OAAO,CAAC,QAAQ,CAAC;oBACnC,MAAMC,eAAeH,QAAQ,CAAC,EAAE,EAAE,QAAQ,MACxC;oBAEF,IAAIG,cACFb,gBAAgBc,mBAAmBD,YAAY,CAAC,EAAE;oBAEpD,MAAME,sBACJL,SAAS,MAAM,GAAG,IACd,IAAI,CAAC,gBAAgB,CAACA,SAAS,GAAG,CAAC,CAACE,IAAMA,EAAE,OAAO,KACnDpE,AAAAA,IAAAA,8BAAAA,yBAAAA,AAAAA,EAA0BkB,WAAW,cAAc;oBACzD,IAAIqD,qBACFnE,aAAamE;gBAEjB;gBAEA,MAAMC,cAAcrE,wBAAwBC;gBAC5C,IAAIoE,aACFlB,aAAa,IAAI,CAACkB;gBAGpB,MAAMC,gBAAgB,GAAGC,AAAAA,IAAAA,kCAAAA,iBAAAA,AAAAA,EACvB;oBACEtE;oBACA,YAAY;wBACV,iBAAiBoD;wBACjB,CAAC7E,8BAAAA,yBAAyBA,CAAC,EAAE6D,yBACzB,cACA;wBACJ,0BAA0Be,iBAAiB,YAAY;wBACvD,wBAAwBA,iBAAiB,UAAU;wBACnD,uBAAuBA,iBAAiB,SAAS;wBACjD,oBAAoBA,iBAAiB,MAAM;wBAC3C,6BAA6BA,iBAAiB,eAAe;wBAC7D,WAAW;oBACb;gBACF,GACAoB,QACAA,QACA,OACA,EAAE,CAAC;gBAELvB,IAAAA,iCAAAA,cAAAA,AAAAA,EAAeT,gBAAgB8B;YACjC;YAEA,MAAMG,eAAerE,yBAAyB+C;YAC9C,IAAIsB,cACFxB,AAAAA,IAAAA,iCAAAA,cAAAA,AAAAA,EAAeT,gBAAgBiC;YAIjCxB,IAAAA,iCAAAA,cAAAA,AAAAA,EAAeT,gBAAgB,GAAGI,WAAW,EAAE,CAAC;YAEhD1B,IAAAA,sBAAAA,MAAAA,AAAAA,EAAO,CAAC,gCAAgC,EAAEsB,gBAAgB;YAG1D,IAAIX,mBAAmB;gBACrB,KAAK,MAAMO,QAAQ,IAAI,CAAC,WAAW,CACjC,IAAKA,KAAK,cAAc,EACxB,IAAI;oBACF,IAAIjD,uBAAuBiD,KAAK,cAAc,GAAG;wBAG/C,MAAMkB,YAAYtE,mCAAAA,OAAY,CAACoD,KAAK,cAAc;wBAClDM,IAAAA,iCAAAA,MAAAA,AAAAA,EAAOY,WAAW;4BAAE,WAAW;4BAAM,OAAO;wBAAK;oBACnD,OACEX,AAAAA,IAAAA,iCAAAA,UAAAA,AAAAA,EAAWP,KAAK,cAAc;gBAElC,EAAE,OAAOsC,OAAO;oBACdxD,IAAAA,sBAAAA,MAAAA,AAAAA,EAAO,CAAC,sBAAsB,EAAEkB,KAAK,cAAc,CAAC,EAAE,EAAEsC,OAAO;gBACjE;gBAEFxD,IAAAA,sBAAAA,MAAAA,AAAAA,EAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,iBAAiB,CAAC;YAC9D;YACA,OAAOsB;QACT,EAAE,OAAOkC,OAAO;YACdxD,IAAAA,sBAAAA,MAAAA,AAAAA,EAAO,CAAC,uBAAuB,EAAEwD,OAAO;YACxC,MAAMA;QACR;IACF;;QA7QA,uBAAQ,eAA0C,EAAE;;AA8QtD;AAsBO,SAASC,yBACdC,QAAgB;IAEhB,IAAIC,WAAoC;IACxC,IAAIC,kBAAkB;IACtB,MAAMC,4BAA4B,IAAIvF;IAEtCZ,IAAAA,8BAAAA,qBAAAA,AAAAA,EAAsBgG,UAAU,CAACI;QAC/B,IAAI,CAACA,WAAW,OAAO,CAAC,QAAQ,CAAC,kBAC/B,OAAO;QAET,MAAMC,cAAc9E,kCAAAA,gBAAAA,CAAAA,oBAAqC,CACvDmB,AAAAA,IAAAA,sBAAAA,mBAAAA,AAAAA,EAAoB0D,WAAW,OAAO;QAExC,KAAK,MAAME,aAAaD,YAAY,UAAU,CAAE;YAC9CH,mBAAmB;YACnB,IAAII,UAAU,EAAE,EACdH,0BAA0B,GAAG,CAACG,UAAU,EAAE,EAAEJ;QAEhD;QACA,OAAO;IACT;IAEA,MAAMzF,aAA+B,EAAE;IACvCyF,kBAAkB;IAClBlG,IAAAA,8BAAAA,qBAAAA,AAAAA,EAAsBgG,UAAU,CAACI;QAC/B,IAAI,CAACA,WAAW,OAAO,CAAC,QAAQ,CAAC,kBAC/B,OAAO;QAGT,MAAMC,cAAc9E,kCAAAA,gBAAAA,CAAAA,oBAAqC,CACvDmB,AAAAA,IAAAA,sBAAAA,mBAAAA,AAAAA,EAAoB0D,WAAW,OAAO;QAExC,IAAI,CAACH,UACHA,WAAWI;QAGb,KAAK,MAAMC,aAAaD,YAAY,UAAU,CAAE;YAC9CH,mBAAmB;YACnB,IACEI,CAAAA,UAAU,EAAE,IACZH,0BAA0B,GAAG,CAACG,UAAU,EAAE,MAAMJ,iBAIlDzF,WAAW,IAAI,CAAC6F;QAClB;QAEA,OAAO;IACT;IAEA,IAAI,CAACL,UACH,MAAM,IAAIpC,MAAM,CAAC,gCAAgC,EAAEmC,UAAU;IAG/D,OAAO;QACLC;QACAxF;IACF;AACF;AAEA,SAAS8F,oBAAoBC,QAAgB;IAC3C,IAAIA,AAAa,gBAAbA,UAA0B,OAAO;IACrC,IAAIA,AAAa,iBAAbA,UAA2B,OAAO;IACtC,MAAM,IAAI3C,MAAM,CAAC,kCAAkC,EAAE2C,UAAU;AACjE;AAEA,SAASC,kCACPH,SAAyB,EACzBtD,IAIC;IAED,MAAM0D,QAAQ,CAACC;QACb,IAAI7F,MAAM,OAAO,CAAC6F,OAAO;YACvB,KAAK,MAAMC,QAAQD,KACjBD,MAAME;YAER;QACF;QAEA,IAAI,AAAgB,YAAhB,OAAOD,QAAqBA,AAAS,SAATA,MAAe;QAE/C,MAAME,MAAMC,AAAAA,IAAAA,oCAAAA,sBAAAA,AAAAA,EAAuBH;QACnC,IAAIE,KAAK;YACP,MAAME,MAAMR,oBAAoBM,IAAI,QAAQ;YAC5C,MAAMG,WAAW,GAAGH,IAAI,EAAE,CAAC,CAAC,EAAEE,KAAK;YACnC,MAAME,eAAe,CAAC,cAAc,EAAED,UAAU;YAChD,MAAME,eAAe9G,mCAAAA,IAAS,CAAC4C,KAAK,cAAc,EAAEgE;YAEpD,IAAI,CAAChE,KAAK,YAAY,CAAC,GAAG,CAACgE,WAAW;gBACpC,MAAMG,WAAWC,AAAAA,IAAAA,oCAAAA,uBAAAA,AAAAA,EAAwBP,KAAK;oBAC5C,YAAY7D,KAAK,QAAQ;gBAC3B;gBACA,IAAImE,AAAkB,eAAlBA,SAAS,IAAI,EAAiB;oBAChC,MAAME,YAAYF,SAAS,OAAO,CAAC,OAAO,CACxC,mCACA;oBAEFG,IAAAA,iCAAAA,aAAAA,AAAAA,EAAcJ,cAAcK,OAAO,IAAI,CAACF,WAAW;gBACrD,OACEpC,AAAAA,IAAAA,iCAAAA,YAAAA,AAAAA,EAAakC,SAAS,QAAQ,EAAED;gBAElClE,KAAK,YAAY,CAAC,GAAG,CAACgE;YACxB;YAEAH,IAAI,OAAO,GAAG;YACdA,IAAI,IAAI,GAAGI;YACX;QACF;QAEA,KAAK,MAAMO,SAASlI,OAAO,MAAM,CAACqH,MAChCD,MAAMc;IAEV;IAEAd,MAAMJ;AACR;AAMO,SAASmB,2BACdC,OAA+B;IAE/B,MAAM,EAAE1B,QAAQ,EAAE7C,SAAS,EAAE,GAAGuE;IAChC,MAAM/C,iBAAiBvE,mCAAAA,IAAS,CAAC+C,WAAW;IAE5CG,IAAAA,iCAAAA,SAAAA,AAAAA,EAAUH,WAAW;QAAE,WAAW;IAAK;IACvCG,IAAAA,iCAAAA,SAAAA,AAAAA,EAAUqB,gBAAgB;QAAE,WAAW;IAAK;IAE5C,MAAMgD,qBAA+B,EAAE;IACvC,MAAMC,yBAAyB,IAAIzG;IACnC,MAAM,EAAE8E,QAAQ,EAAExF,UAAU,EAAE,GAAGsF,yBAAyBC;IAE1D,IAAI6B,YAAY;IAChB,KAAK,MAAMvB,aAAa7F,WAAY;QAClCoH,aAAa;QACbpB,kCAAkCH,WAAW;YAC3CN;YACArB;YACA,cAAciD;QAChB;QACA,MAAME,sBAAsB,IAAIvG,kCAAAA,gBAAgBA,CAAC;YAC/C,YAAY0E,SAAS,UAAU;YAC/B,WAAWA,SAAS,SAAS;YAC7B,kBAAkBA,SAAS,gBAAgB;YAC3C,aAAaA,SAAS,WAAW;YACjC,YAAYA,SAAS,UAAU;YAC/B,YAAY;gBAACK;aAAU;QACzB;QAEA,MAAMyB,eAAe3H,mCAAAA,IAAS,CAAC+C,WAAW,GAAG0E,UAAU,eAAe,CAAC;QACvEP,IAAAA,iCAAAA,aAAAA,AAAAA,EAAcS,cAAcD,oBAAoB,SAAS,CAAC,IAAI;QAC9DH,mBAAmB,IAAI,CAACI;IAC1B;IAEA,OAAO;QACLJ;QACA,iBAAiB7G,MAAM,IAAI,CAAC8G,wBACzB,IAAI,GACJ,GAAG,CAAC,CAACZ,WAAa5G,mCAAAA,IAAS,CAACuE,gBAAgBqC;IACjD;AACF"}
@@ -30,7 +30,6 @@ const errors_js_namespaceObject = require("../ai-model/errors.js");
30
30
  const inspect_js_namespaceObject = require("../ai-model/inspect.js");
31
31
  const describe_js_namespaceObject = require("../ai-model/prompt/describe.js");
32
32
  const index_js_namespaceObject = require("../ai-model/service-caller/index.js");
33
- const external_common_js_namespaceObject = require("../common.js");
34
33
  const external_types_js_namespaceObject = require("../types.js");
35
34
  const img_namespaceObject = require("@midscene/shared/img");
36
35
  const logger_namespaceObject = require("@midscene/shared/logger");
@@ -75,9 +74,9 @@ class Service {
75
74
  const taskInfo = {
76
75
  ...this.taskInfo ? this.taskInfo : {},
77
76
  durationMs: timeCost,
78
- rawResponse: JSON.stringify(rawResponse),
77
+ rawResponse,
79
78
  rawChoiceMessage,
80
- formatResponse: JSON.stringify(parseResult),
79
+ formatResponse: parseResult,
81
80
  usage,
82
81
  searchArea: searchArea.trace.sourceRect,
83
82
  searchAreaRawResponse: searchArea.trace.rawResponse,
@@ -241,7 +240,7 @@ class Service {
241
240
  durationMs: timeCost,
242
241
  rawResponse,
243
242
  rawChoiceMessage,
244
- formatResponse: JSON.stringify(parseResult),
243
+ formatResponse: parseResult,
245
244
  usage,
246
245
  reasoning_content
247
246
  };
@@ -278,28 +277,113 @@ class Service {
278
277
  (0, utils_namespaceObject.assert)(screenshotBase64, 'screenshot is required for service.describe');
279
278
  const systemPrompt = (0, describe_js_namespaceObject.elementDescriberInstruction)();
280
279
  const defaultRectSize = 30;
281
- const targetRect = Array.isArray(target) ? {
280
+ const targetFromPoint = Array.isArray(target);
281
+ const targetRect = targetFromPoint ? {
282
282
  left: Math.floor(target[0] - defaultRectSize / 2),
283
283
  top: Math.floor(target[1] - defaultRectSize / 2),
284
284
  width: defaultRectSize,
285
285
  height: defaultRectSize
286
286
  } : target;
287
- let imagePayload = await (0, img_namespaceObject.compositeElementInfoImg)({
287
+ const targetPoint = targetFromPoint ? {
288
+ x: target[0],
289
+ y: target[1]
290
+ } : void 0;
291
+ const usePointMarker = targetFromPoint;
292
+ const imagePayload = usePointMarker ? await (0, img_namespaceObject.compositePointMarkerImg)({
293
+ inputImgBase64: screenshotBase64,
294
+ size: shotSize,
295
+ point: targetPoint
296
+ }) : await (0, img_namespaceObject.compositeElementInfoImg)({
288
297
  inputImgBase64: screenshotBase64,
289
298
  size: shotSize,
290
299
  elementsPositionInfo: [
291
300
  {
292
- rect: targetRect
301
+ rect: (0, external_utils_js_namespaceObject.getDescribeMarkerRect)(targetRect)
293
302
  }
294
303
  ],
295
- borderThickness: 3
304
+ borderThickness: (0, external_utils_js_namespaceObject.getDescribeMarkerBorderThickness)(targetRect),
305
+ centerPoint: true
296
306
  });
297
- if (opt?.deepLocate) {
298
- const searchArea = (0, external_common_js_namespaceObject.expandSearchArea)(targetRect, shotSize);
299
- debug('describe: cropping to searchArea', searchArea);
300
- const croppedResult = await (0, img_namespaceObject.cropByRect)(imagePayload, searchArea);
301
- imagePayload = croppedResult.imageBase64;
302
- }
307
+ const shouldDeepDescribe = opt?.deepDescribe;
308
+ let imageContent;
309
+ if (shouldDeepDescribe) {
310
+ const contextAreas = (0, external_utils_js_namespaceObject.getDescribeDeepContextAreas)(targetRect, shotSize);
311
+ const contextImages = await Promise.all(contextAreas.map(async (area)=>{
312
+ debug('describe: cropping deep context area', area);
313
+ const croppedResult = await (0, img_namespaceObject.cropByRect)(screenshotBase64, area.rect);
314
+ const cropSize = {
315
+ width: croppedResult.width,
316
+ height: croppedResult.height
317
+ };
318
+ const targetInCrop = (0, external_utils_js_namespaceObject.getRectInCrop)(targetRect, area.rect, cropSize);
319
+ const markedCropPayload = targetFromPoint ? await (0, img_namespaceObject.compositePointMarkerImg)({
320
+ inputImgBase64: croppedResult.imageBase64,
321
+ size: cropSize,
322
+ point: {
323
+ x: targetPoint.x - area.rect.left,
324
+ y: targetPoint.y - area.rect.top
325
+ }
326
+ }) : await (0, img_namespaceObject.compositeElementInfoImg)({
327
+ inputImgBase64: croppedResult.imageBase64,
328
+ size: cropSize,
329
+ elementsPositionInfo: [
330
+ {
331
+ rect: (0, external_utils_js_namespaceObject.getDescribeMarkerRect)(targetInCrop)
332
+ }
333
+ ],
334
+ borderThickness: (0, external_utils_js_namespaceObject.getDescribeMarkerBorderThickness)(targetInCrop),
335
+ centerPoint: true
336
+ });
337
+ const resizeSize = (0, external_utils_js_namespaceObject.getDescribeDeepLocateResizeSize)(croppedResult);
338
+ return {
339
+ kind: area.kind,
340
+ imageBase64: resizeSize ? await (0, img_namespaceObject.resizeImgBase64)(markedCropPayload, resizeSize) : markedCropPayload
341
+ };
342
+ }));
343
+ const contextImageContent = contextImages.flatMap((item, index)=>[
344
+ {
345
+ type: 'text',
346
+ text: `Image ${index + 2}: focused detail crop around the target, for reading text, icon shape, and exact local boundaries.`
347
+ },
348
+ {
349
+ type: 'image_url',
350
+ image_url: {
351
+ url: item.imageBase64,
352
+ detail: 'high'
353
+ }
354
+ }
355
+ ]);
356
+ imageContent = [
357
+ {
358
+ type: 'text',
359
+ text: 'Use these images together to describe the real UI target marked by the temporary callout. Do not describe the marker itself.'
360
+ },
361
+ {
362
+ type: 'text',
363
+ text: 'Image 1: full screenshot overview with the target marker, for page position and ownership context.'
364
+ },
365
+ {
366
+ type: 'image_url',
367
+ image_url: {
368
+ url: imagePayload,
369
+ detail: 'high'
370
+ }
371
+ },
372
+ ...contextImageContent
373
+ ];
374
+ } else imageContent = [
375
+ {
376
+ type: 'text',
377
+ text: 'Full screenshot with a temporary callout marking the target:'
378
+ },
379
+ {
380
+ type: 'image_url',
381
+ image_url: {
382
+ url: imagePayload,
383
+ detail: 'high'
384
+ }
385
+ }
386
+ ];
303
387
  const msgs = [
304
388
  {
305
389
  role: 'system',
@@ -307,15 +391,7 @@ class Service {
307
391
  },
308
392
  {
309
393
  role: 'user',
310
- content: [
311
- {
312
- type: 'image_url',
313
- image_url: {
314
- url: imagePayload,
315
- detail: 'high'
316
- }
317
- }
318
- ]
394
+ content: imageContent
319
395
  }
320
396
  ];
321
397
  let res;
@@ -1 +1 @@
1
- {"version":3,"file":"service/index.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/service/index.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { defaultModelFamilyRequiredForLocateMessage } from '@/ai-model/errors';\nimport {\n AiExtractElementInfo,\n AiLocateElement,\n AiLocateSection,\n buildSearchAreaConfig,\n} from '@/ai-model/inspect';\nimport type { ModelRuntime } from '@/ai-model/models';\nimport { elementDescriberInstruction } from '@/ai-model/prompt/describe';\nimport {\n AIResponseParseError,\n callAIWithObjectResponse,\n} from '@/ai-model/service-caller';\nimport type { AIArgs } from '@/ai-model/types';\nimport type { SearchAreaConfig } from '@/ai-model/workflows/inspect/types';\nimport { expandSearchArea } from '@/common';\nimport type {\n AIDescribeElementResponse,\n AIUsageInfo,\n DetailedLocateParam,\n LocateResultElement,\n LocateResultWithDump,\n PartialServiceDumpFromSDK,\n PlanningLocateParam,\n Rect,\n ServiceExtractOption,\n ServiceExtractParam,\n ServiceExtractResult,\n ServiceTaskInfo,\n UIContext,\n} from '@/types';\nimport { ServiceError } from '@/types';\nimport { compositeElementInfoImg, cropByRect } from '@midscene/shared/img';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\nimport type { TMultimodalPrompt, TUserPrompt } from '../common';\nimport {\n createServiceDump,\n recoverDescribeResponseFromParseError,\n} from './utils';\n\nexport interface LocateOpts {\n context?: UIContext;\n planLocatedElement?: LocateResultElement;\n}\n\nexport type AnyValue<T> = {\n [K in keyof T]: unknown extends T[K] ? any : T[K];\n};\n\ninterface ServiceOptions {\n taskInfo?: Omit<ServiceTaskInfo, 'durationMs'>;\n}\n\ninterface LocateSearchAreaResult {\n config?: SearchAreaConfig;\n trace: {\n sourceRect?: Rect;\n rawResponse?: string;\n rawChoiceMessage?: unknown;\n usage?: AIUsageInfo;\n };\n}\n\nconst debug = getDebug('ai:service');\nexport default class Service {\n contextRetrieverFn: () => Promise<UIContext> | UIContext;\n\n taskInfo?: Omit<ServiceTaskInfo, 'durationMs'>;\n\n constructor(\n context: UIContext | (() => Promise<UIContext> | UIContext),\n opt?: ServiceOptions,\n ) {\n assert(context, 'context is required for Service');\n if (typeof context === 'function') {\n this.contextRetrieverFn = context;\n } else {\n this.contextRetrieverFn = () => Promise.resolve(context);\n }\n\n if (typeof opt?.taskInfo !== 'undefined') {\n this.taskInfo = opt.taskInfo;\n }\n }\n\n async locate(\n query: PlanningLocateParam,\n opt: LocateOpts,\n modelRuntime: ModelRuntime,\n abortSignal?: AbortSignal,\n ): Promise<LocateResultWithDump> {\n const { config: modelConfig } = modelRuntime;\n const queryPrompt = typeof query === 'string' ? query : query.prompt;\n assert(queryPrompt, 'query is required for locate');\n\n assert(typeof query === 'object', 'query should be an object for locate');\n\n if (!modelConfig.modelFamily) {\n throw new Error(defaultModelFamilyRequiredForLocateMessage);\n }\n\n const context = opt?.context || (await this.contextRetrieverFn());\n\n const searchArea = await this.resolveLocateSearchArea({\n query,\n queryPrompt,\n opt,\n context,\n modelRuntime,\n abortSignal,\n });\n\n const startTime = Date.now();\n const {\n parseResult,\n rect,\n rawResponse,\n rawChoiceMessage,\n usage,\n reasoning_content,\n } = await AiLocateElement({\n context,\n targetElementDescription: queryPrompt,\n searchConfig: searchArea.config,\n modelRuntime,\n abortSignal,\n });\n\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse: JSON.stringify(rawResponse),\n rawChoiceMessage,\n formatResponse: JSON.stringify(parseResult),\n usage,\n searchArea: searchArea.trace.sourceRect,\n searchAreaRawResponse: searchArea.trace.rawResponse,\n searchAreaRawChoiceMessage: searchArea.trace.rawChoiceMessage,\n searchAreaUsage: searchArea.trace.usage,\n reasoning_content,\n };\n\n let errorLog: string | undefined;\n if (parseResult.errors?.length) {\n errorLog = `failed to locate element: \\n${parseResult.errors.join('\\n')}`;\n }\n\n const dumpData: PartialServiceDumpFromSDK = {\n type: 'locate',\n userQuery: {\n element: queryPrompt,\n },\n matchedRect: rect,\n data: null,\n taskInfo,\n deepLocate: !!searchArea.trace.sourceRect,\n error: errorLog,\n };\n\n const element = parseResult.element;\n\n const dump = createServiceDump({\n ...dumpData,\n matchedElement: element ? [element] : [],\n });\n\n if (errorLog) {\n throw new ServiceError(errorLog, dump);\n }\n\n if (element) {\n return {\n element: {\n center: element.center,\n rect: element.rect,\n description: element.description,\n },\n rect,\n dump,\n };\n }\n\n return {\n element: null,\n rect,\n dump,\n };\n }\n\n private async resolveLocateSearchArea(options: {\n query: PlanningLocateParam;\n queryPrompt: TUserPrompt;\n opt: LocateOpts;\n context: UIContext;\n modelRuntime: ModelRuntime;\n abortSignal?: AbortSignal;\n }): Promise<LocateSearchAreaResult> {\n const { query, queryPrompt, opt, context, modelRuntime, abortSignal } =\n options;\n const { adapter } = modelRuntime;\n const hasPlanLocatedElement = !!opt?.planLocatedElement?.rect;\n\n if (!query.deepLocate) {\n return { trace: {} };\n }\n\n if (hasPlanLocatedElement) {\n const config = await buildSearchAreaConfig({\n context,\n baseRect: opt.planLocatedElement!.rect,\n });\n\n return {\n config,\n trace: {\n sourceRect: config.sourceRect,\n rawResponse: JSON.stringify({\n source: 'plan-located-element',\n rect: opt.planLocatedElement!.rect,\n }),\n },\n };\n }\n\n if (adapter.locate.supportsSearchArea) {\n const searchAreaResponse = await AiLocateSection({\n context,\n sectionDescription: queryPrompt,\n modelRuntime,\n abortSignal,\n });\n const { searchAreaConfig } = searchAreaResponse;\n assert(\n searchAreaConfig,\n `cannot find search area for \"${queryPrompt}\"${\n searchAreaResponse.error ? `: ${searchAreaResponse.error}` : ''\n }`,\n );\n\n return {\n config: searchAreaConfig,\n trace: {\n sourceRect: searchAreaConfig.sourceRect,\n rawResponse: searchAreaResponse.rawResponse,\n rawChoiceMessage: searchAreaResponse.rawChoiceMessage,\n usage: searchAreaResponse.usage,\n },\n };\n }\n\n const firstPassLocateResult = await AiLocateElement({\n context,\n targetElementDescription: queryPrompt,\n modelRuntime,\n abortSignal,\n });\n assert(\n firstPassLocateResult.rect,\n `cannot find search area for \"${queryPrompt}\"${\n firstPassLocateResult.parseResult.errors?.length\n ? `: ${firstPassLocateResult.parseResult.errors.join('\\n')}`\n : ''\n }`,\n );\n\n const config = await buildSearchAreaConfig({\n context,\n baseRect: firstPassLocateResult.rect,\n });\n\n return {\n config,\n trace: {\n sourceRect: config.sourceRect,\n rawResponse: JSON.stringify({\n source: 'deep-locate-first-pass',\n rect: firstPassLocateResult.rect,\n rawResponse: firstPassLocateResult.rawResponse,\n }),\n rawChoiceMessage: firstPassLocateResult.rawChoiceMessage,\n usage: firstPassLocateResult.usage,\n },\n };\n }\n\n async extract<T>(\n dataDemand: ServiceExtractParam,\n modelRuntime: ModelRuntime,\n opt?: ServiceExtractOption,\n pageDescription?: string,\n multimodalPrompt?: TMultimodalPrompt,\n context?: UIContext,\n executionOptions?: {\n abortSignal?: AbortSignal;\n },\n ): Promise<ServiceExtractResult<T>> {\n assert(context, 'context is required for extract');\n assert(\n typeof dataDemand === 'object' || typeof dataDemand === 'string',\n `dataDemand should be object or string, but get ${typeof dataDemand}`,\n );\n\n const startTime = Date.now();\n\n let parseResult: Awaited<\n ReturnType<typeof AiExtractElementInfo<T>>\n >['parseResult'];\n let rawResponse: string;\n let rawChoiceMessage: unknown;\n let usage: Awaited<ReturnType<typeof AiExtractElementInfo<T>>>['usage'];\n let reasoning_content: string | undefined;\n\n try {\n const result = await AiExtractElementInfo<T>({\n context,\n dataQuery: dataDemand,\n multimodalPrompt,\n extractOption: opt,\n modelRuntime,\n pageDescription,\n abortSignal: executionOptions?.abortSignal,\n });\n parseResult = result.parseResult;\n rawResponse = result.rawResponse;\n rawChoiceMessage = result.rawChoiceMessage;\n usage = result.usage;\n reasoning_content = result.reasoning_content;\n } catch (error) {\n if (error instanceof AIResponseParseError) {\n // Create dump with usage and rawResponse from the error\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse: error.rawResponse,\n rawChoiceMessage: error.rawChoiceMessage,\n usage: error.usage,\n };\n const dump = createServiceDump({\n type: 'extract',\n userQuery: { dataDemand },\n data: null,\n taskInfo,\n error: error.message,\n });\n throw new ServiceError(error.message, dump);\n }\n throw error;\n }\n\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse,\n rawChoiceMessage,\n formatResponse: JSON.stringify(parseResult),\n usage,\n reasoning_content,\n };\n\n let errorLog: string | undefined;\n if (parseResult.errors?.length) {\n errorLog = `AI response error: \\n${parseResult.errors.join('\\n')}`;\n }\n\n const dumpData: PartialServiceDumpFromSDK = {\n type: 'extract',\n userQuery: {\n dataDemand,\n },\n data: null,\n taskInfo,\n error: errorLog,\n };\n\n const { data, thought } = parseResult || {};\n\n const dump = createServiceDump({\n ...dumpData,\n data,\n });\n\n if (errorLog && !data) {\n throw new ServiceError(errorLog, dump);\n }\n\n return {\n data,\n thought,\n usage,\n reasoning_content,\n dump,\n };\n }\n\n async describe(\n target: Rect | [number, number],\n modelRuntime: ModelRuntime,\n opt?: {\n deepLocate?: boolean;\n context?: UIContext;\n },\n ): Promise<Pick<AIDescribeElementResponse, 'description'>> {\n assert(target, 'target is required for service.describe');\n const context = opt?.context || (await this.contextRetrieverFn());\n const { shotSize } = context;\n const screenshotBase64 = context.screenshot.base64;\n assert(screenshotBase64, 'screenshot is required for service.describe');\n const systemPrompt = elementDescriberInstruction();\n\n // Convert [x,y] center point to Rect if needed\n const defaultRectSize = 30;\n const targetRect: Rect = Array.isArray(target)\n ? {\n left: Math.floor(target[0] - defaultRectSize / 2),\n top: Math.floor(target[1] - defaultRectSize / 2),\n width: defaultRectSize,\n height: defaultRectSize,\n }\n : target;\n\n let imagePayload = await compositeElementInfoImg({\n inputImgBase64: screenshotBase64,\n size: shotSize,\n elementsPositionInfo: [\n {\n rect: targetRect,\n },\n ],\n borderThickness: 3,\n });\n\n if (opt?.deepLocate) {\n const searchArea = expandSearchArea(targetRect, shotSize);\n // Always crop in describe mode. Unlike locate's deepLocate (where\n // cropping too small loses context for finding elements), describe's\n // deepLocate intentionally zooms in so the model produces a more\n // precise description from a focused view. expandSearchArea already\n // guarantees a minimum 400x400 area with surrounding context.\n // Describe is not a coordinate-parsing flow, so it does not need image\n // padding for bbox normalization.\n debug('describe: cropping to searchArea', searchArea);\n const croppedResult = await cropByRect(imagePayload, searchArea);\n imagePayload = croppedResult.imageBase64;\n }\n\n const msgs: AIArgs = [\n { role: 'system', content: systemPrompt },\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: imagePayload,\n detail: 'high',\n },\n },\n ],\n },\n ];\n\n let res: Awaited<\n ReturnType<typeof callAIWithObjectResponse<AIDescribeElementResponse>>\n >;\n try {\n res = await callAIWithObjectResponse<AIDescribeElementResponse>(\n msgs,\n modelRuntime,\n );\n } catch (error) {\n const recoveredResponse = recoverDescribeResponseFromParseError(error);\n if (!recoveredResponse) {\n throw error;\n }\n debug('describe: recovered malformed description JSON response');\n return recoveredResponse;\n }\n\n const { content } = res;\n assert(!content.error, `describe failed: ${content.error}`);\n assert(content.description, 'failed to describe the element');\n return content;\n }\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","debug","getDebug","Service","query","opt","modelRuntime","abortSignal","modelConfig","queryPrompt","assert","Error","defaultModelFamilyRequiredForLocateMessage","context","searchArea","startTime","Date","parseResult","rect","rawResponse","rawChoiceMessage","usage","reasoning_content","AiLocateElement","timeCost","taskInfo","JSON","errorLog","dumpData","element","dump","createServiceDump","ServiceError","options","adapter","hasPlanLocatedElement","config","buildSearchAreaConfig","searchAreaResponse","AiLocateSection","searchAreaConfig","firstPassLocateResult","dataDemand","pageDescription","multimodalPrompt","executionOptions","result","AiExtractElementInfo","error","AIResponseParseError","data","thought","target","shotSize","screenshotBase64","systemPrompt","elementDescriberInstruction","defaultRectSize","targetRect","Array","Math","imagePayload","compositeElementInfoImg","expandSearchArea","croppedResult","cropByRect","msgs","res","callAIWithObjectResponse","recoveredResponse","recoverDescribeResponseFromParseError","content","Promise"],"mappings":";;;IAAAA,oBAAoB,CAAC,GAAG,CAAC,UAASC;QACjC,IAAI,IAAIC,OAAOD,WACR,IAAGD,oBAAoB,CAAC,CAACC,YAAYC,QAAQ,CAACF,oBAAoB,CAAC,CAAC,UAASE,MACzEC,OAAO,cAAc,CAAC,UAASD,KAAK;YAAE,YAAY;YAAM,KAAKD,UAAU,CAACC,IAAI;QAAC;IAGzF;;;ICNAF,oBAAoB,CAAC,GAAG,CAACI,KAAKC,OAAUF,OAAO,SAAS,CAAC,cAAc,CAAC,IAAI,CAACC,KAAKC;;;ICClFL,oBAAoB,CAAC,GAAG,CAAC;QACxB,IAAG,AAAkB,eAAlB,OAAOM,UAA0BA,OAAO,WAAW,EACrDH,OAAO,cAAc,CAAC,UAASG,OAAO,WAAW,EAAE;YAAE,OAAO;QAAS;QAEtEH,OAAO,cAAc,CAAC,UAAS,cAAc;YAAE,OAAO;QAAK;IAC5D;;;;;;;;;;;;;;;;;;;;;;;;;;;AC0DA,MAAMI,QAAQC,AAAAA,IAAAA,uBAAAA,QAAAA,AAAAA,EAAS;AACR,MAAMC;IAqBnB,MAAM,OACJC,KAA0B,EAC1BC,GAAe,EACfC,YAA0B,EAC1BC,WAAyB,EACM;QAC/B,MAAM,EAAE,QAAQC,WAAW,EAAE,GAAGF;QAChC,MAAMG,cAAc,AAAiB,YAAjB,OAAOL,QAAqBA,QAAQA,MAAM,MAAM;QACpEM,IAAAA,sBAAAA,MAAAA,AAAAA,EAAOD,aAAa;QAEpBC,IAAAA,sBAAAA,MAAAA,AAAAA,EAAO,AAAiB,YAAjB,OAAON,OAAoB;QAElC,IAAI,CAACI,YAAY,WAAW,EAC1B,MAAM,IAAIG,MAAMC,0BAAAA,0CAA0CA;QAG5D,MAAMC,UAAUR,KAAK,WAAY,MAAM,IAAI,CAAC,kBAAkB;QAE9D,MAAMS,aAAa,MAAM,IAAI,CAAC,uBAAuB,CAAC;YACpDV;YACAK;YACAJ;YACAQ;YACAP;YACAC;QACF;QAEA,MAAMQ,YAAYC,KAAK,GAAG;QAC1B,MAAM,EACJC,WAAW,EACXC,IAAI,EACJC,WAAW,EACXC,gBAAgB,EAChBC,KAAK,EACLC,iBAAiB,EAClB,GAAG,MAAMC,AAAAA,IAAAA,2BAAAA,eAAAA,AAAAA,EAAgB;YACxBV;YACA,0BAA0BJ;YAC1B,cAAcK,WAAW,MAAM;YAC/BR;YACAC;QACF;QAEA,MAAMiB,WAAWR,KAAK,GAAG,KAAKD;QAC9B,MAAMU,WAA4B;YAChC,GAAI,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;YACtC,YAAYD;YACZ,aAAaE,KAAK,SAAS,CAACP;YAC5BC;YACA,gBAAgBM,KAAK,SAAS,CAACT;YAC/BI;YACA,YAAYP,WAAW,KAAK,CAAC,UAAU;YACvC,uBAAuBA,WAAW,KAAK,CAAC,WAAW;YACnD,4BAA4BA,WAAW,KAAK,CAAC,gBAAgB;YAC7D,iBAAiBA,WAAW,KAAK,CAAC,KAAK;YACvCQ;QACF;QAEA,IAAIK;QACJ,IAAIV,YAAY,MAAM,EAAE,QACtBU,WAAW,CAAC,4BAA4B,EAAEV,YAAY,MAAM,CAAC,IAAI,CAAC,OAAO;QAG3E,MAAMW,WAAsC;YAC1C,MAAM;YACN,WAAW;gBACT,SAASnB;YACX;YACA,aAAaS;YACb,MAAM;YACNO;YACA,YAAY,CAAC,CAACX,WAAW,KAAK,CAAC,UAAU;YACzC,OAAOa;QACT;QAEA,MAAME,UAAUZ,YAAY,OAAO;QAEnC,MAAMa,OAAOC,AAAAA,IAAAA,kCAAAA,iBAAAA,AAAAA,EAAkB;YAC7B,GAAGH,QAAQ;YACX,gBAAgBC,UAAU;gBAACA;aAAQ,GAAG,EAAE;QAC1C;QAEA,IAAIF,UACF,MAAM,IAAIK,kCAAAA,YAAYA,CAACL,UAAUG;QAGnC,IAAID,SACF,OAAO;YACL,SAAS;gBACP,QAAQA,QAAQ,MAAM;gBACtB,MAAMA,QAAQ,IAAI;gBAClB,aAAaA,QAAQ,WAAW;YAClC;YACAX;YACAY;QACF;QAGF,OAAO;YACL,SAAS;YACTZ;YACAY;QACF;IACF;IAEA,MAAc,wBAAwBG,OAOrC,EAAmC;QAClC,MAAM,EAAE7B,KAAK,EAAEK,WAAW,EAAEJ,GAAG,EAAEQ,OAAO,EAAEP,YAAY,EAAEC,WAAW,EAAE,GACnE0B;QACF,MAAM,EAAEC,OAAO,EAAE,GAAG5B;QACpB,MAAM6B,wBAAwB,CAAC,CAAC9B,KAAK,oBAAoB;QAEzD,IAAI,CAACD,MAAM,UAAU,EACnB,OAAO;YAAE,OAAO,CAAC;QAAE;QAGrB,IAAI+B,uBAAuB;YACzB,MAAMC,SAAS,MAAMC,AAAAA,IAAAA,2BAAAA,qBAAAA,AAAAA,EAAsB;gBACzCxB;gBACA,UAAUR,IAAI,kBAAkB,CAAE,IAAI;YACxC;YAEA,OAAO;gBACL+B;gBACA,OAAO;oBACL,YAAYA,OAAO,UAAU;oBAC7B,aAAaV,KAAK,SAAS,CAAC;wBAC1B,QAAQ;wBACR,MAAMrB,IAAI,kBAAkB,CAAE,IAAI;oBACpC;gBACF;YACF;QACF;QAEA,IAAI6B,QAAQ,MAAM,CAAC,kBAAkB,EAAE;YACrC,MAAMI,qBAAqB,MAAMC,AAAAA,IAAAA,2BAAAA,eAAAA,AAAAA,EAAgB;gBAC/C1B;gBACA,oBAAoBJ;gBACpBH;gBACAC;YACF;YACA,MAAM,EAAEiC,gBAAgB,EAAE,GAAGF;YAC7B5B,IAAAA,sBAAAA,MAAAA,AAAAA,EACE8B,kBACA,CAAC,6BAA6B,EAAE/B,YAAY,CAAC,EAC3C6B,mBAAmB,KAAK,GAAG,CAAC,EAAE,EAAEA,mBAAmB,KAAK,EAAE,GAAG,IAC7D;YAGJ,OAAO;gBACL,QAAQE;gBACR,OAAO;oBACL,YAAYA,iBAAiB,UAAU;oBACvC,aAAaF,mBAAmB,WAAW;oBAC3C,kBAAkBA,mBAAmB,gBAAgB;oBACrD,OAAOA,mBAAmB,KAAK;gBACjC;YACF;QACF;QAEA,MAAMG,wBAAwB,MAAMlB,AAAAA,IAAAA,2BAAAA,eAAAA,AAAAA,EAAgB;YAClDV;YACA,0BAA0BJ;YAC1BH;YACAC;QACF;QACAG,IAAAA,sBAAAA,MAAAA,AAAAA,EACE+B,sBAAsB,IAAI,EAC1B,CAAC,6BAA6B,EAAEhC,YAAY,CAAC,EAC3CgC,sBAAsB,WAAW,CAAC,MAAM,EAAE,SACtC,CAAC,EAAE,EAAEA,sBAAsB,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,GAC1D,IACJ;QAGJ,MAAML,SAAS,MAAMC,AAAAA,IAAAA,2BAAAA,qBAAAA,AAAAA,EAAsB;YACzCxB;YACA,UAAU4B,sBAAsB,IAAI;QACtC;QAEA,OAAO;YACLL;YACA,OAAO;gBACL,YAAYA,OAAO,UAAU;gBAC7B,aAAaV,KAAK,SAAS,CAAC;oBAC1B,QAAQ;oBACR,MAAMe,sBAAsB,IAAI;oBAChC,aAAaA,sBAAsB,WAAW;gBAChD;gBACA,kBAAkBA,sBAAsB,gBAAgB;gBACxD,OAAOA,sBAAsB,KAAK;YACpC;QACF;IACF;IAEA,MAAM,QACJC,UAA+B,EAC/BpC,YAA0B,EAC1BD,GAA0B,EAC1BsC,eAAwB,EACxBC,gBAAoC,EACpC/B,OAAmB,EACnBgC,gBAEC,EACiC;QAClCnC,IAAAA,sBAAAA,MAAAA,AAAAA,EAAOG,SAAS;QAChBH,IAAAA,sBAAAA,MAAAA,AAAAA,EACE,AAAsB,YAAtB,OAAOgC,cAA2B,AAAsB,YAAtB,OAAOA,YACzC,CAAC,+CAA+C,EAAE,OAAOA,YAAY;QAGvE,MAAM3B,YAAYC,KAAK,GAAG;QAE1B,IAAIC;QAGJ,IAAIE;QACJ,IAAIC;QACJ,IAAIC;QACJ,IAAIC;QAEJ,IAAI;YACF,MAAMwB,SAAS,MAAMC,AAAAA,IAAAA,2BAAAA,oBAAAA,AAAAA,EAAwB;gBAC3ClC;gBACA,WAAW6B;gBACXE;gBACA,eAAevC;gBACfC;gBACAqC;gBACA,aAAaE,kBAAkB;YACjC;YACA5B,cAAc6B,OAAO,WAAW;YAChC3B,cAAc2B,OAAO,WAAW;YAChC1B,mBAAmB0B,OAAO,gBAAgB;YAC1CzB,QAAQyB,OAAO,KAAK;YACpBxB,oBAAoBwB,OAAO,iBAAiB;QAC9C,EAAE,OAAOE,OAAO;YACd,IAAIA,iBAAiBC,yBAAAA,oBAAoBA,EAAE;gBAEzC,MAAMzB,WAAWR,KAAK,GAAG,KAAKD;gBAC9B,MAAMU,WAA4B;oBAChC,GAAI,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;oBACtC,YAAYD;oBACZ,aAAawB,MAAM,WAAW;oBAC9B,kBAAkBA,MAAM,gBAAgB;oBACxC,OAAOA,MAAM,KAAK;gBACpB;gBACA,MAAMlB,OAAOC,AAAAA,IAAAA,kCAAAA,iBAAAA,AAAAA,EAAkB;oBAC7B,MAAM;oBACN,WAAW;wBAAEW;oBAAW;oBACxB,MAAM;oBACNjB;oBACA,OAAOuB,MAAM,OAAO;gBACtB;gBACA,MAAM,IAAIhB,kCAAAA,YAAYA,CAACgB,MAAM,OAAO,EAAElB;YACxC;YACA,MAAMkB;QACR;QAEA,MAAMxB,WAAWR,KAAK,GAAG,KAAKD;QAC9B,MAAMU,WAA4B;YAChC,GAAI,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;YACtC,YAAYD;YACZL;YACAC;YACA,gBAAgBM,KAAK,SAAS,CAACT;YAC/BI;YACAC;QACF;QAEA,IAAIK;QACJ,IAAIV,YAAY,MAAM,EAAE,QACtBU,WAAW,CAAC,qBAAqB,EAAEV,YAAY,MAAM,CAAC,IAAI,CAAC,OAAO;QAGpE,MAAMW,WAAsC;YAC1C,MAAM;YACN,WAAW;gBACTc;YACF;YACA,MAAM;YACNjB;YACA,OAAOE;QACT;QAEA,MAAM,EAAEuB,IAAI,EAAEC,OAAO,EAAE,GAAGlC,eAAe,CAAC;QAE1C,MAAMa,OAAOC,AAAAA,IAAAA,kCAAAA,iBAAAA,AAAAA,EAAkB;YAC7B,GAAGH,QAAQ;YACXsB;QACF;QAEA,IAAIvB,YAAY,CAACuB,MACf,MAAM,IAAIlB,kCAAAA,YAAYA,CAACL,UAAUG;QAGnC,OAAO;YACLoB;YACAC;YACA9B;YACAC;YACAQ;QACF;IACF;IAEA,MAAM,SACJsB,MAA+B,EAC/B9C,YAA0B,EAC1BD,GAGC,EACwD;QACzDK,IAAAA,sBAAAA,MAAAA,AAAAA,EAAO0C,QAAQ;QACf,MAAMvC,UAAUR,KAAK,WAAY,MAAM,IAAI,CAAC,kBAAkB;QAC9D,MAAM,EAAEgD,QAAQ,EAAE,GAAGxC;QACrB,MAAMyC,mBAAmBzC,QAAQ,UAAU,CAAC,MAAM;QAClDH,IAAAA,sBAAAA,MAAAA,AAAAA,EAAO4C,kBAAkB;QACzB,MAAMC,eAAeC,AAAAA,IAAAA,4BAAAA,2BAAAA,AAAAA;QAGrB,MAAMC,kBAAkB;QACxB,MAAMC,aAAmBC,MAAM,OAAO,CAACP,UACnC;YACE,MAAMQ,KAAK,KAAK,CAACR,MAAM,CAAC,EAAE,GAAGK,kBAAkB;YAC/C,KAAKG,KAAK,KAAK,CAACR,MAAM,CAAC,EAAE,GAAGK,kBAAkB;YAC9C,OAAOA;YACP,QAAQA;QACV,IACAL;QAEJ,IAAIS,eAAe,MAAMC,AAAAA,IAAAA,oBAAAA,uBAAAA,AAAAA,EAAwB;YAC/C,gBAAgBR;YAChB,MAAMD;YACN,sBAAsB;gBACpB;oBACE,MAAMK;gBACR;aACD;YACD,iBAAiB;QACnB;QAEA,IAAIrD,KAAK,YAAY;YACnB,MAAMS,aAAaiD,AAAAA,IAAAA,mCAAAA,gBAAAA,AAAAA,EAAiBL,YAAYL;YAQhDpD,MAAM,oCAAoCa;YAC1C,MAAMkD,gBAAgB,MAAMC,AAAAA,IAAAA,oBAAAA,UAAAA,AAAAA,EAAWJ,cAAc/C;YACrD+C,eAAeG,cAAc,WAAW;QAC1C;QAEA,MAAME,OAAe;YACnB;gBAAE,MAAM;gBAAU,SAASX;YAAa;YACxC;gBACE,MAAM;gBACN,SAAS;oBACP;wBACE,MAAM;wBACN,WAAW;4BACT,KAAKM;4BACL,QAAQ;wBACV;oBACF;iBACD;YACH;SACD;QAED,IAAIM;QAGJ,IAAI;YACFA,MAAM,MAAMC,AAAAA,IAAAA,yBAAAA,wBAAAA,AAAAA,EACVF,MACA5D;QAEJ,EAAE,OAAO0C,OAAO;YACd,MAAMqB,oBAAoBC,AAAAA,IAAAA,kCAAAA,qCAAAA,AAAAA,EAAsCtB;YAChE,IAAI,CAACqB,mBACH,MAAMrB;YAER/C,MAAM;YACN,OAAOoE;QACT;QAEA,MAAM,EAAEE,OAAO,EAAE,GAAGJ;QACpBzD,IAAAA,sBAAAA,MAAAA,AAAAA,EAAO,CAAC6D,QAAQ,KAAK,EAAE,CAAC,iBAAiB,EAAEA,QAAQ,KAAK,EAAE;QAC1D7D,IAAAA,sBAAAA,MAAAA,AAAAA,EAAO6D,QAAQ,WAAW,EAAE;QAC5B,OAAOA;IACT;IAhaA,YACE1D,OAA2D,EAC3DR,GAAoB,CACpB;QAPF;QAEA;QAMEK,IAAAA,sBAAAA,MAAAA,AAAAA,EAAOG,SAAS;QAChB,IAAI,AAAmB,cAAnB,OAAOA,SACT,IAAI,CAAC,kBAAkB,GAAGA;aAE1B,IAAI,CAAC,kBAAkB,GAAG,IAAM2D,QAAQ,OAAO,CAAC3D;QAGlD,IAAI,AAAyB,WAAlBR,KAAK,UACd,IAAI,CAAC,QAAQ,GAAGA,IAAI,QAAQ;IAEhC;AAmZF"}
1
+ {"version":3,"file":"service/index.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../../src/service/index.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { defaultModelFamilyRequiredForLocateMessage } from '@/ai-model/errors';\nimport {\n AiExtractElementInfo,\n AiLocateElement,\n AiLocateSection,\n buildSearchAreaConfig,\n} from '@/ai-model/inspect';\nimport type { ModelRuntime } from '@/ai-model/models';\nimport { elementDescriberInstruction } from '@/ai-model/prompt/describe';\nimport {\n AIResponseParseError,\n callAIWithObjectResponse,\n} from '@/ai-model/service-caller';\nimport type { AIArgs } from '@/ai-model/types';\nimport type { SearchAreaConfig } from '@/ai-model/workflows/inspect/types';\nimport type {\n AIDescribeElementResponse,\n AIUsageInfo,\n DetailedLocateParam,\n LocateResultElement,\n LocateResultWithDump,\n PartialServiceDumpFromSDK,\n PlanningLocateParam,\n Rect,\n ServiceExtractOption,\n ServiceExtractParam,\n ServiceExtractResult,\n ServiceTaskInfo,\n UIContext,\n} from '@/types';\nimport { ServiceError } from '@/types';\nimport {\n compositeElementInfoImg,\n compositePointMarkerImg,\n cropByRect,\n resizeImgBase64,\n} from '@midscene/shared/img';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\nimport type { ChatCompletionContentPart } from 'openai/resources/index';\nimport type { TMultimodalPrompt, TUserPrompt } from '../common';\nimport {\n createServiceDump,\n getDescribeDeepContextAreas,\n getDescribeDeepLocateResizeSize,\n getDescribeMarkerBorderThickness,\n getDescribeMarkerRect,\n getRectInCrop,\n recoverDescribeResponseFromParseError,\n} from './utils';\n\nexport interface LocateOpts {\n context?: UIContext;\n planLocatedElement?: LocateResultElement;\n}\n\nexport type AnyValue<T> = {\n [K in keyof T]: unknown extends T[K] ? any : T[K];\n};\n\ninterface ServiceOptions {\n taskInfo?: Omit<ServiceTaskInfo, 'durationMs'>;\n}\n\ninterface LocateSearchAreaResult {\n config?: SearchAreaConfig;\n trace: {\n sourceRect?: Rect;\n rawResponse?: string;\n rawChoiceMessage?: unknown;\n usage?: AIUsageInfo;\n };\n}\n\nconst debug = getDebug('ai:service');\n\nexport default class Service {\n contextRetrieverFn: () => Promise<UIContext> | UIContext;\n\n taskInfo?: Omit<ServiceTaskInfo, 'durationMs'>;\n\n constructor(\n context: UIContext | (() => Promise<UIContext> | UIContext),\n opt?: ServiceOptions,\n ) {\n assert(context, 'context is required for Service');\n if (typeof context === 'function') {\n this.contextRetrieverFn = context;\n } else {\n this.contextRetrieverFn = () => Promise.resolve(context);\n }\n\n if (typeof opt?.taskInfo !== 'undefined') {\n this.taskInfo = opt.taskInfo;\n }\n }\n\n async locate(\n query: PlanningLocateParam,\n opt: LocateOpts,\n modelRuntime: ModelRuntime,\n abortSignal?: AbortSignal,\n ): Promise<LocateResultWithDump> {\n const { config: modelConfig } = modelRuntime;\n const queryPrompt = typeof query === 'string' ? query : query.prompt;\n assert(queryPrompt, 'query is required for locate');\n\n assert(typeof query === 'object', 'query should be an object for locate');\n\n if (!modelConfig.modelFamily) {\n throw new Error(defaultModelFamilyRequiredForLocateMessage);\n }\n\n const context = opt?.context || (await this.contextRetrieverFn());\n\n const searchArea = await this.resolveLocateSearchArea({\n query,\n queryPrompt,\n opt,\n context,\n modelRuntime,\n abortSignal,\n });\n\n const startTime = Date.now();\n const {\n parseResult,\n rect,\n rawResponse,\n rawChoiceMessage,\n usage,\n reasoning_content,\n } = await AiLocateElement({\n context,\n targetElementDescription: queryPrompt,\n searchConfig: searchArea.config,\n modelRuntime,\n abortSignal,\n });\n\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse,\n rawChoiceMessage,\n formatResponse: parseResult,\n usage,\n searchArea: searchArea.trace.sourceRect,\n searchAreaRawResponse: searchArea.trace.rawResponse,\n searchAreaRawChoiceMessage: searchArea.trace.rawChoiceMessage,\n searchAreaUsage: searchArea.trace.usage,\n reasoning_content,\n };\n\n let errorLog: string | undefined;\n if (parseResult.errors?.length) {\n errorLog = `failed to locate element: \\n${parseResult.errors.join('\\n')}`;\n }\n\n const dumpData: PartialServiceDumpFromSDK = {\n type: 'locate',\n userQuery: {\n element: queryPrompt,\n },\n matchedRect: rect,\n data: null,\n taskInfo,\n deepLocate: !!searchArea.trace.sourceRect,\n error: errorLog,\n };\n\n const element = parseResult.element;\n\n const dump = createServiceDump({\n ...dumpData,\n matchedElement: element ? [element] : [],\n });\n\n if (errorLog) {\n throw new ServiceError(errorLog, dump);\n }\n\n if (element) {\n return {\n element: {\n center: element.center,\n rect: element.rect,\n description: element.description,\n },\n rect,\n dump,\n };\n }\n\n return {\n element: null,\n rect,\n dump,\n };\n }\n\n private async resolveLocateSearchArea(options: {\n query: PlanningLocateParam;\n queryPrompt: TUserPrompt;\n opt: LocateOpts;\n context: UIContext;\n modelRuntime: ModelRuntime;\n abortSignal?: AbortSignal;\n }): Promise<LocateSearchAreaResult> {\n const { query, queryPrompt, opt, context, modelRuntime, abortSignal } =\n options;\n const { adapter } = modelRuntime;\n const hasPlanLocatedElement = !!opt?.planLocatedElement?.rect;\n\n if (!query.deepLocate) {\n return { trace: {} };\n }\n\n if (hasPlanLocatedElement) {\n const config = await buildSearchAreaConfig({\n context,\n baseRect: opt.planLocatedElement!.rect,\n });\n\n return {\n config,\n trace: {\n sourceRect: config.sourceRect,\n rawResponse: JSON.stringify({\n source: 'plan-located-element',\n rect: opt.planLocatedElement!.rect,\n }),\n },\n };\n }\n\n if (adapter.locate.supportsSearchArea) {\n const searchAreaResponse = await AiLocateSection({\n context,\n sectionDescription: queryPrompt,\n modelRuntime,\n abortSignal,\n });\n const { searchAreaConfig } = searchAreaResponse;\n assert(\n searchAreaConfig,\n `cannot find search area for \"${queryPrompt}\"${\n searchAreaResponse.error ? `: ${searchAreaResponse.error}` : ''\n }`,\n );\n\n return {\n config: searchAreaConfig,\n trace: {\n sourceRect: searchAreaConfig.sourceRect,\n rawResponse: searchAreaResponse.rawResponse,\n rawChoiceMessage: searchAreaResponse.rawChoiceMessage,\n usage: searchAreaResponse.usage,\n },\n };\n }\n\n const firstPassLocateResult = await AiLocateElement({\n context,\n targetElementDescription: queryPrompt,\n modelRuntime,\n abortSignal,\n });\n assert(\n firstPassLocateResult.rect,\n `cannot find search area for \"${queryPrompt}\"${\n firstPassLocateResult.parseResult.errors?.length\n ? `: ${firstPassLocateResult.parseResult.errors.join('\\n')}`\n : ''\n }`,\n );\n\n const config = await buildSearchAreaConfig({\n context,\n baseRect: firstPassLocateResult.rect,\n });\n\n return {\n config,\n trace: {\n sourceRect: config.sourceRect,\n rawResponse: JSON.stringify({\n source: 'deep-locate-first-pass',\n rect: firstPassLocateResult.rect,\n rawResponse: firstPassLocateResult.rawResponse,\n }),\n rawChoiceMessage: firstPassLocateResult.rawChoiceMessage,\n usage: firstPassLocateResult.usage,\n },\n };\n }\n\n async extract<T>(\n dataDemand: ServiceExtractParam,\n modelRuntime: ModelRuntime,\n opt?: ServiceExtractOption,\n pageDescription?: string,\n multimodalPrompt?: TMultimodalPrompt,\n context?: UIContext,\n executionOptions?: {\n abortSignal?: AbortSignal;\n },\n ): Promise<ServiceExtractResult<T>> {\n assert(context, 'context is required for extract');\n assert(\n typeof dataDemand === 'object' || typeof dataDemand === 'string',\n `dataDemand should be object or string, but get ${typeof dataDemand}`,\n );\n\n const startTime = Date.now();\n\n let parseResult: Awaited<\n ReturnType<typeof AiExtractElementInfo<T>>\n >['parseResult'];\n let rawResponse: string;\n let rawChoiceMessage: unknown;\n let usage: Awaited<ReturnType<typeof AiExtractElementInfo<T>>>['usage'];\n let reasoning_content: string | undefined;\n\n try {\n const result = await AiExtractElementInfo<T>({\n context,\n dataQuery: dataDemand,\n multimodalPrompt,\n extractOption: opt,\n modelRuntime,\n pageDescription,\n abortSignal: executionOptions?.abortSignal,\n });\n parseResult = result.parseResult;\n rawResponse = result.rawResponse;\n rawChoiceMessage = result.rawChoiceMessage;\n usage = result.usage;\n reasoning_content = result.reasoning_content;\n } catch (error) {\n if (error instanceof AIResponseParseError) {\n // Create dump with usage and rawResponse from the error\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse: error.rawResponse,\n rawChoiceMessage: error.rawChoiceMessage,\n usage: error.usage,\n };\n const dump = createServiceDump({\n type: 'extract',\n userQuery: { dataDemand },\n data: null,\n taskInfo,\n error: error.message,\n });\n throw new ServiceError(error.message, dump);\n }\n throw error;\n }\n\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse,\n rawChoiceMessage,\n formatResponse: parseResult,\n usage,\n reasoning_content,\n };\n\n let errorLog: string | undefined;\n if (parseResult.errors?.length) {\n errorLog = `AI response error: \\n${parseResult.errors.join('\\n')}`;\n }\n\n const dumpData: PartialServiceDumpFromSDK = {\n type: 'extract',\n userQuery: {\n dataDemand,\n },\n data: null,\n taskInfo,\n error: errorLog,\n };\n\n const { data, thought } = parseResult || {};\n\n const dump = createServiceDump({\n ...dumpData,\n data,\n });\n\n if (errorLog && !data) {\n throw new ServiceError(errorLog, dump);\n }\n\n return {\n data,\n thought,\n usage,\n reasoning_content,\n dump,\n };\n }\n\n async describe(\n target: Rect | [number, number],\n modelRuntime: ModelRuntime,\n opt?: {\n deepDescribe?: boolean;\n context?: UIContext;\n },\n ): Promise<Pick<AIDescribeElementResponse, 'description'>> {\n assert(target, 'target is required for service.describe');\n const context = opt?.context || (await this.contextRetrieverFn());\n const { shotSize } = context;\n const screenshotBase64 = context.screenshot.base64;\n assert(screenshotBase64, 'screenshot is required for service.describe');\n const systemPrompt = elementDescriberInstruction();\n\n // Convert [x,y] center point to Rect if needed\n const defaultRectSize = 30;\n const targetFromPoint = Array.isArray(target);\n const targetRect: Rect = targetFromPoint\n ? {\n left: Math.floor(target[0] - defaultRectSize / 2),\n top: Math.floor(target[1] - defaultRectSize / 2),\n width: defaultRectSize,\n height: defaultRectSize,\n }\n : target;\n const targetPoint = targetFromPoint\n ? {\n x: target[0],\n y: target[1],\n }\n : undefined;\n\n const usePointMarker = targetFromPoint;\n const imagePayload = usePointMarker\n ? await compositePointMarkerImg({\n inputImgBase64: screenshotBase64,\n size: shotSize,\n point: targetPoint!,\n })\n : await compositeElementInfoImg({\n inputImgBase64: screenshotBase64,\n size: shotSize,\n elementsPositionInfo: [\n {\n rect: getDescribeMarkerRect(targetRect),\n },\n ],\n borderThickness: getDescribeMarkerBorderThickness(targetRect),\n centerPoint: true,\n });\n\n const shouldDeepDescribe = opt?.deepDescribe;\n let imageContent: ChatCompletionContentPart[];\n if (shouldDeepDescribe) {\n const contextAreas = getDescribeDeepContextAreas(targetRect, shotSize);\n const contextImages = await Promise.all(\n contextAreas.map(async (area) => {\n debug('describe: cropping deep context area', area);\n const croppedResult = await cropByRect(screenshotBase64, area.rect);\n const cropSize = {\n width: croppedResult.width,\n height: croppedResult.height,\n };\n const targetInCrop = getRectInCrop(targetRect, area.rect, cropSize);\n const markedCropPayload = targetFromPoint\n ? await compositePointMarkerImg({\n inputImgBase64: croppedResult.imageBase64,\n size: cropSize,\n point: {\n x: targetPoint!.x - area.rect.left,\n y: targetPoint!.y - area.rect.top,\n },\n })\n : await compositeElementInfoImg({\n inputImgBase64: croppedResult.imageBase64,\n size: cropSize,\n elementsPositionInfo: [\n {\n rect: getDescribeMarkerRect(targetInCrop),\n },\n ],\n borderThickness: getDescribeMarkerBorderThickness(targetInCrop),\n centerPoint: true,\n });\n const resizeSize = getDescribeDeepLocateResizeSize(croppedResult);\n return {\n kind: area.kind,\n imageBase64: resizeSize\n ? await resizeImgBase64(markedCropPayload, resizeSize)\n : markedCropPayload,\n };\n }),\n );\n const contextImageContent =\n contextImages.flatMap<ChatCompletionContentPart>((item, index) => [\n {\n type: 'text',\n text: `Image ${index + 2}: focused detail crop around the target, for reading text, icon shape, and exact local boundaries.`,\n },\n {\n type: 'image_url',\n image_url: {\n url: item.imageBase64,\n detail: 'high',\n },\n },\n ]);\n\n imageContent = [\n {\n type: 'text' as const,\n text: 'Use these images together to describe the real UI target marked by the temporary callout. Do not describe the marker itself.',\n },\n {\n type: 'text' as const,\n text: 'Image 1: full screenshot overview with the target marker, for page position and ownership context.',\n },\n {\n type: 'image_url' as const,\n image_url: {\n url: imagePayload,\n detail: 'high',\n },\n },\n ...contextImageContent,\n ];\n } else {\n imageContent = [\n {\n type: 'text' as const,\n text: 'Full screenshot with a temporary callout marking the target:',\n },\n {\n type: 'image_url' as const,\n image_url: {\n url: imagePayload,\n detail: 'high',\n },\n },\n ];\n }\n\n const msgs: AIArgs = [\n { role: 'system', content: systemPrompt },\n {\n role: 'user',\n content: imageContent,\n },\n ];\n\n let res: Awaited<\n ReturnType<typeof callAIWithObjectResponse<AIDescribeElementResponse>>\n >;\n try {\n res = await callAIWithObjectResponse<AIDescribeElementResponse>(\n msgs,\n modelRuntime,\n );\n } catch (error) {\n const recoveredResponse = recoverDescribeResponseFromParseError(error);\n if (!recoveredResponse) {\n throw error;\n }\n debug('describe: recovered malformed description JSON response');\n return recoveredResponse;\n }\n\n const { content } = res;\n assert(!content.error, `describe failed: ${content.error}`);\n assert(content.description, 'failed to describe the element');\n return content;\n }\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","debug","getDebug","Service","query","opt","modelRuntime","abortSignal","modelConfig","queryPrompt","assert","Error","defaultModelFamilyRequiredForLocateMessage","context","searchArea","startTime","Date","parseResult","rect","rawResponse","rawChoiceMessage","usage","reasoning_content","AiLocateElement","timeCost","taskInfo","errorLog","dumpData","element","dump","createServiceDump","ServiceError","options","adapter","hasPlanLocatedElement","config","buildSearchAreaConfig","JSON","searchAreaResponse","AiLocateSection","searchAreaConfig","firstPassLocateResult","dataDemand","pageDescription","multimodalPrompt","executionOptions","result","AiExtractElementInfo","error","AIResponseParseError","data","thought","target","shotSize","screenshotBase64","systemPrompt","elementDescriberInstruction","defaultRectSize","targetFromPoint","Array","targetRect","Math","targetPoint","undefined","usePointMarker","imagePayload","compositePointMarkerImg","compositeElementInfoImg","getDescribeMarkerRect","getDescribeMarkerBorderThickness","shouldDeepDescribe","imageContent","contextAreas","getDescribeDeepContextAreas","contextImages","Promise","area","croppedResult","cropByRect","cropSize","targetInCrop","getRectInCrop","markedCropPayload","resizeSize","getDescribeDeepLocateResizeSize","resizeImgBase64","contextImageContent","item","index","msgs","res","callAIWithObjectResponse","recoveredResponse","recoverDescribeResponseFromParseError","content"],"mappings":"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"}