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
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""stream_chat — Aria cloud SSE provider extracted from aria_cli.py.
|
|
2
|
+
|
|
3
|
+
Streams AI responses from the Aria backend via Server-Sent Events (SSE).
|
|
4
|
+
Supports cancellation, thinking tokens, tool calls, retries, and usage stats.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
from typing import Callable, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def stream_chat(
|
|
14
|
+
base_url: str,
|
|
15
|
+
message: str,
|
|
16
|
+
history: list,
|
|
17
|
+
model: str = "qwen2.5:7b",
|
|
18
|
+
thinking_mode: str = "auto",
|
|
19
|
+
user_context: Optional[dict] = None,
|
|
20
|
+
auth_token: Optional[str] = None,
|
|
21
|
+
on_token: Optional[Callable[[str], None]] = None,
|
|
22
|
+
on_thinking: Optional[Callable[[str], None]] = None,
|
|
23
|
+
on_tool_call: Optional[Callable[[str, dict], None]] = None,
|
|
24
|
+
on_tool_result: Optional[Callable[[str, str], None]] = None,
|
|
25
|
+
on_status: Optional[Callable[[str, str], None]] = None,
|
|
26
|
+
cancel_event: Optional[asyncio.Event] = None,
|
|
27
|
+
project_context: str = "",
|
|
28
|
+
) -> dict:
|
|
29
|
+
"""Stream AI chat via SSE with cancel support and user context.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
project_context:
|
|
34
|
+
ARIA.md / CLAUDE.md content to inject into user_context.
|
|
35
|
+
Callers pass ``_PROJECT_CONTEXT`` from aria_cli, keeping this
|
|
36
|
+
module free of global state.
|
|
37
|
+
"""
|
|
38
|
+
import aiohttp
|
|
39
|
+
|
|
40
|
+
url = f"{base_url}/api/v2/ai/chat/stream"
|
|
41
|
+
|
|
42
|
+
payload: dict = {
|
|
43
|
+
"message": message,
|
|
44
|
+
"conversation_history": history[-20:],
|
|
45
|
+
"model": model,
|
|
46
|
+
"thinking_mode": thinking_mode,
|
|
47
|
+
"stream": True,
|
|
48
|
+
}
|
|
49
|
+
if user_context:
|
|
50
|
+
if project_context:
|
|
51
|
+
user_context = {**user_context, "project_context": project_context}
|
|
52
|
+
payload["user_context"] = user_context
|
|
53
|
+
|
|
54
|
+
headers: dict = {}
|
|
55
|
+
if auth_token:
|
|
56
|
+
headers["Authorization"] = f"Bearer {auth_token}"
|
|
57
|
+
|
|
58
|
+
full_response = ""
|
|
59
|
+
thinking_content = ""
|
|
60
|
+
tools_used: list = []
|
|
61
|
+
sources: list = []
|
|
62
|
+
tool_calls_pending: list = []
|
|
63
|
+
usage: dict = {"prompt_tokens": 0, "completion_tokens": 0, "thinking_tokens": 0}
|
|
64
|
+
|
|
65
|
+
_max_connect_retries = 2
|
|
66
|
+
_last_connect_error: Optional[str] = None
|
|
67
|
+
|
|
68
|
+
for _attempt in range(_max_connect_retries + 1):
|
|
69
|
+
if cancel_event and cancel_event.is_set():
|
|
70
|
+
return {
|
|
71
|
+
"success": True, "response": "", "cancelled": True,
|
|
72
|
+
"tools_used": [], "sources": [], "usage": usage,
|
|
73
|
+
}
|
|
74
|
+
# Reset per-attempt accumulators
|
|
75
|
+
full_response = ""
|
|
76
|
+
thinking_content = ""
|
|
77
|
+
tools_used = []
|
|
78
|
+
sources = []
|
|
79
|
+
tool_calls_pending = []
|
|
80
|
+
usage = {"prompt_tokens": 0, "completion_tokens": 0, "thinking_tokens": 0}
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
async with aiohttp.ClientSession() as session:
|
|
84
|
+
async with session.post(
|
|
85
|
+
url, json=payload, headers=headers,
|
|
86
|
+
timeout=aiohttp.ClientTimeout(total=120),
|
|
87
|
+
) as resp:
|
|
88
|
+
if resp.status != 200:
|
|
89
|
+
error_text = await resp.text()
|
|
90
|
+
return {"success": False, "error": f"HTTP {resp.status}: {error_text[:200]}"}
|
|
91
|
+
|
|
92
|
+
buffer = ""
|
|
93
|
+
event_type = "delta"
|
|
94
|
+
|
|
95
|
+
async for chunk in resp.content:
|
|
96
|
+
if cancel_event and cancel_event.is_set():
|
|
97
|
+
try:
|
|
98
|
+
await session.post(
|
|
99
|
+
f"{base_url}/api/v2/ai/chat/cancel",
|
|
100
|
+
headers=headers,
|
|
101
|
+
timeout=aiohttp.ClientTimeout(total=3),
|
|
102
|
+
)
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
return {
|
|
106
|
+
"success": True, "response": full_response,
|
|
107
|
+
"cancelled": True, "tools_used": tools_used,
|
|
108
|
+
"sources": sources, "usage": usage,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
text = chunk.decode("utf-8", errors="ignore")
|
|
112
|
+
buffer += text
|
|
113
|
+
|
|
114
|
+
while "\n" in buffer:
|
|
115
|
+
line, buffer = buffer.split("\n", 1)
|
|
116
|
+
line = line.strip()
|
|
117
|
+
|
|
118
|
+
if not line or line.startswith(":"):
|
|
119
|
+
continue
|
|
120
|
+
if line.startswith("event:"):
|
|
121
|
+
event_type = line[6:].strip()
|
|
122
|
+
continue
|
|
123
|
+
if not line.startswith("data:"):
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
data_str = line[5:].strip()
|
|
127
|
+
if data_str == "[DONE]":
|
|
128
|
+
break
|
|
129
|
+
try:
|
|
130
|
+
data = json.loads(data_str)
|
|
131
|
+
except json.JSONDecodeError:
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
# Backend error: {"success": false, "error": "..."}
|
|
135
|
+
if data.get("success") is False:
|
|
136
|
+
err_msg = data.get("error", "Backend error")
|
|
137
|
+
return {"success": False, "error": f"Backend: {err_msg}"}
|
|
138
|
+
|
|
139
|
+
evt = data.get("type", event_type)
|
|
140
|
+
|
|
141
|
+
if evt == "delta":
|
|
142
|
+
token = data.get("text", data.get("content", ""))
|
|
143
|
+
if token:
|
|
144
|
+
full_response += token
|
|
145
|
+
usage["completion_tokens"] += 1
|
|
146
|
+
if on_token:
|
|
147
|
+
on_token(token)
|
|
148
|
+
|
|
149
|
+
elif evt == "thinking_content":
|
|
150
|
+
tc = data.get("content", "")
|
|
151
|
+
if tc:
|
|
152
|
+
thinking_content += tc
|
|
153
|
+
usage["thinking_tokens"] += 1
|
|
154
|
+
if on_thinking:
|
|
155
|
+
on_thinking(tc)
|
|
156
|
+
|
|
157
|
+
elif evt == "tool_call":
|
|
158
|
+
tool = data.get("tool", data.get("name", ""))
|
|
159
|
+
params = data.get("params", {})
|
|
160
|
+
tools_used.append(tool)
|
|
161
|
+
tool_calls_pending.append({"tool": tool, "params": params})
|
|
162
|
+
if on_tool_call:
|
|
163
|
+
on_tool_call(tool, params)
|
|
164
|
+
|
|
165
|
+
elif evt == "tool_result":
|
|
166
|
+
if on_tool_result:
|
|
167
|
+
on_tool_result(data.get("tool", ""), data.get("summary", ""))
|
|
168
|
+
|
|
169
|
+
elif evt == "status":
|
|
170
|
+
if on_status:
|
|
171
|
+
on_status(data.get("state", ""), data.get("message", ""))
|
|
172
|
+
|
|
173
|
+
elif evt == "final":
|
|
174
|
+
full_response = data.get("answer", full_response)
|
|
175
|
+
sources = data.get("sources", [])
|
|
176
|
+
if data.get("usage"):
|
|
177
|
+
u = data["usage"]
|
|
178
|
+
usage["prompt_tokens"] = u.get("prompt_tokens", usage["prompt_tokens"])
|
|
179
|
+
usage["completion_tokens"] = u.get("completion_tokens", usage["completion_tokens"])
|
|
180
|
+
|
|
181
|
+
elif evt == "error":
|
|
182
|
+
return {"success": False, "error": data.get("message", "Unknown error")}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
"success": True,
|
|
186
|
+
"response": full_response,
|
|
187
|
+
"thinking": thinking_content,
|
|
188
|
+
"tools_used": tools_used,
|
|
189
|
+
"sources": sources,
|
|
190
|
+
"tool_calls_pending": tool_calls_pending,
|
|
191
|
+
"usage": usage,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
except asyncio.TimeoutError:
|
|
195
|
+
return {"success": False, "error": "Request timed out (120s)"}
|
|
196
|
+
except asyncio.CancelledError:
|
|
197
|
+
return {
|
|
198
|
+
"success": True, "response": full_response, "cancelled": True,
|
|
199
|
+
"tools_used": tools_used, "sources": sources, "usage": usage,
|
|
200
|
+
}
|
|
201
|
+
except aiohttp.ClientConnectorError as exc:
|
|
202
|
+
_last_connect_error = str(exc)
|
|
203
|
+
if _attempt < _max_connect_retries:
|
|
204
|
+
wait = 1.5 * (_attempt + 1)
|
|
205
|
+
await asyncio.sleep(wait)
|
|
206
|
+
if on_status:
|
|
207
|
+
on_status("retry", f"Connection failed, retrying ({_attempt + 2}/{_max_connect_retries + 1})...")
|
|
208
|
+
continue
|
|
209
|
+
break
|
|
210
|
+
except Exception as exc:
|
|
211
|
+
return {"success": False, "error": str(exc)}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
"success": False,
|
|
215
|
+
"error": f"Connection failed after {_max_connect_retries + 1} attempts: {_last_connect_error}",
|
|
216
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Phase-3 bridge: run one chat turn through the shared ``runtime.run_agent``.
|
|
2
|
+
|
|
3
|
+
The documented runtime next step is to route the CLI tool loop through
|
|
4
|
+
``run_agent`` and keep aria_cli as orchestration glue. This module is that engine
|
|
5
|
+
— the two adapters run_agent needs plus a thin driver:
|
|
6
|
+
|
|
7
|
+
• build_tool_executor() — wraps the CLI's ``LOCAL_TOOLS`` ({name: (handler, schema)})
|
|
8
|
+
• make_provider_fn() — selects the provider (chat_routing) + streams it
|
|
9
|
+
• run_chat_via_runtime() — drives run_agent, renders via callbacks, returns text
|
|
10
|
+
|
|
11
|
+
It is opt-in: ``send_message`` only uses it when ``config['use_runtime_loop']`` is
|
|
12
|
+
on, and falls back to the proven inline loop on any error. That keeps the live
|
|
13
|
+
path untouched until the runtime path is verified in a real REPL.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any, Callable, List, Optional
|
|
19
|
+
|
|
20
|
+
from .chat_routing import first_round_route, is_placeholder_response, should_fallback
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_tool_executor(local_tools, config: Optional[dict] = None):
|
|
24
|
+
"""Wrap the CLI's LOCAL_TOOLS registry for run_agent."""
|
|
25
|
+
from runtime.tool_executor import ToolExecutor
|
|
26
|
+
return ToolExecutor(local_tools, config=config or {})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def run_with_fallback(
|
|
30
|
+
route: str,
|
|
31
|
+
*,
|
|
32
|
+
run_cloud: Callable,
|
|
33
|
+
run_ollama: Callable,
|
|
34
|
+
on_token: Optional[Callable[[str], None]] = None,
|
|
35
|
+
) -> dict:
|
|
36
|
+
"""Primary generation per ``route``, with cloud→Ollama fallback parity.
|
|
37
|
+
|
|
38
|
+
Mirrors ``send_message``'s inline fallback, but keyed on *route* (via
|
|
39
|
+
``should_fallback``) so a genuinely-good forced-backend answer is kept
|
|
40
|
+
instead of being discarded and re-run:
|
|
41
|
+
|
|
42
|
+
• ``skip`` / ``ollama`` → run local Ollama directly (no cloud round)
|
|
43
|
+
• ``cloud`` → run cloud; if it fails or returns a placeholder
|
|
44
|
+
(empty / canned / backend stub), fall back to
|
|
45
|
+
local Ollama
|
|
46
|
+
|
|
47
|
+
``run_cloud`` / ``run_ollama`` are async ``(on_token) -> result dict``
|
|
48
|
+
closures; injecting them keeps this orchestration unit-testable without
|
|
49
|
+
real providers or network.
|
|
50
|
+
"""
|
|
51
|
+
if route in ("skip", "ollama"):
|
|
52
|
+
return await run_ollama(on_token)
|
|
53
|
+
|
|
54
|
+
# route == "cloud": count streamed tokens so a long-but-unstreamed canned
|
|
55
|
+
# backend reply is recognised as a placeholder, not a real generation.
|
|
56
|
+
_tokens = [0]
|
|
57
|
+
|
|
58
|
+
def _counting_on_token(tok: str) -> None:
|
|
59
|
+
_tokens[0] += 1
|
|
60
|
+
if on_token is not None:
|
|
61
|
+
on_token(tok)
|
|
62
|
+
|
|
63
|
+
result = await run_cloud(_counting_on_token)
|
|
64
|
+
if result.get("cancelled"):
|
|
65
|
+
return result # user-cancelled — never silently re-run on a different provider
|
|
66
|
+
|
|
67
|
+
placeholder = is_placeholder_response(result.get("response", ""), _tokens[0])
|
|
68
|
+
if should_fallback("cloud", result, is_placeholder=placeholder):
|
|
69
|
+
return await run_ollama(on_token)
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def make_provider_fn(
|
|
74
|
+
*,
|
|
75
|
+
model: str,
|
|
76
|
+
config: dict,
|
|
77
|
+
api_url: Optional[str],
|
|
78
|
+
ollama_url: str,
|
|
79
|
+
tool_schemas: List[dict],
|
|
80
|
+
thinking_mode: str = "auto",
|
|
81
|
+
user_context: Optional[dict] = None,
|
|
82
|
+
auth_token: Optional[str] = None,
|
|
83
|
+
project_context: Any = None,
|
|
84
|
+
system_override: Optional[str] = None,
|
|
85
|
+
) -> Callable:
|
|
86
|
+
"""Build an async ``provider_fn`` for run_agent.
|
|
87
|
+
|
|
88
|
+
Selects the provider per chat_routing (cloud → AriaSSE backend; ollama/skip →
|
|
89
|
+
local Ollama) and streams it through the shared ``stream_provider_result``.
|
|
90
|
+
A pending system-role override is threaded the same way ``send_message`` does
|
|
91
|
+
it: cloud via ``user_context['system_role_override']``, Ollama via the
|
|
92
|
+
provider's ``system_override`` argument.
|
|
93
|
+
"""
|
|
94
|
+
from apps.cli.providers.base import AriaSSEProvider, OllamaProvider
|
|
95
|
+
from packages.aria_sdk.streaming import stream_provider_result
|
|
96
|
+
|
|
97
|
+
_cloud_uctx = dict(user_context or {})
|
|
98
|
+
if system_override:
|
|
99
|
+
_cloud_uctx["system_role_override"] = system_override
|
|
100
|
+
|
|
101
|
+
async def _provider_fn(prompt, history, *, on_token=None, on_thinking=None,
|
|
102
|
+
on_tool_call=None, on_tool_result=None, on_status=None,
|
|
103
|
+
cancel_event=None):
|
|
104
|
+
route = first_round_route(model, config, api_url)
|
|
105
|
+
|
|
106
|
+
async def _stream(provider, _on_token):
|
|
107
|
+
return await stream_provider_result(
|
|
108
|
+
provider, prompt, history, tools=tool_schemas,
|
|
109
|
+
cancel_event=cancel_event, on_token=_on_token, on_thinking=on_thinking,
|
|
110
|
+
on_tool_call=on_tool_call, on_tool_result=on_tool_result, on_status=on_status,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
async def run_cloud(_on_token):
|
|
114
|
+
return await _stream(
|
|
115
|
+
AriaSSEProvider(
|
|
116
|
+
api_url, model, thinking_mode=thinking_mode,
|
|
117
|
+
user_context=_cloud_uctx, auth_token=auth_token,
|
|
118
|
+
project_context=project_context,
|
|
119
|
+
),
|
|
120
|
+
_on_token,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
async def run_ollama(_on_token):
|
|
124
|
+
return await _stream(
|
|
125
|
+
OllamaProvider(ollama_url, model, system_override=system_override),
|
|
126
|
+
_on_token,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return await run_with_fallback(
|
|
130
|
+
route, run_cloud=run_cloud, run_ollama=run_ollama, on_token=on_token,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return _provider_fn
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def run_chat_via_runtime(
|
|
137
|
+
*,
|
|
138
|
+
prompt: str,
|
|
139
|
+
history: list,
|
|
140
|
+
local_tools,
|
|
141
|
+
tool_schemas: List[dict],
|
|
142
|
+
model: str,
|
|
143
|
+
config: dict,
|
|
144
|
+
api_url: Optional[str],
|
|
145
|
+
ollama_url: str,
|
|
146
|
+
cancel_event=None,
|
|
147
|
+
on_token: Optional[Callable[[str], None]] = None,
|
|
148
|
+
on_thinking: Optional[Callable[[str], None]] = None,
|
|
149
|
+
on_tool_call: Optional[Callable[[str, dict], None]] = None,
|
|
150
|
+
on_tool_result: Optional[Callable[[str, dict], None]] = None,
|
|
151
|
+
on_status: Optional[Callable[[str, str], None]] = None,
|
|
152
|
+
thinking_mode: str = "auto",
|
|
153
|
+
user_context: Optional[dict] = None,
|
|
154
|
+
auth_token: Optional[str] = None,
|
|
155
|
+
project_context: Any = None,
|
|
156
|
+
system_override: Optional[str] = None,
|
|
157
|
+
max_rounds: int = 30,
|
|
158
|
+
) -> str:
|
|
159
|
+
"""Run one chat turn through the shared runtime Gateway; return the text.
|
|
160
|
+
|
|
161
|
+
This is the CLI *adapter* for ``runtime.gateway.run_turn``: it builds the
|
|
162
|
+
CLI's ``provider_fn`` (AriaSSE/Ollama selection + cloud→Ollama fallback) and
|
|
163
|
+
tool executor (the LOCAL_TOOLS registry), then hands them to the neutral
|
|
164
|
+
gateway, which drives ``run_agent`` and streams via the callbacks. Returns
|
|
165
|
+
the assistant response text ("" if none).
|
|
166
|
+
"""
|
|
167
|
+
from runtime.gateway import run_turn
|
|
168
|
+
|
|
169
|
+
provider_fn = make_provider_fn(
|
|
170
|
+
model=model, config=config, api_url=api_url, ollama_url=ollama_url,
|
|
171
|
+
tool_schemas=tool_schemas, thinking_mode=thinking_mode,
|
|
172
|
+
user_context=user_context, auth_token=auth_token, project_context=project_context,
|
|
173
|
+
system_override=system_override,
|
|
174
|
+
)
|
|
175
|
+
executor = build_tool_executor(local_tools, config)
|
|
176
|
+
|
|
177
|
+
result = await run_turn(
|
|
178
|
+
prompt, history,
|
|
179
|
+
provider_fn=provider_fn, tool_executor=executor,
|
|
180
|
+
tool_schemas=list(tool_schemas),
|
|
181
|
+
on_token=on_token, on_thinking=on_thinking,
|
|
182
|
+
on_tool_call=on_tool_call, on_tool_result=on_tool_result, on_status=on_status,
|
|
183
|
+
cancel_event=cancel_event, max_rounds=max_rounds,
|
|
184
|
+
)
|
|
185
|
+
return result.text
|