diff --git a/.gitignore b/.gitignore index 47a6807f7..5d9605dae 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,4 @@ frontends/conductor_im_plugins/* # 本地 bug / 局限报告草稿(交给维护者参考,不进仓库) /BUG_*.md +uv.lock diff --git a/assets/configure_mykey.py b/assets/configure_mykey.py index b9ed19d7e..de685e6cc 100644 --- a/assets/configure_mykey.py +++ b/assets/configure_mykey.py @@ -90,6 +90,7 @@ 'name': 'deepseek', 'apikey': 'sk-', 'apibase': 'https://api.deepseek.com', 'model': 'deepseek-v4-pro', 'api_mode': 'chat_completions', 'reasoning_effort': 'high', + 'context_win': 1000000, 'max_tokens': 384000, }, 'key_hint': '在 https://platform.deepseek.com/api_keys 获取', 'model_choices': ['deepseek-v4-pro', 'deepseek-v4-flash'], @@ -266,6 +267,7 @@ 'apibase': 'https://api.xiaomimimo.com/v1', 'model': 'mimo-v2.5-pro', 'api_mode': 'chat_completions', + 'context_win': 1000000, 'max_tokens': 128000, }, 'key_hint': '在 https://x.xiaomi.com/ 获取 API Key', 'model_choices': ['mimo-v2.5-pro', 'mimo-v2-flash'], diff --git a/frontends/cost_tracker.py b/frontends/cost_tracker.py index 744258982..d322f6533 100644 --- a/frontends/cost_tracker.py +++ b/frontends/cost_tracker.py @@ -39,15 +39,15 @@ def elapsed_seconds(self) -> float: return max(0.0, time.time() - self.started_at) -# GA's real context budget lives on `BaseSession.context_win` (chars). The -# trim trigger is `context_win * 3` (see llmcore.trim_messages_history), so -# `/cost` compares actual-history chars against that cap for consistent units. +# GA's real context budget lives on `BaseSession.context_win` (chars). +# `/cost` displays this value directly so users see the actual context window. def context_window_chars(backend) -> int: - """`context_win * 3` — the char cap before `trim_messages_history` kicks - in. Reads dynamically so a `mykey.py` override propagates. Returns 0 on - bad/missing backend so the caller can hide the row.""" + """Returns the actual context_win (no multiplier). The internal trim + trigger is `context_win * 3` (see llmcore.trim_messages_history), but the + display should show the real model capacity. Returns 0 on bad/missing + backend so the caller can hide the row.""" try: - return int(getattr(backend, 'context_win', 0)) * 3 + return int(getattr(backend, 'context_win', 0)) except (TypeError, ValueError): return 0 diff --git a/frontends/tui_v3.py b/frontends/tui_v3.py index 0684d538d..1d1e458e6 100644 --- a/frontends/tui_v3.py +++ b/frontends/tui_v3.py @@ -90,7 +90,7 @@ "Tip: /rewind [n] rewinds the last n turns; /stop aborts the current task.", "Tip: /continue lists past sessions — arrow keys to pick, Enter to restore.", "Tip: Ctrl+J / Shift+Enter inserts a newline in multi-line input; Enter sends.", - "Tip: put [multi-select] in an ask_user prompt to switch to a multi-pick picker.", + "Tip: in a multi-choice picker, Space toggles options and Enter confirms your selection.", "Tip: /cost shows token usage; /llm views / switches the model.", "Tip: /new [name] starts a fresh session; /language switches the interface language.", "Tip: /export clip copies the last reply to your system clipboard; /export all prints the log path.", @@ -113,7 +113,7 @@ "Tip: /rewind [n] 回退最近 n 轮对话;/stop 中止当前任务。", "Tip: /continue 列出历史会话 —— 方向键选择,Enter 恢复。", "Tip: 多行输入用 Ctrl+J / Shift+Enter 换行;Enter 直接发送。", - "Tip: ask_user 题目里写 [多选] 会自动切到多选 picker。", + "Tip: 多选选择器中,空格切换选项,Enter 确认选择。", "Tip: /cost 查看 token 用量;/llm 查看 / 切换模型。", "Tip: /new [name] 新建会话;/language 切换界面语言。", "Tip: /export clip 把最后回复复制到系统剪贴板;/export all 打印日志路径。", @@ -1349,14 +1349,6 @@ def _extract_ask_user(ctx: dict | None) -> AskUserEvent | None: return None data = payload.get('data') or {} candidates = data.get('candidates') or [] - # v2 parity: skip the ask card when the agent didn't supply candidates — - # the 'Waiting for your answer ...' marker already lands in scrollback as - # part of the assistant stream, and the user replies via the normal input - # box. Pushing an empty-candidate event onto the queue would route us - # through _enter_ask → free-text ask card, which freezes the live region - # in some terminals. - if not candidates: - return None return AskUserEvent( question=data.get('question', ''), candidates=candidates, @@ -2333,7 +2325,7 @@ def _indent_rows(rows: list[str], width: int) -> list[str]: def _cost_str(agent) -> str: - """Context-window usage view (cc/v2 style): used / cap of context_win*3.""" + """Context-window usage view (cc/v2 style): used / cap of context_win.""" try: from frontends import cost_tracker be = agent.llmclient.backend @@ -3180,6 +3172,7 @@ def row(text: str, style: str = '') -> list[str]: return r rows = [top] + multi = self._picker_mode == 'multi' for ln in (ae.question or _t('ask.default_q')).strip().split('\n'): rows.extend(row(ln, _BOLD)) if ae.candidates: @@ -3188,7 +3181,6 @@ def row(text: str, style: str = '') -> list[str]: # current row and (in multi) a [ ]/[x] marker; in free-text mode # candidates show only as numbered hints — typing the number # still picks that row at submit time. - multi = self._picker_mode == 'multi' for i, c in enumerate(ae.candidates): if multi: mark = '[x]' if i in self._picker_checked else '[ ]' @@ -4478,8 +4470,8 @@ def _cmd(self, raw: str) -> None: try: from frontends import cost_tracker as _ct cap = _ct.context_window_chars(be) if be is not None else 0 - used = _ct.context_chars_used(be) if be is not None else 0 - ctx_use = _t('status.ctx.fmt', used=used, cap=cap * 3) if cap else _t('status.ctx.unknown') + used = _ct.current_input_chars(be) if be is not None else 0 + ctx_use = _t('status.ctx.fmt', used=used, cap=cap) if cap else _t('status.ctx.unknown') except Exception: ctx_use = _t('status.ctx.unknown') cwd = os.getcwd().replace(os.path.expanduser('~'), '~') @@ -5970,6 +5962,12 @@ def _pre_run() -> None: _w('\x1b[>4;1m') # ask supporting terminals to distinguish Shift+Enter self.commit(Block('banner', '')) + # Some terminals (IDE consoles, certain SSH configs, etc.) do not + # respond to CPR (cursor position report) requests, causing a loud + # warning. Suppress just the warning — keep CPR probing enabled so + # that _min_available_height stays accurate for dynamic-height regions + # like the ask_user picker card. + _ptk_output = create_output(stdout=so) app = Application( layout=layout, key_bindings=kb, @@ -5982,8 +5980,14 @@ def _pre_run() -> None: terminal_size_polling_interval=0.2, mouse_support=False, before_render=_before_render, - output=create_output(stdout=so), + output=_ptk_output, ) + # Silence the CPR warning. The Application's callback is checked by + # prompt_toolkit's Application.run() path, but the Renderer captures its + # own copy during __init__ and that is the one that actually fires the + # warning. Both must be no-ops to prevent the brief flash on startup. + app.cpr_not_supported_callback = lambda: None + app.renderer.cpr_not_supported_callback = lambda: None self._ptk_app = app try: diff --git a/llmcore.py b/llmcore.py index 5eaa016d4..9fb57791d 100644 --- a/llmcore.py +++ b/llmcore.py @@ -530,7 +530,7 @@ def __init__(self, cfg): self.model = cfg.get('model', '') default_context_win = 30000 if 'deepseek' in self.model.lower(): - default_context_win = 70000; self.cut_msg_interval = 25; self.trim_keep_rate = 0.3 + default_context_win = 1000000; self.cut_msg_interval = 25; self.trim_keep_rate = 0.3 self.context_win = cfg.get('context_win', default_context_win) self.history = []; self.lock = threading.Lock(); self.system = "" self.name = cfg.get('name', self.model) diff --git a/pyproject.toml b/pyproject.toml index cf243449c..dfbdb7d4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,8 @@ dependencies = [ "bottle>=0.12", "simple-websocket-server>=0.4", "aiohttp>=3.9", + "rich>=15.0.0", + "prompt-toolkit>=3.0.52", ] [project.optional-dependencies] @@ -48,4 +50,4 @@ py-modules = [] packages = ["ga_cli"] [project.scripts] -ga = "ga_cli.cli:main" \ No newline at end of file +ga = "ga_cli.cli:main"