Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,4 @@ frontends/conductor_im_plugins/*

# 本地 bug / 局限报告草稿(交给维护者参考,不进仓库)
/BUG_*.md
uv.lock
2 changes: 2 additions & 0 deletions assets/configure_mykey.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
'name': 'deepseek', 'apikey': 'sk-<your-deepseek-key>',
'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'],
Expand Down Expand Up @@ -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'],
Expand Down
14 changes: 7 additions & 7 deletions frontends/cost_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
34 changes: 19 additions & 15 deletions frontends/tui_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand All @@ -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 打印日志路径。",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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 '[ ]'
Expand Down Expand Up @@ -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('~'), '~')
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion llmcore.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -48,4 +50,4 @@ py-modules = []
packages = ["ga_cli"]

[project.scripts]
ga = "ga_cli.cli:main"
ga = "ga_cli.cli:main"