gemcode 0.3.108__tar.gz → 0.3.109__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.108/src/gemcode.egg-info → gemcode-0.3.109}/PKG-INFO +1 -1
- {gemcode-0.3.108 → gemcode-0.3.109}/pyproject.toml +1 -1
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/repl_commands.py +3 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/repl_slash.py +91 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/session_runtime.py +57 -0
- {gemcode-0.3.108 → gemcode-0.3.109/src/gemcode.egg-info}/PKG-INFO +1 -1
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode.egg-info/SOURCES.txt +1 -0
- gemcode-0.3.109/tests/test_session_runtime_cache.py +39 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/LICENSE +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/MANIFEST.in +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/README.md +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/setup.cfg +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/__init__.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/__main__.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/agent.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/audit.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/autotune.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/checkpoints.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/cli.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/compaction.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/config.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/credentials.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/curated_memory.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/dynamic_policy.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/evals/harness.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/hooks.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/ide_protocol.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/ide_stdio.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/intent_classifier.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/interactions.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/invoke.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/kaira_client.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/kaira_daemon.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/kaira_ipc.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/kaira_job_store.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/learning.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/limits.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/multimodal_input.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/openapi_loader.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/org.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/output_styles.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/paths.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/permissions.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/policy_profile.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/pricing.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/config.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/query_sanitizer.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/refine.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/review_agent.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/rules.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/session_store.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/session_summariser.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/skills.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/thinking.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tool_prompt_manifest.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tool_result_store.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/__init__.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/compress_memory.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/curated_memory.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/notebook.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/notes.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/org_tools.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/repo_map.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/skills.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/subtask.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/tasks.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/user_choice.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/veomem_tools.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools/web_search.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/trust.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tui/input_handler.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tui/scrollback.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/veomem_bridge.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/version.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/vertex.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/wal.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/web/sse_adapter.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/web/web_sse_compat.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_add_dir.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_autocompact.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_capability_routing.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_checkpoint_diff_command.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_cli_init.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_compress_memory_tool.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_context_budget.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_context_warning.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_credentials.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_eval_harness_layout.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_ide_stdio_attachments.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_kaira_scheduler.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_modality_tools.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_model_errors.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_model_routing.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_multimodal_input.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_output_styles_and_rules.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_paths.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_permissions.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_repl_commands.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_repl_slash.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_skills.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_slash_commands.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_slash_completion_registry.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_thinking_config.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_token_budget.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_tools.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_web_sse_adapter.py +0 -0
- {gemcode-0.3.108 → gemcode-0.3.109}/tests/test_workspace_hints.py +0 -0
|
@@ -378,6 +378,9 @@ def slash_help_lines() -> list[str]:
|
|
|
378
378
|
" /audit [N] Tail of .gemcode/audit.log (default 40 lines)",
|
|
379
379
|
" /tools List tool inventory for this config",
|
|
380
380
|
" /tools smoke Declaration compile check only (failures listed)",
|
|
381
|
+
" /mcp MCP status (reads .gemcode/mcp.json; shows loaded toolsets)",
|
|
382
|
+
" /mcp list List configured MCP servers",
|
|
383
|
+
" /mcp reload Rebuild runner to reload MCP toolsets",
|
|
381
384
|
" /eval [llm] Run tools_smoke (+ pytest if tests/ exist); optional LLM goldens",
|
|
382
385
|
" /autotune init <tag> Git branch autotune/<tag> for experiment tracking",
|
|
383
386
|
" /autotune eval [llm] Eval + append .gemcode/evals/autotune_ledger.jsonl",
|
|
@@ -1245,6 +1245,97 @@ async def process_repl_slash(
|
|
|
1245
1245
|
out()
|
|
1246
1246
|
return ReplSlashResult(skip_model_turn=True)
|
|
1247
1247
|
|
|
1248
|
+
# ── /mcp (Model Context Protocol toolsets) ────────────────────────────────
|
|
1249
|
+
if name == "mcp":
|
|
1250
|
+
args_m = (sc.args or "").strip()
|
|
1251
|
+
sub = (args_m.split()[0].strip().lower() if args_m else "status")
|
|
1252
|
+
mcp_path = cfg.project_root / ".gemcode" / "mcp.json"
|
|
1253
|
+
|
|
1254
|
+
if sub in ("help", "?"):
|
|
1255
|
+
out("Usage:")
|
|
1256
|
+
out(" /mcp Show MCP config + loaded toolsets (same as /mcp status)")
|
|
1257
|
+
out(" /mcp status Show MCP config + loaded toolsets")
|
|
1258
|
+
out(" /mcp list List configured servers from .gemcode/mcp.json")
|
|
1259
|
+
out(" /mcp reload Rebuild runner to reload MCP toolsets from disk")
|
|
1260
|
+
out()
|
|
1261
|
+
out("Config:")
|
|
1262
|
+
out(f" {mcp_path}")
|
|
1263
|
+
out()
|
|
1264
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
1265
|
+
|
|
1266
|
+
if sub in ("reload", "refresh"):
|
|
1267
|
+
out("MCP: runner will rebuild on the next turn (reload .gemcode/mcp.json).")
|
|
1268
|
+
out()
|
|
1269
|
+
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
1270
|
+
|
|
1271
|
+
# Read config if present.
|
|
1272
|
+
servers: list[dict] = []
|
|
1273
|
+
parse_error: str | None = None
|
|
1274
|
+
if mcp_path.is_file():
|
|
1275
|
+
try:
|
|
1276
|
+
import json
|
|
1277
|
+
|
|
1278
|
+
data = json.loads(mcp_path.read_text(encoding="utf-8"))
|
|
1279
|
+
servers = list(data.get("servers") or [])
|
|
1280
|
+
except Exception as e:
|
|
1281
|
+
parse_error = str(e)
|
|
1282
|
+
|
|
1283
|
+
# Inspect currently loaded toolsets (best-effort; depends on how caller wired extra_tools).
|
|
1284
|
+
loaded_prefixes: list[str] = []
|
|
1285
|
+
loaded_count = 0
|
|
1286
|
+
try:
|
|
1287
|
+
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset # type: ignore
|
|
1288
|
+
|
|
1289
|
+
for t in list(extra_tools or []):
|
|
1290
|
+
if isinstance(t, McpToolset):
|
|
1291
|
+
loaded_count += 1
|
|
1292
|
+
try:
|
|
1293
|
+
p = getattr(t, "tool_name_prefix", None)
|
|
1294
|
+
if isinstance(p, str) and p and p not in loaded_prefixes:
|
|
1295
|
+
loaded_prefixes.append(p)
|
|
1296
|
+
except Exception:
|
|
1297
|
+
pass
|
|
1298
|
+
except Exception:
|
|
1299
|
+
# MCP extras not installed or ADK missing MCP toolset types.
|
|
1300
|
+
pass
|
|
1301
|
+
|
|
1302
|
+
if sub in ("list", "ls"):
|
|
1303
|
+
out(f"mcp.json: {mcp_path} ({'exists' if mcp_path.is_file() else 'missing'})")
|
|
1304
|
+
if parse_error:
|
|
1305
|
+
out(f"error: {parse_error}")
|
|
1306
|
+
out()
|
|
1307
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
1308
|
+
if not servers:
|
|
1309
|
+
out("(no servers configured)")
|
|
1310
|
+
out()
|
|
1311
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
1312
|
+
out("Servers:")
|
|
1313
|
+
for s in servers[:200]:
|
|
1314
|
+
try:
|
|
1315
|
+
nm = (s.get("name") or "mcp").strip()
|
|
1316
|
+
kind = "stdio" if "stdio" in s else ("http" if "http" in s else ("sse" if "sse" in s else "?"))
|
|
1317
|
+
out(f" - {nm} ({kind})")
|
|
1318
|
+
except Exception:
|
|
1319
|
+
continue
|
|
1320
|
+
if len(servers) > 200:
|
|
1321
|
+
out(f" … (+{len(servers) - 200} more)")
|
|
1322
|
+
out()
|
|
1323
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
1324
|
+
|
|
1325
|
+
# Default: status.
|
|
1326
|
+
out("MCP:")
|
|
1327
|
+
out(f" mcp.json: {mcp_path} ({'exists' if mcp_path.is_file() else 'missing'})")
|
|
1328
|
+
if parse_error:
|
|
1329
|
+
out(f" parse_error: {parse_error}")
|
|
1330
|
+
out(f" configured_servers: {len(servers)}")
|
|
1331
|
+
suffix = f" (prefixes: {', '.join(sorted(loaded_prefixes))})" if loaded_prefixes else ""
|
|
1332
|
+
out(f" loaded_toolsets: {loaded_count}{suffix}")
|
|
1333
|
+
out()
|
|
1334
|
+
if not mcp_path.is_file():
|
|
1335
|
+
out("Tip: create .gemcode/mcp.json to enable MCP toolsets for this project.")
|
|
1336
|
+
out()
|
|
1337
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
1338
|
+
|
|
1248
1339
|
if name == "tools":
|
|
1249
1340
|
args_t = (sc.args or "").strip().lower()
|
|
1250
1341
|
if args_t in ("smoke", "decl", "declarations"):
|
|
@@ -31,6 +31,63 @@ from gemcode.plugins.terminal_hooks_plugin import GemCodeTerminalHooksPlugin
|
|
|
31
31
|
from gemcode.plugins.tool_recovery_plugin import GemCodeReflectAndRetryToolPlugin
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# ADK: Gemini context cache — quiet "stale delete" failures
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _gemini_cache_delete_already_gone(exc: BaseException) -> bool:
|
|
40
|
+
"""True when delete failed only because the cache entry is already gone.
|
|
41
|
+
|
|
42
|
+
The Gemini API often returns ``403 PERMISSION_DENIED`` with a body like
|
|
43
|
+
``CachedContent not found`` after TTL expiry or server-side eviction. ADK's
|
|
44
|
+
default cleanup logs that as WARNING every time — noisy and usually harmless.
|
|
45
|
+
"""
|
|
46
|
+
msg = str(exc).lower()
|
|
47
|
+
if "cachedcontent not found" in msg:
|
|
48
|
+
return True
|
|
49
|
+
if "not found" in msg and "cached" in msg:
|
|
50
|
+
return True
|
|
51
|
+
code = getattr(exc, "code", None)
|
|
52
|
+
if code == 404:
|
|
53
|
+
return True
|
|
54
|
+
status = (getattr(exc, "status", None) or "").upper()
|
|
55
|
+
if code == 403 and status == "PERMISSION_DENIED" and ("cachedcontent" in msg or "cached content" in msg):
|
|
56
|
+
return True
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _patch_gemini_adk_cache_cleanup() -> None:
|
|
61
|
+
"""Downgrade benign cache-delete failures to DEBUG (see ``_gemini_cache_delete_already_gone``)."""
|
|
62
|
+
try:
|
|
63
|
+
from google.adk.models import gemini_context_cache_manager as gccm
|
|
64
|
+
except Exception:
|
|
65
|
+
return
|
|
66
|
+
if getattr(gccm.GeminiContextCacheManager, "_gemcode_cleanup_patch", False):
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
async def _cleanup(self, cache_name: str) -> None:
|
|
70
|
+
gccm.logger.debug("Attempting to delete cache: %s", cache_name)
|
|
71
|
+
try:
|
|
72
|
+
await self.genai_client.aio.caches.delete(name=cache_name)
|
|
73
|
+
gccm.logger.info("Cache cleaned up: %s", cache_name)
|
|
74
|
+
except BaseException as e:
|
|
75
|
+
if _gemini_cache_delete_already_gone(e):
|
|
76
|
+
gccm.logger.debug(
|
|
77
|
+
"Cache delete no-op (already expired or gone): %s — %s",
|
|
78
|
+
cache_name,
|
|
79
|
+
e,
|
|
80
|
+
)
|
|
81
|
+
return
|
|
82
|
+
gccm.logger.warning("Failed to cleanup cache %s: %s", cache_name, e)
|
|
83
|
+
|
|
84
|
+
gccm.GeminiContextCacheManager.cleanup_cache = _cleanup # type: ignore[method-assign]
|
|
85
|
+
gccm.GeminiContextCacheManager._gemcode_cleanup_patch = True
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
_patch_gemini_adk_cache_cleanup()
|
|
89
|
+
|
|
90
|
+
|
|
34
91
|
# ---------------------------------------------------------------------------
|
|
35
92
|
# ADK App-level feature helpers
|
|
36
93
|
# ---------------------------------------------------------------------------
|
|
@@ -149,6 +149,7 @@ tests/test_permissions.py
|
|
|
149
149
|
tests/test_prompt_suggestions.py
|
|
150
150
|
tests/test_repl_commands.py
|
|
151
151
|
tests/test_repl_slash.py
|
|
152
|
+
tests/test_session_runtime_cache.py
|
|
152
153
|
tests/test_skills.py
|
|
153
154
|
tests/test_slash_commands.py
|
|
154
155
|
tests/test_slash_completion_registry.py
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Tests for Gemini context-cache cleanup heuristics (session_runtime)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from gemcode.session_runtime import _gemini_cache_delete_already_gone
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_cache_delete_harmless_user_reported_403() -> None:
|
|
11
|
+
pytest.importorskip("google.genai.errors")
|
|
12
|
+
from google.genai.errors import ClientError
|
|
13
|
+
|
|
14
|
+
exc = ClientError(
|
|
15
|
+
403,
|
|
16
|
+
{"error": {"message": "CachedContent not found (or permission denied)", "status": "PERMISSION_DENIED"}},
|
|
17
|
+
None,
|
|
18
|
+
)
|
|
19
|
+
assert _gemini_cache_delete_already_gone(exc) is True
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_cache_delete_not_harmless_other_403() -> None:
|
|
23
|
+
pytest.importorskip("google.genai.errors")
|
|
24
|
+
from google.genai.errors import ClientError
|
|
25
|
+
|
|
26
|
+
exc = ClientError(
|
|
27
|
+
403,
|
|
28
|
+
{"error": {"message": "Permission denied on billing account", "status": "PERMISSION_DENIED"}},
|
|
29
|
+
None,
|
|
30
|
+
)
|
|
31
|
+
assert _gemini_cache_delete_already_gone(exc) is False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_cache_delete_harmless_404() -> None:
|
|
35
|
+
pytest.importorskip("google.genai.errors")
|
|
36
|
+
from google.genai.errors import ClientError
|
|
37
|
+
|
|
38
|
+
exc = ClientError(404, {"error": {"message": "Not found", "status": "NOT_FOUND"}}, None)
|
|
39
|
+
assert _gemini_cache_delete_already_gone(exc) is True
|
|
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
|
|
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
|