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.
- mobile_agent/__init__.py +4 -0
- mobile_agent/__main__.py +598 -0
- mobile_agent/accounts/__init__.py +1 -0
- mobile_agent/accounts/context.py +80 -0
- mobile_agent/accounts/mcp.py +55 -0
- mobile_agent/accounts/pacing.py +318 -0
- mobile_agent/accounts/register.py +78 -0
- mobile_agent/accounts/routes.py +212 -0
- mobile_agent/accounts/routing.py +69 -0
- mobile_agent/accounts/screen.py +273 -0
- mobile_agent/accounts/stats.py +54 -0
- mobile_agent/accounts/store.py +394 -0
- mobile_agent/agent_hooks.py +123 -0
- mobile_agent/ai_plans/__init__.py +7 -0
- mobile_agent/ai_plans/agents.py +96 -0
- mobile_agent/ai_plans/claude_code.py +208 -0
- mobile_agent/ai_plans/cli.py +400 -0
- mobile_agent/ai_plans/codex.py +258 -0
- mobile_agent/ai_plans/compat_chat.py +307 -0
- mobile_agent/ai_plans/device_login.py +164 -0
- mobile_agent/ai_plans/draft_client.py +68 -0
- mobile_agent/ai_plans/gemini.py +137 -0
- mobile_agent/ai_plans/instructions.py +65 -0
- mobile_agent/ai_plans/mcp_http.py +160 -0
- mobile_agent/ai_plans/phone_tools.py +892 -0
- mobile_agent/ai_plans/providers.py +235 -0
- mobile_agent/ai_plans/routes.py +160 -0
- mobile_agent/ai_plans/runner.py +400 -0
- mobile_agent/ai_plans/vault.py +114 -0
- mobile_agent/alerts.py +337 -0
- mobile_agent/android/__init__.py +18 -0
- mobile_agent/android/adb.py +268 -0
- mobile_agent/android/conditions.py +128 -0
- mobile_agent/android/doctor.py +54 -0
- mobile_agent/android/driver.py +137 -0
- mobile_agent/android/emulator.py +216 -0
- mobile_agent/android/logs.py +85 -0
- mobile_agent/android/manager.py +225 -0
- mobile_agent/android/records.py +31 -0
- mobile_agent/android/tree.py +96 -0
- mobile_agent/answer_shapes.py +230 -0
- mobile_agent/api_devices.py +333 -0
- mobile_agent/api_errors.py +17 -0
- mobile_agent/api_get.py +249 -0
- mobile_agent/api_http.py +467 -0
- mobile_agent/api_post.py +453 -0
- mobile_agent/api_routes.py +173 -0
- mobile_agent/app_api/__init__.py +4 -0
- mobile_agent/app_api/cli.py +151 -0
- mobile_agent/app_api/endpoints.py +193 -0
- mobile_agent/app_api/mcp.py +54 -0
- mobile_agent/app_api/openapi.py +146 -0
- mobile_agent/app_api/reading.py +172 -0
- mobile_agent/app_api/register.py +10 -0
- mobile_agent/app_api/screen.py +216 -0
- mobile_agent/app_api/service.py +382 -0
- mobile_agent/app_api/sessions.py +404 -0
- mobile_agent/app_api/tokens.py +167 -0
- mobile_agent/app_choice.py +131 -0
- mobile_agent/app_icons.py +183 -0
- mobile_agent/app_killer.py +69 -0
- mobile_agent/artifacts.py +606 -0
- mobile_agent/attach/__init__.py +13 -0
- mobile_agent/attach/cli.py +46 -0
- mobile_agent/attach/flutter.py +28 -0
- mobile_agent/attach/score.py +119 -0
- mobile_agent/attach/web.py +13 -0
- mobile_agent/attachments/__init__.py +1 -0
- mobile_agent/attachments/agent.py +418 -0
- mobile_agent/attachments/extract.py +217 -0
- mobile_agent/attachments/mcp.py +104 -0
- mobile_agent/attachments/register.py +73 -0
- mobile_agent/attachments/routes.py +326 -0
- mobile_agent/attachments/service.py +170 -0
- mobile_agent/attachments/sniff.py +152 -0
- mobile_agent/attachments/store.py +441 -0
- mobile_agent/audit_verify.py +436 -0
- mobile_agent/bench/README.md +410 -0
- mobile_agent/bench/__init__.py +0 -0
- mobile_agent/bench/__main__.py +394 -0
- mobile_agent/bench/ab.py +168 -0
- mobile_agent/bench/actuation.py +368 -0
- mobile_agent/bench/agents/__init__.py +9 -0
- mobile_agent/bench/agents/base.py +105 -0
- mobile_agent/bench/agents/plan_clients.py +245 -0
- mobile_agent/bench/agents/smart.py +533 -0
- mobile_agent/bench/checks.py +358 -0
- mobile_agent/bench/decide_gate.py +214 -0
- mobile_agent/bench/diagnose.py +181 -0
- mobile_agent/bench/evals/__init__.py +14 -0
- mobile_agent/bench/evals/build.py +565 -0
- mobile_agent/bench/evals/checkpoint.py +214 -0
- mobile_agent/bench/evals/irt.py +219 -0
- mobile_agent/bench/evals/l0.py +647 -0
- mobile_agent/bench/evals/l1.py +866 -0
- mobile_agent/bench/evals/models.py +124 -0
- mobile_agent/bench/evals/pairwise.py +189 -0
- mobile_agent/bench/evals/proxy.py +90 -0
- mobile_agent/bench/evals/sets.py +149 -0
- mobile_agent/bench/evals/traces.py +446 -0
- mobile_agent/bench/fixtures.py +176 -0
- mobile_agent/bench/guard_replay.py +129 -0
- mobile_agent/bench/h2-manifest.json +20 -0
- mobile_agent/bench/h3-manifest.json +20 -0
- mobile_agent/bench/heldout-manifest.json +32 -0
- mobile_agent/bench/heldout.py +375 -0
- mobile_agent/bench/iosworld.py +901 -0
- mobile_agent/bench/iosworld_stats.py +510 -0
- mobile_agent/bench/llm_units.py +116 -0
- mobile_agent/bench/manifest.json +93 -0
- mobile_agent/bench/metrics.py +66 -0
- mobile_agent/bench/phone.py +44 -0
- mobile_agent/bench/replay.py +172 -0
- mobile_agent/bench/report.py +207 -0
- mobile_agent/bench/runner.py +456 -0
- mobile_agent/bench/safety.py +310 -0
- mobile_agent/bench/stream_timing.py +213 -0
- mobile_agent/bench/suite.py +444 -0
- mobile_agent/bench/sweep.py +176 -0
- mobile_agent/bench/tape.py +798 -0
- mobile_agent/bench/tape_replay.py +88 -0
- mobile_agent/bench/trace_evals.py +128 -0
- mobile_agent/bench/trace_view.py +81 -0
- mobile_agent/bench/truth/iphone15pro.json +138 -0
- mobile_agent/bench/truth.py +340 -0
- mobile_agent/bench/tuning.py +543 -0
- mobile_agent/bench/tuning_analysis.py +656 -0
- mobile_agent/bench/vague-heldout-manifest.json +12 -0
- mobile_agent/bench/vague_rubric.py +135 -0
- mobile_agent/bench/vague_run.py +243 -0
- mobile_agent/brand.py +454 -0
- mobile_agent/brand_fonts.py +756 -0
- mobile_agent/build_identity.py +21 -0
- mobile_agent/cable_bt.py +623 -0
- mobile_agent/cable_bt_runtime.py +128 -0
- mobile_agent/catalog.py +69 -0
- mobile_agent/chart_svg.py +415 -0
- mobile_agent/chat_here.py +189 -0
- mobile_agent/cli_constants.py +5 -0
- mobile_agent/cli_parser.py +611 -0
- mobile_agent/cli_run.py +115 -0
- mobile_agent/compiled_read/__init__.py +21 -0
- mobile_agent/compiled_read/delta.py +89 -0
- mobile_agent/compiled_read/extractors.py +202 -0
- mobile_agent/compiled_read/routes.py +56 -0
- mobile_agent/completion.py +267 -0
- mobile_agent/compose.py +48 -0
- mobile_agent/config.py +114 -0
- mobile_agent/connectors/__init__.py +1 -0
- mobile_agent/connectors/api_session.py +76 -0
- mobile_agent/connectors/callback_page.py +146 -0
- mobile_agent/connectors/client.py +582 -0
- mobile_agent/connectors/github_app.py +55 -0
- mobile_agent/connectors/graph_api.py +396 -0
- mobile_agent/connectors/imports.py +170 -0
- mobile_agent/connectors/issue.py +66 -0
- mobile_agent/connectors/keystore.py +85 -0
- mobile_agent/connectors/marks.py +222 -0
- mobile_agent/connectors/oauth.py +476 -0
- mobile_agent/connectors/picker.py +228 -0
- mobile_agent/connectors/presets.py +591 -0
- mobile_agent/connectors/register.py +48 -0
- mobile_agent/connectors/routes.py +153 -0
- mobile_agent/connectors/service.py +966 -0
- mobile_agent/connectors/shared.py +136 -0
- mobile_agent/connectors/skill.py +446 -0
- mobile_agent/connectors/slack_api.py +269 -0
- mobile_agent/connectors/steps.py +92 -0
- mobile_agent/connectors/store.py +144 -0
- mobile_agent/connectors/vault.py +72 -0
- mobile_agent/console.py +178 -0
- mobile_agent/content_queue/__init__.py +1 -0
- mobile_agent/content_queue/client.py +263 -0
- mobile_agent/content_queue/importing.py +153 -0
- mobile_agent/content_queue/media_check.py +224 -0
- mobile_agent/content_queue/prompt.py +64 -0
- mobile_agent/content_queue/register.py +91 -0
- mobile_agent/content_queue/routes.py +370 -0
- mobile_agent/content_queue/runner.py +475 -0
- mobile_agent/content_queue/store.py +521 -0
- mobile_agent/contract.py +996 -0
- mobile_agent/contract_asks.py +248 -0
- mobile_agent/contract_prompt.py +45 -0
- mobile_agent/contract_screen.py +981 -0
- mobile_agent/contract_view.py +84 -0
- mobile_agent/corpus/__init__.py +26 -0
- mobile_agent/corpus/cli.py +63 -0
- mobile_agent/corpus/consent.py +55 -0
- mobile_agent/corpus/listener.py +76 -0
- mobile_agent/corpus/register.py +9 -0
- mobile_agent/corpus/skeleton.py +72 -0
- mobile_agent/corpus/store.py +143 -0
- mobile_agent/costs.py +161 -0
- mobile_agent/decider_scope.py +40 -0
- mobile_agent/decision_provider.py +189 -0
- mobile_agent/deeplinks.py +352 -0
- mobile_agent/device_info.py +116 -0
- mobile_agent/device_manager.py +978 -0
- mobile_agent/device_status.py +195 -0
- mobile_agent/device_targets.py +217 -0
- mobile_agent/devices.py +676 -0
- mobile_agent/devkit/__init__.py +64 -0
- mobile_agent/devkit/account_cli.py +90 -0
- mobile_agent/devkit/asc.py +562 -0
- mobile_agent/devkit/asc_cli.py +214 -0
- mobile_agent/devkit/ci.py +737 -0
- mobile_agent/devkit/ci_cli.py +196 -0
- mobile_agent/devkit/ci_hosts.py +194 -0
- mobile_agent/devkit/es256.py +197 -0
- mobile_agent/devkit/expo.py +440 -0
- mobile_agent/devkit/expo_cli.py +405 -0
- mobile_agent/devkit/expo_dev.py +203 -0
- mobile_agent/devkit/expo_routes.py +90 -0
- mobile_agent/devkit/fixtures_cli.py +133 -0
- mobile_agent/devkit/fixtures_routes.py +82 -0
- mobile_agent/devkit/links.py +375 -0
- mobile_agent/devkit/links_cli.py +123 -0
- mobile_agent/devkit/links_routes.py +83 -0
- mobile_agent/devkit/nightly.py +152 -0
- mobile_agent/devkit/shots.py +203 -0
- mobile_agent/devkit/testflight.py +155 -0
- mobile_agent/devkit/testflight_cli.py +127 -0
- mobile_agent/devkit/testflight_routes.py +111 -0
- mobile_agent/devkit/xcode.py +236 -0
- mobile_agent/devkit/xcode_cli.py +101 -0
- mobile_agent/devtools.py +419 -0
- mobile_agent/doctor.py +509 -0
- mobile_agent/doctor_fix.py +467 -0
- mobile_agent/downloads.py +55 -0
- mobile_agent/drivers.py +793 -0
- mobile_agent/dry_run.py +102 -0
- mobile_agent/effect_ledger.py +83 -0
- mobile_agent/engine_health.py +378 -0
- mobile_agent/engine_hooks.py +96 -0
- mobile_agent/engines.py +408 -0
- mobile_agent/env_file.py +63 -0
- mobile_agent/env_secrets.py +149 -0
- mobile_agent/errors.py +15 -0
- mobile_agent/evals/__init__.py +3 -0
- mobile_agent/evals/app_survey.py +149 -0
- mobile_agent/evals/development/__init__.py +1 -0
- mobile_agent/evals/development/score.py +276 -0
- mobile_agent/evals/latency_report.py +312 -0
- mobile_agent/evals/offline_report.py +374 -0
- mobile_agent/evals/oracles.py +298 -0
- mobile_agent/evals/redteam/__init__.py +15 -0
- mobile_agent/evals/redteam/__main__.py +72 -0
- mobile_agent/evals/redteam/cases.py +730 -0
- mobile_agent/evals/redteam/harness.py +604 -0
- mobile_agent/evals/redteam/live.py +139 -0
- mobile_agent/evals/redteam/plain.py +85 -0
- mobile_agent/evals/vision/__init__.py +0 -0
- mobile_agent/evals/vision/images/dog_001.jpg +0 -0
- mobile_agent/evals/vision/images/dog_002.jpg +0 -0
- mobile_agent/evals/vision/images/dog_003.jpg +0 -0
- mobile_agent/evals/vision/images/dog_004.jpg +0 -0
- mobile_agent/evals/vision/images/dog_005.jpg +0 -0
- mobile_agent/evals/vision/images/dog_006.jpg +0 -0
- mobile_agent/evals/vision/images/dog_007.jpg +0 -0
- mobile_agent/evals/vision/images/dog_010.jpg +0 -0
- mobile_agent/evals/vision/images/dog_011.jpg +0 -0
- mobile_agent/evals/vision/images/dog_013.jpg +0 -0
- mobile_agent/evals/vision/images/dog_014.jpg +0 -0
- mobile_agent/evals/vision/images/dog_015.jpg +0 -0
- mobile_agent/evals/vision/images/dog_016.jpg +0 -0
- mobile_agent/evals/vision/images/dog_017.jpg +0 -0
- mobile_agent/evals/vision/images/dog_019.jpg +0 -0
- mobile_agent/evals/vision/images/dog_020.jpg +0 -0
- mobile_agent/evals/vision/images/dog_021.jpg +0 -0
- mobile_agent/evals/vision/images/dog_022.jpg +0 -0
- mobile_agent/evals/vision/images/dog_023.jpg +0 -0
- mobile_agent/evals/vision/images/dog_024.jpg +0 -0
- mobile_agent/evals/vision/images/dog_026.jpg +0 -0
- mobile_agent/evals/vision/images/dog_027.jpg +0 -0
- mobile_agent/evals/vision/images/dog_034.jpg +0 -0
- mobile_agent/evals/vision/images/dog_035.jpg +0 -0
- mobile_agent/evals/vision/images/dog_038.jpg +0 -0
- mobile_agent/evals/vision/images/dog_042.jpg +0 -0
- mobile_agent/evals/vision/images/dog_047.jpg +0 -0
- mobile_agent/evals/vision/images/dog_052.jpg +0 -0
- mobile_agent/evals/vision/images/dog_058.jpg +0 -0
- mobile_agent/evals/vision/images/dog_062.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_000.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_010.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_011.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_015.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_016.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_017.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_019.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_021.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_025.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_026.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_027.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_030.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_032.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_033.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_034.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_035.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_036.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_037.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_039.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_040.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_041.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_042.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_043.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_044.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_046.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_047.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_048.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_049.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_051.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_053.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_054.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_055.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_058.jpg +0 -0
- mobile_agent/evals/vision/images/eyes_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_002.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_003.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_004.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_007.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_009.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_010.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_011.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_012.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_013.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_014.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_016.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_017.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_023.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_027.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_029.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_032.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_033.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_036.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_041.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_043.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_047.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_049.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_051.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_053.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_056.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_057.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_058.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_059.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_060.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_061.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_062.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_063.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_064.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_065.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_066.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_067.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_068.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_069.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_070.jpg +0 -0
- mobile_agent/evals/vision/images/receipt_071.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_000.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_001.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_002.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_004.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_005.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_008.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_009.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_010.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_012.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_013.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_015.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_016.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_017.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_018.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_021.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_022.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_023.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_024.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_030.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_031.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_032.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_033.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_034.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_036.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_038.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_039.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_040.jpg +0 -0
- mobile_agent/evals/vision/images/sunglasses_041.jpg +0 -0
- mobile_agent/evals/vision/manifest.json +2355 -0
- mobile_agent/eventbus.py +110 -0
- mobile_agent/explain.py +34 -0
- mobile_agent/failure_line.py +197 -0
- mobile_agent/fanout.py +663 -0
- mobile_agent/feedback/__init__.py +2 -0
- mobile_agent/feedback/bundle.py +204 -0
- mobile_agent/feedback/outbox.py +196 -0
- mobile_agent/feedback/register.py +9 -0
- mobile_agent/feedback/routes.py +177 -0
- mobile_agent/fleet.py +841 -0
- mobile_agent/fleet_health.py +729 -0
- mobile_agent/fleet_phone.py +186 -0
- mobile_agent/frontier/__init__.py +79 -0
- mobile_agent/frontier/act.py +380 -0
- mobile_agent/frontier/actions.py +70 -0
- mobile_agent/frontier/agent.py +256 -0
- mobile_agent/frontier/approvals.py +366 -0
- mobile_agent/frontier/hooks.py +199 -0
- mobile_agent/frontier/interrupts.py +125 -0
- mobile_agent/frontier/observe.py +331 -0
- mobile_agent/frontier/phases.py +381 -0
- mobile_agent/frontier/pipeline.py +61 -0
- mobile_agent/frontier/prompt.py +384 -0
- mobile_agent/frontier/protocol.py +91 -0
- mobile_agent/frontier/safety.py +467 -0
- mobile_agent/frontier/saved.py +375 -0
- mobile_agent/frontier/screen.py +419 -0
- mobile_agent/frontier/skills.py +147 -0
- mobile_agent/frontier/stages/__init__.py +1 -0
- mobile_agent/frontier/stages/after.py +192 -0
- mobile_agent/frontier/stages/approve.py +138 -0
- mobile_agent/frontier/stages/controls.py +115 -0
- mobile_agent/frontier/stages/ends.py +102 -0
- mobile_agent/frontier/stages/gate.py +92 -0
- mobile_agent/frontier/stages/guards.py +132 -0
- mobile_agent/frontier/stages/points.py +155 -0
- mobile_agent/frontier/stages/send.py +177 -0
- mobile_agent/frontier/turn.py +151 -0
- mobile_agent/full_disk_access.py +83 -0
- mobile_agent/gemini.py +69 -0
- mobile_agent/genuine.py +120 -0
- mobile_agent/graph/__init__.py +4 -0
- mobile_agent/graph/catalog.py +789 -0
- mobile_agent/graph/chain.py +306 -0
- mobile_agent/graph/dispatch.py +98 -0
- mobile_agent/graph/draft.py +373 -0
- mobile_agent/graph/edit.py +76 -0
- mobile_agent/graph/estimate.py +189 -0
- mobile_agent/graph/expr.py +562 -0
- mobile_agent/graph/functions.py +271 -0
- mobile_agent/graph/infer.py +144 -0
- mobile_agent/graph/nodes.py +936 -0
- mobile_agent/graph/phone_steps.py +178 -0
- mobile_agent/graph/rerun.py +327 -0
- mobile_agent/graph/run.py +899 -0
- mobile_agent/graph/schema.py +842 -0
- mobile_agent/graph/state.py +161 -0
- mobile_agent/graph/values.py +198 -0
- mobile_agent/harness/__init__.py +2 -0
- mobile_agent/harness/checkpoints.py +468 -0
- mobile_agent/harness/control.py +101 -0
- mobile_agent/harness/prewarm.py +213 -0
- mobile_agent/harness/register.py +43 -0
- mobile_agent/harness/routes.py +114 -0
- mobile_agent/harness/settings.py +66 -0
- mobile_agent/harness/tools/__init__.py +67 -0
- mobile_agent/harness/tools/ask_user.py +102 -0
- mobile_agent/harness/tools/watch.py +120 -0
- mobile_agent/harness_api.py +815 -0
- mobile_agent/hedge.py +203 -0
- mobile_agent/help_topics.py +305 -0
- mobile_agent/hints.py +248 -0
- mobile_agent/history.py +69 -0
- mobile_agent/home.py +214 -0
- mobile_agent/http_pool.py +175 -0
- mobile_agent/image_policy.py +139 -0
- mobile_agent/inbox.py +148 -0
- mobile_agent/installed_apps.py +344 -0
- mobile_agent/integrations/__init__.py +1 -0
- mobile_agent/integrations/cli.py +472 -0
- mobile_agent/integrations/client_files.py +65 -0
- mobile_agent/integrations/clients.py +998 -0
- mobile_agent/integrations/doctor.py +185 -0
- mobile_agent/integrations/jsonc.py +383 -0
- mobile_agent/integrations/routes.py +135 -0
- mobile_agent/integrations/rules.py +112 -0
- mobile_agent/integrations/server_options.py +46 -0
- mobile_agent/integrations/skill.py +152 -0
- mobile_agent/integrations/skill_files.py +10 -0
- mobile_agent/integrations/textedit.py +309 -0
- mobile_agent/intents/__init__.py +17 -0
- mobile_agent/intents/atlas.py +216 -0
- mobile_agent/intents/callback.py +44 -0
- mobile_agent/intents/cli.py +55 -0
- mobile_agent/intents/harvest.py +91 -0
- mobile_agent/intents/plan.py +93 -0
- mobile_agent/ios/__init__.py +1 -0
- mobile_agent/ios/guide.py +16 -0
- mobile_agent/ios/kb.py +212 -0
- mobile_agent/ios/kb_data.py +1588 -0
- mobile_agent/ios/mcp.py +44 -0
- mobile_agent/ios/register.py +8 -0
- mobile_agent/ios/system.py +369 -0
- mobile_agent/journal.py +531 -0
- mobile_agent/keychain_acl.py +374 -0
- mobile_agent/keys.py +395 -0
- mobile_agent/latency_overlap.py +36 -0
- mobile_agent/latency_trace.py +170 -0
- mobile_agent/learned/__init__.py +15 -0
- mobile_agent/learned/app_path.py +160 -0
- mobile_agent/learned/code_read.py +38 -0
- mobile_agent/learned/parts.py +546 -0
- mobile_agent/learned/programs.py +691 -0
- mobile_agent/learned/register.py +171 -0
- mobile_agent/learned/routes.py +61 -0
- mobile_agent/learned/taught.py +329 -0
- mobile_agent/learned/teach.py +538 -0
- mobile_agent/learned/workflows.py +647 -0
- mobile_agent/learned/zero.py +41 -0
- mobile_agent/links.py +37 -0
- mobile_agent/listen.py +607 -0
- mobile_agent/lock_spike.py +278 -0
- mobile_agent/lockscreen.py +526 -0
- mobile_agent/login.py +354 -0
- mobile_agent/longform/__init__.py +15 -0
- mobile_agent/longform/ads.py +191 -0
- mobile_agent/longform/blocks.py +117 -0
- mobile_agent/longform/budget.py +265 -0
- mobile_agent/longform/context.py +285 -0
- mobile_agent/longform/costs.py +93 -0
- mobile_agent/longform/digest.py +145 -0
- mobile_agent/longform/extract.py +508 -0
- mobile_agent/longform/feed.py +505 -0
- mobile_agent/longform/followup.py +168 -0
- mobile_agent/longform/frames.py +82 -0
- mobile_agent/longform/identity.py +87 -0
- mobile_agent/longform/inbox.py +221 -0
- mobile_agent/longform/items.py +425 -0
- mobile_agent/longform/models.py +115 -0
- mobile_agent/longform/notebook.py +329 -0
- mobile_agent/longform/plan.py +614 -0
- mobile_agent/longform/recall.py +195 -0
- mobile_agent/longform/receipts.py +81 -0
- mobile_agent/longform/register.py +124 -0
- mobile_agent/longform/report.py +343 -0
- mobile_agent/longform/routes.py +102 -0
- mobile_agent/longform/session.py +644 -0
- mobile_agent/longform/store.py +146 -0
- mobile_agent/longform/synthesis.py +347 -0
- mobile_agent/mac_tools.py +223 -0
- mobile_agent/manual_control.py +143 -0
- mobile_agent/maps.py +149 -0
- mobile_agent/marks.py +92 -0
- mobile_agent/mcp_server/__init__.py +3 -0
- mobile_agent/mcp_server/__main__.py +29 -0
- mobile_agent/mcp_server/action_journal.py +24 -0
- mobile_agent/mcp_server/action_tools.py +638 -0
- mobile_agent/mcp_server/approvals.py +219 -0
- mobile_agent/mcp_server/catalog.py +92 -0
- mobile_agent/mcp_server/cli.py +194 -0
- mobile_agent/mcp_server/codes.py +147 -0
- mobile_agent/mcp_server/device_tools.py +306 -0
- mobile_agent/mcp_server/direct.py +272 -0
- mobile_agent/mcp_server/expo_tools.py +105 -0
- mobile_agent/mcp_server/phone_tools.py +510 -0
- mobile_agent/mcp_server/protocol.py +364 -0
- mobile_agent/mcp_server/registry.py +55 -0
- mobile_agent/mcp_server/run_tools.py +531 -0
- mobile_agent/mcp_server/schemas.py +425 -0
- mobile_agent/mcp_server/screen_tools.py +168 -0
- mobile_agent/mcp_server/session.py +469 -0
- mobile_agent/mcp_server/support.py +145 -0
- mobile_agent/mcp_server/tools.py +493 -0
- mobile_agent/memory/__init__.py +9 -0
- mobile_agent/memory/cli.py +207 -0
- mobile_agent/memory/extract.py +191 -0
- mobile_agent/memory/listener.py +100 -0
- mobile_agent/memory/model.py +144 -0
- mobile_agent/memory/project.py +96 -0
- mobile_agent/memory/provider.py +127 -0
- mobile_agent/memory/register.py +21 -0
- mobile_agent/memory/retrieval.py +172 -0
- mobile_agent/memory/routes.py +216 -0
- mobile_agent/memory/runs.py +124 -0
- mobile_agent/memory/store.py +806 -0
- mobile_agent/memory/writer.py +278 -0
- mobile_agent/mirroring.py +585 -0
- mobile_agent/mirroring_runtime.py +221 -0
- mobile_agent/model_clients.py +604 -0
- mobile_agent/narrate.py +813 -0
- mobile_agent/native_helpers.py +84 -0
- mobile_agent/netxray/__init__.py +1 -0
- mobile_agent/netxray/analyze.py +322 -0
- mobile_agent/netxray/attribution.py +62 -0
- mobile_agent/netxray/capture.py +88 -0
- mobile_agent/netxray/cli.py +142 -0
- mobile_agent/netxray/job.py +219 -0
- mobile_agent/netxray/mobster-pcap.c +560 -0
- mobile_agent/netxray/packets.py +410 -0
- mobile_agent/netxray/pcapng.py +223 -0
- mobile_agent/netxray/register.py +93 -0
- mobile_agent/netxray/report.py +214 -0
- mobile_agent/netxray/store.py +48 -0
- mobile_agent/netxray/summary.py +157 -0
- mobile_agent/netxray/target.py +67 -0
- mobile_agent/netxray/tool.py +66 -0
- mobile_agent/netxray/trackers.py +194 -0
- mobile_agent/notify_triggers/__init__.py +1 -0
- mobile_agent/notify_triggers/rules.py +39 -0
- mobile_agent/notify_triggers/sources.py +315 -0
- mobile_agent/notify_triggers/watch.py +135 -0
- mobile_agent/offers.py +86 -0
- mobile_agent/orchestrate.py +215 -0
- mobile_agent/outlets/__init__.py +1 -0
- mobile_agent/outlets/connect.py +249 -0
- mobile_agent/outlets/folder.py +180 -0
- mobile_agent/outlets/hooks.py +453 -0
- mobile_agent/outlets/inbound.py +472 -0
- mobile_agent/outlets/inbound_reply.py +57 -0
- mobile_agent/outlets/inbound_routes.py +81 -0
- mobile_agent/outlets/kinds.py +308 -0
- mobile_agent/outlets/mail.py +110 -0
- mobile_agent/outlets/net.py +86 -0
- mobile_agent/outlets/notion.py +244 -0
- mobile_agent/outlets/register.py +56 -0
- mobile_agent/outlets/routes.py +135 -0
- mobile_agent/outlets/service.py +580 -0
- mobile_agent/outlets/sheets.py +169 -0
- mobile_agent/outlets/slack.py +177 -0
- mobile_agent/outlets/store.py +194 -0
- mobile_agent/outlets/vault.py +80 -0
- mobile_agent/outlets/webhook.py +316 -0
- mobile_agent/output_contract.py +141 -0
- mobile_agent/owner_presence.py +211 -0
- mobile_agent/ownership.py +219 -0
- mobile_agent/packs/__init__.py +1 -0
- mobile_agent/packs/calendar.py +243 -0
- mobile_agent/packs/context.py +68 -0
- mobile_agent/packs/core.py +757 -0
- mobile_agent/packs/front_door.py +316 -0
- mobile_agent/packs/heal.py +214 -0
- mobile_agent/packs/health.py +122 -0
- mobile_agent/packs/insights.py +76 -0
- mobile_agent/packs/instagram.py +640 -0
- mobile_agent/packs/linkedin.py +106 -0
- mobile_agent/packs/maps.py +196 -0
- mobile_agent/packs/mcp.py +149 -0
- mobile_agent/packs/messages.py +270 -0
- mobile_agent/packs/notes.py +226 -0
- mobile_agent/packs/photos.py +166 -0
- mobile_agent/packs/pins.py +47 -0
- mobile_agent/packs/reddit.py +110 -0
- mobile_agent/packs/register.py +276 -0
- mobile_agent/packs/rows.py +108 -0
- mobile_agent/packs/safari.py +143 -0
- mobile_agent/packs/settings.py +253 -0
- mobile_agent/packs/social.py +300 -0
- mobile_agent/packs/threads.py +97 -0
- mobile_agent/packs/tiktok.py +455 -0
- mobile_agent/packs/tools.py +290 -0
- mobile_agent/packs/x.py +127 -0
- mobile_agent/packs/youtube.py +199 -0
- mobile_agent/parity.py +270 -0
- mobile_agent/paths.py +99 -0
- mobile_agent/perception.py +470 -0
- mobile_agent/permissions/__init__.py +32 -0
- mobile_agent/permissions/consequence.py +535 -0
- mobile_agent/permissions/exceptions.py +99 -0
- mobile_agent/permissions/gate.py +316 -0
- mobile_agent/permissions/outside.py +316 -0
- mobile_agent/permissions/policy.py +288 -0
- mobile_agent/permissions/project_policy.py +411 -0
- mobile_agent/permissions/register.py +148 -0
- mobile_agent/permissions/rules.py +291 -0
- mobile_agent/phone_io/__init__.py +146 -0
- mobile_agent/phone_io/cli.py +427 -0
- mobile_agent/phone_io/clipboard.py +78 -0
- mobile_agent/phone_io/controls.py +78 -0
- mobile_agent/phone_io/files.py +289 -0
- mobile_agent/phone_io/handoff.py +123 -0
- mobile_agent/phone_io/icloud.py +118 -0
- mobile_agent/phone_io/install.py +188 -0
- mobile_agent/phone_io/network_capture.py +188 -0
- mobile_agent/phone_io/network_cli.py +138 -0
- mobile_agent/phone_io/network_profiles.py +138 -0
- mobile_agent/phone_io/photos.py +512 -0
- mobile_agent/phone_io/recording.py +115 -0
- mobile_agent/phone_io/secrets.py +146 -0
- mobile_agent/phone_io/transfer.py +895 -0
- mobile_agent/phone_io/tunnel.py +129 -0
- mobile_agent/phone_ready.py +91 -0
- mobile_agent/pinning.py +158 -0
- mobile_agent/plan_gate.py +807 -0
- mobile_agent/plugins/__init__.py +3 -0
- mobile_agent/plugins/install.py +163 -0
- mobile_agent/plugins/manifest.py +242 -0
- mobile_agent/plugins/register.py +15 -0
- mobile_agent/plugins/routes.py +82 -0
- mobile_agent/plugins/service.py +322 -0
- mobile_agent/plugins/skills.py +76 -0
- mobile_agent/plugins/store.py +79 -0
- mobile_agent/pool.py +285 -0
- mobile_agent/power_watch.py +275 -0
- mobile_agent/premium_model.py +22 -0
- mobile_agent/project_env.py +291 -0
- mobile_agent/project_env_cli.py +74 -0
- mobile_agent/project_env_routes.py +55 -0
- mobile_agent/projects/__init__.py +27 -0
- mobile_agent/projects/ci.py +332 -0
- mobile_agent/projects/clone.py +339 -0
- mobile_agent/projects/drafts.py +158 -0
- mobile_agent/projects/folder.py +436 -0
- mobile_agent/projects/github.py +131 -0
- mobile_agent/projects/register.py +31 -0
- mobile_agent/projects/revisions.py +253 -0
- mobile_agent/projects/routes.py +234 -0
- mobile_agent/projects/runs.py +297 -0
- mobile_agent/projects/service.py +695 -0
- mobile_agent/projects/setup.py +84 -0
- mobile_agent/projects/store.py +379 -0
- mobile_agent/projects/sync.py +380 -0
- mobile_agent/projects/workflow_files.py +270 -0
- mobile_agent/replay_core/__init__.py +26 -0
- mobile_agent/replay_core/checks.py +594 -0
- mobile_agent/replay_core/engine.py +596 -0
- mobile_agent/replay_core/heal.py +364 -0
- mobile_agent/replay_core/path.py +240 -0
- mobile_agent/replay_core/recorder.py +206 -0
- mobile_agent/replay_core/store.py +171 -0
- mobile_agent/result_blocks.py +941 -0
- mobile_agent/run.py +642 -0
- mobile_agent/run_delivery.py +123 -0
- mobile_agent/run_executor.py +200 -0
- mobile_agent/run_export.py +284 -0
- mobile_agent/run_gif.py +891 -0
- mobile_agent/runner_hints.py +86 -0
- mobile_agent/runner_renewal.py +205 -0
- mobile_agent/runtime_create.py +549 -0
- mobile_agent/runtime_driver.py +458 -0
- mobile_agent/runtime_limits.py +319 -0
- mobile_agent/runtime_queue.py +394 -0
- mobile_agent/runtime_runs.py +383 -0
- mobile_agent/runtime_status.py +181 -0
- mobile_agent/runtime_teach.py +295 -0
- mobile_agent/runtime_work.py +599 -0
- mobile_agent/screen_text.py +101 -0
- mobile_agent/secret_filter.py +113 -0
- mobile_agent/serve.py +160 -0
- mobile_agent/server.py +363 -0
- mobile_agent/service_support.py +42 -0
- mobile_agent/settle/__init__.py +1 -0
- mobile_agent/settle/predict.py +102 -0
- mobile_agent/settle/replay.py +92 -0
- mobile_agent/settle/store.py +93 -0
- mobile_agent/settle/stream.py +43 -0
- mobile_agent/setup_cli.py +225 -0
- mobile_agent/setup_errors.py +87 -0
- mobile_agent/setup_service.py +527 -0
- mobile_agent/share.py +104 -0
- mobile_agent/signing.py +184 -0
- mobile_agent/sim/__init__.py +6 -0
- mobile_agent/sim/__main__.py +26 -0
- mobile_agent/sim/api.py +952 -0
- mobile_agent/sim/apps.py +172 -0
- mobile_agent/sim/biometrics.py +84 -0
- mobile_agent/sim/cli.py +271 -0
- mobile_agent/sim/doctor.py +293 -0
- mobile_agent/sim/governor.py +90 -0
- mobile_agent/sim/holders.py +88 -0
- mobile_agent/sim/models.py +62 -0
- mobile_agent/sim/registry.py +161 -0
- mobile_agent/sim/simctl.py +269 -0
- mobile_agent/sim/wda.py +618 -0
- mobile_agent/sim/xcode.py +74 -0
- mobile_agent/skills/__init__.py +12 -0
- mobile_agent/skills/codes.py +597 -0
- mobile_agent/skills/notifications.py +281 -0
- mobile_agent/skills/open_url.py +127 -0
- mobile_agent/smart_events.py +588 -0
- mobile_agent/smart_result.py +490 -0
- mobile_agent/smart_run.py +413 -0
- mobile_agent/state.py +662 -0
- mobile_agent/states.py +217 -0
- mobile_agent/steering.py +70 -0
- mobile_agent/style.py +178 -0
- mobile_agent/supervised.py +234 -0
- mobile_agent/swift/helper_bt_hid.swift +534 -0
- mobile_agent/swift/helper_doc_text.swift +184 -0
- mobile_agent/swift/helper_draft_reply.swift +170 -0
- mobile_agent/swift/helper_map.swift +110 -0
- mobile_agent/swift/helper_mirroring.swift +1099 -0
- mobile_agent/swift/helper_mjpeg_mp4.swift +103 -0
- mobile_agent/swift/helper_pdf.swift +100 -0
- mobile_agent/swift/helper_phone_audio.swift +993 -0
- mobile_agent/swift/helper_phone_screen.swift +416 -0
- mobile_agent/swift/helper_screen_text.swift +24 -0
- mobile_agent/swift/helper_transfer.swift +246 -0
- mobile_agent/swift/ocr.swift +22 -0
- mobile_agent/task_policy.py +337 -0
- mobile_agent/team_signing/__init__.py +34 -0
- mobile_agent/team_signing/apple_ca.py +37 -0
- mobile_agent/team_signing/bundle.py +136 -0
- mobile_agent/team_signing/cli.py +208 -0
- mobile_agent/team_signing/identity.py +248 -0
- mobile_agent/team_signing/ledger.py +105 -0
- mobile_agent/team_signing/prebuilt.py +61 -0
- mobile_agent/team_signing/resign.py +256 -0
- mobile_agent/team_signing/service.py +382 -0
- mobile_agent/team_signing/tooling.py +243 -0
- mobile_agent/team_signing/writes.py +268 -0
- mobile_agent/terminal_image.py +81 -0
- mobile_agent/testkit/__init__.py +1 -0
- mobile_agent/testkit/a11y_baseline.py +132 -0
- mobile_agent/testkit/api.py +403 -0
- mobile_agent/testkit/capsule.py +400 -0
- mobile_agent/testkit/capsule_cli.py +57 -0
- mobile_agent/testkit/cli.py +948 -0
- mobile_agent/testkit/cli_arguments.py +193 -0
- mobile_agent/testkit/comparison.py +288 -0
- mobile_agent/testkit/comparison_cli.py +41 -0
- mobile_agent/testkit/discover.py +257 -0
- mobile_agent/testkit/editor.py +243 -0
- mobile_agent/testkit/execute.py +515 -0
- mobile_agent/testkit/github_checks.py +248 -0
- mobile_agent/testkit/gitref.py +66 -0
- mobile_agent/testkit/heal_review.py +176 -0
- mobile_agent/testkit/health.py +480 -0
- mobile_agent/testkit/history.py +255 -0
- mobile_agent/testkit/hosted_results.py +321 -0
- mobile_agent/testkit/input_files.py +173 -0
- mobile_agent/testkit/jobs.py +285 -0
- mobile_agent/testkit/junit_verdict.py +33 -0
- mobile_agent/testkit/maestro.py +802 -0
- mobile_agent/testkit/mcp.py +562 -0
- mobile_agent/testkit/mcp_comparison.py +88 -0
- mobile_agent/testkit/minimize.py +471 -0
- mobile_agent/testkit/minimizer_cli.py +48 -0
- mobile_agent/testkit/notify.py +212 -0
- mobile_agent/testkit/phone_video.py +191 -0
- mobile_agent/testkit/pr.py +491 -0
- mobile_agent/testkit/pr_report.py +562 -0
- mobile_agent/testkit/prbuild.py +148 -0
- mobile_agent/testkit/proof_report.py +55 -0
- mobile_agent/testkit/provenance.py +119 -0
- mobile_agent/testkit/prrun.py +244 -0
- mobile_agent/testkit/recent.py +166 -0
- mobile_agent/testkit/recipes.py +146 -0
- mobile_agent/testkit/record.py +204 -0
- mobile_agent/testkit/register.py +288 -0
- mobile_agent/testkit/reports.py +998 -0
- mobile_agent/testkit/repro.py +432 -0
- mobile_agent/testkit/scenario.py +440 -0
- mobile_agent/testkit/scenario_cli.py +37 -0
- mobile_agent/testkit/snapshots_cli.py +60 -0
- mobile_agent/testkit/suite.py +670 -0
- mobile_agent/testkit/targets.py +497 -0
- mobile_agent/testkit/xctest.py +378 -0
- mobile_agent/threads/__init__.py +1 -0
- mobile_agent/threads/chat_once.py +264 -0
- mobile_agent/threads/cli.py +740 -0
- mobile_agent/threads/client.py +326 -0
- mobile_agent/threads/context.py +246 -0
- mobile_agent/threads/mcp.py +739 -0
- mobile_agent/threads/recent.py +34 -0
- mobile_agent/threads/register.py +54 -0
- mobile_agent/threads/routes.py +188 -0
- mobile_agent/threads/service.py +975 -0
- mobile_agent/threads/store.py +361 -0
- mobile_agent/threads/summary.py +117 -0
- mobile_agent/threads/term.py +101 -0
- mobile_agent/threads/workflow_cli.py +104 -0
- mobile_agent/tls.py +36 -0
- mobile_agent/traces.py +271 -0
- mobile_agent/tracks.py +235 -0
- mobile_agent/transport.py +271 -0
- mobile_agent/triggers/__init__.py +1 -0
- mobile_agent/triggers/actions.py +204 -0
- mobile_agent/triggers/model.py +146 -0
- mobile_agent/triggers/register.py +29 -0
- mobile_agent/triggers/routes.py +101 -0
- mobile_agent/triggers/service.py +387 -0
- mobile_agent/triggers/store.py +142 -0
- mobile_agent/tui/__init__.py +89 -0
- mobile_agent/tui/app.py +582 -0
- mobile_agent/tui/chrome.py +321 -0
- mobile_agent/tui/commands.py +375 -0
- mobile_agent/tui/demo_phone.py +369 -0
- mobile_agent/tui/images.py +29 -0
- mobile_agent/tui/keys.py +64 -0
- mobile_agent/tui/prompt.py +195 -0
- mobile_agent/tui/render.py +395 -0
- mobile_agent/tui/screens.py +180 -0
- mobile_agent/tui/session.py +308 -0
- mobile_agent/tui/tasks.py +287 -0
- mobile_agent/tui/theme.py +118 -0
- mobile_agent/tui/widgets.py +272 -0
- mobile_agent/update_check.py +213 -0
- mobile_agent/usage.py +184 -0
- mobile_agent/verify/__init__.py +0 -0
- mobile_agent/verify/__main__.py +41 -0
- mobile_agent/verify/a11y.py +189 -0
- mobile_agent/verify/actions.py +290 -0
- mobile_agent/verify/assertions.py +570 -0
- mobile_agent/verify/audit.py +312 -0
- mobile_agent/verify/business.py +221 -0
- mobile_agent/verify/business_run.py +131 -0
- mobile_agent/verify/business_state.py +168 -0
- mobile_agent/verify/check_schema.py +398 -0
- mobile_agent/verify/checks.py +614 -0
- mobile_agent/verify/cli.py +423 -0
- mobile_agent/verify/conditions.py +483 -0
- mobile_agent/verify/crashes.py +321 -0
- mobile_agent/verify/device_audit.py +246 -0
- mobile_agent/verify/dialogs.py +287 -0
- mobile_agent/verify/evidence.py +705 -0
- mobile_agent/verify/fixtures.py +305 -0
- mobile_agent/verify/flow.py +213 -0
- mobile_agent/verify/l10n.py +141 -0
- mobile_agent/verify/netcerts.py +67 -0
- mobile_agent/verify/netproxy.py +356 -0
- mobile_agent/verify/netrun.py +81 -0
- mobile_agent/verify/netshim.m +28 -0
- mobile_agent/verify/netshim.py +48 -0
- mobile_agent/verify/netstubs.py +132 -0
- mobile_agent/verify/overlay.py +174 -0
- mobile_agent/verify/perf.py +239 -0
- mobile_agent/verify/proof.py +192 -0
- mobile_agent/verify/replay_admission.py +48 -0
- mobile_agent/verify/report.py +952 -0
- mobile_agent/verify/run_cleanup.py +67 -0
- mobile_agent/verify/run_links.py +48 -0
- mobile_agent/verify/run_perf.py +44 -0
- mobile_agent/verify/run_secrets.py +39 -0
- mobile_agent/verify/runner.py +976 -0
- mobile_agent/verify/runs.py +57 -0
- mobile_agent/verify/screen_coverage.py +181 -0
- mobile_agent/verify/smart.py +106 -0
- mobile_agent/verify/snapshot_run.py +114 -0
- mobile_agent/verify/snapshots.py +306 -0
- mobile_agent/verify/sound.py +335 -0
- mobile_agent/verify/source_observation.py +130 -0
- mobile_agent/verify/system_surfaces/__init__.py +7 -0
- mobile_agent/verify/system_surfaces/play.py +106 -0
- mobile_agent/verify/system_surfaces/spec.py +139 -0
- mobile_agent/verify/system_surfaces/stage.py +138 -0
- mobile_agent/verify/system_surfaces/surfaces.py +121 -0
- mobile_agent/verify/tree.py +223 -0
- mobile_agent/verify/verdict.py +377 -0
- mobile_agent/verify/wire.py +392 -0
- mobile_agent/verify/wirestore.py +176 -0
- mobile_agent/waiting/__init__.py +1 -0
- mobile_agent/waiting/contacts.py +73 -0
- mobile_agent/waiting/drafts.py +136 -0
- mobile_agent/waiting/job.py +145 -0
- mobile_agent/waiting/messages.py +174 -0
- mobile_agent/waiting/rank.py +115 -0
- mobile_agent/waiting/register.py +26 -0
- mobile_agent/watch.py +502 -0
- mobile_agent/wda/__init__.py +2 -0
- mobile_agent/wda/frame_clock.py +646 -0
- mobile_agent/wda/frame_source.py +325 -0
- mobile_agent/wda/frame_store.py +115 -0
- mobile_agent/wda/gate.py +163 -0
- mobile_agent/wda/gestures.py +291 -0
- mobile_agent/wda/guards.py +94 -0
- mobile_agent/wda/identity.py +55 -0
- mobile_agent/wda/observe.py +884 -0
- mobile_agent/wda/patches.py +856 -0
- mobile_agent/wda/point_gestures.py +150 -0
- mobile_agent/wda/source.py +32 -0
- mobile_agent/wda/system_dialogs.py +571 -0
- mobile_agent/wda/text.py +694 -0
- mobile_agent/wda/timeouts.py +77 -0
- mobile_agent/wda/video.py +505 -0
- mobile_agent/wda/watchdog.py +243 -0
- mobile_agent/wireless/__init__.py +15 -0
- mobile_agent/wireless/api.py +354 -0
- mobile_agent/wireless/cli.py +287 -0
- mobile_agent/wireless/devicectl.py +229 -0
- mobile_agent/wireless/exposure.py +33 -0
- mobile_agent/wireless/mobster-wifi-pair.c +122 -0
- mobile_agent/wireless/monitor.py +332 -0
- mobile_agent/wireless/network.py +334 -0
- mobile_agent/wireless/onboarding.py +293 -0
- mobile_agent/wireless/pair.py +124 -0
- mobile_agent/wireless/probe.py +322 -0
- mobile_agent/wireless/register.py +8 -0
- mobile_agent/wireless/relay.py +179 -0
- mobile_agent/wireless/result.py +87 -0
- mobile_agent/wireless/store.py +85 -0
- mobile_agent/wireless/switch.py +77 -0
- mobile_agent/wireless/transport.py +118 -0
- mobile_agent/wireless/words.py +81 -0
- mobile_agent/wireless/xctestrun.py +83 -0
- mobile_agent/workflow_tools.py +59 -0
- mobile_agent/workflows/__init__.py +2 -0
- mobile_agent/workflows/brief.py +201 -0
- mobile_agent/workflows/cron.py +110 -0
- mobile_agent/workflows/health.py +257 -0
- mobile_agent/workflows/history.py +126 -0
- mobile_agent/workflows/records.py +442 -0
- mobile_agent/workflows/runs.py +1000 -0
- mobile_agent/workflows/scheduler.py +134 -0
- mobile_agent/workflows/service.py +973 -0
- mobile_agent/workflows/starters.py +285 -0
- mobile_agent/workflows/triggers.py +248 -0
- mobile_agent/xray/__init__.py +1 -0
- mobile_agent/xray/app_cli.py +116 -0
- mobile_agent/xray/cli.py +21 -0
- mobile_agent/xray/crawl.py +754 -0
- mobile_agent/xray/diff.py +171 -0
- mobile_agent/xray/diff_cli.py +43 -0
- mobile_agent/xray/job.py +184 -0
- mobile_agent/xray/paywall.py +188 -0
- mobile_agent/xray/policy.py +305 -0
- mobile_agent/xray/register.py +43 -0
- mobile_agent/xray/report.py +283 -0
- mobile_agent/xray/screens.py +117 -0
- mobster_cli-0.4.3.dist-info/METADATA +93 -0
- mobster_cli-0.4.3.dist-info/RECORD +1009 -0
- mobster_cli-0.4.3.dist-info/WHEEL +5 -0
- mobster_cli-0.4.3.dist-info/entry_points.txt +3 -0
- mobster_cli-0.4.3.dist-info/licenses/LICENSE +21 -0
- 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__
|