gemcode 0.3.53__py3-none-any.whl → 0.3.55__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.
gemcode/model_routing.py CHANGED
@@ -60,7 +60,10 @@ def pick_effective_model(cfg: GemCodeConfig, prompt: str) -> str:
60
60
  # (Deep research already handled above.)
61
61
  if getattr(cfg, "enable_audio", False):
62
62
  return getattr(cfg, "model_audio_live", None) or cfg.model
63
- if getattr(cfg, "enable_computer_use", False):
63
+ # Only switch to the computer-use model when Playwright is actually available.
64
+ # `_computer_use_available` is set False by session_runtime when the probe fails,
65
+ # preventing an HTTP 400 ("model requires Computer Use tool") from the API.
66
+ if getattr(cfg, "enable_computer_use", False) and getattr(cfg, "_computer_use_available", True):
64
67
  return getattr(cfg, "model_computer_use", None) or cfg.model
65
68
 
66
69
  primary_fast = cfg.model
@@ -12,8 +12,13 @@ token budget helpers, and `GemCodeQueryEngine`.
12
12
  from __future__ import annotations
13
13
 
14
14
  import os
15
+ import warnings
15
16
  from pathlib import Path
16
17
 
18
+ # Suppress ADK's noisy "EXPERIMENTAL feature" UserWarning globally.
19
+ # Users get enough context from gemcode's own messages; the warning is redundant.
20
+ warnings.filterwarnings("ignore", category=UserWarning, message=".*EXPERIMENTAL.*")
21
+
17
22
  from google.adk.runners import Runner
18
23
  from google.adk.sessions.sqlite_session_service import SqliteSessionService
19
24
 
@@ -30,6 +35,29 @@ def session_db_path(cfg: GemCodeConfig) -> Path:
30
35
  return cfg.project_root / ".gemcode" / "sessions.sqlite"
31
36
 
32
37
 
38
+ def _playwright_available() -> bool:
39
+ """
40
+ Quick synchronous check: does a usable Playwright browser executable exist?
41
+
42
+ Runs playwright.sync_api.sync_playwright() briefly to resolve the browser
43
+ path, without actually launching a browser. Falls back to a path probe if
44
+ playwright is not installed.
45
+
46
+ Returns True only when Playwright AND at least one browser binary are present.
47
+ This is called before building the runner so we can disable computer-use
48
+ early and prevent model routing from switching to gemini-2.5-computer-use-*.
49
+ """
50
+ try:
51
+ from playwright.sync_api import sync_playwright
52
+ with sync_playwright() as p:
53
+ exe = p.chromium.executable_path
54
+ return bool(exe) and Path(exe).exists()
55
+ except Exception:
56
+ pass
57
+ # playwright not installed at all
58
+ return False
59
+
60
+
33
61
  def _make_safe_computer_toolset(computer):
34
62
  """
35
63
  Build a BaseToolset-compatible wrapper around ComputerUseToolset that catches
@@ -183,31 +211,47 @@ def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None) -> Runner
183
211
  pass # OpenAPIToolset not in this ADK version — continue without
184
212
 
185
213
  # Computer-use: ADK ComputerUseToolset backed by our Playwright BrowserComputer.
186
- # Also inject standalone browser inspection tools (screenshot, get_text, etc.)
187
- # so the agent can read page state without performing side-effecting actions.
214
+ # Probe Playwright BEFORE building the agent so model routing (which runs
215
+ # inside build_root_agent pick_effective_model) never switches to
216
+ # gemini-2.5-computer-use-* when the browser binary is missing.
188
217
  if getattr(cfg, "enable_computer_use", False):
189
- headless_env = os.environ.get("GEMCODE_COMPUTER_HEADLESS", "1").lower()
190
- headless = headless_env in ("1", "true", "yes", "on")
191
- viewport_w = int(os.environ.get("GEMCODE_BROWSER_WIDTH", "1280"))
192
- viewport_h = int(os.environ.get("GEMCODE_BROWSER_HEIGHT", "720"))
193
- from gemcode.computer_use.browser_computer import BrowserComputer
194
-
195
- computer = BrowserComputer(
196
- headless=headless,
197
- viewport_size=(viewport_w, viewport_h),
198
- )
199
- computer_toolset = _make_safe_computer_toolset(computer)
200
- merged_extra_tools = list(merged_extra_tools or [])
201
- if computer_toolset is not None:
202
- merged_extra_tools.append(computer_toolset)
203
-
204
- # Standalone read-only browser tools (browser_screenshot, browser_get_text, etc.)
205
- from gemcode.tools.browser import build_browser_inspection_tools
206
- browser_tools = build_browser_inspection_tools(cfg, computer)
207
- merged_extra_tools.extend(browser_tools)
208
-
209
- # Store reference on cfg so slash commands / TUI can check browser state.
210
- cfg._browser_computer = computer # type: ignore[attr-defined]
218
+ if not _playwright_available():
219
+ import sys
220
+ print(
221
+ "\n[gemcode] Browser (computer-use) is unavailable — Playwright browsers "
222
+ "are not installed.\n"
223
+ " Run: playwright install chromium\n"
224
+ " Then restart GemCode with /computer on (or --computer flag).\n"
225
+ " Disabling computer-use for this session.\n",
226
+ file=sys.stderr,
227
+ )
228
+ # Disable so model_routing stays on the normal model (not computer-use preview).
229
+ cfg.enable_computer_use = False
230
+ cfg._computer_use_available = False # type: ignore[attr-defined]
231
+ else:
232
+ cfg._computer_use_available = True # type: ignore[attr-defined]
233
+ headless_env = os.environ.get("GEMCODE_COMPUTER_HEADLESS", "1").lower()
234
+ headless = headless_env in ("1", "true", "yes", "on")
235
+ viewport_w = int(os.environ.get("GEMCODE_BROWSER_WIDTH", "1280"))
236
+ viewport_h = int(os.environ.get("GEMCODE_BROWSER_HEIGHT", "720"))
237
+ from gemcode.computer_use.browser_computer import BrowserComputer
238
+
239
+ computer = BrowserComputer(
240
+ headless=headless,
241
+ viewport_size=(viewport_w, viewport_h),
242
+ )
243
+ computer_toolset = _make_safe_computer_toolset(computer)
244
+ merged_extra_tools = list(merged_extra_tools or [])
245
+ if computer_toolset is not None:
246
+ merged_extra_tools.append(computer_toolset)
247
+
248
+ # Standalone read-only browser tools (browser_screenshot, browser_get_text, etc.)
249
+ from gemcode.tools.browser import build_browser_inspection_tools
250
+ browser_tools = build_browser_inspection_tools(cfg, computer)
251
+ merged_extra_tools.extend(browser_tools)
252
+
253
+ # Store reference on cfg so slash commands / TUI can check browser state.
254
+ cfg._browser_computer = computer # type: ignore[attr-defined]
211
255
 
212
256
  agent = build_root_agent(cfg, extra_tools=merged_extra_tools)
213
257
  db = session_db_path(cfg)
@@ -10,21 +10,21 @@ from gemcode.vertex import vertex_env_active
10
10
  from gemcode.workspace_hints import narrow_workspace_tip
11
11
 
12
12
  _GEM_LINES = (
13
- r"██████╗ ███████╗███╗ ███╗",
14
- r"██╔════╝ ██╔════╝████╗ ████║",
15
- r"██║ ███╗█████╗ ██╔████╔██║",
16
- r"██║ ██║██╔══╝ ██║╚██╔╝██║",
17
- r"╚██████╔╝███████╗██║ ╚═╝ ██║",
18
- r" ╚═════╝ ╚══════╝╚═╝ ╚═╝",
13
+ r" ██████╗ ███████╗███╗ ███╗",
14
+ r" ██╔════╝ ██╔════╝████╗ ████║",
15
+ r" ██║ ███╗█████╗ ██╔████╔██║",
16
+ r" ██║ ██║██╔══╝ ██║╚██╔╝██║",
17
+ r" ╚██████╔╝███████╗██║ ╚═╝ ██║",
18
+ r" ╚═════╝ ╚══════╝╚═╝ ╚═╝",
19
19
  )
20
20
 
21
21
  _CODE_LINES = (
22
- r"██████╗ ██████╗ ██████╗ ███████╗",
23
- r"██╔════╝██╔═══██╗██╔══██╗██╔════╝",
24
- r"██║ ██║ ██║██║ ██║█████╗ ",
25
- r"██║ ██║ ██║██║ ██║██╔══╝ ",
26
- r"╚██████╗╚██████╔╝██████╔╝███████╗",
27
- r" ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝",
22
+ r" ██████╗ ██████╗ ██████╗ ███████╗",
23
+ r" ██╔════╝██╔═══██╗██╔══██╗██╔════╝",
24
+ r" ██║ ██║ ██║██║ ██║█████╗ ",
25
+ r" ██║ ██║ ██║██║ ██║██╔══╝ ",
26
+ r" ╚██████╗╚██████╔╝██████╔╝███████╗",
27
+ r" ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝",
28
28
  )
29
29
 
30
30
  _TAGLINE = "✦ Gemini-powered coding agent. Fast. Capable. Local. ✦"
@@ -72,66 +72,56 @@ def _kv_line(inner: int, label: str, value: str) -> str:
72
72
 
73
73
 
74
74
  def format_welcome_banner(cfg, *, term_width: int = 100) -> str:
75
+ inner = min(60, max(48, term_width - 4))
76
+ top = "╔" + ("═" * inner) + "╗"
77
+ mid = "╠" + ("═" * inner) + "╣"
78
+ bot = "╚" + ("═" * inner) + "╝"
79
+
75
80
  provider, model, endpoint = _provider_model_endpoint(cfg)
76
- # Banner style: "solid" removes the boxed provider block and blank spacer
77
- # lines for a single rigid header (OpenClaude-like).
78
- style = (os.environ.get("GEMCODE_TUI_BANNER_STYLE", "solid") or "solid").strip().lower()
81
+ row_p = _kv_line(inner, "Provider", provider)
82
+ row_m = _kv_line(inner, "Model", model)
83
+ row_e = _kv_line(inner, "Endpoint", endpoint)
84
+
85
+ mode = "vertex" if vertex_env_active() else "cloud"
86
+ status_raw = f" ● {mode} Ready — type /help to begin"
87
+ if len(status_raw) > inner:
88
+ status_raw = status_raw[: max(0, inner - 3)] + "..."
89
+ row_s = status_raw.ljust(inner)[:inner]
79
90
 
80
- bw = max(_banner_width(), min(80, max(48, term_width)))
91
+ bw = max(_banner_width(), inner + 2)
81
92
  lines: list[str] = []
82
93
  for row in _GEM_LINES:
83
94
  lines.append(_center(row, bw))
95
+ lines.append("")
84
96
  for row in _CODE_LINES:
85
97
  lines.append(_center(row, bw))
86
- lines.append(_center(_TAGLINE, bw))
87
-
88
- if style not in ("solid", "rigid", "compact"):
89
- # Backward-compat: unknown values fall back to the old boxed layout.
90
- style = "boxed"
91
-
92
- if style == "boxed":
93
- inner = min(60, max(48, term_width - 4))
94
- top = "╔" + ("═" * inner) + "╗"
95
- mid = "╠" + ("═" * inner) + "╣"
96
- bot = "╚" + ("" * inner) + ""
97
-
98
- row_p = _kv_line(inner, "Provider", provider)
99
- row_m = _kv_line(inner, "Model", model)
100
- row_e = _kv_line(inner, "Endpoint", endpoint)
101
-
102
- mode = "vertex" if vertex_env_active() else "cloud"
103
- status_raw = f" ● {mode} Ready — type /help to begin"
104
- if len(status_raw) > inner:
105
- status_raw = status_raw[: max(0, inner - 3)] + "..."
106
- row_s = status_raw.ljust(inner)[:inner]
107
-
108
- box_w = inner + 2
109
- pad_b = max(0, (bw - box_w) // 2)
110
- bp = " " * pad_b
111
-
112
- def box_row(body: str) -> str:
113
- b = body[:inner].ljust(inner)
114
- return bp + "│" + b + "│"
115
-
116
- lines.append(bp + top)
117
- lines.append(box_row(row_p))
118
- lines.append(box_row(row_m))
119
- lines.append(box_row(row_e))
120
- lines.append(bp + mid)
121
- lines.append(box_row(row_s))
122
- lines.append(bp + bot)
123
- else:
124
- # Solid header: one rigid info line, no extra blocks.
125
- mode = "vertex" if vertex_env_active() else "cloud"
126
- info = f"{provider} · {model} · {endpoint} · {mode} ready — type /help"
127
- lines.append(_center(info, bw))
98
+ lines.append("")
99
+ lines.append(_center(f" {_TAGLINE} ", bw))
100
+ lines.append("")
101
+
102
+ box_w = inner + 2
103
+ pad_b = max(0, (bw - box_w) // 2)
104
+ bp = " " * pad_b
105
+
106
+ def box_row(body: str) -> str:
107
+ b = body[:inner].ljust(inner)
108
+ return bp + "" + b + ""
109
+
110
+ lines.append(bp + top)
111
+ lines.append(box_row(row_p))
112
+ lines.append(box_row(row_m))
113
+ lines.append(box_row(row_e))
114
+ lines.append(bp + mid)
115
+ lines.append(box_row(row_s))
116
+ lines.append(bp + bot)
128
117
 
129
118
  ver = os.environ.get("GEMCODE_VERSION", get_version())
130
- lines.append(_center(f"gemcode v{ver}", bw))
119
+ lines.append(_center(f" gemcode v{ver} ", bw))
131
120
 
132
121
  root = getattr(cfg, "project_root", None)
133
122
  if isinstance(root, Path):
134
123
  nt = narrow_workspace_tip(root)
135
124
  if nt:
136
125
  lines.append(_center(nt, bw))
126
+ lines.append("")
137
127
  return "\n".join(lines)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.53
3
+ Version: 0.3.55
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -23,7 +23,7 @@ gemcode/logging_config.py,sha256=nw_FyJbXM3StzaAx6b6BLLNvzuxsGg-sdQ3QRw7BWgs,108
23
23
  gemcode/mcp_loader.py,sha256=hQcUZ0ddlqmON0BUo6IQQ4E-BDEGuF2ppXbsuCWuEA0,4978
24
24
  gemcode/modality_tools.py,sha256=Gx-hDXr5Y364jl2oiUvBNWOZj7Z6bUXxmwVHUpc1L44,6875
25
25
  gemcode/model_errors.py,sha256=6I2Gl2HbxINMmJOjSdM5rH6rCUDLA4ellGZsQrzy47Q,2948
26
- gemcode/model_routing.py,sha256=Q42HZtXQa6rao2O2vYMHxohrTgD-wq4t7qGxU4_38Jg,4881
26
+ gemcode/model_routing.py,sha256=8oSgz4qjueEkuZ-ZLme2C5a_nAKUMAwMrV2vq634QyY,5174
27
27
  gemcode/openapi_loader.py,sha256=g_NZD8YL9_9iIJJ9qykhdbBrylJ1195A4FyHGC0mroc,4157
28
28
  gemcode/paths.py,sha256=U6cEH9jfIcSc4NO8Ke0jniZSiJTfCIJPvSMue3hR0ZU,768
29
29
  gemcode/permissions.py,sha256=0gQ63Ll-KPlZVU6KigIpwSwKL5-OWqYMB6a0x2wpc28,6766
@@ -33,7 +33,7 @@ gemcode/refine.py,sha256=BijEZ4Z32wGa9aK_WottyAhZF-j0xEqRg5UpjedNv2A,7653
33
33
  gemcode/repl_commands.py,sha256=qPUVSjNLR0QnABmURoYP58quu2pAcz3F0wUvDd_1v3o,11340
34
34
  gemcode/repl_slash.py,sha256=JomAwJpOQvRJKnjaLIW2X2VWVNXSCvZ9s6P0iYUhC98,46969
35
35
  gemcode/review_agent.py,sha256=4t7_5-aE60b4-EheJ_eSB_H2eQYf9GppKoui6jw0TME,5264
36
- gemcode/session_runtime.py,sha256=yhDBGhmMEDCEqyA85tyZcGj2crnIwSMU3pZMaaoOd8A,9693
36
+ gemcode/session_runtime.py,sha256=3MRKg2Cu-OjT5tsmtDb5-jmwV3RX1VVkdCnyXFoD4NM,11513
37
37
  gemcode/session_store.py,sha256=POUT_QQf715c74jbXj0s5vCd4dlAgJz_CLsIWuEUoO0,6051
38
38
  gemcode/slash_commands.py,sha256=Qylzsj1notk0xN_hvd3CR4HD8g-l99UENDMcg1pKeBA,794
39
39
  gemcode/thinking.py,sha256=RanBf_x9fKv1o4DNyNXPLfOdn2xT0KybJb65nYgmMEE,4885
@@ -75,14 +75,14 @@ gemcode/tools/web.py,sha256=ULg1e3inG4FjPSUCYI8dVBzTrcCHINNRo76SIU9qw-A,4489
75
75
  gemcode/tui/input_handler.py,sha256=Off7hBXXqw7JxkKzlmtMHtg25Y9dkO275dC35brK-TE,10676
76
76
  gemcode/tui/scrollback.py,sha256=RxwS0DsjkkX_jHvnlF2XMi9z69yGfCiwPuFIHCnb8VU,32535
77
77
  gemcode/tui/spinner.py,sha256=AJrApG5od-Sh40-5uWcNM9RHb5ax7gr-NbgAZmTbIYY,4848
78
- gemcode/tui/welcome_banner.py,sha256=DLm-Zhi194zYFNCJ5qQdikPTIvaD3rf9EXJjuW1_0x0,4981
78
+ gemcode/tui/welcome_banner.py,sha256=aocl1lnoyLIM6RN4f65g3i0wRA71RqUlgPrGsXeVLW4,4387
79
79
  gemcode/tui/welcome_rich.py,sha256=8FEZzLXrzqly5JWiDgV9ooRV1LNXDk-CXV1a7K6ua-U,4048
80
80
  gemcode/web/__init__.py,sha256=EysmUAWs6g-lmMk4VFljKfaHVrEgb_FiIzwQmBdORJc,40
81
81
  gemcode/web/claude_sse_adapter.py,sha256=HcNp0Lh4DdBZBLOpstsqa-VzfqAUrRngZ6FSuJ-mIMg,8609
82
82
  gemcode/web/terminal_repl.py,sha256=k2irvFGbCY8gDm_pbirR7b_cakaeafcctoTIvnJkVXk,3902
83
- gemcode-0.3.53.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
84
- gemcode-0.3.53.dist-info/METADATA,sha256=b1pSz3zOjci_o1s66dLdgMXQM9UlM1QxJlzdbY3oVn8,23695
85
- gemcode-0.3.53.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
86
- gemcode-0.3.53.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
87
- gemcode-0.3.53.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
88
- gemcode-0.3.53.dist-info/RECORD,,
83
+ gemcode-0.3.55.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
84
+ gemcode-0.3.55.dist-info/METADATA,sha256=KfVPnNOamQoajR7-nwtbnOo6N7_9S3Yhf4mX4GUsBhI,23695
85
+ gemcode-0.3.55.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
86
+ gemcode-0.3.55.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
87
+ gemcode-0.3.55.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
88
+ gemcode-0.3.55.dist-info/RECORD,,