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,489 @@
|
|
|
1
|
+
"""CLI consumers for runtime/model events.
|
|
2
|
+
|
|
3
|
+
The runtime core emits typed events and asks for approval decisions. This
|
|
4
|
+
module keeps terminal-specific rendering and prompts at the CLI adapter layer
|
|
5
|
+
instead of letting them spread through the agent loop.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
from typing import Any, Callable
|
|
14
|
+
|
|
15
|
+
from runtime import (
|
|
16
|
+
AgentEventStatus,
|
|
17
|
+
AgentEventThinking,
|
|
18
|
+
AgentEventToken,
|
|
19
|
+
AgentEventToolCall,
|
|
20
|
+
AgentEventToolResult,
|
|
21
|
+
ApprovalDecision,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_REPETITION_MARKER = "*[model stopped — repetition detected]*"
|
|
26
|
+
_REPETITION_NOTICE = (
|
|
27
|
+
"\n\n> 已检测到模型开始重复输出,已自动停止展开。"
|
|
28
|
+
"上方结果仍然有效;如需继续,请指定要补充的部分。"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TerminalRuntimeEventConsumer:
|
|
33
|
+
"""Consume runtime/provider events and render them to a terminal."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
*,
|
|
38
|
+
terminal: Any,
|
|
39
|
+
console: Any,
|
|
40
|
+
has_rich: bool,
|
|
41
|
+
markdown_cls: type | None,
|
|
42
|
+
live_cls: type | None,
|
|
43
|
+
strip_latex: Callable[[str], str],
|
|
44
|
+
set_robot_state: Callable[[Any], None] | None = None,
|
|
45
|
+
streaming_state: Any = None,
|
|
46
|
+
print_tool_call: Callable[[str, dict], None] | None = None,
|
|
47
|
+
print_tool_done: Callable[[str, int, bool], None] | None = None,
|
|
48
|
+
fallback_from: str = "local",
|
|
49
|
+
live_update_interval: float = 0.08,
|
|
50
|
+
) -> None:
|
|
51
|
+
self.terminal = terminal
|
|
52
|
+
self.console = console
|
|
53
|
+
self.has_rich = has_rich
|
|
54
|
+
self.markdown_cls = markdown_cls
|
|
55
|
+
self.live_cls = live_cls
|
|
56
|
+
self.strip_latex = strip_latex
|
|
57
|
+
self.set_robot_state = set_robot_state
|
|
58
|
+
self.streaming_state = streaming_state
|
|
59
|
+
self.print_tool_call = print_tool_call
|
|
60
|
+
self.print_tool_done = print_tool_done
|
|
61
|
+
self.fallback_from = fallback_from
|
|
62
|
+
self.live_update_interval = live_update_interval
|
|
63
|
+
|
|
64
|
+
self.response_text = ""
|
|
65
|
+
self.streamed_any = False
|
|
66
|
+
self.token_count = 0
|
|
67
|
+
self.thinking_tokens = 0
|
|
68
|
+
self.thinking_shown = False
|
|
69
|
+
self.thinking_start: float | None = None
|
|
70
|
+
self.thinking_finished = False
|
|
71
|
+
self.thinking_preview_buf: list[str] = []
|
|
72
|
+
self.thinking_full_buf: list[str] = []
|
|
73
|
+
self.tool_start_times: dict[str, float] = {}
|
|
74
|
+
self.repetition_stopped = False
|
|
75
|
+
self.repetition_notice_printed = False
|
|
76
|
+
|
|
77
|
+
self.live_display = None
|
|
78
|
+
self.spinner = None
|
|
79
|
+
self.first_token_received_ref = [False]
|
|
80
|
+
self.token_start_time: float | None = None
|
|
81
|
+
self.last_live_update = 0.0
|
|
82
|
+
self.use_plain_print_ref = [False]
|
|
83
|
+
self.use_batch_render_ref = [False]
|
|
84
|
+
self.latex_buf_ref = [""]
|
|
85
|
+
self.in_latex_ref = [False]
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def first_token_received(self) -> bool:
|
|
89
|
+
return self.first_token_received_ref[0]
|
|
90
|
+
|
|
91
|
+
@first_token_received.setter
|
|
92
|
+
def first_token_received(self, value: bool) -> None:
|
|
93
|
+
self.first_token_received_ref[0] = value
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def use_plain_print(self) -> bool:
|
|
97
|
+
return self.use_plain_print_ref[0]
|
|
98
|
+
|
|
99
|
+
@use_plain_print.setter
|
|
100
|
+
def use_plain_print(self, value: bool) -> None:
|
|
101
|
+
self.use_plain_print_ref[0] = value
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def use_batch_render(self) -> bool:
|
|
105
|
+
return self.use_batch_render_ref[0]
|
|
106
|
+
|
|
107
|
+
@use_batch_render.setter
|
|
108
|
+
def use_batch_render(self, value: bool) -> None:
|
|
109
|
+
self.use_batch_render_ref[0] = value
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def latex_buf(self) -> str:
|
|
113
|
+
return self.latex_buf_ref[0]
|
|
114
|
+
|
|
115
|
+
@latex_buf.setter
|
|
116
|
+
def latex_buf(self, value: str) -> None:
|
|
117
|
+
self.latex_buf_ref[0] = value
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def in_latex(self) -> bool:
|
|
121
|
+
return self.in_latex_ref[0]
|
|
122
|
+
|
|
123
|
+
@in_latex.setter
|
|
124
|
+
def in_latex(self, value: bool) -> None:
|
|
125
|
+
self.in_latex_ref[0] = value
|
|
126
|
+
|
|
127
|
+
def start_spinner(self) -> None:
|
|
128
|
+
if self.has_rich and self.spinner is None and not self.first_token_received:
|
|
129
|
+
self.spinner = self.console.status(
|
|
130
|
+
"[dim]思考中… [/dim][dim italic]esc 取消[/dim italic]",
|
|
131
|
+
spinner="dots",
|
|
132
|
+
spinner_style="dim",
|
|
133
|
+
)
|
|
134
|
+
self.spinner.__enter__()
|
|
135
|
+
|
|
136
|
+
def stop_spinner(self) -> None:
|
|
137
|
+
if self.spinner is not None:
|
|
138
|
+
try:
|
|
139
|
+
self.spinner.__exit__(None, None, None)
|
|
140
|
+
except Exception:
|
|
141
|
+
pass
|
|
142
|
+
self.spinner = None
|
|
143
|
+
|
|
144
|
+
def stop_live(self, discard: bool = False) -> None:
|
|
145
|
+
self.stop_spinner()
|
|
146
|
+
if self.live_display:
|
|
147
|
+
try:
|
|
148
|
+
if discard:
|
|
149
|
+
try:
|
|
150
|
+
from rich.text import Text as _RichText
|
|
151
|
+
|
|
152
|
+
self.live_display.update(_RichText(""))
|
|
153
|
+
self.live_display.refresh()
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
self.live_display.stop()
|
|
157
|
+
except Exception:
|
|
158
|
+
pass
|
|
159
|
+
self.live_display = None
|
|
160
|
+
elif self.first_token_received and not discard and not self.use_batch_render:
|
|
161
|
+
print(flush=True)
|
|
162
|
+
|
|
163
|
+
def set_batch_render_mode(self, enabled: bool = True) -> None:
|
|
164
|
+
self.use_plain_print = enabled
|
|
165
|
+
self.use_batch_render = enabled
|
|
166
|
+
|
|
167
|
+
def reset_stream_state(self) -> None:
|
|
168
|
+
self.response_text = ""
|
|
169
|
+
self.streamed_any = False
|
|
170
|
+
self.token_count = 0
|
|
171
|
+
self.first_token_received = False
|
|
172
|
+
self.token_start_time = None
|
|
173
|
+
self.latex_buf = ""
|
|
174
|
+
self.in_latex = False
|
|
175
|
+
|
|
176
|
+
def flush_latex_buf(self) -> str:
|
|
177
|
+
raw = self.latex_buf
|
|
178
|
+
self.latex_buf = ""
|
|
179
|
+
self.in_latex = False
|
|
180
|
+
return self.strip_latex(raw) if raw.strip() else raw
|
|
181
|
+
|
|
182
|
+
def _show_repetition_notice(self) -> None:
|
|
183
|
+
if self.repetition_notice_printed or self.use_batch_render:
|
|
184
|
+
return
|
|
185
|
+
self.repetition_notice_printed = True
|
|
186
|
+
if self.live_display and self.has_rich and self.markdown_cls is not None:
|
|
187
|
+
clean_text = self.response_text.split(_REPETITION_MARKER, 1)[0].rstrip()
|
|
188
|
+
self.live_display.update(self.markdown_cls(self.strip_latex(clean_text + _REPETITION_NOTICE)))
|
|
189
|
+
self.live_display.refresh()
|
|
190
|
+
return
|
|
191
|
+
print(_REPETITION_NOTICE, end="", flush=True)
|
|
192
|
+
|
|
193
|
+
def finalize_text(self, final_text: str) -> str:
|
|
194
|
+
if self.in_latex and self.latex_buf:
|
|
195
|
+
leftover = self.flush_latex_buf()
|
|
196
|
+
final_text = (final_text or "") + leftover
|
|
197
|
+
if self.use_plain_print and not self.use_batch_render:
|
|
198
|
+
print(leftover, end="", flush=True)
|
|
199
|
+
return final_text
|
|
200
|
+
|
|
201
|
+
def _finish_thinking(self) -> None:
|
|
202
|
+
if not self.thinking_shown or self.thinking_finished:
|
|
203
|
+
return
|
|
204
|
+
self.thinking_finished = True
|
|
205
|
+
self.stop_spinner()
|
|
206
|
+
elapsed_t = time.time() - self.thinking_start if self.thinking_start else 0
|
|
207
|
+
self.terminal._last_thinking = "".join(self.thinking_full_buf).strip()
|
|
208
|
+
t_info = f"Thought for {elapsed_t:.1f}s"
|
|
209
|
+
if self.thinking_tokens > 0:
|
|
210
|
+
t_info += f" · {self.thinking_tokens:,} tokens"
|
|
211
|
+
ctrlo = " [dim]· Ctrl+O 展开[/dim]" if self.terminal._last_thinking else ""
|
|
212
|
+
if self.has_rich:
|
|
213
|
+
sys.stdout.write("\r\033[K")
|
|
214
|
+
sys.stdout.flush()
|
|
215
|
+
self.console.print(f" [dim]✻[/dim] [dim]{t_info}[/dim]{ctrlo}")
|
|
216
|
+
if self.terminal.config.get("thinking_preview") and self.thinking_preview_buf:
|
|
217
|
+
preview_text = "".join(self.thinking_preview_buf)[:280].strip()
|
|
218
|
+
if len("".join(self.thinking_preview_buf)) > 280:
|
|
219
|
+
preview_text += "…"
|
|
220
|
+
self.console.print(f" [dim italic]{preview_text}[/dim italic]")
|
|
221
|
+
else:
|
|
222
|
+
print(f"\r ✻ {t_info}")
|
|
223
|
+
|
|
224
|
+
def on_token(self, token: str) -> None:
|
|
225
|
+
if not self.first_token_received:
|
|
226
|
+
self.first_token_received = True
|
|
227
|
+
self.token_start_time = time.time()
|
|
228
|
+
if self.set_robot_state is not None:
|
|
229
|
+
self.set_robot_state(self.streaming_state)
|
|
230
|
+
if not self.use_batch_render:
|
|
231
|
+
self.stop_spinner()
|
|
232
|
+
|
|
233
|
+
if "<|im_start|>" in token or "<|im_end|>" in token:
|
|
234
|
+
token = token.replace("<|im_start|>", "").replace("<|im_end|>", "")
|
|
235
|
+
if not token.strip():
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
meta_artifacts = (
|
|
239
|
+
"(注释:",
|
|
240
|
+
"(注释:",
|
|
241
|
+
"(提示:",
|
|
242
|
+
"(提示:",
|
|
243
|
+
"请使用实际注入的数据",
|
|
244
|
+
"请使用实际数据",
|
|
245
|
+
"实际注入的数据",
|
|
246
|
+
"[system]",
|
|
247
|
+
"[/system]",
|
|
248
|
+
"[INST]",
|
|
249
|
+
"[/INST]",
|
|
250
|
+
)
|
|
251
|
+
if any(a in token for a in meta_artifacts):
|
|
252
|
+
token = re.sub(
|
|
253
|
+
r"\(注[释释]:[^))]*[))]|(注[释释]:[^))]*[))]"
|
|
254
|
+
r"|\(提示:[^))]*[))]|(提示:[^))]*[))]"
|
|
255
|
+
r"|请使用实际(?:注入的)?数据[^。\n]*"
|
|
256
|
+
r"|\[/?(?:system|INST)\]",
|
|
257
|
+
"",
|
|
258
|
+
token,
|
|
259
|
+
)
|
|
260
|
+
if not token.strip():
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
if _REPETITION_MARKER in token:
|
|
264
|
+
if self.use_batch_render:
|
|
265
|
+
self.response_text += token
|
|
266
|
+
self.streamed_any = True
|
|
267
|
+
self.token_count += 1
|
|
268
|
+
self.repetition_stopped = True
|
|
269
|
+
return
|
|
270
|
+
before, _, _after = token.partition(_REPETITION_MARKER)
|
|
271
|
+
if before:
|
|
272
|
+
self.on_token(before)
|
|
273
|
+
self.response_text += _REPETITION_MARKER
|
|
274
|
+
self.streamed_any = True
|
|
275
|
+
self.token_count += 1
|
|
276
|
+
self.repetition_stopped = True
|
|
277
|
+
self._show_repetition_notice()
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
self._finish_thinking()
|
|
281
|
+
|
|
282
|
+
if self.use_batch_render:
|
|
283
|
+
self.response_text += token
|
|
284
|
+
self.streamed_any = True
|
|
285
|
+
self.token_count += 1
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
open_delims = (r"\(", r"\[", "$$")
|
|
289
|
+
close_delims = (r"\)", r"\]", "$$")
|
|
290
|
+
if not self.in_latex:
|
|
291
|
+
if any(d in token for d in open_delims):
|
|
292
|
+
self.in_latex = True
|
|
293
|
+
self.latex_buf = token
|
|
294
|
+
tail = token
|
|
295
|
+
for od, cd in zip(open_delims, close_delims):
|
|
296
|
+
if od in tail:
|
|
297
|
+
after = tail[tail.index(od) + len(od):]
|
|
298
|
+
if cd in after:
|
|
299
|
+
token = self.flush_latex_buf()
|
|
300
|
+
break
|
|
301
|
+
else:
|
|
302
|
+
self.response_text += self.latex_buf
|
|
303
|
+
self.streamed_any = True
|
|
304
|
+
self.token_count += 1
|
|
305
|
+
return
|
|
306
|
+
else:
|
|
307
|
+
token = self.strip_latex(token)
|
|
308
|
+
else:
|
|
309
|
+
self.latex_buf += token
|
|
310
|
+
if any(d in token for d in close_delims):
|
|
311
|
+
token = self.flush_latex_buf()
|
|
312
|
+
else:
|
|
313
|
+
self.response_text += token
|
|
314
|
+
self.streamed_any = True
|
|
315
|
+
self.token_count += 1
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
self.response_text += token
|
|
319
|
+
self.streamed_any = True
|
|
320
|
+
self.token_count += 1
|
|
321
|
+
can_live = (
|
|
322
|
+
self.has_rich
|
|
323
|
+
and not self.use_plain_print
|
|
324
|
+
and getattr(self.console, "is_terminal", False)
|
|
325
|
+
and not getattr(self.console, "is_dumb_terminal", True)
|
|
326
|
+
and self.markdown_cls is not None
|
|
327
|
+
and self.live_cls is not None
|
|
328
|
+
)
|
|
329
|
+
if can_live:
|
|
330
|
+
now = time.time()
|
|
331
|
+
md = self.markdown_cls(self.strip_latex(self.response_text))
|
|
332
|
+
if self.live_display is None:
|
|
333
|
+
self.live_display = self.live_cls(
|
|
334
|
+
md,
|
|
335
|
+
console=self.console,
|
|
336
|
+
refresh_per_second=12,
|
|
337
|
+
vertical_overflow="visible",
|
|
338
|
+
)
|
|
339
|
+
self.live_display.start()
|
|
340
|
+
self.last_live_update = now
|
|
341
|
+
elif now - self.last_live_update >= self.live_update_interval:
|
|
342
|
+
self.live_display.update(md)
|
|
343
|
+
self.last_live_update = now
|
|
344
|
+
else:
|
|
345
|
+
print(token, end="", flush=True)
|
|
346
|
+
|
|
347
|
+
def on_thinking(self, content: str) -> None:
|
|
348
|
+
if not self.thinking_shown:
|
|
349
|
+
self.stop_spinner()
|
|
350
|
+
self.thinking_start = time.time()
|
|
351
|
+
self.thinking_shown = True
|
|
352
|
+
self.thinking_tokens += 1
|
|
353
|
+
if self.thinking_tokens % 30 == 1:
|
|
354
|
+
elapsed = time.time() - self.thinking_start
|
|
355
|
+
sys.stdout.write(
|
|
356
|
+
f"\r \033[2m✻\033[0m \033[2m思考中 {elapsed:.1f}s "
|
|
357
|
+
f"({self.thinking_tokens} tokens)\033[0m "
|
|
358
|
+
)
|
|
359
|
+
sys.stdout.flush()
|
|
360
|
+
if len("".join(self.thinking_preview_buf)) < 300:
|
|
361
|
+
self.thinking_preview_buf.append(content)
|
|
362
|
+
if len("".join(self.thinking_full_buf)) < 8000:
|
|
363
|
+
self.thinking_full_buf.append(content)
|
|
364
|
+
|
|
365
|
+
def on_tool_call(self, tool: str, params: dict) -> None:
|
|
366
|
+
self._finish_thinking()
|
|
367
|
+
if self.print_tool_call is not None:
|
|
368
|
+
self.print_tool_call(tool, params if isinstance(params, dict) else {})
|
|
369
|
+
self.tool_start_times[tool] = time.time()
|
|
370
|
+
|
|
371
|
+
def on_tool_result(self, tool: str, summary: Any) -> None:
|
|
372
|
+
elapsed_ms = int((time.time() - self.tool_start_times.pop(tool, time.time())) * 1000)
|
|
373
|
+
ok = not (isinstance(summary, dict) and not summary.get("success", True))
|
|
374
|
+
if self.print_tool_done is not None:
|
|
375
|
+
self.print_tool_done(tool, elapsed_ms, success=ok)
|
|
376
|
+
|
|
377
|
+
ts = time.strftime("%H:%M:%S")
|
|
378
|
+
entry = f"[{ts}] {tool}: {str(summary)[:100]}"
|
|
379
|
+
self.terminal._transcript_log.append(entry)
|
|
380
|
+
if len(self.terminal._transcript_log) > 100:
|
|
381
|
+
self.terminal._transcript_log = self.terminal._transcript_log[-100:]
|
|
382
|
+
|
|
383
|
+
if tool in ("TaskCreate", "TaskUpdate") and isinstance(summary, dict):
|
|
384
|
+
tid = summary.get("id") or summary.get("task_id")
|
|
385
|
+
title = summary.get("title", "")
|
|
386
|
+
status = summary.get("status", "pending")
|
|
387
|
+
if tid:
|
|
388
|
+
existing = next((t for t in self.terminal._task_list if t.get("id") == tid), None)
|
|
389
|
+
if existing:
|
|
390
|
+
existing["status"] = status
|
|
391
|
+
if title:
|
|
392
|
+
existing["title"] = title
|
|
393
|
+
else:
|
|
394
|
+
self.terminal._task_list.append({"id": tid, "title": title, "status": status})
|
|
395
|
+
|
|
396
|
+
def on_status(self, state: str, message: str) -> None:
|
|
397
|
+
if state != "fallback":
|
|
398
|
+
return
|
|
399
|
+
match = re.search(r"(?:from\s+)?(\w+)\s*(?:→|->|to)\s*(\w+)", message or "", re.I)
|
|
400
|
+
if match:
|
|
401
|
+
from_provider, to_provider = match.group(1), match.group(2)
|
|
402
|
+
else:
|
|
403
|
+
from_provider, to_provider = self.fallback_from, "cloud"
|
|
404
|
+
from ui.render.output import print_fallback_toast
|
|
405
|
+
|
|
406
|
+
print_fallback_toast(
|
|
407
|
+
from_provider,
|
|
408
|
+
to_provider,
|
|
409
|
+
message or "",
|
|
410
|
+
console=self.console,
|
|
411
|
+
has_rich=self.has_rich,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
def handle_runtime_event(self, event: Any) -> None:
|
|
415
|
+
if isinstance(event, AgentEventToken):
|
|
416
|
+
self.on_token(event.text)
|
|
417
|
+
elif isinstance(event, AgentEventThinking):
|
|
418
|
+
self.on_thinking(event.content)
|
|
419
|
+
elif isinstance(event, AgentEventToolCall):
|
|
420
|
+
self.on_tool_call(event.tool, event.params)
|
|
421
|
+
elif isinstance(event, AgentEventToolResult):
|
|
422
|
+
self.on_tool_result(event.tool, event.result)
|
|
423
|
+
elif isinstance(event, AgentEventStatus):
|
|
424
|
+
self.on_status(event.state, event.message)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class TerminalApprovalEventConsumer:
|
|
428
|
+
"""Terminal-side approval prompt consumer for runtime tool execution."""
|
|
429
|
+
|
|
430
|
+
def __init__(
|
|
431
|
+
self,
|
|
432
|
+
*,
|
|
433
|
+
terminal: Any,
|
|
434
|
+
console: Any,
|
|
435
|
+
has_rich: bool,
|
|
436
|
+
confirm_decision: Callable[..., ApprovalDecision],
|
|
437
|
+
apply_decision: Callable[[dict, ApprovalDecision], dict],
|
|
438
|
+
save_config: Callable[[dict], None],
|
|
439
|
+
) -> None:
|
|
440
|
+
self.terminal = terminal
|
|
441
|
+
self.console = console
|
|
442
|
+
self.has_rich = has_rich
|
|
443
|
+
self.confirm_decision = confirm_decision
|
|
444
|
+
self.apply_decision = apply_decision
|
|
445
|
+
self.save_config = save_config
|
|
446
|
+
|
|
447
|
+
async def approve(
|
|
448
|
+
self,
|
|
449
|
+
tool_name: str,
|
|
450
|
+
tool_params: dict,
|
|
451
|
+
*,
|
|
452
|
+
stop_before_prompt: Callable[[], None] | None = None,
|
|
453
|
+
) -> ApprovalDecision:
|
|
454
|
+
if stop_before_prompt is not None:
|
|
455
|
+
stop_before_prompt()
|
|
456
|
+
try:
|
|
457
|
+
approval = self.confirm_decision(
|
|
458
|
+
tool_name,
|
|
459
|
+
tool_params,
|
|
460
|
+
config_policy=self.terminal.config.get("command_policy", "safe"),
|
|
461
|
+
)
|
|
462
|
+
except KeyboardInterrupt:
|
|
463
|
+
approval = ApprovalDecision.deny("KeyboardInterrupt")
|
|
464
|
+
|
|
465
|
+
self.terminal._record_feedback(
|
|
466
|
+
"tool_accept" if approval.approved else "tool_reject",
|
|
467
|
+
tool_name,
|
|
468
|
+
)
|
|
469
|
+
if not approval.approved:
|
|
470
|
+
from ui.render.output import print_tool_blocked
|
|
471
|
+
|
|
472
|
+
print_tool_blocked(tool_name, "用户取消", console=self.console, has_rich=self.has_rich)
|
|
473
|
+
return approval
|
|
474
|
+
|
|
475
|
+
def apply(self, tool_params: dict, approval: ApprovalDecision) -> dict:
|
|
476
|
+
self.apply_decision(tool_params, approval)
|
|
477
|
+
if approval.upgrade_policy:
|
|
478
|
+
tool_params.pop("_upgrade_policy", None)
|
|
479
|
+
self.terminal.config["command_policy"] = "balanced"
|
|
480
|
+
try:
|
|
481
|
+
self.save_config(self.terminal.config)
|
|
482
|
+
if self.has_rich:
|
|
483
|
+
self.console.print(" [dim]策略已升级为 balanced 并保存[/dim]")
|
|
484
|
+
except Exception:
|
|
485
|
+
pass
|
|
486
|
+
return tool_params
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
__all__ = ["TerminalApprovalEventConsumer", "TerminalRuntimeEventConsumer"]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Session export helpers for Aria Code."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any, Optional, Sequence
|
|
8
|
+
|
|
9
|
+
from artifacts import artifact_summary as build_artifact_summary
|
|
10
|
+
from apps.cli.config_paths import config_snapshot
|
|
11
|
+
from packages.aria_core import build_session_diagnostic_bundle
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _safe_title_from_conversation(conversation: Sequence[dict]) -> str:
|
|
15
|
+
for msg in conversation:
|
|
16
|
+
if msg.get("role") == "user":
|
|
17
|
+
return str(msg.get("content", ""))[:60]
|
|
18
|
+
return "Aria Code Session"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_session_export_payload(
|
|
22
|
+
fmt: str,
|
|
23
|
+
conversation: Sequence[dict],
|
|
24
|
+
*,
|
|
25
|
+
session_id: str = "",
|
|
26
|
+
config: Optional[dict] = None,
|
|
27
|
+
paths: Optional[dict] = None,
|
|
28
|
+
trace: Any = None,
|
|
29
|
+
provider_health: Optional[list] = None,
|
|
30
|
+
) -> tuple[str, str, str]:
|
|
31
|
+
"""Return (content, extension, suggested_filename_prefix)."""
|
|
32
|
+
fmt = (fmt or "json").lower().strip()
|
|
33
|
+
|
|
34
|
+
if fmt == "json":
|
|
35
|
+
content = json.dumps(list(conversation), indent=2, ensure_ascii=False)
|
|
36
|
+
return content, "json", "aria_code_chat"
|
|
37
|
+
|
|
38
|
+
if fmt == "csv":
|
|
39
|
+
lines = ["role,content"]
|
|
40
|
+
for msg in conversation:
|
|
41
|
+
escaped = str(msg.get("content", "")).replace('"', '""').replace("\n", " ")
|
|
42
|
+
lines.append(f'{msg.get("role", "user")},"{escaped}"')
|
|
43
|
+
return "\n".join(lines), "csv", "aria_code_chat"
|
|
44
|
+
|
|
45
|
+
if fmt == "md":
|
|
46
|
+
lines = [f"# Aria Code Chat Export — {datetime.now().strftime('%Y-%m-%d %H:%M')}\n"]
|
|
47
|
+
for msg in conversation:
|
|
48
|
+
prefix = "**You:**" if msg.get("role") == "user" else "**Aria:**"
|
|
49
|
+
lines.append(f"{prefix}\n{msg.get('content', '')}\n")
|
|
50
|
+
return "\n".join(lines), "md", "aria_code_chat"
|
|
51
|
+
|
|
52
|
+
if fmt == "sft":
|
|
53
|
+
pairs = []
|
|
54
|
+
conv = list(conversation)
|
|
55
|
+
i = 0
|
|
56
|
+
while i < len(conv) - 1:
|
|
57
|
+
if conv[i].get("role") == "user" and conv[i + 1].get("role") == "assistant":
|
|
58
|
+
user_text = str(conv[i].get("content", "")).strip()
|
|
59
|
+
assistant_text = str(conv[i + 1].get("content", "")).strip()
|
|
60
|
+
if len(user_text) > 10 and len(assistant_text) > 20 and not user_text.startswith("Tool results:"):
|
|
61
|
+
pairs.append({
|
|
62
|
+
"instruction": user_text,
|
|
63
|
+
"input": "",
|
|
64
|
+
"output": assistant_text,
|
|
65
|
+
"source": "aria_cli_export",
|
|
66
|
+
"timestamp": datetime.now().strftime("%Y-%m-%d"),
|
|
67
|
+
})
|
|
68
|
+
i += 2
|
|
69
|
+
else:
|
|
70
|
+
i += 1
|
|
71
|
+
if not pairs:
|
|
72
|
+
raise ValueError("No user→assistant pairs to export")
|
|
73
|
+
return json.dumps(pairs, indent=2, ensure_ascii=False), "json", "aria_sft"
|
|
74
|
+
|
|
75
|
+
if fmt == "bundle":
|
|
76
|
+
bundle = build_session_diagnostic_bundle(
|
|
77
|
+
session_id=session_id,
|
|
78
|
+
conversation=conversation,
|
|
79
|
+
config=config,
|
|
80
|
+
paths=paths or config_snapshot(),
|
|
81
|
+
trace=trace,
|
|
82
|
+
provider_health=provider_health,
|
|
83
|
+
artifact_summary=build_artifact_summary(),
|
|
84
|
+
)
|
|
85
|
+
return json.dumps(bundle, indent=2, ensure_ascii=False), "json", "aria_bundle"
|
|
86
|
+
|
|
87
|
+
raise ValueError("Unsupported export format")
|