@donggui/core 1.5.4-donggui.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 (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/es/agent/agent.mjs +709 -0
  4. package/dist/es/agent/agent.mjs.map +1 -0
  5. package/dist/es/agent/common.mjs +0 -0
  6. package/dist/es/agent/execution-session.mjs +41 -0
  7. package/dist/es/agent/execution-session.mjs.map +1 -0
  8. package/dist/es/agent/index.mjs +6 -0
  9. package/dist/es/agent/task-builder.mjs +330 -0
  10. package/dist/es/agent/task-builder.mjs.map +1 -0
  11. package/dist/es/agent/task-cache.mjs +186 -0
  12. package/dist/es/agent/task-cache.mjs.map +1 -0
  13. package/dist/es/agent/tasks.mjs +422 -0
  14. package/dist/es/agent/tasks.mjs.map +1 -0
  15. package/dist/es/agent/ui-utils.mjs +91 -0
  16. package/dist/es/agent/ui-utils.mjs.map +1 -0
  17. package/dist/es/agent/utils.mjs +198 -0
  18. package/dist/es/agent/utils.mjs.map +1 -0
  19. package/dist/es/ai-model/auto-glm/actions.mjs +224 -0
  20. package/dist/es/ai-model/auto-glm/actions.mjs.map +1 -0
  21. package/dist/es/ai-model/auto-glm/index.mjs +6 -0
  22. package/dist/es/ai-model/auto-glm/parser.mjs +239 -0
  23. package/dist/es/ai-model/auto-glm/parser.mjs.map +1 -0
  24. package/dist/es/ai-model/auto-glm/planning.mjs +71 -0
  25. package/dist/es/ai-model/auto-glm/planning.mjs.map +1 -0
  26. package/dist/es/ai-model/auto-glm/prompt.mjs +222 -0
  27. package/dist/es/ai-model/auto-glm/prompt.mjs.map +1 -0
  28. package/dist/es/ai-model/auto-glm/util.mjs +9 -0
  29. package/dist/es/ai-model/auto-glm/util.mjs.map +1 -0
  30. package/dist/es/ai-model/conversation-history.mjs +195 -0
  31. package/dist/es/ai-model/conversation-history.mjs.map +1 -0
  32. package/dist/es/ai-model/index.mjs +11 -0
  33. package/dist/es/ai-model/inspect.mjs +386 -0
  34. package/dist/es/ai-model/inspect.mjs.map +1 -0
  35. package/dist/es/ai-model/llm-planning.mjs +233 -0
  36. package/dist/es/ai-model/llm-planning.mjs.map +1 -0
  37. package/dist/es/ai-model/prompt/common.mjs +7 -0
  38. package/dist/es/ai-model/prompt/common.mjs.map +1 -0
  39. package/dist/es/ai-model/prompt/describe.mjs +66 -0
  40. package/dist/es/ai-model/prompt/describe.mjs.map +1 -0
  41. package/dist/es/ai-model/prompt/extraction.mjs +129 -0
  42. package/dist/es/ai-model/prompt/extraction.mjs.map +1 -0
  43. package/dist/es/ai-model/prompt/llm-locator.mjs +51 -0
  44. package/dist/es/ai-model/prompt/llm-locator.mjs.map +1 -0
  45. package/dist/es/ai-model/prompt/llm-planning.mjs +364 -0
  46. package/dist/es/ai-model/prompt/llm-planning.mjs.map +1 -0
  47. package/dist/es/ai-model/prompt/llm-section-locator.mjs +44 -0
  48. package/dist/es/ai-model/prompt/llm-section-locator.mjs.map +1 -0
  49. package/dist/es/ai-model/prompt/order-sensitive-judge.mjs +35 -0
  50. package/dist/es/ai-model/prompt/order-sensitive-judge.mjs.map +1 -0
  51. package/dist/es/ai-model/prompt/playwright-generator.mjs +117 -0
  52. package/dist/es/ai-model/prompt/playwright-generator.mjs.map +1 -0
  53. package/dist/es/ai-model/prompt/ui-tars-planning.mjs +36 -0
  54. package/dist/es/ai-model/prompt/ui-tars-planning.mjs.map +1 -0
  55. package/dist/es/ai-model/prompt/util.mjs +59 -0
  56. package/dist/es/ai-model/prompt/util.mjs.map +1 -0
  57. package/dist/es/ai-model/prompt/yaml-generator.mjs +219 -0
  58. package/dist/es/ai-model/prompt/yaml-generator.mjs.map +1 -0
  59. package/dist/es/ai-model/service-caller/index.mjs +466 -0
  60. package/dist/es/ai-model/service-caller/index.mjs.map +1 -0
  61. package/dist/es/ai-model/ui-tars-planning.mjs +249 -0
  62. package/dist/es/ai-model/ui-tars-planning.mjs.map +1 -0
  63. package/dist/es/common.mjs +371 -0
  64. package/dist/es/common.mjs.map +1 -0
  65. package/dist/es/device/device-options.mjs +0 -0
  66. package/dist/es/device/index.mjs +300 -0
  67. package/dist/es/device/index.mjs.map +1 -0
  68. package/dist/es/dump/html-utils.mjs +211 -0
  69. package/dist/es/dump/html-utils.mjs.map +1 -0
  70. package/dist/es/dump/image-restoration.mjs +43 -0
  71. package/dist/es/dump/image-restoration.mjs.map +1 -0
  72. package/dist/es/dump/index.mjs +3 -0
  73. package/dist/es/index.mjs +15 -0
  74. package/dist/es/index.mjs.map +1 -0
  75. package/dist/es/report-generator.mjs +134 -0
  76. package/dist/es/report-generator.mjs.map +1 -0
  77. package/dist/es/report.mjs +111 -0
  78. package/dist/es/report.mjs.map +1 -0
  79. package/dist/es/screenshot-item.mjs +105 -0
  80. package/dist/es/screenshot-item.mjs.map +1 -0
  81. package/dist/es/service/index.mjs +256 -0
  82. package/dist/es/service/index.mjs.map +1 -0
  83. package/dist/es/service/utils.mjs +15 -0
  84. package/dist/es/service/utils.mjs.map +1 -0
  85. package/dist/es/skill/index.mjs +38 -0
  86. package/dist/es/skill/index.mjs.map +1 -0
  87. package/dist/es/task-runner.mjs +258 -0
  88. package/dist/es/task-runner.mjs.map +1 -0
  89. package/dist/es/task-timing.mjs +12 -0
  90. package/dist/es/task-timing.mjs.map +1 -0
  91. package/dist/es/tree.mjs +13 -0
  92. package/dist/es/tree.mjs.map +1 -0
  93. package/dist/es/types.mjs +196 -0
  94. package/dist/es/types.mjs.map +1 -0
  95. package/dist/es/utils.mjs +218 -0
  96. package/dist/es/utils.mjs.map +1 -0
  97. package/dist/es/yaml/builder.mjs +13 -0
  98. package/dist/es/yaml/builder.mjs.map +1 -0
  99. package/dist/es/yaml/index.mjs +4 -0
  100. package/dist/es/yaml/player.mjs +418 -0
  101. package/dist/es/yaml/player.mjs.map +1 -0
  102. package/dist/es/yaml/utils.mjs +73 -0
  103. package/dist/es/yaml/utils.mjs.map +1 -0
  104. package/dist/es/yaml.mjs +0 -0
  105. package/dist/lib/agent/agent.js +757 -0
  106. package/dist/lib/agent/agent.js.map +1 -0
  107. package/dist/lib/agent/common.js +5 -0
  108. package/dist/lib/agent/execution-session.js +75 -0
  109. package/dist/lib/agent/execution-session.js.map +1 -0
  110. package/dist/lib/agent/index.js +81 -0
  111. package/dist/lib/agent/index.js.map +1 -0
  112. package/dist/lib/agent/task-builder.js +367 -0
  113. package/dist/lib/agent/task-builder.js.map +1 -0
  114. package/dist/lib/agent/task-cache.js +238 -0
  115. package/dist/lib/agent/task-cache.js.map +1 -0
  116. package/dist/lib/agent/tasks.js +465 -0
  117. package/dist/lib/agent/tasks.js.map +1 -0
  118. package/dist/lib/agent/ui-utils.js +143 -0
  119. package/dist/lib/agent/ui-utils.js.map +1 -0
  120. package/dist/lib/agent/utils.js +275 -0
  121. package/dist/lib/agent/utils.js.map +1 -0
  122. package/dist/lib/ai-model/auto-glm/actions.js +258 -0
  123. package/dist/lib/ai-model/auto-glm/actions.js.map +1 -0
  124. package/dist/lib/ai-model/auto-glm/index.js +66 -0
  125. package/dist/lib/ai-model/auto-glm/index.js.map +1 -0
  126. package/dist/lib/ai-model/auto-glm/parser.js +282 -0
  127. package/dist/lib/ai-model/auto-glm/parser.js.map +1 -0
  128. package/dist/lib/ai-model/auto-glm/planning.js +105 -0
  129. package/dist/lib/ai-model/auto-glm/planning.js.map +1 -0
  130. package/dist/lib/ai-model/auto-glm/prompt.js +259 -0
  131. package/dist/lib/ai-model/auto-glm/prompt.js.map +1 -0
  132. package/dist/lib/ai-model/auto-glm/util.js +46 -0
  133. package/dist/lib/ai-model/auto-glm/util.js.map +1 -0
  134. package/dist/lib/ai-model/conversation-history.js +229 -0
  135. package/dist/lib/ai-model/conversation-history.js.map +1 -0
  136. package/dist/lib/ai-model/index.js +125 -0
  137. package/dist/lib/ai-model/index.js.map +1 -0
  138. package/dist/lib/ai-model/inspect.js +429 -0
  139. package/dist/lib/ai-model/inspect.js.map +1 -0
  140. package/dist/lib/ai-model/llm-planning.js +270 -0
  141. package/dist/lib/ai-model/llm-planning.js.map +1 -0
  142. package/dist/lib/ai-model/prompt/common.js +41 -0
  143. package/dist/lib/ai-model/prompt/common.js.map +1 -0
  144. package/dist/lib/ai-model/prompt/describe.js +100 -0
  145. package/dist/lib/ai-model/prompt/describe.js.map +1 -0
  146. package/dist/lib/ai-model/prompt/extraction.js +169 -0
  147. package/dist/lib/ai-model/prompt/extraction.js.map +1 -0
  148. package/dist/lib/ai-model/prompt/llm-locator.js +88 -0
  149. package/dist/lib/ai-model/prompt/llm-locator.js.map +1 -0
  150. package/dist/lib/ai-model/prompt/llm-planning.js +401 -0
  151. package/dist/lib/ai-model/prompt/llm-planning.js.map +1 -0
  152. package/dist/lib/ai-model/prompt/llm-section-locator.js +81 -0
  153. package/dist/lib/ai-model/prompt/llm-section-locator.js.map +1 -0
  154. package/dist/lib/ai-model/prompt/order-sensitive-judge.js +72 -0
  155. package/dist/lib/ai-model/prompt/order-sensitive-judge.js.map +1 -0
  156. package/dist/lib/ai-model/prompt/playwright-generator.js +178 -0
  157. package/dist/lib/ai-model/prompt/playwright-generator.js.map +1 -0
  158. package/dist/lib/ai-model/prompt/ui-tars-planning.js +73 -0
  159. package/dist/lib/ai-model/prompt/ui-tars-planning.js.map +1 -0
  160. package/dist/lib/ai-model/prompt/util.js +105 -0
  161. package/dist/lib/ai-model/prompt/util.js.map +1 -0
  162. package/dist/lib/ai-model/prompt/yaml-generator.js +280 -0
  163. package/dist/lib/ai-model/prompt/yaml-generator.js.map +1 -0
  164. package/dist/lib/ai-model/service-caller/index.js +531 -0
  165. package/dist/lib/ai-model/service-caller/index.js.map +1 -0
  166. package/dist/lib/ai-model/ui-tars-planning.js +283 -0
  167. package/dist/lib/ai-model/ui-tars-planning.js.map +1 -0
  168. package/dist/lib/common.js +480 -0
  169. package/dist/lib/common.js.map +1 -0
  170. package/dist/lib/device/device-options.js +20 -0
  171. package/dist/lib/device/device-options.js.map +1 -0
  172. package/dist/lib/device/index.js +418 -0
  173. package/dist/lib/device/index.js.map +1 -0
  174. package/dist/lib/dump/html-utils.js +281 -0
  175. package/dist/lib/dump/html-utils.js.map +1 -0
  176. package/dist/lib/dump/image-restoration.js +77 -0
  177. package/dist/lib/dump/image-restoration.js.map +1 -0
  178. package/dist/lib/dump/index.js +60 -0
  179. package/dist/lib/dump/index.js.map +1 -0
  180. package/dist/lib/index.js +146 -0
  181. package/dist/lib/index.js.map +1 -0
  182. package/dist/lib/report-generator.js +172 -0
  183. package/dist/lib/report-generator.js.map +1 -0
  184. package/dist/lib/report.js +145 -0
  185. package/dist/lib/report.js.map +1 -0
  186. package/dist/lib/screenshot-item.js +139 -0
  187. package/dist/lib/screenshot-item.js.map +1 -0
  188. package/dist/lib/service/index.js +290 -0
  189. package/dist/lib/service/index.js.map +1 -0
  190. package/dist/lib/service/utils.js +49 -0
  191. package/dist/lib/service/utils.js.map +1 -0
  192. package/dist/lib/skill/index.js +72 -0
  193. package/dist/lib/skill/index.js.map +1 -0
  194. package/dist/lib/task-runner.js +295 -0
  195. package/dist/lib/task-runner.js.map +1 -0
  196. package/dist/lib/task-timing.js +46 -0
  197. package/dist/lib/task-timing.js.map +1 -0
  198. package/dist/lib/tree.js +53 -0
  199. package/dist/lib/tree.js.map +1 -0
  200. package/dist/lib/types.js +285 -0
  201. package/dist/lib/types.js.map +1 -0
  202. package/dist/lib/utils.js +297 -0
  203. package/dist/lib/utils.js.map +1 -0
  204. package/dist/lib/yaml/builder.js +57 -0
  205. package/dist/lib/yaml/builder.js.map +1 -0
  206. package/dist/lib/yaml/index.js +81 -0
  207. package/dist/lib/yaml/index.js.map +1 -0
  208. package/dist/lib/yaml/player.js +452 -0
  209. package/dist/lib/yaml/player.js.map +1 -0
  210. package/dist/lib/yaml/utils.js +126 -0
  211. package/dist/lib/yaml/utils.js.map +1 -0
  212. package/dist/lib/yaml.js +20 -0
  213. package/dist/lib/yaml.js.map +1 -0
  214. package/dist/types/agent/agent.d.ts +190 -0
  215. package/dist/types/agent/common.d.ts +0 -0
  216. package/dist/types/agent/execution-session.d.ts +36 -0
  217. package/dist/types/agent/index.d.ts +10 -0
  218. package/dist/types/agent/task-builder.d.ts +34 -0
  219. package/dist/types/agent/task-cache.d.ts +48 -0
  220. package/dist/types/agent/tasks.d.ts +70 -0
  221. package/dist/types/agent/ui-utils.d.ts +14 -0
  222. package/dist/types/agent/utils.d.ts +29 -0
  223. package/dist/types/ai-model/auto-glm/actions.d.ts +77 -0
  224. package/dist/types/ai-model/auto-glm/index.d.ts +6 -0
  225. package/dist/types/ai-model/auto-glm/parser.d.ts +18 -0
  226. package/dist/types/ai-model/auto-glm/planning.d.ts +10 -0
  227. package/dist/types/ai-model/auto-glm/prompt.d.ts +27 -0
  228. package/dist/types/ai-model/auto-glm/util.d.ts +13 -0
  229. package/dist/types/ai-model/conversation-history.d.ts +105 -0
  230. package/dist/types/ai-model/index.d.ts +14 -0
  231. package/dist/types/ai-model/inspect.d.ts +58 -0
  232. package/dist/types/ai-model/llm-planning.d.ts +19 -0
  233. package/dist/types/ai-model/prompt/common.d.ts +2 -0
  234. package/dist/types/ai-model/prompt/describe.d.ts +1 -0
  235. package/dist/types/ai-model/prompt/extraction.d.ts +7 -0
  236. package/dist/types/ai-model/prompt/llm-locator.d.ts +3 -0
  237. package/dist/types/ai-model/prompt/llm-planning.d.ts +10 -0
  238. package/dist/types/ai-model/prompt/llm-section-locator.d.ts +3 -0
  239. package/dist/types/ai-model/prompt/order-sensitive-judge.d.ts +2 -0
  240. package/dist/types/ai-model/prompt/playwright-generator.d.ts +26 -0
  241. package/dist/types/ai-model/prompt/ui-tars-planning.d.ts +2 -0
  242. package/dist/types/ai-model/prompt/util.d.ts +33 -0
  243. package/dist/types/ai-model/prompt/yaml-generator.d.ts +100 -0
  244. package/dist/types/ai-model/service-caller/index.d.ts +49 -0
  245. package/dist/types/ai-model/ui-tars-planning.d.ts +72 -0
  246. package/dist/types/common.d.ts +288 -0
  247. package/dist/types/device/device-options.d.ts +142 -0
  248. package/dist/types/device/index.d.ts +2315 -0
  249. package/dist/types/dump/html-utils.d.ts +52 -0
  250. package/dist/types/dump/image-restoration.d.ts +6 -0
  251. package/dist/types/dump/index.d.ts +5 -0
  252. package/dist/types/index.d.ts +17 -0
  253. package/dist/types/report-generator.d.ts +48 -0
  254. package/dist/types/report.d.ts +15 -0
  255. package/dist/types/screenshot-item.d.ts +66 -0
  256. package/dist/types/service/index.d.ts +23 -0
  257. package/dist/types/service/utils.d.ts +2 -0
  258. package/dist/types/skill/index.d.ts +25 -0
  259. package/dist/types/task-runner.d.ts +48 -0
  260. package/dist/types/task-timing.d.ts +8 -0
  261. package/dist/types/tree.d.ts +4 -0
  262. package/dist/types/types.d.ts +645 -0
  263. package/dist/types/utils.d.ts +40 -0
  264. package/dist/types/yaml/builder.d.ts +2 -0
  265. package/dist/types/yaml/index.d.ts +4 -0
  266. package/dist/types/yaml/player.d.ts +34 -0
  267. package/dist/types/yaml/utils.d.ts +9 -0
  268. package/dist/types/yaml.d.ts +203 -0
  269. package/package.json +111 -0
@@ -0,0 +1,249 @@
1
+ import { getDebug } from "@midscene/shared/logger";
2
+ import { transformHotkeyInput } from "@midscene/shared/us-keyboard-layout";
3
+ import { assert } from "@midscene/shared/utils";
4
+ import { actionParser } from "@ui-tars/action-parser";
5
+ import { getSummary, getUiTarsPlanningPrompt } from "./prompt/ui-tars-planning.mjs";
6
+ import { AIResponseParseError, callAIWithStringResponse } from "./service-caller/index.mjs";
7
+ const debug = getDebug('ui-tars-planning');
8
+ const warnLog = getDebug('ui-tars-planning', {
9
+ console: true
10
+ });
11
+ const bboxSize = 10;
12
+ const pointToBbox = (point, width, height)=>[
13
+ Math.round(Math.max(point.x - bboxSize / 2, 0)),
14
+ Math.round(Math.max(point.y - bboxSize / 2, 0)),
15
+ Math.round(Math.min(point.x + bboxSize / 2, width)),
16
+ Math.round(Math.min(point.y + bboxSize / 2, height))
17
+ ];
18
+ async function uiTarsPlanning(userInstruction, options) {
19
+ const { conversationHistory, context, modelConfig, actionContext } = options;
20
+ const { uiTarsModelVersion } = modelConfig;
21
+ let instruction = userInstruction;
22
+ if (actionContext) instruction = `<high_priority_knowledge>${actionContext}</high_priority_knowledge>\n<user_instruction>${userInstruction}</user_instruction>`;
23
+ const systemPrompt = getUiTarsPlanningPrompt() + instruction;
24
+ const screenshotBase64 = context.screenshot.base64;
25
+ conversationHistory.append({
26
+ role: 'user',
27
+ content: [
28
+ {
29
+ type: 'image_url',
30
+ image_url: {
31
+ url: screenshotBase64
32
+ }
33
+ }
34
+ ]
35
+ });
36
+ const res = await callAIWithStringResponse([
37
+ {
38
+ role: 'user',
39
+ content: systemPrompt
40
+ },
41
+ ...conversationHistory.snapshot()
42
+ ], modelConfig, {
43
+ abortSignal: options.abortSignal
44
+ });
45
+ let convertedText;
46
+ let parsed;
47
+ try {
48
+ convertedText = convertBboxToCoordinates(res.content);
49
+ const { shotSize } = context;
50
+ const parseResult = actionParser({
51
+ prediction: convertedText,
52
+ factor: [
53
+ 1000,
54
+ 1000
55
+ ],
56
+ screenContext: {
57
+ width: shotSize.width,
58
+ height: shotSize.height
59
+ },
60
+ modelVer: uiTarsModelVersion
61
+ });
62
+ parsed = parseResult.parsed;
63
+ } catch (parseError) {
64
+ const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
65
+ throw new AIResponseParseError(`Parse error: ${errorMessage}`, JSON.stringify(res.content, void 0, 2), res.usage);
66
+ }
67
+ const { shotSize } = context;
68
+ debug('ui-tars modelVer', uiTarsModelVersion, ', parsed', JSON.stringify(parsed));
69
+ const transformActions = [];
70
+ const unhandledActions = [];
71
+ let shouldContinue = true;
72
+ parsed.forEach((action)=>{
73
+ const actionType = (action.action_type || '').toLowerCase();
74
+ if ('click' === actionType) {
75
+ assert(action.action_inputs.start_box, 'start_box is required');
76
+ const point = getPoint(action.action_inputs.start_box, shotSize);
77
+ const locate = {
78
+ prompt: action.thought || '',
79
+ bbox: pointToBbox({
80
+ x: point[0],
81
+ y: point[1]
82
+ }, shotSize.width, shotSize.height)
83
+ };
84
+ transformActions.push({
85
+ type: 'Tap',
86
+ param: {
87
+ locate: locate
88
+ }
89
+ });
90
+ } else if ('left_double' === actionType) {
91
+ assert(action.action_inputs.start_box, 'start_box is required');
92
+ const point = getPoint(action.action_inputs.start_box, shotSize);
93
+ const locate = {
94
+ prompt: action.thought || '',
95
+ bbox: pointToBbox({
96
+ x: point[0],
97
+ y: point[1]
98
+ }, shotSize.width, shotSize.height)
99
+ };
100
+ transformActions.push({
101
+ type: 'DoubleClick',
102
+ param: {
103
+ locate: locate
104
+ },
105
+ thought: action.thought || ''
106
+ });
107
+ } else if ('right_single' === actionType) {
108
+ assert(action.action_inputs.start_box, 'start_box is required');
109
+ const point = getPoint(action.action_inputs.start_box, shotSize);
110
+ const locate = {
111
+ prompt: action.thought || '',
112
+ bbox: pointToBbox({
113
+ x: point[0],
114
+ y: point[1]
115
+ }, shotSize.width, shotSize.height)
116
+ };
117
+ transformActions.push({
118
+ type: 'RightClick',
119
+ param: {
120
+ locate: locate
121
+ },
122
+ thought: action.thought || ''
123
+ });
124
+ } else if ('drag' === actionType) {
125
+ assert(action.action_inputs.start_box, 'start_box is required');
126
+ assert(action.action_inputs.end_box, 'end_box is required');
127
+ const startPoint = getPoint(action.action_inputs.start_box, shotSize);
128
+ const endPoint = getPoint(action.action_inputs.end_box, shotSize);
129
+ transformActions.push({
130
+ type: 'DragAndDrop',
131
+ param: {
132
+ from: {
133
+ prompt: action.thought || '',
134
+ bbox: pointToBbox({
135
+ x: startPoint[0],
136
+ y: startPoint[1]
137
+ }, shotSize.width, shotSize.height)
138
+ },
139
+ to: {
140
+ prompt: action.thought || '',
141
+ bbox: pointToBbox({
142
+ x: endPoint[0],
143
+ y: endPoint[1]
144
+ }, shotSize.width, shotSize.height)
145
+ }
146
+ },
147
+ thought: action.thought || ''
148
+ });
149
+ } else if ('type' === actionType) transformActions.push({
150
+ type: 'Input',
151
+ param: {
152
+ value: action.action_inputs.content
153
+ },
154
+ thought: action.thought || ''
155
+ });
156
+ else if ('scroll' === actionType) transformActions.push({
157
+ type: 'Scroll',
158
+ param: {
159
+ direction: action.action_inputs.direction
160
+ },
161
+ thought: action.thought || ''
162
+ });
163
+ else if ('finished' === actionType) {
164
+ shouldContinue = false;
165
+ transformActions.push({
166
+ type: 'Finished',
167
+ param: {},
168
+ thought: action.thought || ''
169
+ });
170
+ } else if ('hotkey' === actionType) if (action.action_inputs.key) {
171
+ const keys = transformHotkeyInput(action.action_inputs.key);
172
+ transformActions.push({
173
+ type: 'KeyboardPress',
174
+ param: {
175
+ keyName: keys.join('+')
176
+ },
177
+ thought: action.thought || ''
178
+ });
179
+ } else warnLog('No key found in action: hotkey. Will not perform action.');
180
+ else if ('wait' === actionType) transformActions.push({
181
+ type: 'Sleep',
182
+ param: {
183
+ timeMs: 1000
184
+ },
185
+ thought: action.thought || ''
186
+ });
187
+ else if (actionType) {
188
+ unhandledActions.push({
189
+ type: actionType,
190
+ thought: action.thought || ''
191
+ });
192
+ debug('Unhandled action type:', actionType, 'thought:', action.thought);
193
+ }
194
+ });
195
+ if (0 === transformActions.length) {
196
+ const errorDetails = [];
197
+ if (0 === parsed.length) {
198
+ errorDetails.push('Action parser returned no actions');
199
+ if (res.content.includes('Thought:') && !res.content.includes('Action:')) errorDetails.push('Response contains "Thought:" but missing "Action:" line');
200
+ else errorDetails.push('Response may be malformed or empty');
201
+ }
202
+ if (unhandledActions.length > 0) {
203
+ const types = unhandledActions.map((a)=>a.type).join(', ');
204
+ errorDetails.push(`Unhandled action types: ${types}`);
205
+ }
206
+ const errorMessage = [
207
+ 'No actions found in UI-TARS response.',
208
+ ...errorDetails
209
+ ].join('\n');
210
+ throw new AIResponseParseError(errorMessage, JSON.stringify(res.content, void 0, 2), res.usage);
211
+ }
212
+ debug('transformActions', JSON.stringify(transformActions, null, 2));
213
+ const log = getSummary(res.content);
214
+ conversationHistory.append({
215
+ role: 'assistant',
216
+ content: log
217
+ });
218
+ return {
219
+ actions: transformActions,
220
+ log,
221
+ usage: res.usage,
222
+ rawResponse: JSON.stringify(res.content, void 0, 2),
223
+ shouldContinuePlanning: shouldContinue
224
+ };
225
+ }
226
+ function convertBboxToCoordinates(text) {
227
+ const pattern = /<bbox>(\d+)\s+(\d+)\s+(\d+)\s+(\d+)<\/bbox>/g;
228
+ function replaceMatch(match, x1, y1, x2, y2) {
229
+ const x1Num = Number.parseInt(x1, 10);
230
+ const y1Num = Number.parseInt(y1, 10);
231
+ const x2Num = Number.parseInt(x2, 10);
232
+ const y2Num = Number.parseInt(y2, 10);
233
+ const x = Math.floor((x1Num + x2Num) / 2);
234
+ const y = Math.floor((y1Num + y2Num) / 2);
235
+ return `(${x},${y})`;
236
+ }
237
+ const cleanedText = text.replace(/\[EOS\]/g, '');
238
+ return cleanedText.replace(pattern, replaceMatch).trim();
239
+ }
240
+ function getPoint(startBox, size) {
241
+ const [x, y] = JSON.parse(startBox);
242
+ return [
243
+ x * size.width,
244
+ y * size.height
245
+ ];
246
+ }
247
+ export { uiTarsPlanning };
248
+
249
+ //# sourceMappingURL=ui-tars-planning.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-model/ui-tars-planning.mjs","sources":["../../../src/ai-model/ui-tars-planning.ts"],"sourcesContent":["import type {\n PlanningAIResponse,\n PlanningAction,\n Size,\n UIContext,\n} from '@/types';\nimport { type IModelConfig, UITarsModelVersion } from '@midscene/shared/env';\nimport { getDebug } from '@midscene/shared/logger';\nimport { transformHotkeyInput } from '@midscene/shared/us-keyboard-layout';\nimport { assert } from '@midscene/shared/utils';\nimport { actionParser } from '@ui-tars/action-parser';\nimport type { ConversationHistory } from './conversation-history';\nimport { getSummary, getUiTarsPlanningPrompt } from './prompt/ui-tars-planning';\nimport {\n AIResponseParseError,\n callAIWithStringResponse,\n} from './service-caller/index';\n\ntype ActionType =\n | 'click'\n | 'left_double'\n | 'right_single'\n | 'drag'\n | 'type'\n | 'hotkey'\n | 'finished'\n | 'scroll'\n | 'wait';\n\nconst debug = getDebug('ui-tars-planning');\nconst warnLog = getDebug('ui-tars-planning', { console: true });\nconst bboxSize = 10;\nconst pointToBbox = (\n point: { x: number; y: number },\n width: number,\n height: number,\n): [number, number, number, number] => {\n return [\n Math.round(Math.max(point.x - bboxSize / 2, 0)),\n Math.round(Math.max(point.y - bboxSize / 2, 0)),\n Math.round(Math.min(point.x + bboxSize / 2, width)),\n Math.round(Math.min(point.y + bboxSize / 2, height)),\n ];\n};\n\nexport async function uiTarsPlanning(\n userInstruction: string,\n options: {\n conversationHistory: ConversationHistory;\n context: UIContext;\n modelConfig: IModelConfig;\n actionContext?: string;\n abortSignal?: AbortSignal;\n },\n): Promise<PlanningAIResponse> {\n const { conversationHistory, context, modelConfig, actionContext } = options;\n const { uiTarsModelVersion } = modelConfig;\n\n let instruction = userInstruction;\n if (actionContext) {\n instruction = `<high_priority_knowledge>${actionContext}</high_priority_knowledge>\\n<user_instruction>${userInstruction}</user_instruction>`;\n }\n\n const systemPrompt = getUiTarsPlanningPrompt() + instruction;\n\n const screenshotBase64 = context.screenshot.base64;\n\n conversationHistory.append({\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: {\n url: screenshotBase64,\n },\n },\n ],\n });\n\n const res = await callAIWithStringResponse(\n [\n {\n role: 'user',\n content: systemPrompt,\n },\n ...conversationHistory.snapshot(),\n ],\n modelConfig,\n { abortSignal: options.abortSignal },\n );\n\n let convertedText: string;\n let parsed: ReturnType<typeof actionParser>['parsed'];\n\n try {\n convertedText = convertBboxToCoordinates(res.content);\n\n const { shotSize } = context;\n const parseResult = actionParser({\n prediction: convertedText,\n factor: [1000, 1000],\n screenContext: {\n width: shotSize.width,\n height: shotSize.height,\n },\n modelVer: uiTarsModelVersion,\n });\n parsed = parseResult.parsed;\n } catch (parseError) {\n // Throw AIResponseParseError with usage and rawResponse preserved\n const errorMessage =\n parseError instanceof Error ? parseError.message : String(parseError);\n throw new AIResponseParseError(\n `Parse error: ${errorMessage}`,\n JSON.stringify(res.content, undefined, 2),\n res.usage,\n );\n }\n\n const { shotSize } = context;\n\n debug(\n 'ui-tars modelVer',\n uiTarsModelVersion,\n ', parsed',\n JSON.stringify(parsed),\n );\n\n const transformActions: PlanningAction[] = [];\n const unhandledActions: Array<{ type: string; thought: string }> = [];\n let shouldContinue = true;\n parsed.forEach((action) => {\n const actionType = (action.action_type || '').toLowerCase();\n if (actionType === 'click') {\n assert(action.action_inputs.start_box, 'start_box is required');\n const point = getPoint(action.action_inputs.start_box, shotSize);\n\n const locate = {\n prompt: action.thought || '',\n bbox: pointToBbox(\n { x: point[0], y: point[1] },\n shotSize.width,\n shotSize.height,\n ),\n };\n\n transformActions.push({\n type: 'Tap',\n param: {\n locate: locate,\n },\n });\n } else if (actionType === 'left_double') {\n assert(action.action_inputs.start_box, 'start_box is required');\n const point = getPoint(action.action_inputs.start_box, shotSize);\n\n const locate = {\n prompt: action.thought || '',\n bbox: pointToBbox(\n { x: point[0], y: point[1] },\n shotSize.width,\n shotSize.height,\n ),\n };\n\n transformActions.push({\n type: 'DoubleClick',\n param: {\n locate: locate,\n },\n thought: action.thought || '',\n });\n } else if (actionType === 'right_single') {\n assert(action.action_inputs.start_box, 'start_box is required');\n const point = getPoint(action.action_inputs.start_box, shotSize);\n\n const locate = {\n prompt: action.thought || '',\n bbox: pointToBbox(\n { x: point[0], y: point[1] },\n shotSize.width,\n shotSize.height,\n ),\n };\n\n transformActions.push({\n type: 'RightClick',\n param: {\n locate: locate,\n },\n thought: action.thought || '',\n });\n } else if (actionType === 'drag') {\n assert(action.action_inputs.start_box, 'start_box is required');\n assert(action.action_inputs.end_box, 'end_box is required');\n const startPoint = getPoint(action.action_inputs.start_box, shotSize);\n const endPoint = getPoint(action.action_inputs.end_box, shotSize);\n transformActions.push({\n type: 'DragAndDrop',\n param: {\n from: {\n prompt: action.thought || '',\n bbox: pointToBbox(\n { x: startPoint[0], y: startPoint[1] },\n shotSize.width,\n shotSize.height,\n ),\n },\n to: {\n prompt: action.thought || '',\n bbox: pointToBbox(\n { x: endPoint[0], y: endPoint[1] },\n shotSize.width,\n shotSize.height,\n ),\n },\n },\n thought: action.thought || '',\n });\n } else if (actionType === 'type') {\n transformActions.push({\n type: 'Input',\n param: {\n value: action.action_inputs.content,\n },\n thought: action.thought || '',\n });\n } else if (actionType === 'scroll') {\n transformActions.push({\n type: 'Scroll',\n param: {\n direction: action.action_inputs.direction,\n },\n thought: action.thought || '',\n });\n } else if (actionType === 'finished') {\n shouldContinue = false;\n transformActions.push({\n type: 'Finished',\n param: {},\n thought: action.thought || '',\n });\n } else if (actionType === 'hotkey') {\n if (!action.action_inputs.key) {\n warnLog('No key found in action: hotkey. Will not perform action.');\n } else {\n const keys = transformHotkeyInput(action.action_inputs.key);\n\n transformActions.push({\n type: 'KeyboardPress',\n param: {\n keyName: keys.join('+'),\n },\n thought: action.thought || '',\n });\n }\n } else if (actionType === 'wait') {\n transformActions.push({\n type: 'Sleep',\n param: {\n timeMs: 1000,\n },\n thought: action.thought || '',\n });\n } else if (actionType) {\n // Track unhandled action types\n unhandledActions.push({\n type: actionType,\n thought: action.thought || '',\n });\n debug('Unhandled action type:', actionType, 'thought:', action.thought);\n }\n });\n\n if (transformActions.length === 0) {\n const errorDetails: string[] = [];\n\n // Check if parsing failed\n if (parsed.length === 0) {\n errorDetails.push('Action parser returned no actions');\n\n // Check if response has Thought but no Action\n if (\n res.content.includes('Thought:') &&\n !res.content.includes('Action:')\n ) {\n errorDetails.push(\n 'Response contains \"Thought:\" but missing \"Action:\" line',\n );\n } else {\n errorDetails.push('Response may be malformed or empty');\n }\n }\n\n // Check if we have unhandled action types\n if (unhandledActions.length > 0) {\n const types = unhandledActions.map((a) => a.type).join(', ');\n errorDetails.push(`Unhandled action types: ${types}`);\n }\n\n const errorMessage = [\n 'No actions found in UI-TARS response.',\n ...errorDetails,\n ].join('\\n');\n\n // Throw AIResponseParseError with usage and rawResponse preserved\n throw new AIResponseParseError(\n errorMessage,\n JSON.stringify(res.content, undefined, 2),\n res.usage,\n );\n }\n\n debug('transformActions', JSON.stringify(transformActions, null, 2));\n const log = getSummary(res.content);\n\n conversationHistory.append({\n role: 'assistant',\n content: log,\n });\n\n return {\n actions: transformActions,\n log,\n usage: res.usage,\n rawResponse: JSON.stringify(res.content, undefined, 2),\n shouldContinuePlanning: shouldContinue,\n };\n}\n\n/**\n * Converts bounding box notation to coordinate points\n * @param text - The text containing bbox tags to be converted\n * @returns The text with bbox tags replaced by coordinate points\n */\nfunction convertBboxToCoordinates(text: string): string {\n // Match the four numbers after <bbox>\n const pattern = /<bbox>(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)<\\/bbox>/g;\n\n function replaceMatch(\n match: string,\n x1: string,\n y1: string,\n x2: string,\n y2: string,\n ): string {\n // Convert strings to numbers and calculate center point\n const x1Num = Number.parseInt(x1, 10);\n const y1Num = Number.parseInt(y1, 10);\n const x2Num = Number.parseInt(x2, 10);\n const y2Num = Number.parseInt(y2, 10);\n\n // Use Math.floor to truncate and calculate center point\n const x = Math.floor((x1Num + x2Num) / 2);\n const y = Math.floor((y1Num + y2Num) / 2);\n\n // Return formatted coordinate string\n return `(${x},${y})`;\n }\n\n // Remove [EOS] and replace <bbox> coordinates\n const cleanedText = text.replace(/\\[EOS\\]/g, '');\n return cleanedText.replace(pattern, replaceMatch).trim();\n}\n\nfunction getPoint(startBox: string, size: { width: number; height: number }) {\n const [x, y] = JSON.parse(startBox);\n return [x * size.width, y * size.height];\n}\n\ninterface BaseAction {\n action_type: ActionType;\n action_inputs: Record<string, any>;\n reflection: string | null;\n thought: string | null;\n}\n\ninterface ClickAction extends BaseAction {\n action_type: 'click';\n action_inputs: {\n start_box: string; // JSON string of [x, y] coordinates\n };\n}\n\ninterface DragAction extends BaseAction {\n action_type: 'drag';\n action_inputs: {\n start_box: string; // JSON string of [x, y] coordinates\n end_box: string; // JSON string of [x, y] coordinates\n };\n}\n\ninterface WaitAction extends BaseAction {\n action_type: 'wait';\n action_inputs: {\n time: string; // JSON string of [x, y] coordinates\n };\n}\n\ninterface LeftDoubleAction extends BaseAction {\n action_type: 'left_double';\n action_inputs: {\n start_box: string; // JSON string of [x, y] coordinates\n };\n}\n\ninterface RightSingleAction extends BaseAction {\n action_type: 'right_single';\n action_inputs: {\n start_box: string; // JSON string of [x, y] coordinates\n };\n}\n\ninterface TypeAction extends BaseAction {\n action_type: 'type';\n action_inputs: {\n content: string;\n };\n}\n\ninterface HotkeyAction extends BaseAction {\n action_type: 'hotkey';\n action_inputs: {\n key: string;\n };\n}\n\ninterface ScrollAction extends BaseAction {\n action_type: 'scroll';\n action_inputs: {\n direction: 'up' | 'down';\n };\n}\n\ninterface FinishedAction extends BaseAction {\n action_type: 'finished';\n action_inputs: Record<string, never>;\n}\n\nexport type Action =\n | ClickAction\n | LeftDoubleAction\n | RightSingleAction\n | DragAction\n | TypeAction\n | HotkeyAction\n | ScrollAction\n | FinishedAction\n | WaitAction;\n"],"names":["debug","getDebug","warnLog","bboxSize","pointToBbox","point","width","height","Math","uiTarsPlanning","userInstruction","options","conversationHistory","context","modelConfig","actionContext","uiTarsModelVersion","instruction","systemPrompt","getUiTarsPlanningPrompt","screenshotBase64","res","callAIWithStringResponse","convertedText","parsed","convertBboxToCoordinates","shotSize","parseResult","actionParser","parseError","errorMessage","Error","String","AIResponseParseError","JSON","undefined","transformActions","unhandledActions","shouldContinue","action","actionType","assert","getPoint","locate","startPoint","endPoint","keys","transformHotkeyInput","errorDetails","types","a","log","getSummary","text","pattern","replaceMatch","match","x1","y1","x2","y2","x1Num","Number","y1Num","x2Num","y2Num","x","y","cleanedText","startBox","size"],"mappings":"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"}
@@ -0,0 +1,371 @@
1
+ import { assert, isPlainObject } from "@midscene/shared/utils";
2
+ import { isUITars } from "./ai-model/auto-glm/util.mjs";
3
+ import { NodeType } from "@midscene/shared/constants";
4
+ import { treeToList } from "@midscene/shared/extractor";
5
+ import { compositeElementInfoImg } from "@midscene/shared/img";
6
+ import { getDebug } from "@midscene/shared/logger";
7
+ import { z } from "zod";
8
+ const defaultBboxSize = 20;
9
+ const debugInspectUtils = getDebug('ai:common');
10
+ function pointToBbox(x, y, bboxSize = defaultBboxSize) {
11
+ const halfSize = bboxSize / 2;
12
+ const x1 = Math.max(x - halfSize, 0);
13
+ const y1 = Math.max(y - halfSize, 0);
14
+ const x2 = Math.min(x + halfSize, 1000);
15
+ const y2 = Math.min(y + halfSize, 1000);
16
+ return [
17
+ x1,
18
+ y1,
19
+ x2,
20
+ y2
21
+ ];
22
+ }
23
+ function fillBboxParam(locate, width, height, modelFamily) {
24
+ if (locate.bbox_2d && !locate?.bbox) {
25
+ locate.bbox = locate.bbox_2d;
26
+ delete locate.bbox_2d;
27
+ }
28
+ if (locate?.bbox) locate.bbox = adaptBbox(locate.bbox, width, height, modelFamily);
29
+ return locate;
30
+ }
31
+ function adaptQwen2_5Bbox(bbox) {
32
+ if (bbox.length < 2) {
33
+ const msg = `invalid bbox data for qwen-vl mode: ${JSON.stringify(bbox)} `;
34
+ throw new Error(msg);
35
+ }
36
+ const result = [
37
+ Math.round(bbox[0]),
38
+ Math.round(bbox[1]),
39
+ 'number' == typeof bbox[2] ? Math.round(bbox[2]) : Math.round(bbox[0] + defaultBboxSize),
40
+ 'number' == typeof bbox[3] ? Math.round(bbox[3]) : Math.round(bbox[1] + defaultBboxSize)
41
+ ];
42
+ return result;
43
+ }
44
+ function adaptGpt5Bbox(bbox) {
45
+ if (!Array.isArray(bbox) || 4 !== bbox.length || !bbox.every((value)=>'number' == typeof value && Number.isFinite(value))) {
46
+ const msg = `invalid bbox data for gpt-5 mode: ${JSON.stringify(bbox)} `;
47
+ throw new Error(msg);
48
+ }
49
+ const numericBbox = bbox;
50
+ return [
51
+ numericBbox[0],
52
+ numericBbox[1],
53
+ numericBbox[2],
54
+ numericBbox[3]
55
+ ];
56
+ }
57
+ function adaptDoubaoBbox(bbox, width, height) {
58
+ assert(width > 0 && height > 0, 'width and height must be greater than 0 in doubao mode');
59
+ if ('string' == typeof bbox) {
60
+ assert(/^(\d+)\s(\d+)\s(\d+)\s(\d+)$/.test(bbox.trim()), `invalid bbox data string for doubao-vision mode: ${bbox}`);
61
+ const splitted = bbox.split(' ');
62
+ if (4 === splitted.length) return [
63
+ Math.round(Number(splitted[0]) * width / 1000),
64
+ Math.round(Number(splitted[1]) * height / 1000),
65
+ Math.round(Number(splitted[2]) * width / 1000),
66
+ Math.round(Number(splitted[3]) * height / 1000)
67
+ ];
68
+ throw new Error(`invalid bbox data string for doubao-vision mode: ${bbox}`);
69
+ }
70
+ let bboxList = [];
71
+ if (Array.isArray(bbox) && 'string' == typeof bbox[0]) bbox.forEach((item)=>{
72
+ if ('string' == typeof item && item.includes(',')) {
73
+ const [x, y] = item.split(',');
74
+ bboxList.push(Number(x.trim()), Number(y.trim()));
75
+ } else if ('string' == typeof item && item.includes(' ')) {
76
+ const [x, y] = item.split(' ');
77
+ bboxList.push(Number(x.trim()), Number(y.trim()));
78
+ } else bboxList.push(Number(item));
79
+ });
80
+ else bboxList = bbox;
81
+ if (4 === bboxList.length || 5 === bboxList.length) return [
82
+ Math.round(bboxList[0] * width / 1000),
83
+ Math.round(bboxList[1] * height / 1000),
84
+ Math.round(bboxList[2] * width / 1000),
85
+ Math.round(bboxList[3] * height / 1000)
86
+ ];
87
+ if (6 === bboxList.length || 2 === bboxList.length || 3 === bboxList.length || 7 === bboxList.length) return [
88
+ Math.max(0, Math.round(bboxList[0] * width / 1000) - defaultBboxSize / 2),
89
+ Math.max(0, Math.round(bboxList[1] * height / 1000) - defaultBboxSize / 2),
90
+ Math.min(width, Math.round(bboxList[0] * width / 1000) + defaultBboxSize / 2),
91
+ Math.min(height, Math.round(bboxList[1] * height / 1000) + defaultBboxSize / 2)
92
+ ];
93
+ if (8 === bbox.length) return [
94
+ Math.round(bboxList[0] * width / 1000),
95
+ Math.round(bboxList[1] * height / 1000),
96
+ Math.round(bboxList[4] * width / 1000),
97
+ Math.round(bboxList[5] * height / 1000)
98
+ ];
99
+ const msg = `invalid bbox data for doubao-vision mode: ${JSON.stringify(bbox)} `;
100
+ throw new Error(msg);
101
+ }
102
+ function normalizeBboxInput(bbox) {
103
+ if (Array.isArray(bbox)) {
104
+ if (Array.isArray(bbox[0])) return bbox[0];
105
+ }
106
+ return bbox;
107
+ }
108
+ function adaptBbox(bbox, width, height, modelFamily) {
109
+ const normalizedBbox = normalizeBboxInput(bbox);
110
+ let result = [
111
+ 0,
112
+ 0,
113
+ 0,
114
+ 0
115
+ ];
116
+ result = 'doubao-vision' === modelFamily || 'doubao-seed' === modelFamily || isUITars(modelFamily) ? adaptDoubaoBbox(normalizedBbox, width, height) : 'gemini' === modelFamily ? adaptGeminiBbox(normalizedBbox, width, height) : 'qwen2.5-vl' === modelFamily ? adaptQwen2_5Bbox(normalizedBbox) : 'gpt-5' === modelFamily ? adaptGpt5Bbox(normalizedBbox) : normalized01000(normalizedBbox, width, height);
117
+ return result;
118
+ }
119
+ function normalized01000(bbox, width, height) {
120
+ return [
121
+ Math.round(bbox[0] * width / 1000),
122
+ Math.round(bbox[1] * height / 1000),
123
+ Math.round(bbox[2] * width / 1000),
124
+ Math.round(bbox[3] * height / 1000)
125
+ ];
126
+ }
127
+ function adaptGeminiBbox(bbox, width, height) {
128
+ const left = Math.round(bbox[1] * width / 1000);
129
+ const top = Math.round(bbox[0] * height / 1000);
130
+ const right = Math.round(bbox[3] * width / 1000);
131
+ const bottom = Math.round(bbox[2] * height / 1000);
132
+ return [
133
+ left,
134
+ top,
135
+ right,
136
+ bottom
137
+ ];
138
+ }
139
+ function adaptBboxToRect(bbox, width, height, offsetX = 0, offsetY = 0, rightLimit = width, bottomLimit = height, modelFamily, scale = 1) {
140
+ debugInspectUtils('adaptBboxToRect', bbox, width, height, 'offset', offsetX, offsetY, 'limit', rightLimit, bottomLimit, 'modelFamily', modelFamily, 'scale', scale);
141
+ const [left, top, right, bottom] = adaptBbox(bbox, width, height, modelFamily);
142
+ const rectLeft = Math.max(0, left);
143
+ const rectTop = Math.max(0, top);
144
+ const boundedRight = Math.min(right, rightLimit);
145
+ const boundedBottom = Math.min(bottom, bottomLimit);
146
+ const rectWidth = boundedRight - rectLeft + 1;
147
+ const rectHeight = boundedBottom - rectTop + 1;
148
+ const finalLeft = 1 !== scale ? Math.round(rectLeft / scale) : rectLeft;
149
+ const finalTop = 1 !== scale ? Math.round(rectTop / scale) : rectTop;
150
+ const finalWidth = 1 !== scale ? Math.round(rectWidth / scale) : rectWidth;
151
+ const finalHeight = 1 !== scale ? Math.round(rectHeight / scale) : rectHeight;
152
+ const rect = {
153
+ left: finalLeft + offsetX,
154
+ top: finalTop + offsetY,
155
+ width: finalWidth,
156
+ height: finalHeight
157
+ };
158
+ debugInspectUtils('adaptBboxToRect, result=', rect);
159
+ return rect;
160
+ }
161
+ function mergeRects(rects) {
162
+ const minLeft = Math.min(...rects.map((r)=>r.left));
163
+ const minTop = Math.min(...rects.map((r)=>r.top));
164
+ const maxRight = Math.max(...rects.map((r)=>r.left + r.width));
165
+ const maxBottom = Math.max(...rects.map((r)=>r.top + r.height));
166
+ return {
167
+ left: minLeft,
168
+ top: minTop,
169
+ width: maxRight - minLeft,
170
+ height: maxBottom - minTop
171
+ };
172
+ }
173
+ function expandSearchArea(rect, screenSize) {
174
+ const minArea = 160000;
175
+ const expandSize = 100;
176
+ const expandedLeft = Math.max(rect.left - expandSize, 0);
177
+ const expandedTop = Math.max(rect.top - expandSize, 0);
178
+ const expandRect = {
179
+ left: expandedLeft,
180
+ top: expandedTop,
181
+ width: Math.min(rect.left - expandedLeft + rect.width + expandSize, screenSize.width - expandedLeft),
182
+ height: Math.min(rect.top - expandedTop + rect.height + expandSize, screenSize.height - expandedTop)
183
+ };
184
+ const currentArea = expandRect.width * expandRect.height;
185
+ if (currentArea >= minArea) return expandRect;
186
+ const centerX = expandRect.left + expandRect.width / 2;
187
+ const centerY = expandRect.top + expandRect.height / 2;
188
+ const scaleFactor = Math.sqrt(minArea / currentArea);
189
+ const newWidth = Math.round(expandRect.width * scaleFactor);
190
+ const newHeight = Math.round(expandRect.height * scaleFactor);
191
+ const newLeft = Math.round(centerX - newWidth / 2);
192
+ const newTop = Math.round(centerY - newHeight / 2);
193
+ const left = Math.max(newLeft, 0);
194
+ const top = Math.max(newTop, 0);
195
+ return {
196
+ left,
197
+ top,
198
+ width: Math.min(newWidth, screenSize.width - left),
199
+ height: Math.min(newHeight, screenSize.height - top)
200
+ };
201
+ }
202
+ async function markupImageForLLM(screenshotBase64, tree, size) {
203
+ const elementsInfo = treeToList(tree);
204
+ const elementsPositionInfoWithoutText = elementsInfo.filter((elementInfo)=>{
205
+ if (elementInfo.attributes.nodeType === NodeType.TEXT) return false;
206
+ return true;
207
+ });
208
+ const imagePayload = await compositeElementInfoImg({
209
+ inputImgBase64: screenshotBase64,
210
+ elementsPositionInfo: elementsPositionInfoWithoutText,
211
+ size
212
+ });
213
+ return imagePayload;
214
+ }
215
+ function buildYamlFlowFromPlans(plans, actionSpace) {
216
+ const flow = [];
217
+ for (const plan of plans){
218
+ const verb = plan.type;
219
+ const action = actionSpace.find((action)=>action.name === verb);
220
+ if (!action) {
221
+ console.warn(`Cannot convert action ${verb} to yaml flow. Will ignore it.`);
222
+ continue;
223
+ }
224
+ const flowKey = action.interfaceAlias || verb;
225
+ const flowParam = action.paramSchema ? dumpActionParam(plan.param || {}, action.paramSchema) : {};
226
+ const flowItem = {
227
+ [flowKey]: '',
228
+ ...flowParam
229
+ };
230
+ flow.push(flowItem);
231
+ }
232
+ return flow;
233
+ }
234
+ const PointSchema = z.object({
235
+ left: z.number(),
236
+ top: z.number()
237
+ });
238
+ const SizeSchema = z.object({
239
+ width: z.number(),
240
+ height: z.number()
241
+ });
242
+ const RectSchema = PointSchema.and(SizeSchema).and(z.object({
243
+ zoom: z.number().optional()
244
+ }));
245
+ const TMultimodalPromptSchema = z.object({
246
+ images: z.array(z.object({
247
+ name: z.string(),
248
+ url: z.string()
249
+ })).optional(),
250
+ convertHttpImage2Base64: z.boolean().optional()
251
+ });
252
+ const TUserPromptSchema = z.union([
253
+ z.string(),
254
+ z.object({
255
+ prompt: z.string()
256
+ }).and(TMultimodalPromptSchema.partial())
257
+ ]);
258
+ const locateFieldFlagName = 'midscene_location_field_flag';
259
+ const MidsceneLocationInput = z.object({
260
+ prompt: TUserPromptSchema,
261
+ deepLocate: z.boolean().optional(),
262
+ deepThink: z.boolean().optional().describe('@deprecated Use `deepLocate` instead.'),
263
+ cacheable: z.boolean().optional(),
264
+ xpath: z.union([
265
+ z.string(),
266
+ z.boolean()
267
+ ]).optional()
268
+ }).passthrough();
269
+ const getMidsceneLocationSchema = ()=>MidsceneLocationInput;
270
+ const ifMidsceneLocatorField = (field)=>{
271
+ let actualField = field;
272
+ if (actualField._def?.typeName === 'ZodOptional') actualField = actualField._def.innerType;
273
+ if (actualField._def?.typeName === 'ZodObject') {
274
+ const shape = actualField._def.shape();
275
+ if (locateFieldFlagName in shape) return true;
276
+ if ('prompt' in shape && shape.prompt) return true;
277
+ }
278
+ return false;
279
+ };
280
+ const dumpMidsceneLocatorField = (field)=>{
281
+ assert(ifMidsceneLocatorField(field), 'field is not a midscene locator field');
282
+ if ('string' == typeof field) return field;
283
+ if (field && 'object' == typeof field && field.prompt) {
284
+ if ('string' == typeof field.prompt) return field.prompt;
285
+ if ('object' == typeof field.prompt && field.prompt.prompt) return field.prompt.prompt;
286
+ }
287
+ return String(field);
288
+ };
289
+ const findAllMidsceneLocatorField = (zodType, requiredOnly)=>{
290
+ if (!zodType) return [];
291
+ const zodObject = zodType;
292
+ if (zodObject._def?.typeName === 'ZodObject' && zodObject.shape) {
293
+ const keys = Object.keys(zodObject.shape);
294
+ return keys.filter((key)=>{
295
+ const field = zodObject.shape[key];
296
+ if (!ifMidsceneLocatorField(field)) return false;
297
+ if (requiredOnly) return field._def?.typeName !== 'ZodOptional';
298
+ return true;
299
+ });
300
+ }
301
+ return [];
302
+ };
303
+ const dumpActionParam = (jsonObject, zodSchema)=>{
304
+ if (!isPlainObject(jsonObject)) return {};
305
+ const locatorFields = findAllMidsceneLocatorField(zodSchema);
306
+ const result = {
307
+ ...jsonObject
308
+ };
309
+ for (const fieldName of locatorFields){
310
+ const fieldValue = result[fieldName];
311
+ if (fieldValue) {
312
+ if ('string' == typeof fieldValue) result[fieldName] = fieldValue;
313
+ else if ('object' == typeof fieldValue) {
314
+ if (fieldValue.prompt) {
315
+ if ('string' == typeof fieldValue.prompt) result[fieldName] = fieldValue.prompt;
316
+ else if ('object' == typeof fieldValue.prompt && fieldValue.prompt.prompt) result[fieldName] = fieldValue.prompt.prompt;
317
+ }
318
+ }
319
+ }
320
+ }
321
+ return result;
322
+ };
323
+ const parseActionParam = (rawParam, zodSchema, options)=>{
324
+ if (!zodSchema) return;
325
+ const param = rawParam ?? {};
326
+ const locateFields = findAllMidsceneLocatorField(zodSchema);
327
+ if (0 === locateFields.length) return zodSchema.parse(param);
328
+ const locateFieldValues = {};
329
+ for (const fieldName of locateFields)if (fieldName in param) locateFieldValues[fieldName] = param[fieldName];
330
+ const paramsForValidation = {};
331
+ for(const key in param)if (locateFields.includes(key)) paramsForValidation[key] = {
332
+ prompt: '_dummy_'
333
+ };
334
+ else paramsForValidation[key] = param[key];
335
+ const validated = zodSchema.parse(paramsForValidation);
336
+ const ratio = options?.shrunkShotToLogicalRatio;
337
+ for(const fieldName in locateFieldValues){
338
+ let value = locateFieldValues[fieldName];
339
+ if (void 0 !== ratio && 1 !== ratio && value && 'object' == typeof value && value.center && value.rect) value = {
340
+ ...value,
341
+ center: [
342
+ Math.round(value.center[0] / ratio),
343
+ Math.round(value.center[1] / ratio)
344
+ ],
345
+ rect: {
346
+ ...value.rect,
347
+ left: Math.round(value.rect.left / ratio),
348
+ top: Math.round(value.rect.top / ratio),
349
+ width: Math.round(value.rect.width / ratio),
350
+ height: Math.round(value.rect.height / ratio)
351
+ }
352
+ };
353
+ validated[fieldName] = value;
354
+ }
355
+ return validated;
356
+ };
357
+ const finalizeActionName = 'Finalize';
358
+ const getReadableTimeString = (format = 'YYYY-MM-DD HH:mm:ss', timestamp)=>{
359
+ const now = void 0 !== timestamp ? new Date(timestamp) : new Date();
360
+ const year = now.getFullYear();
361
+ const month = String(now.getMonth() + 1).padStart(2, '0');
362
+ const day = String(now.getDate()).padStart(2, '0');
363
+ const hours = String(now.getHours()).padStart(2, '0');
364
+ const minutes = String(now.getMinutes()).padStart(2, '0');
365
+ const seconds = String(now.getSeconds()).padStart(2, '0');
366
+ const timeString = format.replace('YYYY', String(year)).replace('MM', month).replace('DD', day).replace('HH', hours).replace('mm', minutes).replace('ss', seconds);
367
+ return `${timeString} (${format})`;
368
+ };
369
+ export { PointSchema, RectSchema, SizeSchema, TMultimodalPromptSchema, TUserPromptSchema, adaptBbox, adaptBboxToRect, adaptDoubaoBbox, adaptGeminiBbox, adaptGpt5Bbox, adaptQwen2_5Bbox, buildYamlFlowFromPlans, dumpActionParam, dumpMidsceneLocatorField, expandSearchArea, fillBboxParam, finalizeActionName, findAllMidsceneLocatorField, getMidsceneLocationSchema, getReadableTimeString, ifMidsceneLocatorField, markupImageForLLM, mergeRects, normalized01000, parseActionParam, pointToBbox };
370
+
371
+ //# sourceMappingURL=common.mjs.map