gemcode 0.3.4__tar.gz → 0.3.8__tar.gz
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.
- {gemcode-0.3.4/src/gemcode.egg-info → gemcode-0.3.8}/PKG-INFO +1 -1
- {gemcode-0.3.4 → gemcode-0.3.8}/pyproject.toml +1 -1
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/agent.py +1 -1
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/repl_commands.py +2 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/repl_slash.py +58 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tui/app.py +65 -7
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tui/scrollback.py +31 -10
- {gemcode-0.3.4 → gemcode-0.3.8/src/gemcode.egg-info}/PKG-INFO +1 -1
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_agent_instruction.py +0 -1
- {gemcode-0.3.4 → gemcode-0.3.8}/LICENSE +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/MANIFEST.in +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/README.md +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/setup.cfg +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/__init__.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/__main__.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/audit.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/cli.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/compaction.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/config.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/credentials.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/interactions.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/invoke.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/kairos_daemon.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/limits.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/paths.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/permissions.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/query/config.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/session_runtime.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/thinking.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tools/__init__.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/trust.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/version.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/vertex.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/web/claude_sse_adapter.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode.egg-info/SOURCES.txt +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_autocompact.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_capability_routing.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_claude_web_adapter_sse.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_cli_init.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_context_budget.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_context_warning.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_credentials.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_kairos_scheduler.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_modality_tools.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_model_errors.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_model_routing.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_paths.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_permissions.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_repl_commands.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_repl_slash.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_slash_commands.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_thinking_config.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_token_budget.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_tools.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.3.4 → gemcode-0.3.8}/tests/test_workspace_hints.py +0 -0
|
@@ -65,7 +65,7 @@ def _build_runtime_facts(cfg: GemCodeConfig) -> str:
|
|
|
65
65
|
model = (getattr(cfg, "model", None) or "").strip() or "(default)"
|
|
66
66
|
return f"""## Runtime facts (authoritative for this session)
|
|
67
67
|
- **Project root** — every filesystem tool path is relative to: `{root}`
|
|
68
|
-
- **Model id in use:** `{model}`.
|
|
68
|
+
- **Model id in use:** `{model}`. In this TUI/REPL you can override it for subsequent turns with `/model use <id>` (use `/model list` to browse IDs). For a full restart you can still use `--model <id>` or env `GEMCODE_MODEL`.
|
|
69
69
|
- **UI banner** phrases such as "GemCode Pro" are **terminal marketing**, not a separate API tier or model you enable from chat.
|
|
70
70
|
- **Env toggles** (`GEMCODE_ENABLE_COMPUTER_USE`, `GEMCODE_MODEL`, etc.) affect only the **OS process** that launched `gemcode`. Pasting `VAR=1` in chat does **not** reconfigure a running session—tell the user to export in their shell, use project `.env`, or restart the CLI.
|
|
71
71
|
- **Working in subfolders** — use tools: e.g. `list_directory("Desktop")`, `glob_files("**/query.ts")`, `read_file("testing/ai-edtech-app/src/app/page.tsx")`, or `run_command` with `cwd_subdir`. Never claim the sandbox cannot reach a subpath unless a tool returned an explicit error."""
|
|
@@ -174,6 +174,8 @@ def slash_help_lines() -> list[str]:
|
|
|
174
174
|
" /tools List tool inventory for this config",
|
|
175
175
|
" /doctor Environment sanity check",
|
|
176
176
|
" /model Show model routing info",
|
|
177
|
+
" /model use <id> Override model for this REPL session",
|
|
178
|
+
" /model list List available Gemini model IDs",
|
|
177
179
|
" /permissions Show permission / HITL settings",
|
|
178
180
|
" /memory Show persistent memory settings",
|
|
179
181
|
" /hooks Show post-turn hook configuration",
|
|
@@ -81,7 +81,65 @@ async def process_repl_slash(
|
|
|
81
81
|
return ReplSlashResult(skip_model_turn=True)
|
|
82
82
|
|
|
83
83
|
if name in ("model", "models"):
|
|
84
|
+
args = (sc.args or "").strip()
|
|
85
|
+
if not args:
|
|
86
|
+
out("\n".join(format_model_lines(cfg)))
|
|
87
|
+
out()
|
|
88
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
89
|
+
|
|
90
|
+
parts = args.split()
|
|
91
|
+
sub = parts[0].lower()
|
|
92
|
+
if sub in ("use", "set") and len(parts) >= 2:
|
|
93
|
+
new_model = " ".join(parts[1:]).strip()
|
|
94
|
+
if not new_model:
|
|
95
|
+
out("Usage: /model use <model-id>")
|
|
96
|
+
out()
|
|
97
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
98
|
+
# Persist override for this session; pick_effective_model() respects this.
|
|
99
|
+
cfg.model = new_model
|
|
100
|
+
setattr(cfg, "model_overridden", True)
|
|
101
|
+
out(f"model: {cfg.model}")
|
|
102
|
+
out("model_overridden: True")
|
|
103
|
+
out("Note: this applies to subsequent turns in this REPL session.")
|
|
104
|
+
out()
|
|
105
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
106
|
+
|
|
107
|
+
if sub in ("list", "ls", "show"):
|
|
108
|
+
# Best-effort list: query Gemini via the same API used by GemCode.
|
|
109
|
+
show_all = "--show-all" in parts or "--show-all" in args
|
|
110
|
+
try:
|
|
111
|
+
from gemcode.config import load_cli_environment
|
|
112
|
+
|
|
113
|
+
load_cli_environment()
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
from gemcode.cli import require_google_api_key
|
|
117
|
+
|
|
118
|
+
require_google_api_key()
|
|
119
|
+
from google.genai import Client
|
|
120
|
+
|
|
121
|
+
client = Client(api_key=os.environ["GOOGLE_API_KEY"])
|
|
122
|
+
models = client.models.list()
|
|
123
|
+
out("Available models:")
|
|
124
|
+
for m in models:
|
|
125
|
+
name = getattr(m, "name", None)
|
|
126
|
+
actions = getattr(m, "supported_actions", None)
|
|
127
|
+
if not name:
|
|
128
|
+
continue
|
|
129
|
+
if not show_all and actions and isinstance(actions, list):
|
|
130
|
+
# Keep only models that support generateContent-style generation.
|
|
131
|
+
if "generateContent" not in actions:
|
|
132
|
+
continue
|
|
133
|
+
if actions and isinstance(actions, list):
|
|
134
|
+
out(f" {name}\t{','.join(actions)}")
|
|
135
|
+
else:
|
|
136
|
+
out(f" {name}")
|
|
137
|
+
out()
|
|
138
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
139
|
+
|
|
140
|
+
# Fallback: show current routing info.
|
|
84
141
|
out("\n".join(format_model_lines(cfg)))
|
|
142
|
+
out("Tip: /model use <model-id> to override for this session.")
|
|
85
143
|
out()
|
|
86
144
|
return ReplSlashResult(skip_model_turn=True)
|
|
87
145
|
|
|
@@ -179,6 +179,8 @@ async def run_gemcode_tui(
|
|
|
179
179
|
|
|
180
180
|
# Non-modal permission prompt state. Modal dialogs can corrupt a full-screen TUI.
|
|
181
181
|
pending_confirm: dict[str, object] = {"future": None, "tool": "", "hint": ""}
|
|
182
|
+
assistant_busy: dict[str, bool] = {"value": False}
|
|
183
|
+
spinner_idx: dict[str, int] = {"value": 0}
|
|
182
184
|
|
|
183
185
|
def _set_input_prompt() -> None:
|
|
184
186
|
if pending_confirm.get("future") is not None:
|
|
@@ -218,6 +220,15 @@ async def run_gemcode_tui(
|
|
|
218
220
|
("class:muted", " "),
|
|
219
221
|
("class:muted", "(Esc cancels)"),
|
|
220
222
|
]
|
|
223
|
+
if assistant_busy.get("value"):
|
|
224
|
+
frames = ["|", "/", "-", "\\"]
|
|
225
|
+
fr = frames[spinner_idx.get("value", 0) % len(frames)]
|
|
226
|
+
return [
|
|
227
|
+
("class:muted", " "),
|
|
228
|
+
("class:pill", f"thinking {fr}"),
|
|
229
|
+
("class:muted", " "),
|
|
230
|
+
("class:muted", "Tip: Esc=interrupt"),
|
|
231
|
+
]
|
|
221
232
|
return [
|
|
222
233
|
("class:muted", " "),
|
|
223
234
|
("class:pill", f"🌿 {_git_branch()}" if _git_branch() else "📁 no-git"),
|
|
@@ -228,6 +239,18 @@ async def run_gemcode_tui(
|
|
|
228
239
|
status.content = FormattedTextControl(_status_text)
|
|
229
240
|
_set_input_prompt()
|
|
230
241
|
|
|
242
|
+
async def _spin_status() -> None:
|
|
243
|
+
frames = ["|", "/", "-", "\\"]
|
|
244
|
+
i = 0
|
|
245
|
+
while assistant_busy.get("value"):
|
|
246
|
+
spinner_idx["value"] = i % len(frames)
|
|
247
|
+
i += 1
|
|
248
|
+
try:
|
|
249
|
+
app.invalidate()
|
|
250
|
+
except Exception:
|
|
251
|
+
pass
|
|
252
|
+
await asyncio.sleep(0.12)
|
|
253
|
+
|
|
231
254
|
input_help = Window(
|
|
232
255
|
height=1,
|
|
233
256
|
dont_extend_height=True,
|
|
@@ -523,6 +546,9 @@ async def run_gemcode_tui(
|
|
|
523
546
|
apply_capability_routing(cfg, prompt, context="prompt")
|
|
524
547
|
cfg.model = pick_effective_model(cfg, prompt)
|
|
525
548
|
|
|
549
|
+
assistant_busy["value"] = True
|
|
550
|
+
spinner_task = asyncio.create_task(_spin_status())
|
|
551
|
+
|
|
526
552
|
try:
|
|
527
553
|
REQUEST_CONFIRMATION_FC = "adk_request_confirmation"
|
|
528
554
|
# Terminal width for stable box rendering.
|
|
@@ -602,12 +628,20 @@ async def run_gemcode_tui(
|
|
|
602
628
|
|
|
603
629
|
assistant_started = False
|
|
604
630
|
|
|
631
|
+
def _normalize_ws(s: str) -> str:
|
|
632
|
+
# For Gemini, "thinking" and final text can sometimes be identical.
|
|
633
|
+
# Normalize whitespace so we can detect exact duplicates robustly.
|
|
634
|
+
return " ".join((s or "").split()).strip().lower()
|
|
635
|
+
|
|
605
636
|
while True:
|
|
606
637
|
# Stream events from ADK runner.
|
|
607
638
|
events: list = []
|
|
608
|
-
# Buffer assistant text for this pass.
|
|
609
|
-
#
|
|
610
|
-
|
|
639
|
+
# Buffer assistant text for this pass.
|
|
640
|
+
# Claude differentiates "thinking" from the final response, and we
|
|
641
|
+
# also do that here by routing streamed parts with `part.thought=True`
|
|
642
|
+
# into a separate buffer.
|
|
643
|
+
buffered_thought: list[str] = []
|
|
644
|
+
buffered_final: list[str] = []
|
|
611
645
|
kwargs = dict(
|
|
612
646
|
user_id="local",
|
|
613
647
|
session_id=session_state["id"],
|
|
@@ -637,7 +671,10 @@ async def run_gemcode_tui(
|
|
|
637
671
|
if not delta:
|
|
638
672
|
continue
|
|
639
673
|
assistant_started = True
|
|
640
|
-
|
|
674
|
+
if getattr(part, "thought", None):
|
|
675
|
+
buffered_thought.append(delta)
|
|
676
|
+
else:
|
|
677
|
+
buffered_final.append(delta)
|
|
641
678
|
except Exception:
|
|
642
679
|
continue
|
|
643
680
|
|
|
@@ -648,10 +685,25 @@ async def run_gemcode_tui(
|
|
|
648
685
|
# Handle in-TUI tool confirmations (HITL) Claude-style.
|
|
649
686
|
confirmation_fcs = _get_confirmation_fcs(events)
|
|
650
687
|
if not confirmation_fcs:
|
|
651
|
-
# Now that we know no confirmation is needed, render buffered
|
|
652
|
-
|
|
688
|
+
# Now that we know no confirmation is needed, render buffered
|
|
689
|
+
# thinking + final response separately.
|
|
690
|
+
thought_text = "".join(buffered_thought)
|
|
691
|
+
final_text = "".join(buffered_final)
|
|
692
|
+
if buffered_thought:
|
|
693
|
+
# If Gemini returns the same content for both "thought" and
|
|
694
|
+
# final text, don't repeat it (Claude typically doesn't).
|
|
695
|
+
if buffered_final and _normalize_ws(thought_text) == _normalize_ws(final_text):
|
|
696
|
+
append_inline("⎿ GemCode (thinking): ")
|
|
697
|
+
await typewrite("(omitted: identical to final response)")
|
|
698
|
+
append("")
|
|
699
|
+
else:
|
|
700
|
+
append_inline("⎿ GemCode (thinking): ")
|
|
701
|
+
await typewrite(thought_text)
|
|
702
|
+
# Ensure visual separation before the final response section.
|
|
703
|
+
append("")
|
|
704
|
+
if buffered_final:
|
|
653
705
|
append_inline("⎿ GemCode: ")
|
|
654
|
-
await typewrite("".join(
|
|
706
|
+
await typewrite("".join(buffered_final))
|
|
655
707
|
break
|
|
656
708
|
|
|
657
709
|
interactive_enabled = bool(getattr(cfg, "interactive_permission_ask", False))
|
|
@@ -724,6 +776,12 @@ async def run_gemcode_tui(
|
|
|
724
776
|
append("\033[2m" + ("─" * max(40, min(cw - 2, 200))) + "\033[0m")
|
|
725
777
|
except Exception as e:
|
|
726
778
|
append(f"GemCode: error: {e}\n")
|
|
779
|
+
finally:
|
|
780
|
+
assistant_busy["value"] = False
|
|
781
|
+
try:
|
|
782
|
+
spinner_task.cancel()
|
|
783
|
+
except Exception:
|
|
784
|
+
pass
|
|
727
785
|
|
|
728
786
|
@kb.add("enter")
|
|
729
787
|
def _enter(event) -> None:
|
|
@@ -346,16 +346,18 @@ async def run_gemcode_scrollback_tui(
|
|
|
346
346
|
apply_capability_routing(cfg, prompt, context="prompt")
|
|
347
347
|
cfg.model = pick_effective_model(cfg, prompt)
|
|
348
348
|
|
|
349
|
-
# Start streaming assistant output.
|
|
350
|
-
sys.stdout.write(f" ⎿ {ansi.bold}GemCode{ansi.reset}: ")
|
|
351
|
-
sys.stdout.flush()
|
|
352
|
-
|
|
353
349
|
current_message = types.Content(role="user", parts=[types.Part(text=prompt)])
|
|
354
350
|
do_reset = True
|
|
351
|
+
def _normalize_ws(s: str) -> str:
|
|
352
|
+
# Gemini can sometimes return identical content for both "thinking" and
|
|
353
|
+
# final text; normalize whitespace to detect exact duplicates.
|
|
354
|
+
return " ".join((s or "").split()).strip().lower()
|
|
355
355
|
|
|
356
356
|
while True:
|
|
357
357
|
events: list = []
|
|
358
358
|
assistant_wrote_text = False
|
|
359
|
+
buffered_thought: list[str] = []
|
|
360
|
+
buffered_final: list[str] = []
|
|
359
361
|
kwargs = dict(
|
|
360
362
|
user_id="local", session_id=current_session_id, new_message=current_message
|
|
361
363
|
)
|
|
@@ -373,9 +375,13 @@ async def run_gemcode_scrollback_tui(
|
|
|
373
375
|
continue
|
|
374
376
|
for part in ev.content.parts:
|
|
375
377
|
delta = getattr(part, "text", None)
|
|
376
|
-
if delta:
|
|
377
|
-
|
|
378
|
-
|
|
378
|
+
if not delta:
|
|
379
|
+
continue
|
|
380
|
+
assistant_wrote_text = True
|
|
381
|
+
if getattr(part, "thought", None):
|
|
382
|
+
buffered_thought.append(delta)
|
|
383
|
+
else:
|
|
384
|
+
buffered_final.append(delta)
|
|
379
385
|
except Exception:
|
|
380
386
|
continue
|
|
381
387
|
|
|
@@ -387,6 +393,24 @@ async def run_gemcode_scrollback_tui(
|
|
|
387
393
|
|
|
388
394
|
confirmation_fcs = _get_confirmation_fcs(events)
|
|
389
395
|
if not confirmation_fcs:
|
|
396
|
+
# Render buffered thinking and final response separately.
|
|
397
|
+
thought_text = "".join(buffered_thought)
|
|
398
|
+
final_text = "".join(buffered_final)
|
|
399
|
+
if buffered_thought:
|
|
400
|
+
if buffered_final and _normalize_ws(thought_text) == _normalize_ws(final_text):
|
|
401
|
+
print(f" ⎿ {ansi.dim}{ansi.bold}GemCode{ansi.reset} (thinking): {ansi.reset}(omitted: identical to final response)")
|
|
402
|
+
print("")
|
|
403
|
+
else:
|
|
404
|
+
sys.stdout.write(f" ⎿ {ansi.dim}{ansi.bold}GemCode{ansi.reset} (thinking): ")
|
|
405
|
+
sys.stdout.flush()
|
|
406
|
+
await typewrite(thought_text)
|
|
407
|
+
sys.stdout.write("\n")
|
|
408
|
+
sys.stdout.flush()
|
|
409
|
+
|
|
410
|
+
if buffered_final:
|
|
411
|
+
sys.stdout.write(f" ⎿ {ansi.bold}GemCode{ansi.reset}: ")
|
|
412
|
+
sys.stdout.flush()
|
|
413
|
+
await typewrite(final_text)
|
|
390
414
|
break
|
|
391
415
|
|
|
392
416
|
interactive_enabled = bool(getattr(cfg, "interactive_permission_ask", False))
|
|
@@ -414,9 +438,6 @@ async def run_gemcode_scrollback_tui(
|
|
|
414
438
|
f" ⎿ Allow? ({ansi.blue_ok}y{ansi.reset}/{ansi.dim}N{ansi.reset}) "
|
|
415
439
|
).strip().lower()
|
|
416
440
|
ok = ans in ("y", "yes")
|
|
417
|
-
# Resume the assistant indent after permission prompt.
|
|
418
|
-
sys.stdout.write(f" ⎿ {ansi.bold}GemCode{ansi.reset}: ")
|
|
419
|
-
sys.stdout.flush()
|
|
420
441
|
|
|
421
442
|
parts.append(
|
|
422
443
|
types.Part(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|