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.
- agents/__init__.py +32 -0
- agents/base.py +190 -0
- agents/deep/__init__.py +37 -0
- agents/deep/calibration_loop.py +144 -0
- agents/deep/critic.py +125 -0
- agents/deep/deepen.py +193 -0
- agents/deep/models.py +149 -0
- agents/deep/pipeline.py +164 -0
- agents/deep/quant_fusion.py +192 -0
- agents/deep/themes.py +95 -0
- agents/deep/tiers.py +106 -0
- agents/financial/__init__.py +10 -0
- agents/financial/catalyst.py +279 -0
- agents/financial/debate.py +145 -0
- agents/financial/earnings.py +303 -0
- agents/financial/fundamental.py +159 -0
- agents/financial/macro.py +99 -0
- agents/financial/news.py +207 -0
- agents/financial/risk.py +132 -0
- agents/financial/sector.py +279 -0
- agents/financial/synthesis.py +274 -0
- agents/financial/technical.py +258 -0
- agents/portfolio_agent.py +333 -0
- agents/realty/__init__.py +62 -0
- agents/realty/asset_diagnosis.py +150 -0
- agents/realty/business_match.py +165 -0
- agents/realty/cashflow_verify.py +208 -0
- agents/realty/contract_rules.py +209 -0
- agents/realty/energy_anomaly.py +188 -0
- agents/realty/exit_settlement.py +207 -0
- agents/realty/fulfillment_risk.py +205 -0
- agents/realty/ops_optimize.py +159 -0
- agents/realty/revenue_share.py +214 -0
- agents/registry.py +144 -0
- agents/sports/__init__.py +0 -0
- agents/sports/football_agent.py +169 -0
- agents/team.py +289 -0
- aliyun_data_client.py +660 -0
- apps/README.md +12 -0
- apps/__init__.py +2 -0
- apps/channels/README.md +15 -0
- apps/cli/README.md +13 -0
- apps/cli/__init__.py +2 -0
- apps/cli/bootstrap.py +99 -0
- apps/cli/codegen_paths.py +29 -0
- apps/cli/commands/__init__.py +16 -0
- apps/cli/commands/analysis_cmds.py +288 -0
- apps/cli/commands/backtest_cmds.py +1887 -0
- apps/cli/commands/broker_cmds.py +1154 -0
- apps/cli/commands/business_workflow_cmds.py +289 -0
- apps/cli/commands/catalog.py +84 -0
- apps/cli/commands/data_cmds.py +405 -0
- apps/cli/commands/diagnostic_cmds.py +179 -0
- apps/cli/commands/diagnostic_ops_cmds.py +696 -0
- apps/cli/commands/finance_render.py +12 -0
- apps/cli/commands/market.py +399 -0
- apps/cli/commands/market_cmds.py +1276 -0
- apps/cli/commands/market_context.py +425 -0
- apps/cli/commands/market_render.py +7 -0
- apps/cli/commands/model_cmds.py +1579 -0
- apps/cli/commands/ops_cmds.py +668 -0
- apps/cli/commands/portfolio_cmds.py +962 -0
- apps/cli/commands/report.py +377 -0
- apps/cli/commands/scaffold_templates.py +617 -0
- apps/cli/commands/session_cmds.py +179 -0
- apps/cli/commands/session_ux_cmds.py +280 -0
- apps/cli/commands/team.py +588 -0
- apps/cli/commands/team_render.py +8 -0
- apps/cli/commands/ui_cmds.py +358 -0
- apps/cli/commands/workflow_cmds.py +279 -0
- apps/cli/commands/workspace_cmds.py +1414 -0
- apps/cli/config_paths.py +70 -0
- apps/cli/config_store.py +61 -0
- apps/cli/deterministic.py +122 -0
- apps/cli/direct.py +48 -0
- apps/cli/github_app_auth.py +135 -0
- apps/cli/handlers/__init__.py +11 -0
- apps/cli/handlers/broker_handlers.py +122 -0
- apps/cli/handlers/chart_handlers.py +1309 -0
- apps/cli/handlers/market_handlers.py +2509 -0
- apps/cli/handlers/realty_handlers.py +114 -0
- apps/cli/handlers/strategy_advice.py +82 -0
- apps/cli/hooks.py +180 -0
- apps/cli/i18n.py +284 -0
- apps/cli/intent.py +136 -0
- apps/cli/intent_router.py +217 -0
- apps/cli/lifecycle_hooks.py +48 -0
- apps/cli/main.py +29 -0
- apps/cli/market_metadata.py +135 -0
- apps/cli/market_universe.py +265 -0
- apps/cli/message_processing.py +257 -0
- apps/cli/plan_mode.py +139 -0
- apps/cli/plotly_html.py +15 -0
- apps/cli/prediction_feedback.py +202 -0
- apps/cli/preflight.py +497 -0
- apps/cli/project_aria.py +60 -0
- apps/cli/prompts/__init__.py +0 -0
- apps/cli/prompts/coding.py +658 -0
- apps/cli/prompts/system_prompts.py +531 -0
- apps/cli/prompts/ui.py +434 -0
- apps/cli/providers/__init__.py +1 -0
- apps/cli/providers/base.py +271 -0
- apps/cli/providers/chat_routing.py +80 -0
- apps/cli/providers/llm/__init__.py +1 -0
- apps/cli/providers/llm/ollama_stream.py +1170 -0
- apps/cli/providers/llm/sse_stream.py +216 -0
- apps/cli/providers/runtime_bridge.py +185 -0
- apps/cli/runtime_consumer.py +489 -0
- apps/cli/session_export.py +87 -0
- apps/cli/session_jsonl.py +207 -0
- apps/cli/session_store.py +112 -0
- apps/cli/todo_tracker.py +190 -0
- apps/cli/tools/__init__.py +40 -0
- apps/cli/tools/context.py +46 -0
- apps/cli/tools/file_tools.py +112 -0
- apps/cli/tools/market_tools.py +549 -0
- apps/cli/tools/notebook_tools.py +111 -0
- apps/cli/tools/system_tools.py +669 -0
- apps/cli/tools/write_tools.py +715 -0
- apps/cli/tradingview_bridge.py +434 -0
- apps/cli/update_check.py +152 -0
- apps/cli/utils/__init__.py +0 -0
- apps/cli/utils/market_detect.py +1578 -0
- apps/daemon/README.md +14 -0
- apps/vscode/README.md +115 -0
- apps/vscode/package.json +70 -0
- aria_cli.py +11636 -0
- aria_code-4.1.3.dist-info/METADATA +952 -0
- aria_code-4.1.3.dist-info/RECORD +284 -0
- aria_code-4.1.3.dist-info/WHEEL +5 -0
- aria_code-4.1.3.dist-info/entry_points.txt +2 -0
- aria_code-4.1.3.dist-info/licenses/LICENSE +121 -0
- aria_code-4.1.3.dist-info/top_level.txt +50 -0
- aria_daemon.py +1295 -0
- aria_feishu_bot.py +1359 -0
- aria_relay_client.py +182 -0
- aria_relay_server.py +405 -0
- aria_telegram_bot.py +202 -0
- ariarc.py +328 -0
- artifacts.py +491 -0
- backtest_report.py +472 -0
- brokers/__init__.py +72 -0
- brokers/base.py +207 -0
- brokers/capabilities.py +264 -0
- brokers/cn/__init__.py +10 -0
- brokers/cn/easytrader_broker.py +193 -0
- brokers/cn/futu_broker.py +194 -0
- brokers/cn/longbridge_broker.py +190 -0
- brokers/cn/tiger_broker.py +196 -0
- brokers/cn/xtquant_broker.py +175 -0
- brokers/config.py +364 -0
- brokers/intl/__init__.py +5 -0
- brokers/intl/alpaca_broker.py +183 -0
- brokers/intl/ibkr_broker.py +215 -0
- brokers/intl/webull_broker.py +156 -0
- brokers/paper_broker.py +259 -0
- brokers/planning.py +296 -0
- brokers/registry.py +181 -0
- brokers/trading.py +237 -0
- change_store.py +127 -0
- command_safety.py +19 -0
- computer_use_tools.py +504 -0
- dashboard_generator.py +578 -0
- data_analysis_tools.py +808 -0
- data_cleaner.py +483 -0
- data_service.py +481 -0
- datasources/__init__.py +23 -0
- datasources/base.py +166 -0
- datasources/router.py +221 -0
- datasources/sources/__init__.py +15 -0
- datasources/sources/akshare_source.py +269 -0
- datasources/sources/alpha_vantage_source.py +202 -0
- datasources/sources/edgar_source.py +218 -0
- datasources/sources/finnhub_source.py +197 -0
- datasources/sources/fred_source.py +219 -0
- datasources/sources/tushare_source.py +141 -0
- datasources/sources/web_scraper_source.py +278 -0
- datasources/sources/world_bank_source.py +205 -0
- datasources/sources/yfinance_source.py +152 -0
- demo_player.py +204 -0
- doctor.py +508 -0
- file_analysis_tools.py +734 -0
- finance_formulas.py +389 -0
- football_data_client.py +1670 -0
- intent_classifier.py +358 -0
- local_finance_tools.py +3221 -0
- local_llm_provider.py +552 -0
- macro_tools.py +368 -0
- market_data_client.py +1899 -0
- mcp_client.py +506 -0
- memory_manager.py +245 -0
- model_capability.py +416 -0
- notification_tools.py +248 -0
- packages/__init__.py +23 -0
- packages/aria_agents/__init__.py +5 -0
- packages/aria_agents/manifest.py +69 -0
- packages/aria_core/__init__.py +34 -0
- packages/aria_core/architecture.py +192 -0
- packages/aria_core/export.py +124 -0
- packages/aria_core/manifest.py +65 -0
- packages/aria_infra/__init__.py +15 -0
- packages/aria_infra/arthera.py +52 -0
- packages/aria_infra/doctor.py +246 -0
- packages/aria_infra/product.py +37 -0
- packages/aria_mcp/__init__.py +25 -0
- packages/aria_mcp/bridge.py +38 -0
- packages/aria_mcp/config.py +97 -0
- packages/aria_mcp/tools.py +61 -0
- packages/aria_sdk/__init__.py +19 -0
- packages/aria_sdk/client.py +396 -0
- packages/aria_sdk/providers.py +70 -0
- packages/aria_sdk/streaming.py +73 -0
- packages/aria_sdk/types.py +86 -0
- packages/aria_services/__init__.py +55 -0
- packages/aria_services/context.py +258 -0
- packages/aria_services/data.py +11 -0
- packages/aria_services/provider_health.py +189 -0
- packages/aria_services/registry.py +213 -0
- packages/aria_services/usage.py +138 -0
- packages/aria_skills/__init__.py +5 -0
- packages/aria_skills/registry.py +59 -0
- packages/aria_tools/__init__.py +5 -0
- packages/aria_tools/registry.py +128 -0
- packages/quant_engine/__init__.py +6 -0
- packages/quant_engine/sports/__init__.py +72 -0
- packages/quant_engine/sports/calibrator.py +353 -0
- packages/quant_engine/sports/dixon_coles.py +234 -0
- packages/quant_engine/sports/elo.py +299 -0
- packages/quant_engine/sports/form.py +188 -0
- packages/quant_engine/sports/h2h.py +195 -0
- packages/quant_engine/sports/ml_model.py +354 -0
- packages/quant_engine/sports/predictor.py +311 -0
- packages/quant_engine/sports/tracker.py +664 -0
- packages/quant_engine/stochastic/__init__.py +27 -0
- packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- packages/quant_engine/stochastic/ito_calculus.py +477 -0
- packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- packages/quant_engine/stochastic/options_pricing.py +573 -0
- packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- plan_utils.py +194 -0
- plugin_loader.py +328 -0
- portfolio_ledger.py +262 -0
- privacy/__init__.py +5 -0
- privacy/feedback.py +123 -0
- project_tools.py +525 -0
- providers/__init__.py +30 -0
- providers/llm/__init__.py +19 -0
- providers/llm/anthropic.py +184 -0
- providers/llm/base.py +139 -0
- providers/llm/ollama.py +128 -0
- providers/llm/openai_compat.py +282 -0
- providers/llm/registry.py +358 -0
- realty_data_tools.py +659 -0
- report_generator.py +1314 -0
- runtime/__init__.py +103 -0
- runtime/agent_loop.py +1183 -0
- runtime/approval.py +51 -0
- runtime/events.py +102 -0
- runtime/gateway.py +128 -0
- runtime/lsp.py +346 -0
- runtime/subagent.py +258 -0
- runtime/tool_executor.py +104 -0
- runtime/tool_policy.py +106 -0
- safety/__init__.py +21 -0
- safety/permissions.py +275 -0
- setup_wizard.py +653 -0
- strategy_vault.py +420 -0
- ui/__init__.py +100 -0
- ui/banner.py +310 -0
- ui/completer.py +391 -0
- ui/console.py +271 -0
- ui/image_render.py +243 -0
- ui/input_box.py +376 -0
- ui/picker.py +195 -0
- ui/render/__init__.py +11 -0
- ui/render/finance.py +1480 -0
- ui/render/market.py +225 -0
- ui/render/output.py +681 -0
- ui/render/team.py +346 -0
- ui/robot.py +235 -0
- workspace/__init__.py +6 -0
- workspace/files.py +170 -0
- 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" ", " ", 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
|
+
}
|