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
project_tools.py ADDED
@@ -0,0 +1,525 @@
1
+ """
2
+ project_tools.py — 项目文件夹分析引擎
3
+ ========================================
4
+ 提供 Claude Code / Codex 同等能力:
5
+ - 递归扫描项目目录,构建文件树
6
+ - 自动检测项目类型(Python / Node / Go / Rust / Java / …)
7
+ - 智能读取关键文件注入 LLM 上下文
8
+ - 跨文件 grep / 符号索引
9
+ - Git 状态集成
10
+ - 按 token 预算动态裁剪上下文
11
+
12
+ ProjectSession 生命周期:
13
+ 1. scan(path) — 扫描目录,建立索引
14
+ 2. build_llm_context — 生成注入 system prompt 的上下文块
15
+ 3. get_file / grep — LLM 工具调用时按需读取
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import fnmatch
21
+ import json
22
+ import os
23
+ import re
24
+ import subprocess
25
+ from dataclasses import dataclass, field
26
+ from pathlib import Path
27
+ from typing import Any, Dict, List, Optional, Set, Tuple
28
+
29
+ # ── 忽略规则 ──────────────────────────────────────────────────────────────────
30
+
31
+ _IGNORE_DIRS: Set[str] = {
32
+ ".git", ".hg", ".svn",
33
+ "node_modules", ".pnp",
34
+ "__pycache__", ".mypy_cache", ".ruff_cache", ".pytest_cache",
35
+ ".venv", "venv", "env", ".env",
36
+ "dist", "build", "out", "target", ".next", ".nuxt",
37
+ "coverage", ".coverage", "htmlcov",
38
+ ".idea", ".vscode", ".DS_Store",
39
+ "eggs", "*.egg-info",
40
+ }
41
+
42
+ _IGNORE_FILE_PATTERNS: List[str] = [
43
+ "*.pyc", "*.pyo", "*.pyd",
44
+ "*.so", "*.dll", "*.dylib",
45
+ "*.class", "*.jar",
46
+ "*.min.js", "*.min.css",
47
+ "*.map",
48
+ "*.lock", # package-lock.json 例外:内容有用,但体积太大
49
+ "*.log",
50
+ ".DS_Store", "Thumbs.db",
51
+ "*.bin", "*.exe", "*.whl", "*.tar.gz", "*.zip",
52
+ "*.jpg", "*.jpeg", "*.png", "*.gif", "*.webp", "*.ico",
53
+ "*.mp4", "*.mp3", "*.pdf",
54
+ ]
55
+
56
+ _BINARY_EXTENSIONS: Set[str] = {
57
+ ".pyc",".pyo",".pyd",".so",".dll",".dylib",".class",".jar",
58
+ ".bin",".exe",".whl",".tar",".gz",".zip",".rar",
59
+ ".jpg",".jpeg",".png",".gif",".bmp",".webp",".ico",
60
+ ".mp4",".mp3",".wav",".avi",".mkv",
61
+ ".pdf",".doc",".xls",".ppt",
62
+ }
63
+
64
+ # ── 项目类型指纹 ──────────────────────────────────────────────────────────────
65
+
66
+ _PROJECT_SIGNATURES: List[Tuple[str, List[str], str]] = [
67
+ # (类型名, 指纹文件列表, 主入口候选)
68
+ ("Python", ["pyproject.toml", "setup.py", "requirements.txt"], "main.py"),
69
+ ("Node.js", ["package.json"], "index.js"),
70
+ ("TypeScript", ["tsconfig.json"], "src/index.ts"),
71
+ ("Go", ["go.mod"], "main.go"),
72
+ ("Rust", ["Cargo.toml"], "src/main.rs"),
73
+ ("Java", ["pom.xml", "build.gradle"], "src/main/java"),
74
+ ("Kotlin", ["build.gradle.kts"], "src/main/kotlin"),
75
+ ("Ruby", ["Gemfile"], "main.rb"),
76
+ ("PHP", ["composer.json"], "index.php"),
77
+ ("C#", ["*.csproj", "*.sln"], "Program.cs"),
78
+ ("C/C++", ["CMakeLists.txt", "Makefile"], "main.c"),
79
+ ("Swift", ["Package.swift"], "Sources/main.swift"),
80
+ ("Dart/Flutter",["pubspec.yaml"], "lib/main.dart"),
81
+ ("Elixir", ["mix.exs"], "lib/main.ex"),
82
+ ]
83
+
84
+ # 关键配置文件(权重高,一定要读)
85
+ _KEY_CONFIG_FILES: List[str] = [
86
+ "README.md", "README.rst", "README.txt",
87
+ "pyproject.toml", "setup.py", "requirements.txt",
88
+ "package.json",
89
+ "go.mod",
90
+ "Cargo.toml",
91
+ "pom.xml",
92
+ "Makefile",
93
+ ".env.example", ".env.sample",
94
+ "docker-compose.yml", "Dockerfile",
95
+ ".github/workflows", # CI
96
+ ]
97
+
98
+ # ── 数据结构 ──────────────────────────────────────────────────────────────────
99
+
100
+ @dataclass
101
+ class FileNode:
102
+ rel_path: str
103
+ abs_path: str
104
+ size: int
105
+ ext: str
106
+ is_dir: bool = False
107
+ language: str = ""
108
+
109
+
110
+ @dataclass
111
+ class ProjectSession:
112
+ """Loaded project state — persisted in ArtheraTerminal._project_session."""
113
+ root: str = ""
114
+ name: str = ""
115
+ project_type: str = ""
116
+ languages: List[str] = field(default_factory=list)
117
+ files: List[FileNode] = field(default_factory=list)
118
+ key_contents: Dict[str, str] = field(default_factory=dict) # rel_path → text
119
+ git_info: Dict[str, Any] = field(default_factory=dict)
120
+ stats: Dict[str, Any] = field(default_factory=dict)
121
+ _tree_cache: str = field(default="", repr=False)
122
+
123
+ # ── Scan ──────────────────────────────────────────────────────────────────
124
+
125
+ def scan(self, path: str, max_files: int = 2000) -> "ProjectSession":
126
+ root = Path(path).expanduser().resolve()
127
+ if not root.exists():
128
+ raise FileNotFoundError(f"路径不存在: {root}")
129
+ if not root.is_dir():
130
+ raise ValueError(f"不是目录: {root}")
131
+
132
+ self.root = str(root)
133
+ self.name = root.name
134
+ self.files = []
135
+ self._tree_cache = ""
136
+
137
+ # Walk directory
138
+ for dirpath, dirnames, filenames in os.walk(root):
139
+ # Prune ignored dirs in-place
140
+ dirnames[:] = [
141
+ d for d in dirnames
142
+ if d not in _IGNORE_DIRS
143
+ and not d.startswith(".")
144
+ or d in {".github", ".gitlab"}
145
+ ]
146
+ # Also skip dirs matching patterns
147
+ dirnames[:] = [
148
+ d for d in dirnames
149
+ if not any(fnmatch.fnmatch(d, p) for p in ["*.egg-info", "*.dist-info"])
150
+ ]
151
+
152
+ for fname in filenames:
153
+ if len(self.files) >= max_files:
154
+ break
155
+ if any(fnmatch.fnmatch(fname, p) for p in _IGNORE_FILE_PATTERNS):
156
+ continue
157
+ fpath = Path(dirpath) / fname
158
+ try:
159
+ size = fpath.stat().st_size
160
+ except OSError:
161
+ continue
162
+ ext = fpath.suffix.lower()
163
+ if ext in _BINARY_EXTENSIONS:
164
+ continue
165
+ rel = str(fpath.relative_to(root))
166
+ self.files.append(FileNode(
167
+ rel_path=rel,
168
+ abs_path=str(fpath),
169
+ size=size,
170
+ ext=ext,
171
+ language=_ext_to_lang(ext),
172
+ ))
173
+
174
+ # Detect project type
175
+ root_names = {f.rel_path for f in self.files}
176
+ self.project_type = "Unknown"
177
+ detected_langs: List[str] = []
178
+ for ptype, fingerprints, _ in _PROJECT_SIGNATURES:
179
+ for fp in fingerprints:
180
+ if "*" in fp:
181
+ if any(fnmatch.fnmatch(r, fp) for r in root_names):
182
+ self.project_type = ptype
183
+ detected_langs.append(ptype)
184
+ break
185
+ elif fp in root_names:
186
+ self.project_type = ptype
187
+ detected_langs.append(ptype)
188
+ break
189
+ # Language frequency from extensions
190
+ lang_counts: Dict[str, int] = {}
191
+ for f in self.files:
192
+ if f.language:
193
+ lang_counts[f.language] = lang_counts.get(f.language, 0) + 1
194
+ top_langs = sorted(lang_counts, key=lang_counts.get, reverse=True)[:4]
195
+ self.languages = list(dict.fromkeys(detected_langs + top_langs))[:5]
196
+
197
+ # Stats
198
+ total_lines = 0
199
+ for f in self.files:
200
+ if f.size < 500_000 and f.ext not in _BINARY_EXTENSIONS:
201
+ try:
202
+ total_lines += Path(f.abs_path).read_text(errors="replace").count("\n")
203
+ except Exception:
204
+ pass
205
+ self.stats = {
206
+ "total_files": len(self.files),
207
+ "total_lines": total_lines,
208
+ "total_size_kb": sum(f.size for f in self.files) // 1024,
209
+ "languages": lang_counts,
210
+ }
211
+
212
+ # Read key files
213
+ self.key_contents = {}
214
+ self._load_key_files(root)
215
+
216
+ # Git info
217
+ self.git_info = _get_git_info(str(root))
218
+
219
+ return self
220
+
221
+ def _load_key_files(self, root: Path, max_chars_each: int = 4000):
222
+ """Read README + config files into key_contents."""
223
+ for rel in _KEY_CONFIG_FILES:
224
+ candidate = root / rel
225
+ if candidate.is_file():
226
+ try:
227
+ text = candidate.read_text(errors="replace")[:max_chars_each]
228
+ self.key_contents[rel] = text
229
+ except Exception:
230
+ pass
231
+
232
+ # Auto-detect main entry point
233
+ for _, fingerprints, entry in _PROJECT_SIGNATURES:
234
+ for fp in fingerprints:
235
+ if "*" in fp:
236
+ continue
237
+ if (root / fp).exists():
238
+ ep = root / entry
239
+ if ep.is_file() and entry not in self.key_contents:
240
+ try:
241
+ self.key_contents[entry] = ep.read_text(errors="replace")[:4000]
242
+ except Exception:
243
+ pass
244
+ break
245
+
246
+ # ── Tree rendering ─────────────────────────────────────────────────────────
247
+
248
+ def get_tree(self, max_lines: int = 80) -> str:
249
+ """Return ASCII file tree string."""
250
+ if self._tree_cache:
251
+ return self._tree_cache
252
+
253
+ root = Path(self.root)
254
+ # Build nested dict
255
+ tree: Dict[str, Any] = {}
256
+ for f in self.files:
257
+ parts = Path(f.rel_path).parts
258
+ node = tree
259
+ for part in parts[:-1]:
260
+ node = node.setdefault(part, {})
261
+ node[parts[-1]] = f.size
262
+
263
+ lines: List[str] = [f"{self.name}/"]
264
+ _render_tree(tree, lines, "", max_lines)
265
+ if len(lines) > max_lines:
266
+ lines = lines[:max_lines]
267
+ lines.append(f" … (仅显示前 {max_lines} 条,共 {len(self.files)} 个文件)")
268
+ result = "\n".join(lines)
269
+ self._tree_cache = result
270
+ return result
271
+
272
+ # ── LLM context ───────────────────────────────────────────────────────────
273
+
274
+ def build_llm_context(self, max_chars: int = 16_000) -> str:
275
+ """Build the system-prompt context block injected for every message."""
276
+ parts: List[str] = []
277
+ budget = max_chars
278
+
279
+ # Header
280
+ git_branch = self.git_info.get("branch", "")
281
+ header = (
282
+ f"=== 已加载项目: {self.name} ===\n"
283
+ f"路径: {self.root}\n"
284
+ f"类型: {self.project_type} | 语言: {', '.join(self.languages[:3])}\n"
285
+ f"文件: {self.stats.get('total_files',0)} 个 "
286
+ f"代码行: {self.stats.get('total_lines',0):,} "
287
+ f"大小: {self.stats.get('total_size_kb',0)} KB"
288
+ )
289
+ if git_branch:
290
+ header += f"\nGit 分支: {git_branch}"
291
+ if self.git_info.get("recent_commits"):
292
+ commits = self.git_info["recent_commits"][:3]
293
+ header += "\n最近提交:\n" + "\n".join(f" {c}" for c in commits)
294
+ parts.append(header)
295
+ budget -= len(header)
296
+
297
+ # File tree (cap at 3000 chars)
298
+ tree_str = self.get_tree(max_lines=60)
299
+ tree_block = f"\n--- 文件结构 ---\n{tree_str}"
300
+ if len(tree_block) <= min(budget, 3000):
301
+ parts.append(tree_block)
302
+ budget -= len(tree_block)
303
+
304
+ # Key file contents
305
+ if budget > 1000:
306
+ for rel, content in self.key_contents.items():
307
+ if budget <= 500:
308
+ break
309
+ snippet = content[:min(len(content), budget // max(1, len(self.key_contents)))]
310
+ block = f"\n--- {rel} ---\n{snippet}"
311
+ parts.append(block)
312
+ budget -= len(block)
313
+
314
+ # Tool capability hint
315
+ tool_hint = (
316
+ "\n--- 可用工具 ---\n"
317
+ "你可以调用以下工具来完成任务:\n"
318
+ " read_file(path) — 读取项目中任意文件\n"
319
+ " write_file(path, content) — 创建或覆写文件\n"
320
+ " edit_file(path, old, new) — 精确查找替换\n"
321
+ " search_code(pattern, path) — 跨文件正则搜索\n"
322
+ " list_files(path) — 列出目录内容\n"
323
+ " run_command(command, cwd) — 在项目目录执行 shell 命令\n"
324
+ "所有路径相对于项目根目录,或使用绝对路径。\n"
325
+ "执行任务时先规划步骤,再逐步调用工具完成。"
326
+ )
327
+ parts.append(tool_hint)
328
+
329
+ return "\n".join(parts)
330
+
331
+ # ── File reading (safe, within project root) ───────────────────────────────
332
+
333
+ def read_file(self, rel_or_abs: str, max_chars: int = 40_000) -> Tuple[bool, str]:
334
+ """Read a file within the project. Returns (ok, content_or_error)."""
335
+ p = Path(rel_or_abs)
336
+ if not p.is_absolute():
337
+ p = Path(self.root) / rel_or_abs
338
+ p = p.resolve()
339
+ root = Path(self.root).resolve()
340
+ if not str(p).startswith(str(root)):
341
+ return False, f"安全限制:路径 {p} 在项目目录之外"
342
+ if not p.exists():
343
+ return False, f"文件不存在: {p}"
344
+ if not p.is_file():
345
+ return False, f"不是文件: {p}"
346
+ try:
347
+ content = p.read_text(errors="replace")[:max_chars]
348
+ return True, content
349
+ except Exception as e:
350
+ return False, str(e)
351
+
352
+ # ── Grep ──────────────────────────────────────────────────────────────────
353
+
354
+ def grep(self, pattern: str, glob: str = "**/*",
355
+ max_results: int = 60) -> List[Dict[str, Any]]:
356
+ """Regex search across project files."""
357
+ results: List[Dict[str, Any]] = []
358
+ try:
359
+ rx = re.compile(pattern, re.IGNORECASE)
360
+ except re.error as e:
361
+ return [{"error": f"正则表达式错误: {e}"}]
362
+
363
+ root = Path(self.root)
364
+ for f in self.files:
365
+ if len(results) >= max_results:
366
+ break
367
+ if f.ext in _BINARY_EXTENSIONS or f.size > 2_000_000:
368
+ continue
369
+ if glob != "**/*":
370
+ if not fnmatch.fnmatch(f.rel_path, glob):
371
+ continue
372
+ try:
373
+ lines = Path(f.abs_path).read_text(errors="replace").splitlines()
374
+ for i, line in enumerate(lines, 1):
375
+ if rx.search(line):
376
+ results.append({
377
+ "file": f.rel_path,
378
+ "line": i,
379
+ "content": line.strip()[:200],
380
+ })
381
+ if len(results) >= max_results:
382
+ break
383
+ except Exception:
384
+ continue
385
+ return results
386
+
387
+ # ── Summary ───────────────────────────────────────────────────────────────
388
+
389
+ def summary(self) -> Dict[str, Any]:
390
+ lang_counts = self.stats.get("languages", {})
391
+ top_langs = sorted(lang_counts, key=lang_counts.get, reverse=True)[:5]
392
+ return {
393
+ "name": self.name,
394
+ "root": self.root,
395
+ "type": self.project_type,
396
+ "languages": top_langs,
397
+ "total_files": self.stats.get("total_files", 0),
398
+ "total_lines": self.stats.get("total_lines", 0),
399
+ "total_size_kb":self.stats.get("total_size_kb", 0),
400
+ "key_files": list(self.key_contents.keys()),
401
+ "git": self.git_info,
402
+ }
403
+
404
+
405
+ # ── Tree renderer ─────────────────────────────────────────────────────────────
406
+
407
+ def _render_tree(node: dict, lines: list, prefix: str, max_lines: int):
408
+ items = sorted(node.items(), key=lambda kv: (isinstance(kv[1], dict), kv[0]))
409
+ for i, (name, val) in enumerate(items):
410
+ if len(lines) >= max_lines:
411
+ return
412
+ is_last = (i == len(items) - 1)
413
+ connector = "└── " if is_last else "├── "
414
+ if isinstance(val, dict):
415
+ lines.append(f"{prefix}{connector}{name}/")
416
+ ext_prefix = prefix + (" " if is_last else "│ ")
417
+ _render_tree(val, lines, ext_prefix, max_lines)
418
+ else:
419
+ size_str = _fmt_size(val)
420
+ lines.append(f"{prefix}{connector}{name} [{size_str}]")
421
+
422
+
423
+ def _fmt_size(size: int) -> str:
424
+ if size < 1024: return f"{size}B"
425
+ if size < 1048576: return f"{size//1024}KB"
426
+ return f"{size//1048576}MB"
427
+
428
+
429
+ # ── Extension → language ──────────────────────────────────────────────────────
430
+
431
+ _EXT_LANG: Dict[str, str] = {
432
+ ".py":"Python", ".pyx":"Python", ".pyi":"Python",
433
+ ".js":"JavaScript", ".jsx":"JavaScript", ".mjs":"JavaScript",
434
+ ".ts":"TypeScript", ".tsx":"TypeScript",
435
+ ".go":"Go",
436
+ ".rs":"Rust",
437
+ ".java":"Java", ".kt":"Kotlin", ".kts":"Kotlin",
438
+ ".rb":"Ruby",
439
+ ".php":"PHP",
440
+ ".cs":"C#",
441
+ ".c":"C", ".h":"C", ".cpp":"C++", ".cc":"C++", ".hpp":"C++",
442
+ ".swift":"Swift",
443
+ ".dart":"Dart",
444
+ ".ex":"Elixir", ".exs":"Elixir",
445
+ ".html":"HTML", ".htm":"HTML",
446
+ ".css":"CSS", ".scss":"CSS", ".sass":"CSS", ".less":"CSS",
447
+ ".json":"JSON", ".jsonl":"JSON",
448
+ ".yaml":"YAML", ".yml":"YAML",
449
+ ".toml":"TOML",
450
+ ".md":"Markdown", ".mdx":"Markdown",
451
+ ".sh":"Shell", ".bash":"Shell", ".zsh":"Shell",
452
+ ".sql":"SQL",
453
+ ".tf":"Terraform", ".hcl":"Terraform",
454
+ ".proto":"Protobuf",
455
+ ".r":"R", ".R":"R",
456
+ ".lua":"Lua",
457
+ ".vim":"VimScript",
458
+ }
459
+
460
+ def _ext_to_lang(ext: str) -> str:
461
+ return _EXT_LANG.get(ext, "")
462
+
463
+
464
+ # ── Git integration ───────────────────────────────────────────────────────────
465
+
466
+ def _get_git_info(root: str) -> Dict[str, Any]:
467
+ info: Dict[str, Any] = {}
468
+ if not (Path(root) / ".git").exists():
469
+ return info
470
+
471
+ def _git(*args, capture=True) -> str:
472
+ try:
473
+ r = subprocess.run(
474
+ ["git", *args], cwd=root,
475
+ capture_output=capture, text=True, timeout=5
476
+ )
477
+ return r.stdout.strip() if r.returncode == 0 else ""
478
+ except Exception:
479
+ return ""
480
+
481
+ info["branch"] = _git("rev-parse", "--abbrev-ref", "HEAD")
482
+ info["remote"] = _git("remote", "get-url", "origin")
483
+ status_out = _git("status", "--short")
484
+ if status_out:
485
+ changed = [l for l in status_out.splitlines() if l.strip()]
486
+ info["changed_files"] = changed[:20]
487
+ info["changed_count"] = len(changed)
488
+ else:
489
+ info["changed_files"] = []
490
+ info["changed_count"] = 0
491
+ log_out = _git("log", "--oneline", "-8")
492
+ if log_out:
493
+ info["recent_commits"] = log_out.splitlines()
494
+ diff_stat = _git("diff", "--stat", "HEAD")
495
+ if diff_stat:
496
+ info["diff_stat"] = diff_stat[:800]
497
+ return info
498
+
499
+
500
+ # ── Standalone helpers (called by /project commands) ─────────────────────────
501
+
502
+ def scan_project(path: str, max_files: int = 2000) -> ProjectSession:
503
+ """Convenience: create + scan a new ProjectSession."""
504
+ s = ProjectSession()
505
+ s.scan(path, max_files=max_files)
506
+ return s
507
+
508
+
509
+ def format_grep_results(results: List[Dict[str, Any]],
510
+ pattern: str, max_show: int = 40) -> str:
511
+ """Format grep results for Rich console display."""
512
+ if not results:
513
+ return f'没有找到匹配 "{pattern}" 的内容'
514
+ if results and "error" in results[0]:
515
+ return results[0]["error"]
516
+ lines = [f'搜索 "{pattern}" — {len(results)} 个匹配\n']
517
+ cur_file = None
518
+ for r in results[:max_show]:
519
+ if r["file"] != cur_file:
520
+ cur_file = r["file"]
521
+ lines.append(f"\n 📄 {cur_file}")
522
+ lines.append(f" {r['line']:>4} │ {r['content']}")
523
+ if len(results) > max_show:
524
+ lines.append(f"\n … 还有 {len(results) - max_show} 个结果")
525
+ return "\n".join(lines)
providers/__init__.py ADDED
@@ -0,0 +1,30 @@
1
+ """
2
+ providers/ — Aria Code 统一 LLM Provider 层
3
+ ============================================
4
+ 用户在 ~/.aria/providers.yaml 或项目 .aria.json 里声明提供商,
5
+ CLI 自动按优先级路由,任何一级失败自动降级到下一级。
6
+
7
+ 支持的提供商:
8
+ 本地: ollama, lmstudio, vllm, llamacpp, jan
9
+ 云端: deepseek, openai, anthropic, groq, together, dashscope
10
+
11
+ 快速使用:
12
+ from providers.llm.registry import get_provider, stream_cloud_fallback
13
+ provider = get_provider("deepseek")
14
+ async for chunk in provider.stream(messages):
15
+ print(chunk["text"], end="", flush=True)
16
+ """
17
+
18
+ from .llm.registry import (
19
+ get_provider,
20
+ list_available_providers,
21
+ stream_cloud_fallback,
22
+ register_provider,
23
+ )
24
+
25
+ __all__ = [
26
+ "get_provider",
27
+ "list_available_providers",
28
+ "stream_cloud_fallback",
29
+ "register_provider",
30
+ ]
@@ -0,0 +1,19 @@
1
+ from .base import BaseLLMProvider, Message, ProviderConfig
2
+ from .ollama import OllamaProvider
3
+ from .openai_compat import (
4
+ DeepSeekProvider, OpenAIProvider, GroqProvider,
5
+ TogetherProvider, DashScopeProvider, LMStudioProvider,
6
+ SiliconFlowProvider, MoonshotProvider, ZhiPuProvider,
7
+ )
8
+ from .anthropic import AnthropicProvider
9
+ from .registry import get_provider, list_available_providers, stream_cloud_fallback, register_provider
10
+
11
+ __all__ = [
12
+ "BaseLLMProvider", "Message", "ProviderConfig",
13
+ "OllamaProvider", "DeepSeekProvider", "OpenAIProvider",
14
+ "AnthropicProvider", "GroqProvider", "TogetherProvider",
15
+ "DashScopeProvider", "LMStudioProvider",
16
+ "SiliconFlowProvider", "MoonshotProvider", "ZhiPuProvider",
17
+ "get_provider", "list_available_providers",
18
+ "stream_cloud_fallback", "register_provider",
19
+ ]