mobster-cli 0.4.3__py3-none-any.whl

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 (1009) hide show
  1. mobile_agent/__init__.py +4 -0
  2. mobile_agent/__main__.py +598 -0
  3. mobile_agent/accounts/__init__.py +1 -0
  4. mobile_agent/accounts/context.py +80 -0
  5. mobile_agent/accounts/mcp.py +55 -0
  6. mobile_agent/accounts/pacing.py +318 -0
  7. mobile_agent/accounts/register.py +78 -0
  8. mobile_agent/accounts/routes.py +212 -0
  9. mobile_agent/accounts/routing.py +69 -0
  10. mobile_agent/accounts/screen.py +273 -0
  11. mobile_agent/accounts/stats.py +54 -0
  12. mobile_agent/accounts/store.py +394 -0
  13. mobile_agent/agent_hooks.py +123 -0
  14. mobile_agent/ai_plans/__init__.py +7 -0
  15. mobile_agent/ai_plans/agents.py +96 -0
  16. mobile_agent/ai_plans/claude_code.py +208 -0
  17. mobile_agent/ai_plans/cli.py +400 -0
  18. mobile_agent/ai_plans/codex.py +258 -0
  19. mobile_agent/ai_plans/compat_chat.py +307 -0
  20. mobile_agent/ai_plans/device_login.py +164 -0
  21. mobile_agent/ai_plans/draft_client.py +68 -0
  22. mobile_agent/ai_plans/gemini.py +137 -0
  23. mobile_agent/ai_plans/instructions.py +65 -0
  24. mobile_agent/ai_plans/mcp_http.py +160 -0
  25. mobile_agent/ai_plans/phone_tools.py +892 -0
  26. mobile_agent/ai_plans/providers.py +235 -0
  27. mobile_agent/ai_plans/routes.py +160 -0
  28. mobile_agent/ai_plans/runner.py +400 -0
  29. mobile_agent/ai_plans/vault.py +114 -0
  30. mobile_agent/alerts.py +337 -0
  31. mobile_agent/android/__init__.py +18 -0
  32. mobile_agent/android/adb.py +268 -0
  33. mobile_agent/android/conditions.py +128 -0
  34. mobile_agent/android/doctor.py +54 -0
  35. mobile_agent/android/driver.py +137 -0
  36. mobile_agent/android/emulator.py +216 -0
  37. mobile_agent/android/logs.py +85 -0
  38. mobile_agent/android/manager.py +225 -0
  39. mobile_agent/android/records.py +31 -0
  40. mobile_agent/android/tree.py +96 -0
  41. mobile_agent/answer_shapes.py +230 -0
  42. mobile_agent/api_devices.py +333 -0
  43. mobile_agent/api_errors.py +17 -0
  44. mobile_agent/api_get.py +249 -0
  45. mobile_agent/api_http.py +467 -0
  46. mobile_agent/api_post.py +453 -0
  47. mobile_agent/api_routes.py +173 -0
  48. mobile_agent/app_api/__init__.py +4 -0
  49. mobile_agent/app_api/cli.py +151 -0
  50. mobile_agent/app_api/endpoints.py +193 -0
  51. mobile_agent/app_api/mcp.py +54 -0
  52. mobile_agent/app_api/openapi.py +146 -0
  53. mobile_agent/app_api/reading.py +172 -0
  54. mobile_agent/app_api/register.py +10 -0
  55. mobile_agent/app_api/screen.py +216 -0
  56. mobile_agent/app_api/service.py +382 -0
  57. mobile_agent/app_api/sessions.py +404 -0
  58. mobile_agent/app_api/tokens.py +167 -0
  59. mobile_agent/app_choice.py +131 -0
  60. mobile_agent/app_icons.py +183 -0
  61. mobile_agent/app_killer.py +69 -0
  62. mobile_agent/artifacts.py +606 -0
  63. mobile_agent/attach/__init__.py +13 -0
  64. mobile_agent/attach/cli.py +46 -0
  65. mobile_agent/attach/flutter.py +28 -0
  66. mobile_agent/attach/score.py +119 -0
  67. mobile_agent/attach/web.py +13 -0
  68. mobile_agent/attachments/__init__.py +1 -0
  69. mobile_agent/attachments/agent.py +418 -0
  70. mobile_agent/attachments/extract.py +217 -0
  71. mobile_agent/attachments/mcp.py +104 -0
  72. mobile_agent/attachments/register.py +73 -0
  73. mobile_agent/attachments/routes.py +326 -0
  74. mobile_agent/attachments/service.py +170 -0
  75. mobile_agent/attachments/sniff.py +152 -0
  76. mobile_agent/attachments/store.py +441 -0
  77. mobile_agent/audit_verify.py +436 -0
  78. mobile_agent/bench/README.md +410 -0
  79. mobile_agent/bench/__init__.py +0 -0
  80. mobile_agent/bench/__main__.py +394 -0
  81. mobile_agent/bench/ab.py +168 -0
  82. mobile_agent/bench/actuation.py +368 -0
  83. mobile_agent/bench/agents/__init__.py +9 -0
  84. mobile_agent/bench/agents/base.py +105 -0
  85. mobile_agent/bench/agents/plan_clients.py +245 -0
  86. mobile_agent/bench/agents/smart.py +533 -0
  87. mobile_agent/bench/checks.py +358 -0
  88. mobile_agent/bench/decide_gate.py +214 -0
  89. mobile_agent/bench/diagnose.py +181 -0
  90. mobile_agent/bench/evals/__init__.py +14 -0
  91. mobile_agent/bench/evals/build.py +565 -0
  92. mobile_agent/bench/evals/checkpoint.py +214 -0
  93. mobile_agent/bench/evals/irt.py +219 -0
  94. mobile_agent/bench/evals/l0.py +647 -0
  95. mobile_agent/bench/evals/l1.py +866 -0
  96. mobile_agent/bench/evals/models.py +124 -0
  97. mobile_agent/bench/evals/pairwise.py +189 -0
  98. mobile_agent/bench/evals/proxy.py +90 -0
  99. mobile_agent/bench/evals/sets.py +149 -0
  100. mobile_agent/bench/evals/traces.py +446 -0
  101. mobile_agent/bench/fixtures.py +176 -0
  102. mobile_agent/bench/guard_replay.py +129 -0
  103. mobile_agent/bench/h2-manifest.json +20 -0
  104. mobile_agent/bench/h3-manifest.json +20 -0
  105. mobile_agent/bench/heldout-manifest.json +32 -0
  106. mobile_agent/bench/heldout.py +375 -0
  107. mobile_agent/bench/iosworld.py +901 -0
  108. mobile_agent/bench/iosworld_stats.py +510 -0
  109. mobile_agent/bench/llm_units.py +116 -0
  110. mobile_agent/bench/manifest.json +93 -0
  111. mobile_agent/bench/metrics.py +66 -0
  112. mobile_agent/bench/phone.py +44 -0
  113. mobile_agent/bench/replay.py +172 -0
  114. mobile_agent/bench/report.py +207 -0
  115. mobile_agent/bench/runner.py +456 -0
  116. mobile_agent/bench/safety.py +310 -0
  117. mobile_agent/bench/stream_timing.py +213 -0
  118. mobile_agent/bench/suite.py +444 -0
  119. mobile_agent/bench/sweep.py +176 -0
  120. mobile_agent/bench/tape.py +798 -0
  121. mobile_agent/bench/tape_replay.py +88 -0
  122. mobile_agent/bench/trace_evals.py +128 -0
  123. mobile_agent/bench/trace_view.py +81 -0
  124. mobile_agent/bench/truth/iphone15pro.json +138 -0
  125. mobile_agent/bench/truth.py +340 -0
  126. mobile_agent/bench/tuning.py +543 -0
  127. mobile_agent/bench/tuning_analysis.py +656 -0
  128. mobile_agent/bench/vague-heldout-manifest.json +12 -0
  129. mobile_agent/bench/vague_rubric.py +135 -0
  130. mobile_agent/bench/vague_run.py +243 -0
  131. mobile_agent/brand.py +454 -0
  132. mobile_agent/brand_fonts.py +756 -0
  133. mobile_agent/build_identity.py +21 -0
  134. mobile_agent/cable_bt.py +623 -0
  135. mobile_agent/cable_bt_runtime.py +128 -0
  136. mobile_agent/catalog.py +69 -0
  137. mobile_agent/chart_svg.py +415 -0
  138. mobile_agent/chat_here.py +189 -0
  139. mobile_agent/cli_constants.py +5 -0
  140. mobile_agent/cli_parser.py +611 -0
  141. mobile_agent/cli_run.py +115 -0
  142. mobile_agent/compiled_read/__init__.py +21 -0
  143. mobile_agent/compiled_read/delta.py +89 -0
  144. mobile_agent/compiled_read/extractors.py +202 -0
  145. mobile_agent/compiled_read/routes.py +56 -0
  146. mobile_agent/completion.py +267 -0
  147. mobile_agent/compose.py +48 -0
  148. mobile_agent/config.py +114 -0
  149. mobile_agent/connectors/__init__.py +1 -0
  150. mobile_agent/connectors/api_session.py +76 -0
  151. mobile_agent/connectors/callback_page.py +146 -0
  152. mobile_agent/connectors/client.py +582 -0
  153. mobile_agent/connectors/github_app.py +55 -0
  154. mobile_agent/connectors/graph_api.py +396 -0
  155. mobile_agent/connectors/imports.py +170 -0
  156. mobile_agent/connectors/issue.py +66 -0
  157. mobile_agent/connectors/keystore.py +85 -0
  158. mobile_agent/connectors/marks.py +222 -0
  159. mobile_agent/connectors/oauth.py +476 -0
  160. mobile_agent/connectors/picker.py +228 -0
  161. mobile_agent/connectors/presets.py +591 -0
  162. mobile_agent/connectors/register.py +48 -0
  163. mobile_agent/connectors/routes.py +153 -0
  164. mobile_agent/connectors/service.py +966 -0
  165. mobile_agent/connectors/shared.py +136 -0
  166. mobile_agent/connectors/skill.py +446 -0
  167. mobile_agent/connectors/slack_api.py +269 -0
  168. mobile_agent/connectors/steps.py +92 -0
  169. mobile_agent/connectors/store.py +144 -0
  170. mobile_agent/connectors/vault.py +72 -0
  171. mobile_agent/console.py +178 -0
  172. mobile_agent/content_queue/__init__.py +1 -0
  173. mobile_agent/content_queue/client.py +263 -0
  174. mobile_agent/content_queue/importing.py +153 -0
  175. mobile_agent/content_queue/media_check.py +224 -0
  176. mobile_agent/content_queue/prompt.py +64 -0
  177. mobile_agent/content_queue/register.py +91 -0
  178. mobile_agent/content_queue/routes.py +370 -0
  179. mobile_agent/content_queue/runner.py +475 -0
  180. mobile_agent/content_queue/store.py +521 -0
  181. mobile_agent/contract.py +996 -0
  182. mobile_agent/contract_asks.py +248 -0
  183. mobile_agent/contract_prompt.py +45 -0
  184. mobile_agent/contract_screen.py +981 -0
  185. mobile_agent/contract_view.py +84 -0
  186. mobile_agent/corpus/__init__.py +26 -0
  187. mobile_agent/corpus/cli.py +63 -0
  188. mobile_agent/corpus/consent.py +55 -0
  189. mobile_agent/corpus/listener.py +76 -0
  190. mobile_agent/corpus/register.py +9 -0
  191. mobile_agent/corpus/skeleton.py +72 -0
  192. mobile_agent/corpus/store.py +143 -0
  193. mobile_agent/costs.py +161 -0
  194. mobile_agent/decider_scope.py +40 -0
  195. mobile_agent/decision_provider.py +189 -0
  196. mobile_agent/deeplinks.py +352 -0
  197. mobile_agent/device_info.py +116 -0
  198. mobile_agent/device_manager.py +978 -0
  199. mobile_agent/device_status.py +195 -0
  200. mobile_agent/device_targets.py +217 -0
  201. mobile_agent/devices.py +676 -0
  202. mobile_agent/devkit/__init__.py +64 -0
  203. mobile_agent/devkit/account_cli.py +90 -0
  204. mobile_agent/devkit/asc.py +562 -0
  205. mobile_agent/devkit/asc_cli.py +214 -0
  206. mobile_agent/devkit/ci.py +737 -0
  207. mobile_agent/devkit/ci_cli.py +196 -0
  208. mobile_agent/devkit/ci_hosts.py +194 -0
  209. mobile_agent/devkit/es256.py +197 -0
  210. mobile_agent/devkit/expo.py +440 -0
  211. mobile_agent/devkit/expo_cli.py +405 -0
  212. mobile_agent/devkit/expo_dev.py +203 -0
  213. mobile_agent/devkit/expo_routes.py +90 -0
  214. mobile_agent/devkit/fixtures_cli.py +133 -0
  215. mobile_agent/devkit/fixtures_routes.py +82 -0
  216. mobile_agent/devkit/links.py +375 -0
  217. mobile_agent/devkit/links_cli.py +123 -0
  218. mobile_agent/devkit/links_routes.py +83 -0
  219. mobile_agent/devkit/nightly.py +152 -0
  220. mobile_agent/devkit/shots.py +203 -0
  221. mobile_agent/devkit/testflight.py +155 -0
  222. mobile_agent/devkit/testflight_cli.py +127 -0
  223. mobile_agent/devkit/testflight_routes.py +111 -0
  224. mobile_agent/devkit/xcode.py +236 -0
  225. mobile_agent/devkit/xcode_cli.py +101 -0
  226. mobile_agent/devtools.py +419 -0
  227. mobile_agent/doctor.py +509 -0
  228. mobile_agent/doctor_fix.py +467 -0
  229. mobile_agent/downloads.py +55 -0
  230. mobile_agent/drivers.py +793 -0
  231. mobile_agent/dry_run.py +102 -0
  232. mobile_agent/effect_ledger.py +83 -0
  233. mobile_agent/engine_health.py +378 -0
  234. mobile_agent/engine_hooks.py +96 -0
  235. mobile_agent/engines.py +408 -0
  236. mobile_agent/env_file.py +63 -0
  237. mobile_agent/env_secrets.py +149 -0
  238. mobile_agent/errors.py +15 -0
  239. mobile_agent/evals/__init__.py +3 -0
  240. mobile_agent/evals/app_survey.py +149 -0
  241. mobile_agent/evals/development/__init__.py +1 -0
  242. mobile_agent/evals/development/score.py +276 -0
  243. mobile_agent/evals/latency_report.py +312 -0
  244. mobile_agent/evals/offline_report.py +374 -0
  245. mobile_agent/evals/oracles.py +298 -0
  246. mobile_agent/evals/redteam/__init__.py +15 -0
  247. mobile_agent/evals/redteam/__main__.py +72 -0
  248. mobile_agent/evals/redteam/cases.py +730 -0
  249. mobile_agent/evals/redteam/harness.py +604 -0
  250. mobile_agent/evals/redteam/live.py +139 -0
  251. mobile_agent/evals/redteam/plain.py +85 -0
  252. mobile_agent/evals/vision/__init__.py +0 -0
  253. mobile_agent/evals/vision/images/dog_001.jpg +0 -0
  254. mobile_agent/evals/vision/images/dog_002.jpg +0 -0
  255. mobile_agent/evals/vision/images/dog_003.jpg +0 -0
  256. mobile_agent/evals/vision/images/dog_004.jpg +0 -0
  257. mobile_agent/evals/vision/images/dog_005.jpg +0 -0
  258. mobile_agent/evals/vision/images/dog_006.jpg +0 -0
  259. mobile_agent/evals/vision/images/dog_007.jpg +0 -0
  260. mobile_agent/evals/vision/images/dog_010.jpg +0 -0
  261. mobile_agent/evals/vision/images/dog_011.jpg +0 -0
  262. mobile_agent/evals/vision/images/dog_013.jpg +0 -0
  263. mobile_agent/evals/vision/images/dog_014.jpg +0 -0
  264. mobile_agent/evals/vision/images/dog_015.jpg +0 -0
  265. mobile_agent/evals/vision/images/dog_016.jpg +0 -0
  266. mobile_agent/evals/vision/images/dog_017.jpg +0 -0
  267. mobile_agent/evals/vision/images/dog_019.jpg +0 -0
  268. mobile_agent/evals/vision/images/dog_020.jpg +0 -0
  269. mobile_agent/evals/vision/images/dog_021.jpg +0 -0
  270. mobile_agent/evals/vision/images/dog_022.jpg +0 -0
  271. mobile_agent/evals/vision/images/dog_023.jpg +0 -0
  272. mobile_agent/evals/vision/images/dog_024.jpg +0 -0
  273. mobile_agent/evals/vision/images/dog_026.jpg +0 -0
  274. mobile_agent/evals/vision/images/dog_027.jpg +0 -0
  275. mobile_agent/evals/vision/images/dog_034.jpg +0 -0
  276. mobile_agent/evals/vision/images/dog_035.jpg +0 -0
  277. mobile_agent/evals/vision/images/dog_038.jpg +0 -0
  278. mobile_agent/evals/vision/images/dog_042.jpg +0 -0
  279. mobile_agent/evals/vision/images/dog_047.jpg +0 -0
  280. mobile_agent/evals/vision/images/dog_052.jpg +0 -0
  281. mobile_agent/evals/vision/images/dog_058.jpg +0 -0
  282. mobile_agent/evals/vision/images/dog_062.jpg +0 -0
  283. mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
  284. mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
  285. mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
  286. mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
  287. mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
  288. mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
  289. mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
  290. mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
  291. mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
  292. mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
  293. mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
  294. mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
  295. mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
  296. mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
  297. mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
  298. mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
  299. mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
  300. mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
  301. mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
  302. mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
  303. mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
  304. mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
  305. mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
  306. mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
  307. mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
  308. mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
  309. mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
  310. mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
  311. mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
  312. mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
  313. mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
  314. mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
  315. mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
  316. mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
  317. mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
  318. mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
  319. mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
  320. mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
  321. mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
  322. mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
  323. mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
  324. mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
  325. mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
  326. mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
  327. mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
  328. mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
  329. mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
  330. mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
  331. mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
  332. mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
  333. mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
  334. mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
  335. mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
  336. mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
  337. mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
  338. mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
  339. mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
  340. mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
  341. mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
  342. mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
  343. mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
  344. mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
  345. mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
  346. mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
  347. mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
  348. mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
  349. mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
  350. mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
  351. mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
  352. mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
  353. mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
  354. mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
  355. mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
  356. mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
  357. mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
  358. mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
  359. mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
  360. mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
  361. mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
  362. mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
  363. mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
  364. mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
  365. mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
  366. mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
  367. mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
  368. mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
  369. mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
  370. mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
  371. mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
  372. mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
  373. mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
  374. mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
  375. mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
  376. mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
  377. mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
  378. mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
  379. mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
  380. mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
  381. mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
  382. mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
  383. mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
  384. mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
  385. mobile_agent/evals/vision/manifest.json +2355 -0
  386. mobile_agent/eventbus.py +110 -0
  387. mobile_agent/explain.py +34 -0
  388. mobile_agent/failure_line.py +197 -0
  389. mobile_agent/fanout.py +663 -0
  390. mobile_agent/feedback/__init__.py +2 -0
  391. mobile_agent/feedback/bundle.py +204 -0
  392. mobile_agent/feedback/outbox.py +196 -0
  393. mobile_agent/feedback/register.py +9 -0
  394. mobile_agent/feedback/routes.py +177 -0
  395. mobile_agent/fleet.py +841 -0
  396. mobile_agent/fleet_health.py +729 -0
  397. mobile_agent/fleet_phone.py +186 -0
  398. mobile_agent/frontier/__init__.py +79 -0
  399. mobile_agent/frontier/act.py +380 -0
  400. mobile_agent/frontier/actions.py +70 -0
  401. mobile_agent/frontier/agent.py +256 -0
  402. mobile_agent/frontier/approvals.py +366 -0
  403. mobile_agent/frontier/hooks.py +199 -0
  404. mobile_agent/frontier/interrupts.py +125 -0
  405. mobile_agent/frontier/observe.py +331 -0
  406. mobile_agent/frontier/phases.py +381 -0
  407. mobile_agent/frontier/pipeline.py +61 -0
  408. mobile_agent/frontier/prompt.py +384 -0
  409. mobile_agent/frontier/protocol.py +91 -0
  410. mobile_agent/frontier/safety.py +467 -0
  411. mobile_agent/frontier/saved.py +375 -0
  412. mobile_agent/frontier/screen.py +419 -0
  413. mobile_agent/frontier/skills.py +147 -0
  414. mobile_agent/frontier/stages/__init__.py +1 -0
  415. mobile_agent/frontier/stages/after.py +192 -0
  416. mobile_agent/frontier/stages/approve.py +138 -0
  417. mobile_agent/frontier/stages/controls.py +115 -0
  418. mobile_agent/frontier/stages/ends.py +102 -0
  419. mobile_agent/frontier/stages/gate.py +92 -0
  420. mobile_agent/frontier/stages/guards.py +132 -0
  421. mobile_agent/frontier/stages/points.py +155 -0
  422. mobile_agent/frontier/stages/send.py +177 -0
  423. mobile_agent/frontier/turn.py +151 -0
  424. mobile_agent/full_disk_access.py +83 -0
  425. mobile_agent/gemini.py +69 -0
  426. mobile_agent/genuine.py +120 -0
  427. mobile_agent/graph/__init__.py +4 -0
  428. mobile_agent/graph/catalog.py +789 -0
  429. mobile_agent/graph/chain.py +306 -0
  430. mobile_agent/graph/dispatch.py +98 -0
  431. mobile_agent/graph/draft.py +373 -0
  432. mobile_agent/graph/edit.py +76 -0
  433. mobile_agent/graph/estimate.py +189 -0
  434. mobile_agent/graph/expr.py +562 -0
  435. mobile_agent/graph/functions.py +271 -0
  436. mobile_agent/graph/infer.py +144 -0
  437. mobile_agent/graph/nodes.py +936 -0
  438. mobile_agent/graph/phone_steps.py +178 -0
  439. mobile_agent/graph/rerun.py +327 -0
  440. mobile_agent/graph/run.py +899 -0
  441. mobile_agent/graph/schema.py +842 -0
  442. mobile_agent/graph/state.py +161 -0
  443. mobile_agent/graph/values.py +198 -0
  444. mobile_agent/harness/__init__.py +2 -0
  445. mobile_agent/harness/checkpoints.py +468 -0
  446. mobile_agent/harness/control.py +101 -0
  447. mobile_agent/harness/prewarm.py +213 -0
  448. mobile_agent/harness/register.py +43 -0
  449. mobile_agent/harness/routes.py +114 -0
  450. mobile_agent/harness/settings.py +66 -0
  451. mobile_agent/harness/tools/__init__.py +67 -0
  452. mobile_agent/harness/tools/ask_user.py +102 -0
  453. mobile_agent/harness/tools/watch.py +120 -0
  454. mobile_agent/harness_api.py +815 -0
  455. mobile_agent/hedge.py +203 -0
  456. mobile_agent/help_topics.py +305 -0
  457. mobile_agent/hints.py +248 -0
  458. mobile_agent/history.py +69 -0
  459. mobile_agent/home.py +214 -0
  460. mobile_agent/http_pool.py +175 -0
  461. mobile_agent/image_policy.py +139 -0
  462. mobile_agent/inbox.py +148 -0
  463. mobile_agent/installed_apps.py +344 -0
  464. mobile_agent/integrations/__init__.py +1 -0
  465. mobile_agent/integrations/cli.py +472 -0
  466. mobile_agent/integrations/client_files.py +65 -0
  467. mobile_agent/integrations/clients.py +998 -0
  468. mobile_agent/integrations/doctor.py +185 -0
  469. mobile_agent/integrations/jsonc.py +383 -0
  470. mobile_agent/integrations/routes.py +135 -0
  471. mobile_agent/integrations/rules.py +112 -0
  472. mobile_agent/integrations/server_options.py +46 -0
  473. mobile_agent/integrations/skill.py +152 -0
  474. mobile_agent/integrations/skill_files.py +10 -0
  475. mobile_agent/integrations/textedit.py +309 -0
  476. mobile_agent/intents/__init__.py +17 -0
  477. mobile_agent/intents/atlas.py +216 -0
  478. mobile_agent/intents/callback.py +44 -0
  479. mobile_agent/intents/cli.py +55 -0
  480. mobile_agent/intents/harvest.py +91 -0
  481. mobile_agent/intents/plan.py +93 -0
  482. mobile_agent/ios/__init__.py +1 -0
  483. mobile_agent/ios/guide.py +16 -0
  484. mobile_agent/ios/kb.py +212 -0
  485. mobile_agent/ios/kb_data.py +1588 -0
  486. mobile_agent/ios/mcp.py +44 -0
  487. mobile_agent/ios/register.py +8 -0
  488. mobile_agent/ios/system.py +369 -0
  489. mobile_agent/journal.py +531 -0
  490. mobile_agent/keychain_acl.py +374 -0
  491. mobile_agent/keys.py +395 -0
  492. mobile_agent/latency_overlap.py +36 -0
  493. mobile_agent/latency_trace.py +170 -0
  494. mobile_agent/learned/__init__.py +15 -0
  495. mobile_agent/learned/app_path.py +160 -0
  496. mobile_agent/learned/code_read.py +38 -0
  497. mobile_agent/learned/parts.py +546 -0
  498. mobile_agent/learned/programs.py +691 -0
  499. mobile_agent/learned/register.py +171 -0
  500. mobile_agent/learned/routes.py +61 -0
  501. mobile_agent/learned/taught.py +329 -0
  502. mobile_agent/learned/teach.py +538 -0
  503. mobile_agent/learned/workflows.py +647 -0
  504. mobile_agent/learned/zero.py +41 -0
  505. mobile_agent/links.py +37 -0
  506. mobile_agent/listen.py +607 -0
  507. mobile_agent/lock_spike.py +278 -0
  508. mobile_agent/lockscreen.py +526 -0
  509. mobile_agent/login.py +354 -0
  510. mobile_agent/longform/__init__.py +15 -0
  511. mobile_agent/longform/ads.py +191 -0
  512. mobile_agent/longform/blocks.py +117 -0
  513. mobile_agent/longform/budget.py +265 -0
  514. mobile_agent/longform/context.py +285 -0
  515. mobile_agent/longform/costs.py +93 -0
  516. mobile_agent/longform/digest.py +145 -0
  517. mobile_agent/longform/extract.py +508 -0
  518. mobile_agent/longform/feed.py +505 -0
  519. mobile_agent/longform/followup.py +168 -0
  520. mobile_agent/longform/frames.py +82 -0
  521. mobile_agent/longform/identity.py +87 -0
  522. mobile_agent/longform/inbox.py +221 -0
  523. mobile_agent/longform/items.py +425 -0
  524. mobile_agent/longform/models.py +115 -0
  525. mobile_agent/longform/notebook.py +329 -0
  526. mobile_agent/longform/plan.py +614 -0
  527. mobile_agent/longform/recall.py +195 -0
  528. mobile_agent/longform/receipts.py +81 -0
  529. mobile_agent/longform/register.py +124 -0
  530. mobile_agent/longform/report.py +343 -0
  531. mobile_agent/longform/routes.py +102 -0
  532. mobile_agent/longform/session.py +644 -0
  533. mobile_agent/longform/store.py +146 -0
  534. mobile_agent/longform/synthesis.py +347 -0
  535. mobile_agent/mac_tools.py +223 -0
  536. mobile_agent/manual_control.py +143 -0
  537. mobile_agent/maps.py +149 -0
  538. mobile_agent/marks.py +92 -0
  539. mobile_agent/mcp_server/__init__.py +3 -0
  540. mobile_agent/mcp_server/__main__.py +29 -0
  541. mobile_agent/mcp_server/action_journal.py +24 -0
  542. mobile_agent/mcp_server/action_tools.py +638 -0
  543. mobile_agent/mcp_server/approvals.py +219 -0
  544. mobile_agent/mcp_server/catalog.py +92 -0
  545. mobile_agent/mcp_server/cli.py +194 -0
  546. mobile_agent/mcp_server/codes.py +147 -0
  547. mobile_agent/mcp_server/device_tools.py +306 -0
  548. mobile_agent/mcp_server/direct.py +272 -0
  549. mobile_agent/mcp_server/expo_tools.py +105 -0
  550. mobile_agent/mcp_server/phone_tools.py +510 -0
  551. mobile_agent/mcp_server/protocol.py +364 -0
  552. mobile_agent/mcp_server/registry.py +55 -0
  553. mobile_agent/mcp_server/run_tools.py +531 -0
  554. mobile_agent/mcp_server/schemas.py +425 -0
  555. mobile_agent/mcp_server/screen_tools.py +168 -0
  556. mobile_agent/mcp_server/session.py +469 -0
  557. mobile_agent/mcp_server/support.py +145 -0
  558. mobile_agent/mcp_server/tools.py +493 -0
  559. mobile_agent/memory/__init__.py +9 -0
  560. mobile_agent/memory/cli.py +207 -0
  561. mobile_agent/memory/extract.py +191 -0
  562. mobile_agent/memory/listener.py +100 -0
  563. mobile_agent/memory/model.py +144 -0
  564. mobile_agent/memory/project.py +96 -0
  565. mobile_agent/memory/provider.py +127 -0
  566. mobile_agent/memory/register.py +21 -0
  567. mobile_agent/memory/retrieval.py +172 -0
  568. mobile_agent/memory/routes.py +216 -0
  569. mobile_agent/memory/runs.py +124 -0
  570. mobile_agent/memory/store.py +806 -0
  571. mobile_agent/memory/writer.py +278 -0
  572. mobile_agent/mirroring.py +585 -0
  573. mobile_agent/mirroring_runtime.py +221 -0
  574. mobile_agent/model_clients.py +604 -0
  575. mobile_agent/narrate.py +813 -0
  576. mobile_agent/native_helpers.py +84 -0
  577. mobile_agent/netxray/__init__.py +1 -0
  578. mobile_agent/netxray/analyze.py +322 -0
  579. mobile_agent/netxray/attribution.py +62 -0
  580. mobile_agent/netxray/capture.py +88 -0
  581. mobile_agent/netxray/cli.py +142 -0
  582. mobile_agent/netxray/job.py +219 -0
  583. mobile_agent/netxray/mobster-pcap.c +560 -0
  584. mobile_agent/netxray/packets.py +410 -0
  585. mobile_agent/netxray/pcapng.py +223 -0
  586. mobile_agent/netxray/register.py +93 -0
  587. mobile_agent/netxray/report.py +214 -0
  588. mobile_agent/netxray/store.py +48 -0
  589. mobile_agent/netxray/summary.py +157 -0
  590. mobile_agent/netxray/target.py +67 -0
  591. mobile_agent/netxray/tool.py +66 -0
  592. mobile_agent/netxray/trackers.py +194 -0
  593. mobile_agent/notify_triggers/__init__.py +1 -0
  594. mobile_agent/notify_triggers/rules.py +39 -0
  595. mobile_agent/notify_triggers/sources.py +315 -0
  596. mobile_agent/notify_triggers/watch.py +135 -0
  597. mobile_agent/offers.py +86 -0
  598. mobile_agent/orchestrate.py +215 -0
  599. mobile_agent/outlets/__init__.py +1 -0
  600. mobile_agent/outlets/connect.py +249 -0
  601. mobile_agent/outlets/folder.py +180 -0
  602. mobile_agent/outlets/hooks.py +453 -0
  603. mobile_agent/outlets/inbound.py +472 -0
  604. mobile_agent/outlets/inbound_reply.py +57 -0
  605. mobile_agent/outlets/inbound_routes.py +81 -0
  606. mobile_agent/outlets/kinds.py +308 -0
  607. mobile_agent/outlets/mail.py +110 -0
  608. mobile_agent/outlets/net.py +86 -0
  609. mobile_agent/outlets/notion.py +244 -0
  610. mobile_agent/outlets/register.py +56 -0
  611. mobile_agent/outlets/routes.py +135 -0
  612. mobile_agent/outlets/service.py +580 -0
  613. mobile_agent/outlets/sheets.py +169 -0
  614. mobile_agent/outlets/slack.py +177 -0
  615. mobile_agent/outlets/store.py +194 -0
  616. mobile_agent/outlets/vault.py +80 -0
  617. mobile_agent/outlets/webhook.py +316 -0
  618. mobile_agent/output_contract.py +141 -0
  619. mobile_agent/owner_presence.py +211 -0
  620. mobile_agent/ownership.py +219 -0
  621. mobile_agent/packs/__init__.py +1 -0
  622. mobile_agent/packs/calendar.py +243 -0
  623. mobile_agent/packs/context.py +68 -0
  624. mobile_agent/packs/core.py +757 -0
  625. mobile_agent/packs/front_door.py +316 -0
  626. mobile_agent/packs/heal.py +214 -0
  627. mobile_agent/packs/health.py +122 -0
  628. mobile_agent/packs/insights.py +76 -0
  629. mobile_agent/packs/instagram.py +640 -0
  630. mobile_agent/packs/linkedin.py +106 -0
  631. mobile_agent/packs/maps.py +196 -0
  632. mobile_agent/packs/mcp.py +149 -0
  633. mobile_agent/packs/messages.py +270 -0
  634. mobile_agent/packs/notes.py +226 -0
  635. mobile_agent/packs/photos.py +166 -0
  636. mobile_agent/packs/pins.py +47 -0
  637. mobile_agent/packs/reddit.py +110 -0
  638. mobile_agent/packs/register.py +276 -0
  639. mobile_agent/packs/rows.py +108 -0
  640. mobile_agent/packs/safari.py +143 -0
  641. mobile_agent/packs/settings.py +253 -0
  642. mobile_agent/packs/social.py +300 -0
  643. mobile_agent/packs/threads.py +97 -0
  644. mobile_agent/packs/tiktok.py +455 -0
  645. mobile_agent/packs/tools.py +290 -0
  646. mobile_agent/packs/x.py +127 -0
  647. mobile_agent/packs/youtube.py +199 -0
  648. mobile_agent/parity.py +270 -0
  649. mobile_agent/paths.py +99 -0
  650. mobile_agent/perception.py +470 -0
  651. mobile_agent/permissions/__init__.py +32 -0
  652. mobile_agent/permissions/consequence.py +535 -0
  653. mobile_agent/permissions/exceptions.py +99 -0
  654. mobile_agent/permissions/gate.py +316 -0
  655. mobile_agent/permissions/outside.py +316 -0
  656. mobile_agent/permissions/policy.py +288 -0
  657. mobile_agent/permissions/project_policy.py +411 -0
  658. mobile_agent/permissions/register.py +148 -0
  659. mobile_agent/permissions/rules.py +291 -0
  660. mobile_agent/phone_io/__init__.py +146 -0
  661. mobile_agent/phone_io/cli.py +427 -0
  662. mobile_agent/phone_io/clipboard.py +78 -0
  663. mobile_agent/phone_io/controls.py +78 -0
  664. mobile_agent/phone_io/files.py +289 -0
  665. mobile_agent/phone_io/handoff.py +123 -0
  666. mobile_agent/phone_io/icloud.py +118 -0
  667. mobile_agent/phone_io/install.py +188 -0
  668. mobile_agent/phone_io/network_capture.py +188 -0
  669. mobile_agent/phone_io/network_cli.py +138 -0
  670. mobile_agent/phone_io/network_profiles.py +138 -0
  671. mobile_agent/phone_io/photos.py +512 -0
  672. mobile_agent/phone_io/recording.py +115 -0
  673. mobile_agent/phone_io/secrets.py +146 -0
  674. mobile_agent/phone_io/transfer.py +895 -0
  675. mobile_agent/phone_io/tunnel.py +129 -0
  676. mobile_agent/phone_ready.py +91 -0
  677. mobile_agent/pinning.py +158 -0
  678. mobile_agent/plan_gate.py +807 -0
  679. mobile_agent/plugins/__init__.py +3 -0
  680. mobile_agent/plugins/install.py +163 -0
  681. mobile_agent/plugins/manifest.py +242 -0
  682. mobile_agent/plugins/register.py +15 -0
  683. mobile_agent/plugins/routes.py +82 -0
  684. mobile_agent/plugins/service.py +322 -0
  685. mobile_agent/plugins/skills.py +76 -0
  686. mobile_agent/plugins/store.py +79 -0
  687. mobile_agent/pool.py +285 -0
  688. mobile_agent/power_watch.py +275 -0
  689. mobile_agent/premium_model.py +22 -0
  690. mobile_agent/project_env.py +291 -0
  691. mobile_agent/project_env_cli.py +74 -0
  692. mobile_agent/project_env_routes.py +55 -0
  693. mobile_agent/projects/__init__.py +27 -0
  694. mobile_agent/projects/ci.py +332 -0
  695. mobile_agent/projects/clone.py +339 -0
  696. mobile_agent/projects/drafts.py +158 -0
  697. mobile_agent/projects/folder.py +436 -0
  698. mobile_agent/projects/github.py +131 -0
  699. mobile_agent/projects/register.py +31 -0
  700. mobile_agent/projects/revisions.py +253 -0
  701. mobile_agent/projects/routes.py +234 -0
  702. mobile_agent/projects/runs.py +297 -0
  703. mobile_agent/projects/service.py +695 -0
  704. mobile_agent/projects/setup.py +84 -0
  705. mobile_agent/projects/store.py +379 -0
  706. mobile_agent/projects/sync.py +380 -0
  707. mobile_agent/projects/workflow_files.py +270 -0
  708. mobile_agent/replay_core/__init__.py +26 -0
  709. mobile_agent/replay_core/checks.py +594 -0
  710. mobile_agent/replay_core/engine.py +596 -0
  711. mobile_agent/replay_core/heal.py +364 -0
  712. mobile_agent/replay_core/path.py +240 -0
  713. mobile_agent/replay_core/recorder.py +206 -0
  714. mobile_agent/replay_core/store.py +171 -0
  715. mobile_agent/result_blocks.py +941 -0
  716. mobile_agent/run.py +642 -0
  717. mobile_agent/run_delivery.py +123 -0
  718. mobile_agent/run_executor.py +200 -0
  719. mobile_agent/run_export.py +284 -0
  720. mobile_agent/run_gif.py +891 -0
  721. mobile_agent/runner_hints.py +86 -0
  722. mobile_agent/runner_renewal.py +205 -0
  723. mobile_agent/runtime_create.py +549 -0
  724. mobile_agent/runtime_driver.py +458 -0
  725. mobile_agent/runtime_limits.py +319 -0
  726. mobile_agent/runtime_queue.py +394 -0
  727. mobile_agent/runtime_runs.py +383 -0
  728. mobile_agent/runtime_status.py +181 -0
  729. mobile_agent/runtime_teach.py +295 -0
  730. mobile_agent/runtime_work.py +599 -0
  731. mobile_agent/screen_text.py +101 -0
  732. mobile_agent/secret_filter.py +113 -0
  733. mobile_agent/serve.py +160 -0
  734. mobile_agent/server.py +363 -0
  735. mobile_agent/service_support.py +42 -0
  736. mobile_agent/settle/__init__.py +1 -0
  737. mobile_agent/settle/predict.py +102 -0
  738. mobile_agent/settle/replay.py +92 -0
  739. mobile_agent/settle/store.py +93 -0
  740. mobile_agent/settle/stream.py +43 -0
  741. mobile_agent/setup_cli.py +225 -0
  742. mobile_agent/setup_errors.py +87 -0
  743. mobile_agent/setup_service.py +527 -0
  744. mobile_agent/share.py +104 -0
  745. mobile_agent/signing.py +184 -0
  746. mobile_agent/sim/__init__.py +6 -0
  747. mobile_agent/sim/__main__.py +26 -0
  748. mobile_agent/sim/api.py +952 -0
  749. mobile_agent/sim/apps.py +172 -0
  750. mobile_agent/sim/biometrics.py +84 -0
  751. mobile_agent/sim/cli.py +271 -0
  752. mobile_agent/sim/doctor.py +293 -0
  753. mobile_agent/sim/governor.py +90 -0
  754. mobile_agent/sim/holders.py +88 -0
  755. mobile_agent/sim/models.py +62 -0
  756. mobile_agent/sim/registry.py +161 -0
  757. mobile_agent/sim/simctl.py +269 -0
  758. mobile_agent/sim/wda.py +618 -0
  759. mobile_agent/sim/xcode.py +74 -0
  760. mobile_agent/skills/__init__.py +12 -0
  761. mobile_agent/skills/codes.py +597 -0
  762. mobile_agent/skills/notifications.py +281 -0
  763. mobile_agent/skills/open_url.py +127 -0
  764. mobile_agent/smart_events.py +588 -0
  765. mobile_agent/smart_result.py +490 -0
  766. mobile_agent/smart_run.py +413 -0
  767. mobile_agent/state.py +662 -0
  768. mobile_agent/states.py +217 -0
  769. mobile_agent/steering.py +70 -0
  770. mobile_agent/style.py +178 -0
  771. mobile_agent/supervised.py +234 -0
  772. mobile_agent/swift/helper_bt_hid.swift +534 -0
  773. mobile_agent/swift/helper_doc_text.swift +184 -0
  774. mobile_agent/swift/helper_draft_reply.swift +170 -0
  775. mobile_agent/swift/helper_map.swift +110 -0
  776. mobile_agent/swift/helper_mirroring.swift +1099 -0
  777. mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
  778. mobile_agent/swift/helper_pdf.swift +100 -0
  779. mobile_agent/swift/helper_phone_audio.swift +993 -0
  780. mobile_agent/swift/helper_phone_screen.swift +416 -0
  781. mobile_agent/swift/helper_screen_text.swift +24 -0
  782. mobile_agent/swift/helper_transfer.swift +246 -0
  783. mobile_agent/swift/ocr.swift +22 -0
  784. mobile_agent/task_policy.py +337 -0
  785. mobile_agent/team_signing/__init__.py +34 -0
  786. mobile_agent/team_signing/apple_ca.py +37 -0
  787. mobile_agent/team_signing/bundle.py +136 -0
  788. mobile_agent/team_signing/cli.py +208 -0
  789. mobile_agent/team_signing/identity.py +248 -0
  790. mobile_agent/team_signing/ledger.py +105 -0
  791. mobile_agent/team_signing/prebuilt.py +61 -0
  792. mobile_agent/team_signing/resign.py +256 -0
  793. mobile_agent/team_signing/service.py +382 -0
  794. mobile_agent/team_signing/tooling.py +243 -0
  795. mobile_agent/team_signing/writes.py +268 -0
  796. mobile_agent/terminal_image.py +81 -0
  797. mobile_agent/testkit/__init__.py +1 -0
  798. mobile_agent/testkit/a11y_baseline.py +132 -0
  799. mobile_agent/testkit/api.py +403 -0
  800. mobile_agent/testkit/capsule.py +400 -0
  801. mobile_agent/testkit/capsule_cli.py +57 -0
  802. mobile_agent/testkit/cli.py +948 -0
  803. mobile_agent/testkit/cli_arguments.py +193 -0
  804. mobile_agent/testkit/comparison.py +288 -0
  805. mobile_agent/testkit/comparison_cli.py +41 -0
  806. mobile_agent/testkit/discover.py +257 -0
  807. mobile_agent/testkit/editor.py +243 -0
  808. mobile_agent/testkit/execute.py +515 -0
  809. mobile_agent/testkit/github_checks.py +248 -0
  810. mobile_agent/testkit/gitref.py +66 -0
  811. mobile_agent/testkit/heal_review.py +176 -0
  812. mobile_agent/testkit/health.py +480 -0
  813. mobile_agent/testkit/history.py +255 -0
  814. mobile_agent/testkit/hosted_results.py +321 -0
  815. mobile_agent/testkit/input_files.py +173 -0
  816. mobile_agent/testkit/jobs.py +285 -0
  817. mobile_agent/testkit/junit_verdict.py +33 -0
  818. mobile_agent/testkit/maestro.py +802 -0
  819. mobile_agent/testkit/mcp.py +562 -0
  820. mobile_agent/testkit/mcp_comparison.py +88 -0
  821. mobile_agent/testkit/minimize.py +471 -0
  822. mobile_agent/testkit/minimizer_cli.py +48 -0
  823. mobile_agent/testkit/notify.py +212 -0
  824. mobile_agent/testkit/phone_video.py +191 -0
  825. mobile_agent/testkit/pr.py +491 -0
  826. mobile_agent/testkit/pr_report.py +562 -0
  827. mobile_agent/testkit/prbuild.py +148 -0
  828. mobile_agent/testkit/proof_report.py +55 -0
  829. mobile_agent/testkit/provenance.py +119 -0
  830. mobile_agent/testkit/prrun.py +244 -0
  831. mobile_agent/testkit/recent.py +166 -0
  832. mobile_agent/testkit/recipes.py +146 -0
  833. mobile_agent/testkit/record.py +204 -0
  834. mobile_agent/testkit/register.py +288 -0
  835. mobile_agent/testkit/reports.py +998 -0
  836. mobile_agent/testkit/repro.py +432 -0
  837. mobile_agent/testkit/scenario.py +440 -0
  838. mobile_agent/testkit/scenario_cli.py +37 -0
  839. mobile_agent/testkit/snapshots_cli.py +60 -0
  840. mobile_agent/testkit/suite.py +670 -0
  841. mobile_agent/testkit/targets.py +497 -0
  842. mobile_agent/testkit/xctest.py +378 -0
  843. mobile_agent/threads/__init__.py +1 -0
  844. mobile_agent/threads/chat_once.py +264 -0
  845. mobile_agent/threads/cli.py +740 -0
  846. mobile_agent/threads/client.py +326 -0
  847. mobile_agent/threads/context.py +246 -0
  848. mobile_agent/threads/mcp.py +739 -0
  849. mobile_agent/threads/recent.py +34 -0
  850. mobile_agent/threads/register.py +54 -0
  851. mobile_agent/threads/routes.py +188 -0
  852. mobile_agent/threads/service.py +975 -0
  853. mobile_agent/threads/store.py +361 -0
  854. mobile_agent/threads/summary.py +117 -0
  855. mobile_agent/threads/term.py +101 -0
  856. mobile_agent/threads/workflow_cli.py +104 -0
  857. mobile_agent/tls.py +36 -0
  858. mobile_agent/traces.py +271 -0
  859. mobile_agent/tracks.py +235 -0
  860. mobile_agent/transport.py +271 -0
  861. mobile_agent/triggers/__init__.py +1 -0
  862. mobile_agent/triggers/actions.py +204 -0
  863. mobile_agent/triggers/model.py +146 -0
  864. mobile_agent/triggers/register.py +29 -0
  865. mobile_agent/triggers/routes.py +101 -0
  866. mobile_agent/triggers/service.py +387 -0
  867. mobile_agent/triggers/store.py +142 -0
  868. mobile_agent/tui/__init__.py +89 -0
  869. mobile_agent/tui/app.py +582 -0
  870. mobile_agent/tui/chrome.py +321 -0
  871. mobile_agent/tui/commands.py +375 -0
  872. mobile_agent/tui/demo_phone.py +369 -0
  873. mobile_agent/tui/images.py +29 -0
  874. mobile_agent/tui/keys.py +64 -0
  875. mobile_agent/tui/prompt.py +195 -0
  876. mobile_agent/tui/render.py +395 -0
  877. mobile_agent/tui/screens.py +180 -0
  878. mobile_agent/tui/session.py +308 -0
  879. mobile_agent/tui/tasks.py +287 -0
  880. mobile_agent/tui/theme.py +118 -0
  881. mobile_agent/tui/widgets.py +272 -0
  882. mobile_agent/update_check.py +213 -0
  883. mobile_agent/usage.py +184 -0
  884. mobile_agent/verify/__init__.py +0 -0
  885. mobile_agent/verify/__main__.py +41 -0
  886. mobile_agent/verify/a11y.py +189 -0
  887. mobile_agent/verify/actions.py +290 -0
  888. mobile_agent/verify/assertions.py +570 -0
  889. mobile_agent/verify/audit.py +312 -0
  890. mobile_agent/verify/business.py +221 -0
  891. mobile_agent/verify/business_run.py +131 -0
  892. mobile_agent/verify/business_state.py +168 -0
  893. mobile_agent/verify/check_schema.py +398 -0
  894. mobile_agent/verify/checks.py +614 -0
  895. mobile_agent/verify/cli.py +423 -0
  896. mobile_agent/verify/conditions.py +483 -0
  897. mobile_agent/verify/crashes.py +321 -0
  898. mobile_agent/verify/device_audit.py +246 -0
  899. mobile_agent/verify/dialogs.py +287 -0
  900. mobile_agent/verify/evidence.py +705 -0
  901. mobile_agent/verify/fixtures.py +305 -0
  902. mobile_agent/verify/flow.py +213 -0
  903. mobile_agent/verify/l10n.py +141 -0
  904. mobile_agent/verify/netcerts.py +67 -0
  905. mobile_agent/verify/netproxy.py +356 -0
  906. mobile_agent/verify/netrun.py +81 -0
  907. mobile_agent/verify/netshim.m +28 -0
  908. mobile_agent/verify/netshim.py +48 -0
  909. mobile_agent/verify/netstubs.py +132 -0
  910. mobile_agent/verify/overlay.py +174 -0
  911. mobile_agent/verify/perf.py +239 -0
  912. mobile_agent/verify/proof.py +192 -0
  913. mobile_agent/verify/replay_admission.py +48 -0
  914. mobile_agent/verify/report.py +952 -0
  915. mobile_agent/verify/run_cleanup.py +67 -0
  916. mobile_agent/verify/run_links.py +48 -0
  917. mobile_agent/verify/run_perf.py +44 -0
  918. mobile_agent/verify/run_secrets.py +39 -0
  919. mobile_agent/verify/runner.py +976 -0
  920. mobile_agent/verify/runs.py +57 -0
  921. mobile_agent/verify/screen_coverage.py +181 -0
  922. mobile_agent/verify/smart.py +106 -0
  923. mobile_agent/verify/snapshot_run.py +114 -0
  924. mobile_agent/verify/snapshots.py +306 -0
  925. mobile_agent/verify/sound.py +335 -0
  926. mobile_agent/verify/source_observation.py +130 -0
  927. mobile_agent/verify/system_surfaces/__init__.py +7 -0
  928. mobile_agent/verify/system_surfaces/play.py +106 -0
  929. mobile_agent/verify/system_surfaces/spec.py +139 -0
  930. mobile_agent/verify/system_surfaces/stage.py +138 -0
  931. mobile_agent/verify/system_surfaces/surfaces.py +121 -0
  932. mobile_agent/verify/tree.py +223 -0
  933. mobile_agent/verify/verdict.py +377 -0
  934. mobile_agent/verify/wire.py +392 -0
  935. mobile_agent/verify/wirestore.py +176 -0
  936. mobile_agent/waiting/__init__.py +1 -0
  937. mobile_agent/waiting/contacts.py +73 -0
  938. mobile_agent/waiting/drafts.py +136 -0
  939. mobile_agent/waiting/job.py +145 -0
  940. mobile_agent/waiting/messages.py +174 -0
  941. mobile_agent/waiting/rank.py +115 -0
  942. mobile_agent/waiting/register.py +26 -0
  943. mobile_agent/watch.py +502 -0
  944. mobile_agent/wda/__init__.py +2 -0
  945. mobile_agent/wda/frame_clock.py +646 -0
  946. mobile_agent/wda/frame_source.py +325 -0
  947. mobile_agent/wda/frame_store.py +115 -0
  948. mobile_agent/wda/gate.py +163 -0
  949. mobile_agent/wda/gestures.py +291 -0
  950. mobile_agent/wda/guards.py +94 -0
  951. mobile_agent/wda/identity.py +55 -0
  952. mobile_agent/wda/observe.py +884 -0
  953. mobile_agent/wda/patches.py +856 -0
  954. mobile_agent/wda/point_gestures.py +150 -0
  955. mobile_agent/wda/source.py +32 -0
  956. mobile_agent/wda/system_dialogs.py +571 -0
  957. mobile_agent/wda/text.py +694 -0
  958. mobile_agent/wda/timeouts.py +77 -0
  959. mobile_agent/wda/video.py +505 -0
  960. mobile_agent/wda/watchdog.py +243 -0
  961. mobile_agent/wireless/__init__.py +15 -0
  962. mobile_agent/wireless/api.py +354 -0
  963. mobile_agent/wireless/cli.py +287 -0
  964. mobile_agent/wireless/devicectl.py +229 -0
  965. mobile_agent/wireless/exposure.py +33 -0
  966. mobile_agent/wireless/mobster-wifi-pair.c +122 -0
  967. mobile_agent/wireless/monitor.py +332 -0
  968. mobile_agent/wireless/network.py +334 -0
  969. mobile_agent/wireless/onboarding.py +293 -0
  970. mobile_agent/wireless/pair.py +124 -0
  971. mobile_agent/wireless/probe.py +322 -0
  972. mobile_agent/wireless/register.py +8 -0
  973. mobile_agent/wireless/relay.py +179 -0
  974. mobile_agent/wireless/result.py +87 -0
  975. mobile_agent/wireless/store.py +85 -0
  976. mobile_agent/wireless/switch.py +77 -0
  977. mobile_agent/wireless/transport.py +118 -0
  978. mobile_agent/wireless/words.py +81 -0
  979. mobile_agent/wireless/xctestrun.py +83 -0
  980. mobile_agent/workflow_tools.py +59 -0
  981. mobile_agent/workflows/__init__.py +2 -0
  982. mobile_agent/workflows/brief.py +201 -0
  983. mobile_agent/workflows/cron.py +110 -0
  984. mobile_agent/workflows/health.py +257 -0
  985. mobile_agent/workflows/history.py +126 -0
  986. mobile_agent/workflows/records.py +442 -0
  987. mobile_agent/workflows/runs.py +1000 -0
  988. mobile_agent/workflows/scheduler.py +134 -0
  989. mobile_agent/workflows/service.py +973 -0
  990. mobile_agent/workflows/starters.py +285 -0
  991. mobile_agent/workflows/triggers.py +248 -0
  992. mobile_agent/xray/__init__.py +1 -0
  993. mobile_agent/xray/app_cli.py +116 -0
  994. mobile_agent/xray/cli.py +21 -0
  995. mobile_agent/xray/crawl.py +754 -0
  996. mobile_agent/xray/diff.py +171 -0
  997. mobile_agent/xray/diff_cli.py +43 -0
  998. mobile_agent/xray/job.py +184 -0
  999. mobile_agent/xray/paywall.py +188 -0
  1000. mobile_agent/xray/policy.py +305 -0
  1001. mobile_agent/xray/register.py +43 -0
  1002. mobile_agent/xray/report.py +283 -0
  1003. mobile_agent/xray/screens.py +117 -0
  1004. mobster_cli-0.4.3.dist-info/METADATA +93 -0
  1005. mobster_cli-0.4.3.dist-info/RECORD +1009 -0
  1006. mobster_cli-0.4.3.dist-info/WHEEL +5 -0
  1007. mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
  1008. mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
  1009. mobster_cli-0.4.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,604 @@
1
+ """Model clients: the price table and the provider transports Mobster calls models through."""
2
+
3
+ import http.client
4
+ import json
5
+ import math
6
+ import os
7
+ import re
8
+ import threading
9
+ import time
10
+
11
+ from . import harness_api
12
+ from .transport import Deadline, TransportError
13
+
14
+ # List prices, USD per 1M tokens (input, output, cached input, cache write; None = input rate), read 2026-09-25 to
15
+ # 2026-10-09; estimates, not invoices.
16
+ PRICES = {"gpt-5.5": (5.0, 30.0, 0.5, None), "gpt-5.6-sol": (4.0, 20.0, 0.4, 5.0),
17
+ "gpt-5.6-terra": (2.0, 12.0, 0.2, 2.5), "gpt-5.6-luna": (0.2, 1.2, 0.02, 0.25),
18
+ "gpt-5-mini": (0.25, 2.0, 0.025, None)}
19
+ # GPT-6: the same page, read 2026-09-28, with GPT-5.6's shape.
20
+ PRICES.update({"gpt-6-astra": (10.0, 50.0, 1.0, 12.5), "gpt-6-sol": (2.0, 10.0, 0.2, 2.5),
21
+ "gpt-6-luna": (0.1, 0.5, 0.01, 0.125)})
22
+ PRICES["gpt-6.1-sol"] = (2.0, 10.0, 0.1, 2.5)
23
+ # Gemini on Vertex: Vertex AI's standard global rates, read 2026-09-23 (3.6-3.8 Flash are half price until
24
+ # 2026-12-31, listed at standard).
25
+ PRICES.update({model: (rate[0], rate[1], None, None) for model, rate in {
26
+ "gemini-3.8-flash": (1.50, 7.50), "gemini-3.7-flash": (1.50, 7.50), "gemini-3.6-flash": (1.50, 7.50),
27
+ "gemini-3.5-flash": (1.50, 9.00), "gemini-3.1-pro-preview": (2.00, 12.00)}.items()})
28
+ # Claude: platform.claude.com/docs/en/about-claude/pricing, read 2026-09-28; 5-minute cache writes are 1.25x, hits 0.1x
29
+ # (Opus 5.5 0.05x, Fable 5.1 0.025x).
30
+ PRICES.update({
31
+ "claude-sonnet-5-5": (2.0, 10.0, 0.1, 2.5), "claude-sonnet-5": (2.0, 10.0, 0.2, 2.5),
32
+ "claude-opus-5-5": (4.0, 20.0, 0.2, 5.0), "claude-opus-5": (5.0, 25.0, 0.5, 6.25),
33
+ "claude-fable-5-1": (10.0, 50.0, 0.25, 12.5), "claude-haiku-4-5": (1.0, 5.0, 0.1, 1.25)})
34
+ # Claude Haiku 5.5 (7 Oct 2026), prompts up to 100k tokens, read 2026-10-09; Sonnet 5.5's hits are 0.05x input there.
35
+ PRICES["claude-haiku-5-5"] = (0.10, 0.50, 0.01, 0.125)
36
+
37
+
38
+ def cost_usd(model, usage):
39
+ """Estimated list-price cost of ``usage`` (OpenAIChat.usage), or None for an unpriced model."""
40
+ if str(model or "").startswith("chatgpt:"):
41
+ return 0.0 if usage else None
42
+ providers = harness_api.hooks().model_routes()
43
+ routed, _ = providers.split(model)
44
+ if routed:
45
+ # A plan costs nothing per call; a provider's key is billed at its own rates, which Mobster doesn't list.
46
+ return (0.0 if usage else None) if providers.get(routed).group == "plan" else None
47
+ price = PRICES.get(model)
48
+ if price is None or not usage:
49
+ return None
50
+ cached, written = usage.get("cached_tokens", 0), usage.get("cache_write_tokens", 0)
51
+ fresh = usage.get("prompt_tokens", 0) - cached - written
52
+ return round((fresh * price[0] + cached * (price[0] if price[2] is None else price[2])
53
+ + written * (price[0] if price[3] is None else price[3])
54
+ + usage.get("completion_tokens", 0) * price[1]) / 1e6, 5)
55
+
56
+
57
+ def ceiling_cost_usd(usage):
58
+ """``usage`` at the highest listed rates, so a spend cap stops a run with no listed price early."""
59
+ prompt = max(max(p[0], p[3] or 0.0) for p in PRICES.values())
60
+ output = max(p[1] for p in PRICES.values())
61
+ usage = usage or {}
62
+ return round((usage.get("prompt_tokens", 0) * prompt + usage.get("completion_tokens", 0) * output) / 1e6, 5)
63
+
64
+
65
+ RATE_LIMIT_RETRIES = 5
66
+ # A model call is a pure read, so one that got a server error is sent again twice, 1 s then 2 s later.
67
+ SERVER_ERROR_RETRIES = 2
68
+ SERVER_ERRORS = frozenset({500, 502, 503, 504})
69
+
70
+
71
+ # The wording matters: as "Your next actions on the phone." chains shortened from 1.9 to 1.4-1.5 actions a call (36
72
+ # replayed turns, 25 Sep).
73
+ TOOL_DESCRIPTION = ("Your answer: the next 1 to 6 actions. Chain every action whose screen you can predict (fill "
74
+ "several fields then Save, search then open the result...); stop only where you must see the "
75
+ "result first.")
76
+
77
+
78
+ class CallCancelled(RuntimeError):
79
+ """The task was stopped while a model call waited to be sent again: it ends ``stopped``, never retried."""
80
+
81
+
82
+ def pause(seconds, cancelled=None):
83
+ """Wait ``seconds`` (a retry's backoff)."""
84
+ seconds = max(0.0, float(seconds))
85
+ if not callable(cancelled):
86
+ time.sleep(seconds)
87
+ return
88
+ slices = max(1, math.ceil(seconds / .2))
89
+ for _ in range(slices):
90
+ if cancelled():
91
+ raise CallCancelled("Stopped while a model call waited to retry")
92
+ time.sleep(seconds / slices)
93
+ if cancelled():
94
+ raise CallCancelled("Stopped while a model call waited to retry")
95
+
96
+
97
+ def error_text(label, status, raw, limit=300):
98
+ """"<label> HTTP <status>: <detail>" for a failed call."""
99
+ body = raw.decode(errors="replace") if isinstance(raw, (bytes, bytearray)) else str(raw or "")
100
+ try:
101
+ data = json.loads(body)
102
+ except ValueError:
103
+ data = None
104
+ error = data.get("error") if isinstance(data, dict) else None
105
+ if isinstance(error, dict):
106
+ names = [error[key] for key in ("code", "type") if isinstance(error.get(key), str) and error[key]]
107
+ message = error.get("message") if isinstance(error.get("message"), str) else ""
108
+ detail = ": ".join([*dict.fromkeys(names), " ".join(message.split())] if message else dict.fromkeys(names))
109
+ if detail:
110
+ return f"{label} HTTP {status}: {detail[:limit]}"
111
+ return f"{label} HTTP {status}: {body[:limit]}"
112
+
113
+
114
+ class _KeptAliveJSON:
115
+ """One kept-alive HTTPS connection for JSON POSTs, shared by the OpenAI and Vertex clients."""
116
+
117
+ cancelled = None
118
+
119
+ # The task contract is compiled while the first decision is in flight,
120
+ # so one call's reasoning is set without touching ``reasoning``.
121
+ per_call_reasoning = True
122
+
123
+ def __init__(self, host):
124
+ self.host = host
125
+ self.connection = None
126
+ self.usage = {"prompt_tokens": 0, "completion_tokens": 0, "cached_tokens": 0, "cache_write_tokens": 0,
127
+ "calls": 0}
128
+ self._claim = threading.Lock() # held while the kept-alive connection carries a request
129
+ self._count_lock = threading.Lock()
130
+
131
+ def _post(self, path, payload, headers, timeout, *, label, final_429=lambda raw: False):
132
+ """``json.loads`` of the reply; ``headers()`` is called per attempt (a Vertex token may refresh)."""
133
+ deadline = Deadline(timeout)
134
+ own = self._claim.acquire(blocking=False)
135
+ connection = self.connection if own else None
136
+ server_errors = 0
137
+ try:
138
+ for attempt in range(RATE_LIMIT_RETRIES + 1):
139
+ left = deadline.remaining()
140
+ if connection is None:
141
+ connection = http.client.HTTPSConnection(self.host, timeout=left)
142
+ if own:
143
+ self.connection = connection
144
+ try:
145
+ request_headers = headers()
146
+ left = deadline.remaining()
147
+ connection.timeout = left
148
+ if getattr(connection, "sock", None) is not None:
149
+ connection.sock.settimeout(left)
150
+ connection.request("POST", path, payload, request_headers)
151
+ response = connection.getresponse()
152
+ raw = response.read()
153
+ except (OSError, http.client.HTTPException):
154
+ connection.close()
155
+ connection = None
156
+ if own:
157
+ self.connection = None
158
+ if attempt >= 1:
159
+ raise
160
+ continue
161
+ if response.status == 429 and not final_429(raw) and attempt < RATE_LIMIT_RETRIES:
162
+ pause(min(2 ** attempt, 16, deadline.remaining()), getattr(self, "cancelled", None))
163
+ continue
164
+ if (response.status in SERVER_ERRORS and server_errors < SERVER_ERROR_RETRIES
165
+ and attempt < RATE_LIMIT_RETRIES):
166
+ server_errors += 1
167
+ pause(min(server_errors, deadline.remaining()), getattr(self, "cancelled", None))
168
+ continue
169
+ if response.status >= 300:
170
+ raise RuntimeError(error_text(label, response.status, raw))
171
+ deadline.remaining()
172
+ return json.loads(raw)
173
+ raise RuntimeError("unreachable")
174
+ finally:
175
+ if own:
176
+ self._claim.release()
177
+ elif connection is not None:
178
+ connection.close()
179
+
180
+ def _count(self, usage):
181
+ """Add one call's normalised usage (prompt/completion/cached/cache-write tokens) to the running total."""
182
+ with self._count_lock:
183
+ for key in ("prompt_tokens", "completion_tokens", "cached_tokens", "cache_write_tokens"):
184
+ self.usage[key] += usage.get(key, 0)
185
+ self.usage["calls"] += 1
186
+
187
+
188
+ def explicit_caching(model):
189
+ """Models that take cache breakpoints (GPT-5.6 on): older ones answer prompt_cache_options with a 400."""
190
+ return model.startswith(("gpt-5.6", "gpt-6"))
191
+
192
+
193
+ class OpenAIChat(_KeptAliveJSON):
194
+ """The Responses API over one kept-alive HTTPS connection; error bodies surface (truncated)."""
195
+
196
+ provider = "openai"
197
+ per_call_strict = True
198
+
199
+ def __init__(self, model, *, key=None, reasoning="low", host="api.openai.com"):
200
+ self.model, self.reasoning = model, reasoning
201
+ self.key = key or os.environ.get("OPENAI_API_KEY", "")
202
+ if not self.key:
203
+ raise ValueError("OPENAI_API_KEY is not set")
204
+ super().__init__(host)
205
+
206
+ def body(self, messages, schema, reasoning=None, *, strict=True):
207
+ """The /v1/responses request for Chat-style ``messages``; ``reasoning`` overrides the client's for it."""
208
+ explicit = explicit_caching(self.model)
209
+ items = []
210
+ for message in messages:
211
+ content = message["content"]
212
+ if isinstance(content, str):
213
+ content = [{"type": "text", "text": content}]
214
+ parts = []
215
+ for part in content:
216
+ if part.get("type") == "image_url":
217
+ parts.append({"type": "input_image", "image_url": part["image_url"]["url"],
218
+ "detail": part["image_url"].get("detail", "auto")})
219
+ continue
220
+ parts.append({"type": "input_text", "text": part["text"]})
221
+ if part.get("cache") and explicit:
222
+ parts[-1]["prompt_cache_breakpoint"] = {"mode": "explicit"}
223
+ items.append({"role": "developer" if message["role"] == "system" else message["role"], "content": parts})
224
+ # A forced function call: as a json_schema text format the answer came back repeated, and billed,
225
+ # in 15 of 36 replies.
226
+ body = {"model": self.model, "input": items, "store": False, "parallel_tool_calls": False,
227
+ "tools": [{"type": "function", "name": "step", "parameters": schema, "strict": strict,
228
+ "description": TOOL_DESCRIPTION if "actions" in schema.get("properties", {})
229
+ else "Your answer."}],
230
+ "tool_choice": {"type": "function", "name": "step"}}
231
+ if explicit:
232
+ body["prompt_cache_options"] = {"mode": "explicit"}
233
+ effort = reasoning or self.reasoning
234
+ if effort:
235
+ body["reasoning"] = {"effort": effort}
236
+ return body
237
+
238
+ def complete(self, messages, schema, *, timeout=60, reasoning=None, strict=True):
239
+ headers = {"Content-Type": "application/json", "Authorization": "Bearer " + self.key}
240
+ body = self.body(messages, schema, reasoning=reasoning, strict=strict)
241
+ # insufficient_quota is an empty account, not a busy one: retrying cannot clear it.
242
+ data = self._post("/v1/responses", json.dumps(body).encode(), lambda: headers,
243
+ timeout, label="OpenAI", final_429=lambda raw: b"insufficient_quota" in raw
244
+ or b"credit_balance_exhausted" in raw)
245
+ raw, details = data.get("usage") or {}, (data.get("usage") or {}).get("input_tokens_details") or {}
246
+ usage = {"prompt_tokens": raw.get("input_tokens", 0), "completion_tokens": raw.get("output_tokens", 0),
247
+ "cached_tokens": details.get("cached_tokens", 0),
248
+ "cache_write_tokens": details.get("cache_write_tokens", 0)}
249
+ self._count(usage)
250
+ return self.answer(data), usage
251
+
252
+ @staticmethod
253
+ def answer(data):
254
+ """The arguments of a /v1/responses reply's function call."""
255
+ call = next((item for item in data.get("output") or () if item.get("type") == "function_call"), None)
256
+ if call is None:
257
+ raise RuntimeError(f"OpenAI: no function call in the reply ({data.get('status')})")
258
+ return json.loads(call.get("arguments") or "")
259
+
260
+
261
+ class GeminiChat(_KeptAliveJSON):
262
+ """Vertex AI generateContent over one kept-alive connection, with OpenAIChat's interface."""
263
+
264
+ # "none" is LOW, the least every Gemini 3 model takes: gemini-3.8-flash answers MINIMAL with a 400.
265
+ LEVELS = {"none": "LOW", "minimal": "MINIMAL", "low": "LOW", "medium": "MEDIUM", "high": "HIGH"}
266
+ provider = "google"
267
+
268
+ def __init__(self, model, *, reasoning="low", project=None, location=None):
269
+ self.model, self.reasoning = model, reasoning
270
+ self.project = project or os.environ.get("GOOGLE_CLOUD_PROJECT", "")
271
+ self.location = location or os.environ.get("GOOGLE_CLOUD_LOCATION", "global") or "global"
272
+ if not self.project:
273
+ raise ValueError("GOOGLE_CLOUD_PROJECT is not set")
274
+ super().__init__("aiplatform.googleapis.com" if self.location == "global"
275
+ else f"{self.location}-aiplatform.googleapis.com")
276
+
277
+ @staticmethod
278
+ def _parts(content):
279
+ if isinstance(content, str):
280
+ return [{"text": content}]
281
+ parts = []
282
+ for part in content:
283
+ if part.get("type") == "text":
284
+ parts.append({"text": part["text"]})
285
+ elif part.get("type") == "image_url":
286
+ # "detail": "low" is not forwarded: Gemini uses its default media resolution (unmeasured).
287
+ header, data = part["image_url"]["url"].split(",", 1)
288
+ parts.append({"inlineData": {"mimeType": header[5:].split(";")[0], "data": data}})
289
+ return parts
290
+
291
+ def complete(self, messages, schema, *, timeout=60, reasoning=None):
292
+ from .gemini import GCloudToken
293
+ system = [p for m in messages if m["role"] == "system" for p in self._parts(m["content"])]
294
+ contents = [{"role": "model" if m["role"] == "assistant" else "user", "parts": self._parts(m["content"])}
295
+ for m in messages if m["role"] != "system"]
296
+ config = {"responseMimeType": "application/json", "responseJsonSchema": schema, "maxOutputTokens": 8000}
297
+ level = reasoning if reasoning in self.LEVELS else self.reasoning # "none" keeps the client's own level
298
+ if level in self.LEVELS:
299
+ config["thinkingConfig"] = {"thinkingLevel": self.LEVELS[level]}
300
+ body = {"contents": contents, "systemInstruction": {"parts": system}, "generationConfig": config}
301
+ path = (f"/v1/projects/{self.project}/locations/{self.location}/publishers/google/models/"
302
+ f"{self.model}:generateContent")
303
+ # Every 429 is retried: shared capacity, not an empty account, so it is waited out.
304
+ data = self._post(path, json.dumps(body).encode(),
305
+ lambda: {"Content-Type": "application/json",
306
+ "Authorization": "Bearer " + GCloudToken.get(20)},
307
+ timeout, label="Vertex")
308
+ meta = data.get("usageMetadata") or {}
309
+ usage = {"prompt_tokens": meta.get("promptTokenCount", 0),
310
+ "completion_tokens": meta.get("candidatesTokenCount", 0) + meta.get("thoughtsTokenCount", 0),
311
+ "cached_tokens": meta.get("cachedContentTokenCount", 0)}
312
+ self._count(usage)
313
+ parts = data["candidates"][0]["content"]["parts"]
314
+ text = "".join(p.get("text", "") for p in parts if not p.get("thought"))
315
+ return json.loads(text), usage
316
+
317
+
318
+ ANTHROPIC_HOST = "api.anthropic.com"
319
+ ANTHROPIC_VERSION = "2023-06-01"
320
+ # Replies are ~160 tokens plus thinking; the cap only stops a runaway reply.
321
+ ANTHROPIC_MAX_TOKENS = 16000
322
+ # One decision attempt's own deadline: p90 is 3.8-6.7 s by model (9 Oct); one Claude call hung 90 s.
323
+ ATTEMPT_CAP_SECONDS = 30.0
324
+ # Claude's structured outputs refuse a schema with any of these (400),
325
+ # so the loop and ``engines.structure`` check them instead.
326
+ UNSUPPORTED_SCHEMA_KEYS = frozenset({
327
+ "maxItems", "minLength", "maxLength", "minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf",
328
+ "pattern", "minProperties", "maxProperties", "uniqueItems", "patternProperties", "propertyNames", "contains",
329
+ "minContains", "maxContains", "not", "if", "then", "else", "dependentRequired", "dependentSchemas",
330
+ "unevaluatedProperties", "unevaluatedItems", "prefixItems"})
331
+ SCHEMA_FORMATS = frozenset({"date-time", "time", "date", "duration", "email", "hostname", "uri", "ipv4", "ipv6",
332
+ "uuid"})
333
+ # Models whose thinking cannot be turned off: "none" runs them at effort low.
334
+ ALWAYS_THINKING = ("claude-opus-5-5", "claude-fable-", "claude-mythos-")
335
+ # Sonnet 5.5 refuses thinking disabled; "between_tools" turns off up-front thinking.
336
+ BETWEEN_TOOLS = ("claude-sonnet-5-5",)
337
+ EFFORTS = ("low", "medium", "high", "xhigh", "max")
338
+
339
+
340
+ def anthropic_schema(schema):
341
+ """``schema`` as Claude's structured outputs take it."""
342
+ if isinstance(schema, list):
343
+ return [anthropic_schema(item) for item in schema]
344
+ if not isinstance(schema, dict):
345
+ return schema
346
+ out = {}
347
+ for key, value in schema.items():
348
+ if key in UNSUPPORTED_SCHEMA_KEYS or key == "format" and value not in SCHEMA_FORMATS:
349
+ continue
350
+ if key in ("properties", "$defs", "definitions") and isinstance(value, dict):
351
+ value = {name: anthropic_schema(sub) for name, sub in value.items()}
352
+ elif key in ("items", "anyOf", "allOf", "oneOf"):
353
+ value = anthropic_schema(value)
354
+ elif key == "minItems" and isinstance(value, int):
355
+ value = min(value, 1)
356
+ out["anyOf" if key == "oneOf" else key] = value
357
+ kinds = out.get("type") if isinstance(out.get("type"), list) else [out.get("type")]
358
+ if "object" in kinds or "properties" in out:
359
+ out["additionalProperties"] = False
360
+ return out
361
+
362
+
363
+ def anthropic_thinking(model, reasoning):
364
+ """(thinking, effort) for an OpenAI-style reasoning level. low ... max: adaptive thinking at that effort."""
365
+ level = {"minimal": "low"}.get(reasoning, reasoning)
366
+ if model.startswith("claude-haiku-4"):
367
+ return None, None # no adaptive thinking and no effort parameter
368
+ # Haiku 5.5 thinks by default (9 Oct) and takes adaptive thinking and effort like Sonnet, which keeps a Smart turn
369
+ # at effort low.
370
+ if level in EFFORTS:
371
+ return {"type": "adaptive"}, level
372
+ if model.startswith(ALWAYS_THINKING):
373
+ return None, "low"
374
+ if model.startswith(BETWEEN_TOOLS):
375
+ return {"type": "between_tools"}, "low"
376
+ return {"type": "disabled"}, None
377
+
378
+
379
+ class AnthropicError(TransportError):
380
+ """A failed Messages API request."""
381
+
382
+
383
+ class AnthropicChat:
384
+ """Claude on the Messages API, with OpenAIChat's interface."""
385
+
386
+ provider = "anthropic"
387
+ per_call_reasoning = True
388
+ cancelled = None # as _KeptAliveJSON's: set by the loop for its run
389
+
390
+ def __init__(self, model, *, key=None, reasoning="low", host=ANTHROPIC_HOST, hedger=None):
391
+ self.model, self.reasoning, self.host = model, reasoning, host
392
+ self.key = key or os.environ.get("ANTHROPIC_API_KEY", "")
393
+ if not self.key:
394
+ raise ValueError("ANTHROPIC_API_KEY is not set")
395
+ self.connection = None
396
+ self.hedger = hedger
397
+ self.usage = {"prompt_tokens": 0, "completion_tokens": 0, "cached_tokens": 0, "cache_write_tokens": 0,
398
+ "calls": 0, "hedges": 0}
399
+ self._claim = threading.Lock() # held while the kept-alive connection carries a request
400
+ self._count_lock = threading.Lock()
401
+ self.on_hedge_usage = None
402
+
403
+ @staticmethod
404
+ def _blocks(content, *, cache=True):
405
+ """Content blocks for Chat-style ``content``; a text part marked ``cache`` ends a breakpoint."""
406
+ if isinstance(content, str):
407
+ content = [{"type": "text", "text": content}]
408
+ blocks = []
409
+ for part in content:
410
+ if part.get("type") == "image_url":
411
+ header, data = part["image_url"]["url"].split(",", 1)
412
+ # Claude sees the whole 640 px shot (~1,150 visual tokens), so OpenAI's "detail" has no counterpart.
413
+ blocks.append({"type": "image", "source": {"type": "base64", "media_type": header[5:].split(";")[0],
414
+ "data": data}})
415
+ continue
416
+ blocks.append({"type": "text", "text": part["text"]})
417
+ if cache and part.get("cache"):
418
+ blocks[-1]["cache_control"] = {"type": "ephemeral"}
419
+ return blocks
420
+
421
+ def body(self, messages, schema, reasoning=None):
422
+ """The /v1/messages request for Chat-style ``messages``; ``reasoning`` overrides the client's for it."""
423
+ system = [block for m in messages if m["role"] == "system" for block in self._blocks(m["content"], cache=False)]
424
+ if system:
425
+ system[-1]["cache_control"] = {"type": "ephemeral"}
426
+ turns = [{"role": "assistant" if m["role"] == "assistant" else "user", "content": self._blocks(m["content"])}
427
+ for m in messages if m["role"] != "system"]
428
+ # Structured output, not a forced tool call: Sonnet 5.5 and Opus 5.5 refuse tool_choice any/tool with a 400.
429
+ body = {"model": self.model, "max_tokens": ANTHROPIC_MAX_TOKENS, "messages": turns,
430
+ "output_config": {"format": {"type": "json_schema", "schema": anthropic_schema(schema)}}}
431
+ if system:
432
+ body["system"] = system
433
+ thinking, effort = anthropic_thinking(self.model, reasoning or self.reasoning)
434
+ if thinking is not None:
435
+ body["thinking"] = thinking
436
+ if effort is not None:
437
+ body["output_config"]["effort"] = effort
438
+ return body
439
+
440
+ def complete(self, messages, schema, *, timeout=60, reasoning=None):
441
+ payload = json.dumps(self.body(messages, schema) if reasoning is None
442
+ else self.body(messages, schema, reasoning=reasoning)).encode()
443
+ hedge = harness_api.hooks().hedge()
444
+ purpose = "decision" if "actions" in (schema.get("properties") or {}) else "other"
445
+ replies = {}
446
+
447
+ def attempt(index, budget):
448
+ # A decision attempt past ATTEMPT_CAP_SECONDS fails in transit (a pure read) so the hedger can resend it.
449
+ cap = min(budget, ATTEMPT_CAP_SECONDS) if purpose == "decision" else budget
450
+ replies[index] = self._attempt(payload, index, cap)
451
+ return replies[index]
452
+
453
+ def lost(index, start, end, ok, won):
454
+ if ok and not won:
455
+ self._count(self.usage_of(replies[index]), hedge=True)
456
+
457
+ if hedge is not None and hedge.enabled():
458
+ hedger = self.hedger or hedge.HEDGERS["anthropic"]
459
+ data = hedger.run(purpose, attempt, timeout=timeout, on_attempt=lost)
460
+ else: # one attempt: hedging off, or the open-source engine, which has no hedge module
461
+ data = attempt(0, timeout)
462
+ usage = self.usage_of(data)
463
+ self._count(usage)
464
+ return self.answer(data), usage
465
+
466
+ @staticmethod
467
+ def usage_of(data):
468
+ """A reply's usage in OpenAIChat's terms."""
469
+ raw = data.get("usage") or {}
470
+ read, written = raw.get("cache_read_input_tokens") or 0, raw.get("cache_creation_input_tokens") or 0
471
+ return {"prompt_tokens": (raw.get("input_tokens") or 0) + read + written,
472
+ "completion_tokens": raw.get("output_tokens") or 0, "cached_tokens": read,
473
+ "cache_write_tokens": written}
474
+
475
+ def _count(self, usage, *, hedge=False):
476
+ with self._count_lock:
477
+ for key in ("prompt_tokens", "completion_tokens", "cached_tokens", "cache_write_tokens"):
478
+ self.usage[key] += usage.get(key, 0)
479
+ self.usage["hedges" if hedge else "calls"] += 1
480
+ hook = self.on_hedge_usage if hedge else None
481
+ if hook is not None:
482
+ try:
483
+ hook(usage)
484
+ except Exception:
485
+ pass # a run that already ended may refuse late events; usage above still counts it
486
+
487
+ @staticmethod
488
+ def answer(data):
489
+ """The JSON in a reply's text; a refusal or a reply cut at max_tokens raises."""
490
+ stop = data.get("stop_reason")
491
+ if stop == "refusal":
492
+ category = (data.get("stop_details") or {}).get("category")
493
+ raise RuntimeError(f"Anthropic declined the request (refusal{': ' + category if category else ''})")
494
+ text = "".join(block.get("text", "") for block in data.get("content") or () if block.get("type") == "text")
495
+ if stop == "max_tokens":
496
+ raise RuntimeError(f"Anthropic: the reply stopped at max_tokens ({ANTHROPIC_MAX_TOKENS})")
497
+ try:
498
+ return json.loads(text)
499
+ except ValueError:
500
+ raise RuntimeError(f"Anthropic: the reply was not JSON ({stop}): {text[:120]!r}") from None
501
+
502
+ def _headers(self):
503
+ headers = {"Content-Type": "application/json", "x-api-key": self.key, "anthropic-version": ANTHROPIC_VERSION}
504
+ workspace = os.environ.get("ANTHROPIC_WORKSPACE_ID", "").strip()
505
+ if workspace:
506
+ headers["anthropic-workspace-id"] = workspace
507
+ return headers
508
+
509
+ def _connect(self, timeout):
510
+ return http.client.HTTPSConnection(self.host, timeout=timeout)
511
+
512
+ def _attempt(self, payload, index, budget):
513
+ """One attempt of a call: the primary (index 0) on the kept-alive connection unless a losing attempt of an
514
+ earlier call still holds it, a twin or a resend on a fresh one."""
515
+ started = time.monotonic()
516
+ own = index == 0 and self._claim.acquire(blocking=False)
517
+ connection, reopened = None, False
518
+ try:
519
+ for tries in range(RATE_LIMIT_RETRIES + 1):
520
+ left = budget - (time.monotonic() - started)
521
+ if left <= .05:
522
+ raise TimeoutError("Anthropic call deadline exceeded")
523
+ if connection is None:
524
+ connection = (self.connection if own and self.connection is not None else self._connect(left))
525
+ if own:
526
+ self.connection = connection
527
+ try:
528
+ connection.timeout = left
529
+ if connection.sock is not None:
530
+ connection.sock.settimeout(left)
531
+ connection.request("POST", "/v1/messages", payload, self._headers())
532
+ response = connection.getresponse()
533
+ raw = response.read()
534
+ status, wait = response.status, response.getheader("retry-after")
535
+ except (OSError, http.client.HTTPException) as error:
536
+ connection.close()
537
+ connection = None
538
+ if own:
539
+ self.connection = None
540
+ if isinstance(error, TimeoutError) or reopened:
541
+ raise AnthropicError(
542
+ f"HTTP {type(error).__name__}; Anthropic request outcome unknown") from None
543
+ reopened = True # a kept-alive connection the server had closed: reopen once
544
+ continue
545
+ # A 429 without retry-after is a spend cap, which waiting can't clear.
546
+ if status in (429, 529) and tries < RATE_LIMIT_RETRIES and (status == 529 or wait is not None):
547
+ try:
548
+ delay = max(0.0, float(wait)) if wait is not None else min(2 ** tries, 16)
549
+ except ValueError:
550
+ delay = min(2 ** tries, 16)
551
+ if delay < budget - (time.monotonic() - started) - 1:
552
+ pause(delay, getattr(self, "cancelled", None))
553
+ continue
554
+ if status >= 300:
555
+ raise AnthropicError(f"HTTP {status}; " + error_text("Anthropic", status, raw))
556
+ return json.loads(raw)
557
+ raise AnthropicError("HTTP 429; Anthropic HTTP 429: still rate limited after retries")
558
+ finally:
559
+ if own:
560
+ self._claim.release()
561
+ elif connection is not None:
562
+ connection.close()
563
+
564
+
565
+ def chat_client(model, reasoning="low", *, key=None):
566
+ """The model's client: a Settings › Models provider for "<provider>:<model>", else Gemini,
567
+ Claude or OpenAI by id."""
568
+ providers = harness_api.hooks().model_routes()
569
+ if providers.split(model)[0]:
570
+ return providers.client(model, reasoning)
571
+ if model.startswith("gemini-"):
572
+ return GeminiChat(model, reasoning=reasoning)
573
+ if model.startswith("claude-"):
574
+ return AnthropicChat(model, key=key, reasoning=reasoning)
575
+ return OpenAIChat(model, key=key, reasoning=reasoning)
576
+
577
+
578
+ def transient_model_error(error):
579
+ """Whether a model call failed in transit, so sending it again may succeed."""
580
+ hedge = harness_api.hooks().hedge()
581
+ text = str(error)
582
+ if any(code in text for code in ("insufficient_quota", "credit_balance_exhausted", "credit balance is too low",
583
+ "billing_error")):
584
+ return False
585
+ if isinstance(error, (TimeoutError, ConnectionError)):
586
+ return True
587
+ try:
588
+ if hedge is not None and hedge.transient(error):
589
+ return True
590
+ except Exception: # noqa: BLE001
591
+ pass
592
+ return bool(re.search(r"\bHTTP (429|5\d\d)\b", text)) or "outcome unknown" in text \
593
+ or "deadline exceeded" in text
594
+
595
+
596
+ def http_detail(error):
597
+ """The failure's HTTP status ("HTTP 400"), "timed out", or the error's type: what a history line names."""
598
+ message = str(error)
599
+ found = re.search(r"\bHTTP (\d{3})\b", message)
600
+ if found:
601
+ return f"HTTP {found.group(1)}"
602
+ if isinstance(error, TimeoutError) or "timeout" in message.casefold():
603
+ return "timed out"
604
+ return type(error).__name__