aria-code 4.1.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 (284) hide show
  1. agents/__init__.py +32 -0
  2. agents/base.py +190 -0
  3. agents/deep/__init__.py +37 -0
  4. agents/deep/calibration_loop.py +144 -0
  5. agents/deep/critic.py +125 -0
  6. agents/deep/deepen.py +193 -0
  7. agents/deep/models.py +149 -0
  8. agents/deep/pipeline.py +164 -0
  9. agents/deep/quant_fusion.py +192 -0
  10. agents/deep/themes.py +95 -0
  11. agents/deep/tiers.py +106 -0
  12. agents/financial/__init__.py +10 -0
  13. agents/financial/catalyst.py +279 -0
  14. agents/financial/debate.py +145 -0
  15. agents/financial/earnings.py +303 -0
  16. agents/financial/fundamental.py +159 -0
  17. agents/financial/macro.py +99 -0
  18. agents/financial/news.py +207 -0
  19. agents/financial/risk.py +132 -0
  20. agents/financial/sector.py +279 -0
  21. agents/financial/synthesis.py +274 -0
  22. agents/financial/technical.py +258 -0
  23. agents/portfolio_agent.py +333 -0
  24. agents/realty/__init__.py +62 -0
  25. agents/realty/asset_diagnosis.py +150 -0
  26. agents/realty/business_match.py +165 -0
  27. agents/realty/cashflow_verify.py +208 -0
  28. agents/realty/contract_rules.py +209 -0
  29. agents/realty/energy_anomaly.py +188 -0
  30. agents/realty/exit_settlement.py +207 -0
  31. agents/realty/fulfillment_risk.py +205 -0
  32. agents/realty/ops_optimize.py +159 -0
  33. agents/realty/revenue_share.py +214 -0
  34. agents/registry.py +144 -0
  35. agents/sports/__init__.py +0 -0
  36. agents/sports/football_agent.py +169 -0
  37. agents/team.py +289 -0
  38. aliyun_data_client.py +660 -0
  39. apps/README.md +12 -0
  40. apps/__init__.py +2 -0
  41. apps/channels/README.md +15 -0
  42. apps/cli/README.md +13 -0
  43. apps/cli/__init__.py +2 -0
  44. apps/cli/bootstrap.py +99 -0
  45. apps/cli/codegen_paths.py +29 -0
  46. apps/cli/commands/__init__.py +16 -0
  47. apps/cli/commands/analysis_cmds.py +288 -0
  48. apps/cli/commands/backtest_cmds.py +1887 -0
  49. apps/cli/commands/broker_cmds.py +1154 -0
  50. apps/cli/commands/business_workflow_cmds.py +289 -0
  51. apps/cli/commands/catalog.py +84 -0
  52. apps/cli/commands/data_cmds.py +405 -0
  53. apps/cli/commands/diagnostic_cmds.py +179 -0
  54. apps/cli/commands/diagnostic_ops_cmds.py +696 -0
  55. apps/cli/commands/finance_render.py +12 -0
  56. apps/cli/commands/market.py +399 -0
  57. apps/cli/commands/market_cmds.py +1276 -0
  58. apps/cli/commands/market_context.py +425 -0
  59. apps/cli/commands/market_render.py +7 -0
  60. apps/cli/commands/model_cmds.py +1579 -0
  61. apps/cli/commands/ops_cmds.py +668 -0
  62. apps/cli/commands/portfolio_cmds.py +962 -0
  63. apps/cli/commands/report.py +377 -0
  64. apps/cli/commands/scaffold_templates.py +617 -0
  65. apps/cli/commands/session_cmds.py +179 -0
  66. apps/cli/commands/session_ux_cmds.py +280 -0
  67. apps/cli/commands/team.py +588 -0
  68. apps/cli/commands/team_render.py +8 -0
  69. apps/cli/commands/ui_cmds.py +358 -0
  70. apps/cli/commands/workflow_cmds.py +279 -0
  71. apps/cli/commands/workspace_cmds.py +1414 -0
  72. apps/cli/config_paths.py +70 -0
  73. apps/cli/config_store.py +61 -0
  74. apps/cli/deterministic.py +122 -0
  75. apps/cli/direct.py +48 -0
  76. apps/cli/github_app_auth.py +135 -0
  77. apps/cli/handlers/__init__.py +11 -0
  78. apps/cli/handlers/broker_handlers.py +122 -0
  79. apps/cli/handlers/chart_handlers.py +1309 -0
  80. apps/cli/handlers/market_handlers.py +2509 -0
  81. apps/cli/handlers/realty_handlers.py +114 -0
  82. apps/cli/handlers/strategy_advice.py +82 -0
  83. apps/cli/hooks.py +180 -0
  84. apps/cli/i18n.py +284 -0
  85. apps/cli/intent.py +136 -0
  86. apps/cli/intent_router.py +217 -0
  87. apps/cli/lifecycle_hooks.py +48 -0
  88. apps/cli/main.py +29 -0
  89. apps/cli/market_metadata.py +135 -0
  90. apps/cli/market_universe.py +265 -0
  91. apps/cli/message_processing.py +257 -0
  92. apps/cli/plan_mode.py +139 -0
  93. apps/cli/plotly_html.py +15 -0
  94. apps/cli/prediction_feedback.py +202 -0
  95. apps/cli/preflight.py +497 -0
  96. apps/cli/project_aria.py +60 -0
  97. apps/cli/prompts/__init__.py +0 -0
  98. apps/cli/prompts/coding.py +658 -0
  99. apps/cli/prompts/system_prompts.py +531 -0
  100. apps/cli/prompts/ui.py +434 -0
  101. apps/cli/providers/__init__.py +1 -0
  102. apps/cli/providers/base.py +271 -0
  103. apps/cli/providers/chat_routing.py +80 -0
  104. apps/cli/providers/llm/__init__.py +1 -0
  105. apps/cli/providers/llm/ollama_stream.py +1170 -0
  106. apps/cli/providers/llm/sse_stream.py +216 -0
  107. apps/cli/providers/runtime_bridge.py +185 -0
  108. apps/cli/runtime_consumer.py +489 -0
  109. apps/cli/session_export.py +87 -0
  110. apps/cli/session_jsonl.py +207 -0
  111. apps/cli/session_store.py +112 -0
  112. apps/cli/todo_tracker.py +190 -0
  113. apps/cli/tools/__init__.py +40 -0
  114. apps/cli/tools/context.py +46 -0
  115. apps/cli/tools/file_tools.py +112 -0
  116. apps/cli/tools/market_tools.py +549 -0
  117. apps/cli/tools/notebook_tools.py +111 -0
  118. apps/cli/tools/system_tools.py +669 -0
  119. apps/cli/tools/write_tools.py +715 -0
  120. apps/cli/tradingview_bridge.py +434 -0
  121. apps/cli/update_check.py +152 -0
  122. apps/cli/utils/__init__.py +0 -0
  123. apps/cli/utils/market_detect.py +1578 -0
  124. apps/daemon/README.md +14 -0
  125. apps/vscode/README.md +115 -0
  126. apps/vscode/package.json +70 -0
  127. aria_cli.py +11636 -0
  128. aria_code-4.1.3.dist-info/METADATA +952 -0
  129. aria_code-4.1.3.dist-info/RECORD +284 -0
  130. aria_code-4.1.3.dist-info/WHEEL +5 -0
  131. aria_code-4.1.3.dist-info/entry_points.txt +2 -0
  132. aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
  133. aria_code-4.1.3.dist-info/top_level.txt +50 -0
  134. aria_daemon.py +1295 -0
  135. aria_feishu_bot.py +1359 -0
  136. aria_relay_client.py +182 -0
  137. aria_relay_server.py +405 -0
  138. aria_telegram_bot.py +202 -0
  139. ariarc.py +328 -0
  140. artifacts.py +491 -0
  141. backtest_report.py +472 -0
  142. brokers/__init__.py +72 -0
  143. brokers/base.py +207 -0
  144. brokers/capabilities.py +264 -0
  145. brokers/cn/__init__.py +10 -0
  146. brokers/cn/easytrader_broker.py +193 -0
  147. brokers/cn/futu_broker.py +194 -0
  148. brokers/cn/longbridge_broker.py +190 -0
  149. brokers/cn/tiger_broker.py +196 -0
  150. brokers/cn/xtquant_broker.py +175 -0
  151. brokers/config.py +364 -0
  152. brokers/intl/__init__.py +5 -0
  153. brokers/intl/alpaca_broker.py +183 -0
  154. brokers/intl/ibkr_broker.py +215 -0
  155. brokers/intl/webull_broker.py +156 -0
  156. brokers/paper_broker.py +259 -0
  157. brokers/planning.py +296 -0
  158. brokers/registry.py +181 -0
  159. brokers/trading.py +237 -0
  160. change_store.py +127 -0
  161. command_safety.py +19 -0
  162. computer_use_tools.py +504 -0
  163. dashboard_generator.py +578 -0
  164. data_analysis_tools.py +808 -0
  165. data_cleaner.py +483 -0
  166. data_service.py +481 -0
  167. datasources/__init__.py +23 -0
  168. datasources/base.py +166 -0
  169. datasources/router.py +221 -0
  170. datasources/sources/__init__.py +15 -0
  171. datasources/sources/akshare_source.py +269 -0
  172. datasources/sources/alpha_vantage_source.py +202 -0
  173. datasources/sources/edgar_source.py +218 -0
  174. datasources/sources/finnhub_source.py +197 -0
  175. datasources/sources/fred_source.py +219 -0
  176. datasources/sources/tushare_source.py +141 -0
  177. datasources/sources/web_scraper_source.py +278 -0
  178. datasources/sources/world_bank_source.py +205 -0
  179. datasources/sources/yfinance_source.py +152 -0
  180. demo_player.py +204 -0
  181. doctor.py +508 -0
  182. file_analysis_tools.py +734 -0
  183. finance_formulas.py +389 -0
  184. football_data_client.py +1670 -0
  185. intent_classifier.py +358 -0
  186. local_finance_tools.py +3221 -0
  187. local_llm_provider.py +552 -0
  188. macro_tools.py +368 -0
  189. market_data_client.py +1899 -0
  190. mcp_client.py +506 -0
  191. memory_manager.py +245 -0
  192. model_capability.py +416 -0
  193. notification_tools.py +248 -0
  194. packages/__init__.py +23 -0
  195. packages/aria_agents/__init__.py +5 -0
  196. packages/aria_agents/manifest.py +69 -0
  197. packages/aria_core/__init__.py +34 -0
  198. packages/aria_core/architecture.py +192 -0
  199. packages/aria_core/export.py +124 -0
  200. packages/aria_core/manifest.py +65 -0
  201. packages/aria_infra/__init__.py +15 -0
  202. packages/aria_infra/arthera.py +52 -0
  203. packages/aria_infra/doctor.py +246 -0
  204. packages/aria_infra/product.py +37 -0
  205. packages/aria_mcp/__init__.py +25 -0
  206. packages/aria_mcp/bridge.py +38 -0
  207. packages/aria_mcp/config.py +97 -0
  208. packages/aria_mcp/tools.py +61 -0
  209. packages/aria_sdk/__init__.py +19 -0
  210. packages/aria_sdk/client.py +396 -0
  211. packages/aria_sdk/providers.py +70 -0
  212. packages/aria_sdk/streaming.py +73 -0
  213. packages/aria_sdk/types.py +86 -0
  214. packages/aria_services/__init__.py +55 -0
  215. packages/aria_services/context.py +258 -0
  216. packages/aria_services/data.py +11 -0
  217. packages/aria_services/provider_health.py +189 -0
  218. packages/aria_services/registry.py +213 -0
  219. packages/aria_services/usage.py +138 -0
  220. packages/aria_skills/__init__.py +5 -0
  221. packages/aria_skills/registry.py +59 -0
  222. packages/aria_tools/__init__.py +5 -0
  223. packages/aria_tools/registry.py +128 -0
  224. packages/quant_engine/__init__.py +6 -0
  225. packages/quant_engine/sports/__init__.py +72 -0
  226. packages/quant_engine/sports/calibrator.py +353 -0
  227. packages/quant_engine/sports/dixon_coles.py +234 -0
  228. packages/quant_engine/sports/elo.py +299 -0
  229. packages/quant_engine/sports/form.py +188 -0
  230. packages/quant_engine/sports/h2h.py +195 -0
  231. packages/quant_engine/sports/ml_model.py +354 -0
  232. packages/quant_engine/sports/predictor.py +311 -0
  233. packages/quant_engine/sports/tracker.py +664 -0
  234. packages/quant_engine/stochastic/__init__.py +27 -0
  235. packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  236. packages/quant_engine/stochastic/ito_calculus.py +477 -0
  237. packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  238. packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  239. packages/quant_engine/stochastic/options_pricing.py +573 -0
  240. packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  241. plan_utils.py +194 -0
  242. plugin_loader.py +328 -0
  243. portfolio_ledger.py +262 -0
  244. privacy/__init__.py +5 -0
  245. privacy/feedback.py +123 -0
  246. project_tools.py +525 -0
  247. providers/__init__.py +30 -0
  248. providers/llm/__init__.py +19 -0
  249. providers/llm/anthropic.py +184 -0
  250. providers/llm/base.py +139 -0
  251. providers/llm/ollama.py +128 -0
  252. providers/llm/openai_compat.py +282 -0
  253. providers/llm/registry.py +358 -0
  254. realty_data_tools.py +659 -0
  255. report_generator.py +1314 -0
  256. runtime/__init__.py +103 -0
  257. runtime/agent_loop.py +1183 -0
  258. runtime/approval.py +51 -0
  259. runtime/events.py +102 -0
  260. runtime/gateway.py +128 -0
  261. runtime/lsp.py +346 -0
  262. runtime/subagent.py +258 -0
  263. runtime/tool_executor.py +104 -0
  264. runtime/tool_policy.py +106 -0
  265. safety/__init__.py +21 -0
  266. safety/permissions.py +275 -0
  267. setup_wizard.py +653 -0
  268. strategy_vault.py +420 -0
  269. ui/__init__.py +100 -0
  270. ui/banner.py +310 -0
  271. ui/completer.py +391 -0
  272. ui/console.py +271 -0
  273. ui/image_render.py +243 -0
  274. ui/input_box.py +376 -0
  275. ui/picker.py +195 -0
  276. ui/render/__init__.py +11 -0
  277. ui/render/finance.py +1480 -0
  278. ui/render/market.py +225 -0
  279. ui/render/output.py +681 -0
  280. ui/render/team.py +346 -0
  281. ui/robot.py +235 -0
  282. workspace/__init__.py +6 -0
  283. workspace/files.py +170 -0
  284. workspace/verify.py +113 -0
computer_use_tools.py ADDED
@@ -0,0 +1,504 @@
1
+ """
2
+ computer_use_tools.py — Browser automation + desktop control tools for Aria Code.
3
+
4
+ Provides three LOCAL_TOOLS-compatible tool functions:
5
+ browser_navigate Open a URL and return page text + links (Playwright with requests fallback)
6
+ browser_screenshot Navigate to URL and capture a full-page screenshot
7
+ computer_screenshot Capture a screenshot of the current desktop
8
+ computer_action Control mouse / keyboard (click, type, scroll, move, hotkey)
9
+
10
+ Screenshot tools store the image in _PENDING_VISION_IMAGE so the agent loop can
11
+ inject it into the follow-up user message for vision-capable models.
12
+
13
+ Install:
14
+ pip install playwright mss pyautogui pillow
15
+ playwright install chromium
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import base64
21
+ import io
22
+ import logging
23
+ import os
24
+ import subprocess
25
+ import sys
26
+ import tempfile
27
+ import time
28
+ from typing import Optional
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ # Module-level slot for the last screenshot (base64 PNG).
33
+ # build_tool_followup in runtime/agent_loop.py checks this and injects
34
+ # the image into the follow-up user message for vision models.
35
+ _PENDING_VISION_IMAGE: Optional[str] = None # base64-encoded PNG
36
+
37
+
38
+ def _store_screenshot(b64: str) -> None:
39
+ global _PENDING_VISION_IMAGE
40
+ _PENDING_VISION_IMAGE = b64
41
+
42
+
43
+ def pop_pending_vision_image() -> Optional[str]:
44
+ """Consume and return the pending screenshot (None if none pending)."""
45
+ global _PENDING_VISION_IMAGE
46
+ val = _PENDING_VISION_IMAGE
47
+ _PENDING_VISION_IMAGE = None
48
+ return val
49
+
50
+
51
+ # ── Browser navigate ──────────────────────────────────────────────────────────
52
+
53
+ def _tool_browser_navigate(params: dict) -> dict:
54
+ """
55
+ Open a URL in a headless browser and return the page text content + links.
56
+ Uses Playwright if installed; falls back to requests+BeautifulSoup.
57
+ """
58
+ url = params.get("url", "").strip()
59
+ if not url:
60
+ return {"success": False, "error": "Missing 'url' parameter"}
61
+ if not url.startswith(("http://", "https://")):
62
+ url = "https://" + url
63
+
64
+ max_chars = min(int(params.get("max_chars", 12000)), 40000)
65
+ wait_for = params.get("wait_for", "domcontentloaded") # or "networkidle"
66
+
67
+ # --- Playwright path ---
68
+ try:
69
+ from playwright.sync_api import sync_playwright
70
+ with sync_playwright() as p:
71
+ browser = p.chromium.launch(headless=True)
72
+ page = browser.new_page(
73
+ user_agent=(
74
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
75
+ "AppleWebKit/537.36 Chrome/124.0 Safari/537.36"
76
+ )
77
+ )
78
+ page.goto(url, wait_until=wait_for, timeout=30000)
79
+ title = page.title()
80
+ text = page.inner_text("body")[:max_chars]
81
+ # Collect visible links
82
+ links = []
83
+ for a in page.query_selector_all("a[href]")[:30]:
84
+ href = a.get_attribute("href") or ""
85
+ label = (a.inner_text() or "").strip()[:60]
86
+ if href.startswith("http") and label:
87
+ links.append(f"[{label}]({href})")
88
+ browser.close()
89
+ return {
90
+ "success": True,
91
+ "data": {
92
+ "url": url,
93
+ "title": title,
94
+ "text": text,
95
+ "links": links[:20],
96
+ "length": len(text),
97
+ "engine": "playwright",
98
+ },
99
+ }
100
+ except ImportError:
101
+ pass
102
+ except Exception as exc:
103
+ logger.warning("playwright navigate failed: %s — falling back to requests", exc)
104
+
105
+ # --- requests fallback ---
106
+ try:
107
+ import re
108
+ import requests
109
+ r = requests.get(
110
+ url,
111
+ headers={"User-Agent": "Mozilla/5.0 Chrome/124.0"},
112
+ timeout=15,
113
+ verify=False,
114
+ )
115
+ r.raise_for_status()
116
+ raw = r.text
117
+ text = re.sub(r"<script[^>]*>.*?</script>", " ", raw, flags=re.DOTALL | re.I)
118
+ text = re.sub(r"<style[^>]*>.*?</style>", " ", text, flags=re.DOTALL | re.I)
119
+ text = re.sub(r"<[^>]+>", " ", text)
120
+ text = re.sub(r"&nbsp;", " ", text)
121
+ text = re.sub(r"\s{3,}", "\n", text).strip()
122
+ title_m = re.search(r"<title[^>]*>(.*?)</title>", raw, re.I | re.DOTALL)
123
+ title = title_m.group(1).strip() if title_m else url
124
+ return {
125
+ "success": True,
126
+ "data": {
127
+ "url": url,
128
+ "title": title,
129
+ "text": text[:max_chars],
130
+ "links": [],
131
+ "length": len(text),
132
+ "engine": "requests",
133
+ },
134
+ }
135
+ except Exception as exc:
136
+ return {"success": False, "error": f"Navigation failed: {exc}"}
137
+
138
+
139
+ # ── Browser screenshot ────────────────────────────────────────────────────────
140
+
141
+ def _tool_browser_screenshot(params: dict) -> dict:
142
+ """
143
+ Navigate to a URL and capture a full-page screenshot.
144
+ Stores the image in _PENDING_VISION_IMAGE for vision-model injection.
145
+ """
146
+ url = params.get("url", "").strip()
147
+ if not url:
148
+ return {"success": False, "error": "Missing 'url' parameter"}
149
+ if not url.startswith(("http://", "https://")):
150
+ url = "https://" + url
151
+
152
+ wait_for = params.get("wait_for", "networkidle")
153
+
154
+ try:
155
+ from playwright.sync_api import sync_playwright
156
+ with sync_playwright() as p:
157
+ browser = p.chromium.launch(headless=True)
158
+ page = browser.new_page(viewport={"width": 1280, "height": 900})
159
+ page.goto(url, wait_until=wait_for, timeout=30000)
160
+ title = page.title()
161
+ png_bytes = page.screenshot(full_page=False)
162
+ browser.close()
163
+
164
+ b64 = base64.b64encode(png_bytes).decode()
165
+ _store_screenshot(b64)
166
+ size_kb = len(png_bytes) // 1024
167
+ return {
168
+ "success": True,
169
+ "data": {
170
+ "url": url,
171
+ "title": title,
172
+ "size_kb": size_kb,
173
+ "width": 1280,
174
+ "height": 900,
175
+ "note": f"Screenshot captured ({size_kb} KB). Image attached to next message.",
176
+ },
177
+ }
178
+ except ImportError:
179
+ return {
180
+ "success": False,
181
+ "error": "Playwright not installed. Run: pip install playwright && playwright install chromium",
182
+ }
183
+ except Exception as exc:
184
+ return {"success": False, "error": f"Browser screenshot failed: {exc}"}
185
+
186
+
187
+ # ── Desktop screenshot ────────────────────────────────────────────────────────
188
+
189
+ def _tool_computer_screenshot(params: dict) -> dict:
190
+ """
191
+ Capture a screenshot of the current desktop screen.
192
+ Stores the image in _PENDING_VISION_IMAGE for vision-model injection.
193
+ """
194
+ monitor_idx = int(params.get("monitor", 1)) # 0 = all screens, 1 = primary
195
+ max_dim = int(params.get("max_dim", 1920)) # resize if larger
196
+
197
+ # --- mss path (fast, cross-platform) ---
198
+ try:
199
+ import mss
200
+ from PIL import Image
201
+
202
+ with mss.mss() as sct:
203
+ monitors = sct.monitors
204
+ mon = monitors[monitor_idx] if monitor_idx < len(monitors) else monitors[1]
205
+ raw = sct.grab(mon)
206
+ img = Image.frombytes("RGB", (raw.width, raw.height), raw.rgb)
207
+
208
+ # Resize if too large (keep aspect ratio)
209
+ w, h = img.size
210
+ if max(w, h) > max_dim:
211
+ scale = max_dim / max(w, h)
212
+ img = img.resize((int(w * scale), int(h * scale)), Image.LANCZOS)
213
+
214
+ buf = io.BytesIO()
215
+ img.save(buf, format="PNG", optimize=True)
216
+ b64 = base64.b64encode(buf.getvalue()).decode()
217
+ _store_screenshot(b64)
218
+ size_kb = len(buf.getvalue()) // 1024
219
+ return {
220
+ "success": True,
221
+ "data": {
222
+ "width": img.width,
223
+ "height": img.height,
224
+ "size_kb": size_kb,
225
+ "note": f"Desktop screenshot captured ({img.width}×{img.height}, {size_kb} KB). Image attached.",
226
+ },
227
+ }
228
+ except ImportError:
229
+ pass
230
+ except Exception as exc:
231
+ logger.warning("mss screenshot failed: %s", exc)
232
+
233
+ # --- screencapture fallback (macOS) ---
234
+ try:
235
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
236
+ tmp = f.name
237
+ subprocess.run(["screencapture", "-x", tmp], check=True, timeout=10)
238
+ from PIL import Image
239
+ img = Image.open(tmp)
240
+ w, h = img.size
241
+ if max(w, h) > max_dim:
242
+ scale = max_dim / max(w, h)
243
+ img = img.resize((int(w * scale), int(h * scale)), Image.LANCZOS)
244
+ buf = io.BytesIO()
245
+ img.save(buf, format="PNG")
246
+ b64 = base64.b64encode(buf.getvalue()).decode()
247
+ _store_screenshot(b64)
248
+ os.unlink(tmp)
249
+ return {
250
+ "success": True,
251
+ "data": {
252
+ "width": img.width,
253
+ "height": img.height,
254
+ "size_kb": len(buf.getvalue()) // 1024,
255
+ "note": f"Screenshot captured ({img.width}×{img.height}). Image attached.",
256
+ },
257
+ }
258
+ except Exception as exc:
259
+ return {"success": False, "error": f"Screenshot failed: {exc}. Install: pip install mss pillow"}
260
+
261
+
262
+ # ── Computer action ───────────────────────────────────────────────────────────
263
+
264
+ def _tool_computer_action(params: dict) -> dict:
265
+ """
266
+ Control the mouse or keyboard. Supported actions:
267
+ click (x, y) — left-click at screen coordinates
268
+ right_click (x, y) — right-click
269
+ double_click (x, y) — double-click
270
+ move (x, y) — move mouse without clicking
271
+ type (text) — type text via keyboard
272
+ key (key) — press a key or combo: "enter", "ctrl+c", "cmd+space"
273
+ scroll (x, y, dy) — scroll wheel: dy > 0 = down, dy < 0 = up
274
+ drag (x, y, ex, ey) — drag from (x,y) to (ex,ey)
275
+ """
276
+ action = params.get("action", "").lower()
277
+ x = int(params.get("x", 0))
278
+ y = int(params.get("y", 0))
279
+ text = params.get("text", "")
280
+ key = params.get("key", "")
281
+ dy = int(params.get("dy", 3))
282
+ ex = int(params.get("ex", x))
283
+ ey = int(params.get("ey", y))
284
+
285
+ if not action:
286
+ return {"success": False, "error": "Missing 'action' parameter"}
287
+
288
+ try:
289
+ import pyautogui
290
+ pyautogui.FAILSAFE = True
291
+ pyautogui.PAUSE = 0.05
292
+
293
+ if action == "click":
294
+ pyautogui.click(x, y)
295
+ return {"success": True, "data": {"action": "click", "x": x, "y": y}}
296
+
297
+ elif action == "right_click":
298
+ pyautogui.rightClick(x, y)
299
+ return {"success": True, "data": {"action": "right_click", "x": x, "y": y}}
300
+
301
+ elif action == "double_click":
302
+ pyautogui.doubleClick(x, y)
303
+ return {"success": True, "data": {"action": "double_click", "x": x, "y": y}}
304
+
305
+ elif action == "move":
306
+ pyautogui.moveTo(x, y, duration=0.2)
307
+ return {"success": True, "data": {"action": "move", "x": x, "y": y}}
308
+
309
+ elif action == "type":
310
+ if not text:
311
+ return {"success": False, "error": "Missing 'text' for type action"}
312
+ pyautogui.typewrite(text, interval=0.03)
313
+ return {"success": True, "data": {"action": "type", "chars": len(text)}}
314
+
315
+ elif action == "key":
316
+ if not key:
317
+ return {"success": False, "error": "Missing 'key' for key action"}
318
+ # Handle combos like "ctrl+c", "cmd+space"
319
+ parts = [k.strip() for k in key.replace("+", " ").split() if k.strip()]
320
+ if len(parts) > 1:
321
+ pyautogui.hotkey(*parts)
322
+ else:
323
+ pyautogui.press(parts[0])
324
+ return {"success": True, "data": {"action": "key", "key": key}}
325
+
326
+ elif action == "scroll":
327
+ pyautogui.scroll(dy, x=x, y=y)
328
+ return {"success": True, "data": {"action": "scroll", "x": x, "y": y, "dy": dy}}
329
+
330
+ elif action == "drag":
331
+ pyautogui.moveTo(x, y)
332
+ pyautogui.dragTo(ex, ey, duration=0.5, button="left")
333
+ return {"success": True, "data": {"action": "drag", "from": [x, y], "to": [ex, ey]}}
334
+
335
+ else:
336
+ return {
337
+ "success": False,
338
+ "error": (
339
+ f"Unknown action '{action}'. "
340
+ "Valid: click, right_click, double_click, move, type, key, scroll, drag"
341
+ ),
342
+ }
343
+ except ImportError:
344
+ return {
345
+ "success": False,
346
+ "error": "pyautogui not installed. Run: pip install pyautogui",
347
+ }
348
+ except pyautogui.FailSafeException:
349
+ return {
350
+ "success": False,
351
+ "error": "Fail-safe triggered: mouse moved to screen corner. Move away and retry.",
352
+ }
353
+ except Exception as exc:
354
+ return {"success": False, "error": f"computer_action failed: {exc}"}
355
+
356
+
357
+ # ── Tool schemas (Ollama / OpenAI function-calling format) ────────────────────
358
+
359
+ COMPUTER_USE_SCHEMAS = [
360
+ {
361
+ "type": "function",
362
+ "function": {
363
+ "name": "browser_navigate",
364
+ "description": (
365
+ "Open a URL in a headless browser and return the full page text content and links. "
366
+ "Use this to read web pages, documentation, news articles, financial data, or any URL. "
367
+ "Supports JavaScript-rendered pages (via Playwright) with fallback to requests."
368
+ ),
369
+ "parameters": {
370
+ "type": "object",
371
+ "properties": {
372
+ "url": {
373
+ "type": "string",
374
+ "description": "Full URL to navigate to, e.g. https://example.com",
375
+ },
376
+ "max_chars": {
377
+ "type": "integer",
378
+ "description": "Max characters of page text to return (default 12000, max 40000)",
379
+ },
380
+ "wait_for": {
381
+ "type": "string",
382
+ "enum": ["domcontentloaded", "networkidle", "load"],
383
+ "description": "When to consider navigation done (default: domcontentloaded)",
384
+ },
385
+ },
386
+ "required": ["url"],
387
+ },
388
+ },
389
+ },
390
+ {
391
+ "type": "function",
392
+ "function": {
393
+ "name": "browser_screenshot",
394
+ "description": (
395
+ "Navigate to a URL and capture a visual screenshot of the page. "
396
+ "The screenshot is automatically attached to the next message for visual analysis. "
397
+ "Use when you need to SEE the visual layout, charts, images, or UI of a web page."
398
+ ),
399
+ "parameters": {
400
+ "type": "object",
401
+ "properties": {
402
+ "url": {
403
+ "type": "string",
404
+ "description": "Full URL to screenshot",
405
+ },
406
+ "wait_for": {
407
+ "type": "string",
408
+ "enum": ["domcontentloaded", "networkidle", "load"],
409
+ "description": "Wait condition before capturing (default: networkidle)",
410
+ },
411
+ },
412
+ "required": ["url"],
413
+ },
414
+ },
415
+ },
416
+ {
417
+ "type": "function",
418
+ "function": {
419
+ "name": "computer_screenshot",
420
+ "description": (
421
+ "Capture a screenshot of the current desktop screen. "
422
+ "The image is automatically attached to the next message so you can see what's on screen. "
423
+ "Use this before computer_action to understand where to click."
424
+ ),
425
+ "parameters": {
426
+ "type": "object",
427
+ "properties": {
428
+ "monitor": {
429
+ "type": "integer",
430
+ "description": "Monitor index: 1 = primary (default), 0 = all monitors",
431
+ },
432
+ "max_dim": {
433
+ "type": "integer",
434
+ "description": "Max pixel dimension for resize (default 1920)",
435
+ },
436
+ },
437
+ "required": [],
438
+ },
439
+ },
440
+ },
441
+ {
442
+ "type": "function",
443
+ "function": {
444
+ "name": "computer_action",
445
+ "description": (
446
+ "Control the mouse and keyboard to interact with the desktop. "
447
+ "Always take a computer_screenshot first to see the current screen state. "
448
+ "Actions: click, right_click, double_click, move, type, key, scroll, drag."
449
+ ),
450
+ "parameters": {
451
+ "type": "object",
452
+ "properties": {
453
+ "action": {
454
+ "type": "string",
455
+ "enum": [
456
+ "click", "right_click", "double_click", "move",
457
+ "type", "key", "scroll", "drag",
458
+ ],
459
+ "description": "Action to perform",
460
+ },
461
+ "x": {
462
+ "type": "integer",
463
+ "description": "Screen X coordinate (pixels from left)",
464
+ },
465
+ "y": {
466
+ "type": "integer",
467
+ "description": "Screen Y coordinate (pixels from top)",
468
+ },
469
+ "text": {
470
+ "type": "string",
471
+ "description": "Text to type (for 'type' action)",
472
+ },
473
+ "key": {
474
+ "type": "string",
475
+ "description": "Key or combo to press, e.g. 'enter', 'ctrl+c', 'cmd+space'",
476
+ },
477
+ "dy": {
478
+ "type": "integer",
479
+ "description": "Scroll amount: positive = down, negative = up (for 'scroll' action)",
480
+ },
481
+ "ex": {
482
+ "type": "integer",
483
+ "description": "Drag end X coordinate (for 'drag' action)",
484
+ },
485
+ "ey": {
486
+ "type": "integer",
487
+ "description": "Drag end Y coordinate (for 'drag' action)",
488
+ },
489
+ },
490
+ "required": ["action"],
491
+ },
492
+ },
493
+ },
494
+ ]
495
+
496
+
497
+ # ── Tool registry for LOCAL_TOOLS ─────────────────────────────────────────────
498
+
499
+ COMPUTER_USE_TOOLS = {
500
+ "browser_navigate": (_tool_browser_navigate, "Open a URL and return page text content"),
501
+ "browser_screenshot": (_tool_browser_screenshot, "Navigate to URL and capture a screenshot"),
502
+ "computer_screenshot":(_tool_computer_screenshot,"Capture a screenshot of the current desktop"),
503
+ "computer_action": (_tool_computer_action, "Control mouse/keyboard (click, type, scroll)"),
504
+ }