gemcode 0.3.27__tar.gz → 0.3.29__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.27/src/gemcode.egg-info → gemcode-0.3.29}/PKG-INFO +1 -1
- {gemcode-0.3.27 → gemcode-0.3.29}/pyproject.toml +1 -1
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/agent.py +161 -9
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/config.py +16 -0
- gemcode-0.3.29/src/gemcode/refine.py +204 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/repl_commands.py +2 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/repl_slash.py +82 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/session_runtime.py +24 -1
- gemcode-0.3.29/src/gemcode/tool_prompt_manifest.py +168 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/__init__.py +24 -2
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tui/input_handler.py +2 -0
- {gemcode-0.3.27 → gemcode-0.3.29/src/gemcode.egg-info}/PKG-INFO +1 -1
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode.egg-info/SOURCES.txt +1 -0
- gemcode-0.3.27/src/gemcode/tool_prompt_manifest.py +0 -142
- {gemcode-0.3.27 → gemcode-0.3.29}/LICENSE +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/MANIFEST.in +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/README.md +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/setup.cfg +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/__init__.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/__main__.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/audit.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/autocompact.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/callbacks.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/capability_routing.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/cli.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/compaction.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/computer_use/__init__.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/computer_use/browser_computer.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/context_budget.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/context_warning.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/credentials.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/hitl_session.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/interactions.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/invoke.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/kairos_daemon.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/limits.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/live_audio_engine.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/logging_config.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/mcp_loader.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/memory/__init__.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/memory/embedding_memory_service.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/memory/file_memory_service.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/modality_tools.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/model_errors.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/model_routing.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/paths.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/permissions.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/plugins/__init__.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/plugins/terminal_hooks_plugin.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/plugins/tool_recovery_plugin.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/prompt_suggestions.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/__init__.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/config.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/deps.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/engine.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/stop_hooks.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/token_budget.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/query/transitions.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/slash_commands.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/thinking.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tool_registry.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/bash.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/browser.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/edit.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/filesystem.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/search.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/shell.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/shell_gate.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/subtask.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/think.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/todo.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools/web.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tools_inspector.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/trust.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tui/scrollback.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tui/spinner.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tui/welcome_banner.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/tui/welcome_rich.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/version.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/vertex.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/web/__init__.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/web/claude_sse_adapter.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/web/terminal_repl.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode/workspace_hints.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode.egg-info/dependency_links.txt +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode.egg-info/entry_points.txt +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode.egg-info/requires.txt +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/src/gemcode.egg-info/top_level.txt +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_agent_instruction.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_autocompact.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_capability_routing.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_claude_web_adapter_sse.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_cli_init.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_computer_use_permissions.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_context_budget.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_context_warning.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_credentials.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_interactive_permission_ask.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_kairos_scheduler.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_modality_tools.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_model_error_retry.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_model_errors.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_model_routing.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_paths.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_permissions.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_prompt_suggestions.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_repl_commands.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_repl_slash.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_slash_commands.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_thinking_config.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_token_budget.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_tool_context_circulation.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_tools.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_tools_inspector.py +0 -0
- {gemcode-0.3.27 → gemcode-0.3.29}/tests/test_workspace_hints.py +0 -0
|
@@ -58,17 +58,97 @@ def _load_gemini_md(project_root: Path) -> str:
|
|
|
58
58
|
|
|
59
59
|
def _build_runtime_facts(cfg: GemCodeConfig) -> str:
|
|
60
60
|
"""
|
|
61
|
-
Injected every session so the model
|
|
62
|
-
|
|
61
|
+
Injected every session so the model is fully self-aware of its own capabilities,
|
|
62
|
+
limits, and the environment — not just generic defaults.
|
|
63
63
|
"""
|
|
64
64
|
root = cfg.project_root.resolve()
|
|
65
65
|
model = (getattr(cfg, "model", None) or "").strip() or "(default)"
|
|
66
|
+
|
|
67
|
+
# ── Active capabilities ──────────────────────────────────────────────────
|
|
68
|
+
caps: list[str] = []
|
|
69
|
+
if getattr(cfg, "enable_deep_research", False):
|
|
70
|
+
dr_extras = " + google_maps_grounding" if getattr(cfg, "enable_maps_grounding", False) else ""
|
|
71
|
+
caps.append(f"deep_research ON (tools: google_search, url_context{dr_extras})")
|
|
72
|
+
if getattr(cfg, "enable_embeddings", False):
|
|
73
|
+
caps.append(f"embeddings ON (tool: semantic_search_files, model: {getattr(cfg, 'embeddings_model', 'default')})")
|
|
74
|
+
if getattr(cfg, "enable_memory", False):
|
|
75
|
+
mem_path = root / ".gemcode" / "memories.jsonl"
|
|
76
|
+
mem_kind = "embedding-backed" if getattr(cfg, "enable_embeddings", False) else "keyword-backed"
|
|
77
|
+
caps.append(f"memory ON ({mem_kind}, stored at {mem_path}; ADK preload_memory injects relevant memories before each turn)")
|
|
78
|
+
if getattr(cfg, "enable_computer_use", False):
|
|
79
|
+
caps.append("computer_use ON (tools: navigate, click_at, type_text_at, browser_screenshot, browser_find_element, etc.)")
|
|
80
|
+
if getattr(cfg, "enable_code_executor", False):
|
|
81
|
+
caps.append("code_executor ON — you can write Python code blocks and they will be executed safely via Gemini's built-in sandboxed executor; results appear as code_execution_result events. Use this for math, data processing, quick tests, and anything that would otherwise require a shell command.")
|
|
82
|
+
if getattr(cfg, "enable_artifacts", True):
|
|
83
|
+
caps.append("artifacts ON — use save_artifact(filename, bytes, mime_type) / load_artifact(filename) to store large/binary outputs (screenshots, PDFs, generated files) outside session history. Artifacts are keyed by filename; prefix 'user:' for cross-session persistence.")
|
|
84
|
+
if not caps:
|
|
85
|
+
caps.append("none enabled (use /research on, /embeddings on, /memory on, /computer on, /code on to enable)")
|
|
86
|
+
caps_text = "\n".join(f" - {c}" for c in caps)
|
|
87
|
+
|
|
88
|
+
# ── Limits ───────────────────────────────────────────────────────────────
|
|
89
|
+
max_calls = getattr(cfg, "max_llm_calls", 256) or 256
|
|
90
|
+
token_budget = getattr(cfg, "token_budget", None)
|
|
91
|
+
max_session_tokens = getattr(cfg, "max_session_tokens", None)
|
|
92
|
+
budget_line = f"{max_calls} model↔tool iterations per user message"
|
|
93
|
+
if token_budget:
|
|
94
|
+
budget_line += f" · token_budget={token_budget:,} per turn"
|
|
95
|
+
if max_session_tokens:
|
|
96
|
+
budget_line += f" · max_session_tokens={max_session_tokens:,}"
|
|
97
|
+
|
|
98
|
+
# ── Kairos ────────────────────────────────────────────────────────────────
|
|
99
|
+
# The user can run `gemcode kairos -C <project>` in a separate terminal to
|
|
100
|
+
# launch a long-lived scheduler. Jobs submitted to it run concurrently with
|
|
101
|
+
# the current session. This is useful for background / parallel heavy work.
|
|
102
|
+
kairos_section = (
|
|
103
|
+
"- **Kairos background scheduler** — `gemcode kairos -C <project>` launches a "
|
|
104
|
+
"long-lived daemon that reads prompts from stdin and runs each as an isolated job "
|
|
105
|
+
"(up to N concurrently). Each job gets `kairos_sleep_ms(ms)` and "
|
|
106
|
+
"`kairos_enqueue_prompt(prompt, priority, session_id)` tools so the model can "
|
|
107
|
+
"schedule follow-up work itself. Useful for: bulk file processing, repeated "
|
|
108
|
+
"polling loops, parallelising large independent tasks. "
|
|
109
|
+
"Tell the user to open a second terminal and run `gemcode kairos` if a task "
|
|
110
|
+
"would benefit from background parallelism."
|
|
111
|
+
)
|
|
112
|
+
|
|
66
113
|
return f"""## Runtime facts (authoritative for this session)
|
|
67
114
|
- **Project root** — every filesystem tool path is relative to: `{root}`
|
|
68
|
-
- **Model id in use:** `{model}`.
|
|
69
|
-
- **
|
|
70
|
-
- **
|
|
71
|
-
|
|
115
|
+
- **Model id in use:** `{model}`. Override mid-session with `/model use <id>` or `/mode fast|balanced|quality|auto`.
|
|
116
|
+
- **Execution budget:** {budget_line}.
|
|
117
|
+
- **Active capabilities:**
|
|
118
|
+
{caps_text}
|
|
119
|
+
- **Capability routing** (`capability_mode={getattr(cfg, 'capability_mode', 'auto')}`): in `auto` mode, GemCode automatically enables deep_research when it detects research-intent keywords in your prompt each turn. You can also type `/research on`, `/embeddings on`, `/memory on`, `/computer on` at the prompt.
|
|
120
|
+
- **Your tool palette can grow mid-session:** if the user enables a capability via a slash command, the runner rebuilds and you get new tools on the next turn.
|
|
121
|
+
- **Memory system:** when `memory ON`, ADK automatically searches `.gemcode/memories.jsonl` and injects relevant past context before each turn. Facts the user tells you in one session can appear in future sessions. You do not need to manage memory explicitly — it is loaded automatically.
|
|
122
|
+
{kairos_section}
|
|
123
|
+
- **UI banner** phrases like "GemCode Pro" are terminal marketing, not a separate API tier.
|
|
124
|
+
- **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.
|
|
125
|
+
- **Working in subfolders** — call `list_directory("Desktop")`, `glob_files("**/query.ts")`, `read_file("testing/ai-edtech-app/src/app/page.tsx")` directly. Never claim access is blocked unless a tool returned an explicit error."""
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _build_memory_section(cfg: GemCodeConfig) -> str:
|
|
129
|
+
"""Injected when enable_memory=True so the agent understands and uses memory."""
|
|
130
|
+
mem_path = cfg.project_root / ".gemcode" / "memories.jsonl"
|
|
131
|
+
kind = "embedding-based (semantic cosine similarity)" if getattr(cfg, "enable_embeddings", False) else "keyword-based"
|
|
132
|
+
return f"""
|
|
133
|
+
## Persistent Memory System
|
|
134
|
+
Memory is **ON** ({kind}). Stored at: `{mem_path}`
|
|
135
|
+
|
|
136
|
+
### How it works
|
|
137
|
+
- Before each turn, ADK automatically searches the memory store for relevant past facts and injects them as context — you do not need to call a tool to load them.
|
|
138
|
+
- After each turn, the session is automatically added to memory by the post-turn plugin.
|
|
139
|
+
- The memory file persists across sessions (JSONL, one entry per session).
|
|
140
|
+
|
|
141
|
+
### What to do with memory
|
|
142
|
+
- **Reference it naturally** — if the injected context mentions past facts ("last time the user's API key was X", "the user prefers TypeScript"), treat that as trusted context.
|
|
143
|
+
- **Update it proactively** — if the user tells you important facts about their project, preferences, or recurring patterns, note them in your response: "I'll remember that for future sessions."
|
|
144
|
+
- **Don't re-explain already-known context** — if the memory already contains the project structure or preferences, skip the discovery step and act on what's known.
|
|
145
|
+
- **Memory is scoped to this project root** — `{cfg.project_root}`. Different project roots have separate memories.
|
|
146
|
+
|
|
147
|
+
### When memory helps most
|
|
148
|
+
- Long-running projects (user preferences, patterns, recurring tasks)
|
|
149
|
+
- Multi-session workflows (continuing work from a previous day)
|
|
150
|
+
- Team conventions stored once and reused automatically
|
|
151
|
+
"""
|
|
72
152
|
|
|
73
153
|
|
|
74
154
|
def _build_computer_use_section(cfg: GemCodeConfig) -> str:
|
|
@@ -350,6 +430,41 @@ Use `run_subtask` when the work is better done in an isolated context:
|
|
|
350
430
|
|
|
351
431
|
The sub-agent inherits your permission settings and returns its final text as `result`. Treat it as a trusted colleague returning a written summary.
|
|
352
432
|
|
|
433
|
+
## ADK Special Tools (always available when ADK supports them)
|
|
434
|
+
|
|
435
|
+
### `get_user_choice`
|
|
436
|
+
Present the user with a structured multi-option prompt rather than open-ended questions.
|
|
437
|
+
Use when you need the user to pick from 2–6 specific options (e.g. "Which framework would you like?", "Choose migration strategy: A, B, or C").
|
|
438
|
+
This provides a better UX than asking them to type a free-form answer.
|
|
439
|
+
|
|
440
|
+
### `load_artifacts`
|
|
441
|
+
Load binary/large artifacts that were saved in a previous turn or by a sub-agent.
|
|
442
|
+
Artifacts are keyed by filename (e.g. "report.pdf", "screenshot.png", "output.json").
|
|
443
|
+
Use `user:filename` prefix for user-scoped artifacts that persist across sessions.
|
|
444
|
+
After loading, the artifact bytes are available for further processing (display, analysis, transformation).
|
|
445
|
+
|
|
446
|
+
### `exit_loop`
|
|
447
|
+
Signal the surrounding LoopAgent to stop iterating and return the final result.
|
|
448
|
+
Only meaningful when this agent is running inside an ADK LoopAgent pipeline.
|
|
449
|
+
Call this when the task is complete and no further iterations are needed.
|
|
450
|
+
|
|
451
|
+
## Artifacts — storing large outputs
|
|
452
|
+
When `artifacts ON` (see Runtime facts above):
|
|
453
|
+
- **Save** large generated content as artifacts instead of printing them inline:
|
|
454
|
+
- Screenshots from computer_use: save as "screenshot.png" artifact
|
|
455
|
+
- Generated reports/PDFs: save as "report.pdf" artifact
|
|
456
|
+
- Large JSON data: save as "data.json" artifact
|
|
457
|
+
- **Reference** artifacts in instructions via `{artifact.filename?}` template syntax
|
|
458
|
+
- Artifacts are keyed by filename; `user:` prefix = cross-session persistence
|
|
459
|
+
|
|
460
|
+
## Code Executor (sandboxed Python)
|
|
461
|
+
When `code_executor ON` (see Runtime facts above):
|
|
462
|
+
- You can write Python code blocks in your response and the Gemini API executes them safely
|
|
463
|
+
- The result appears as a `code_execution_result` event with stdout and the outcome
|
|
464
|
+
- Best for: math calculations, data transformation, unit testing logic, quick experiments
|
|
465
|
+
- The sandbox does NOT have internet access or filesystem access — use for pure computation
|
|
466
|
+
- For file I/O or shell commands, use the standard tools (`bash`, `write_file`, etc.)
|
|
467
|
+
|
|
353
468
|
## Evaluator-optimizer loop
|
|
354
469
|
For tasks where quality matters:
|
|
355
470
|
1. Complete the task (execute tools, write code, run commands)
|
|
@@ -378,10 +493,13 @@ For tasks where quality matters:
|
|
|
378
493
|
## Workspace scope
|
|
379
494
|
All file tools use paths **relative to the project root** (where GemCode was started). The root may be the home folder — subfolders like `Desktop`, `Desktop/code`, `Documents` are inside the sandbox. Call `list_directory("Desktop")` or `glob_files("**/*name*.ts")` instead of assuming access is blocked. Only treat access as denied when a tool returns an explicit `error`."""
|
|
380
495
|
|
|
381
|
-
# Inject
|
|
496
|
+
# Inject capability-specific strategy sections only when those caps are on.
|
|
382
497
|
if getattr(cfg, "enable_computer_use", False):
|
|
383
498
|
base = f"{base}\n\n{_build_computer_use_section(cfg)}"
|
|
384
499
|
|
|
500
|
+
if getattr(cfg, "enable_memory", False):
|
|
501
|
+
base = f"{base}\n\n{_build_memory_section(cfg)}"
|
|
502
|
+
|
|
385
503
|
tool_manifest = build_tool_manifest(cfg)
|
|
386
504
|
if tool_manifest:
|
|
387
505
|
base = f"{base}\n\n{tool_manifest}"
|
|
@@ -391,6 +509,17 @@ All file tools use paths **relative to the project root** (where GemCode was sta
|
|
|
391
509
|
return base
|
|
392
510
|
|
|
393
511
|
|
|
512
|
+
def _build_code_executor(cfg: GemCodeConfig):
|
|
513
|
+
"""Return an ADK BuiltInCodeExecutor when enable_code_executor=True, else None."""
|
|
514
|
+
if not getattr(cfg, "enable_code_executor", False):
|
|
515
|
+
return None
|
|
516
|
+
try:
|
|
517
|
+
from google.adk.code_executors import BuiltInCodeExecutor
|
|
518
|
+
return BuiltInCodeExecutor()
|
|
519
|
+
except Exception:
|
|
520
|
+
return None
|
|
521
|
+
|
|
522
|
+
|
|
394
523
|
def build_root_agent(
|
|
395
524
|
cfg: GemCodeConfig,
|
|
396
525
|
extra_tools: list | None = None,
|
|
@@ -413,8 +542,15 @@ def build_root_agent(
|
|
|
413
542
|
if getattr(cfg, "enable_memory", False):
|
|
414
543
|
# ADK preload_memory injects retrieved memories into the next llm_request.
|
|
415
544
|
from google.adk.tools import preload_memory
|
|
416
|
-
|
|
417
545
|
tools = [preload_memory, *tools]
|
|
546
|
+
|
|
547
|
+
# ADK built-in interactive + artifact tools — always available when ADK supports them.
|
|
548
|
+
try:
|
|
549
|
+
from google.adk.tools import get_user_choice, load_artifacts, exit_loop
|
|
550
|
+
tools = [*tools, get_user_choice, load_artifacts, exit_loop]
|
|
551
|
+
except Exception:
|
|
552
|
+
pass
|
|
553
|
+
|
|
418
554
|
if extra_tools:
|
|
419
555
|
tools = [*tools, *extra_tools]
|
|
420
556
|
|
|
@@ -469,15 +605,31 @@ def build_root_agent(
|
|
|
469
605
|
tool_config=tool_cfg,
|
|
470
606
|
)
|
|
471
607
|
|
|
472
|
-
|
|
608
|
+
# global_instruction applies to the entire agent tree (including sub-agents
|
|
609
|
+
# spawned via run_subtask or multi-agent delegation). Keep it short — it's
|
|
610
|
+
# prepended to every agent's effective instruction.
|
|
611
|
+
global_instr = (
|
|
612
|
+
"You are GemCode, an expert software engineering agent powered by Google Gemini. "
|
|
613
|
+
"Act, don't advise. Complete tasks fully and autonomously. "
|
|
614
|
+
"Think before destructive actions. Use read-only tools before shell/write tools."
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
agent_kwargs: dict = dict(
|
|
473
618
|
model=cfg.model,
|
|
474
619
|
name="gemcode",
|
|
475
620
|
instruction=build_instruction(cfg),
|
|
621
|
+
global_instruction=global_instr,
|
|
476
622
|
tools=tools,
|
|
477
623
|
generate_content_config=gen_cfg,
|
|
478
624
|
**cb_kwargs,
|
|
479
625
|
)
|
|
480
626
|
|
|
627
|
+
code_executor = _build_code_executor(cfg)
|
|
628
|
+
if code_executor is not None:
|
|
629
|
+
agent_kwargs["code_executor"] = code_executor
|
|
630
|
+
|
|
631
|
+
return LlmAgent(**agent_kwargs)
|
|
632
|
+
|
|
481
633
|
|
|
482
634
|
def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None):
|
|
483
635
|
"""Backward-compatible: prefer `gemcode.session_runtime.create_runner`."""
|
|
@@ -251,6 +251,22 @@ class GemCodeConfig:
|
|
|
251
251
|
default_factory=lambda: _truthy_env("GEMCODE_SHOW_FULL_THINKING", default=False)
|
|
252
252
|
)
|
|
253
253
|
|
|
254
|
+
# Enable ADK BuiltInCodeExecutor for safe sandboxed Python execution via
|
|
255
|
+
# Gemini's code execution API. When on, the agent can write and run Python
|
|
256
|
+
# snippets inline (math, data processing, quick tests) without requiring
|
|
257
|
+
# bash/shell permissions. Requires a Gemini model that supports code execution
|
|
258
|
+
# (gemini-2.5-flash, gemini-2.5-pro, gemini-3.x).
|
|
259
|
+
enable_code_executor: bool = field(
|
|
260
|
+
default_factory=lambda: _truthy_env("GEMCODE_ENABLE_CODE_EXECUTOR", default=False)
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Enable ADK artifact service for storing large/binary outputs (screenshots,
|
|
264
|
+
# generated files, reports) outside of session history.
|
|
265
|
+
# When on, the agent can save_artifact / load_artifact keyed by filename.
|
|
266
|
+
enable_artifacts: bool = field(
|
|
267
|
+
default_factory=lambda: _truthy_env("GEMCODE_ENABLE_ARTIFACTS", default=True)
|
|
268
|
+
)
|
|
269
|
+
|
|
254
270
|
def __post_init__(self) -> None:
|
|
255
271
|
self.project_root = self.project_root.resolve()
|
|
256
272
|
# Default agentic depth when env omits GEMCODE_MAX_LLM_CALLS (was: None → SDK default).
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Iterative refinement pipelines using ADK LoopAgent + SequentialAgent.
|
|
3
|
+
|
|
4
|
+
This module exposes factory functions for building multi-agent orchestration
|
|
5
|
+
patterns that GemCode can invoke for complex, multi-step tasks:
|
|
6
|
+
|
|
7
|
+
- build_refine_loop(): LoopAgent that loops write→test→fix until tests pass
|
|
8
|
+
or max_iterations is reached (escalates via exit_loop tool).
|
|
9
|
+
|
|
10
|
+
- build_sequential_pipeline(): SequentialAgent that runs N specialist agents
|
|
11
|
+
in order, passing results through session state via output_key.
|
|
12
|
+
|
|
13
|
+
- build_parallel_research(): ParallelAgent that fans out N research sub-agents
|
|
14
|
+
then collects their outputs.
|
|
15
|
+
|
|
16
|
+
These are "heavy" patterns; they are NOT wired by default — callers instantiate
|
|
17
|
+
them on demand (e.g. via /refine slash command or run_subtask).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
log = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Guard: check what's available in the installed ADK version
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
try:
|
|
31
|
+
from google.adk.agents import LoopAgent, SequentialAgent, ParallelAgent, LlmAgent # type: ignore
|
|
32
|
+
_ADK_WORKFLOW_AGENTS_OK = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
_ADK_WORKFLOW_AGENTS_OK = False
|
|
35
|
+
log.warning("google.adk workflow agents (LoopAgent/SequentialAgent/ParallelAgent) "
|
|
36
|
+
"not available — refine pipelines disabled.")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _require_workflow() -> None:
|
|
40
|
+
if not _ADK_WORKFLOW_AGENTS_OK:
|
|
41
|
+
raise RuntimeError(
|
|
42
|
+
"ADK workflow agents (LoopAgent / SequentialAgent / ParallelAgent) are not "
|
|
43
|
+
"available in the installed google-adk version. "
|
|
44
|
+
"Try: pip install -U google-adk"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Write → Test → Fix Loop
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
def build_refine_loop(
|
|
53
|
+
*,
|
|
54
|
+
model: str,
|
|
55
|
+
task_description: str,
|
|
56
|
+
test_command: str,
|
|
57
|
+
max_iterations: int = 8,
|
|
58
|
+
extra_tools: list | None = None,
|
|
59
|
+
) -> Any:
|
|
60
|
+
"""
|
|
61
|
+
Build a LoopAgent that iteratively writes code, runs tests, and fixes
|
|
62
|
+
failures until all tests pass or ``max_iterations`` is exhausted.
|
|
63
|
+
|
|
64
|
+
Architecture:
|
|
65
|
+
LoopAgent(max_iterations=N)
|
|
66
|
+
├─ writer_agent — writes / edits code based on current task + test output
|
|
67
|
+
├─ tester_agent — runs the test command via bash, stores result in state
|
|
68
|
+
└─ checker_agent — inspects test result; calls exit_loop if tests pass
|
|
69
|
+
|
|
70
|
+
State keys used:
|
|
71
|
+
"task" — original task description (seeded by caller)
|
|
72
|
+
"test_command" — shell command to run tests
|
|
73
|
+
"test_output" — stdout/stderr from last test run
|
|
74
|
+
"test_passed" — "yes" if tests passed
|
|
75
|
+
"iteration" — current loop count (auto-incremented by writer)
|
|
76
|
+
|
|
77
|
+
Returns the LoopAgent instance, ready to run via a Runner.
|
|
78
|
+
|
|
79
|
+
Example usage:
|
|
80
|
+
loop = build_refine_loop(model="gemini-2.5-flash",
|
|
81
|
+
task_description="add unit tests for parser.py",
|
|
82
|
+
test_command="pytest tests/test_parser.py -x")
|
|
83
|
+
runner = Runner(agent=loop, app_name="refine", session_service=...)
|
|
84
|
+
async for event in runner.run_async(...):
|
|
85
|
+
...
|
|
86
|
+
"""
|
|
87
|
+
_require_workflow()
|
|
88
|
+
from google.adk.tools import exit_loop # type: ignore
|
|
89
|
+
|
|
90
|
+
from gemcode.tools import build_function_tools # type: ignore
|
|
91
|
+
|
|
92
|
+
# Shared tools that the sub-agents may use (no run_subtask to avoid recursion)
|
|
93
|
+
tools = extra_tools or []
|
|
94
|
+
|
|
95
|
+
writer = LlmAgent(
|
|
96
|
+
model=model,
|
|
97
|
+
name="writer_agent",
|
|
98
|
+
description="Writes or edits source code to fulfil the task, using test failure output as feedback.",
|
|
99
|
+
instruction=(
|
|
100
|
+
"You are a coding agent. Your goal: {task}.\n\n"
|
|
101
|
+
"Test command: {test_command}\n"
|
|
102
|
+
"Last test output:\n{test_output?}\n\n"
|
|
103
|
+
"Write or fix code to make the tests pass. "
|
|
104
|
+
"After making your edits, update state key 'iteration' by incrementing it."
|
|
105
|
+
),
|
|
106
|
+
tools=tools,
|
|
107
|
+
output_key="writer_notes",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
tester = LlmAgent(
|
|
111
|
+
model=model,
|
|
112
|
+
name="tester_agent",
|
|
113
|
+
description="Runs the test suite and stores the output in session state.",
|
|
114
|
+
instruction=(
|
|
115
|
+
"Run the test command stored in state: {test_command}.\n"
|
|
116
|
+
"Use the bash tool to execute it. Store ALL stdout+stderr verbatim in "
|
|
117
|
+
"state key 'test_output'. "
|
|
118
|
+
"If the command exits with code 0 (success), set state key 'test_passed' to 'yes'. "
|
|
119
|
+
"If it fails, set 'test_passed' to 'no'."
|
|
120
|
+
),
|
|
121
|
+
tools=tools,
|
|
122
|
+
output_key="test_output",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
checker = LlmAgent(
|
|
126
|
+
model=model,
|
|
127
|
+
name="checker_agent",
|
|
128
|
+
description=(
|
|
129
|
+
"Checks whether tests passed. If yes, calls exit_loop to terminate the "
|
|
130
|
+
"refinement cycle. If no, does nothing (loop continues)."
|
|
131
|
+
),
|
|
132
|
+
instruction=(
|
|
133
|
+
"Check the value of state key 'test_passed'.\n"
|
|
134
|
+
"If 'test_passed' is 'yes': call the exit_loop tool to stop the loop — "
|
|
135
|
+
"the task is complete.\n"
|
|
136
|
+
"If 'test_passed' is 'no': output a brief summary of the failures so the "
|
|
137
|
+
"writer knows what to fix next. Do NOT call exit_loop."
|
|
138
|
+
),
|
|
139
|
+
tools=[exit_loop, *tools],
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return LoopAgent(
|
|
143
|
+
name="refine_loop",
|
|
144
|
+
description=(
|
|
145
|
+
f"Iterative write→test→fix loop for: {task_description[:120]}"
|
|
146
|
+
),
|
|
147
|
+
sub_agents=[writer, tester, checker],
|
|
148
|
+
max_iterations=max_iterations,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# Sequential Pipeline
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
def build_sequential_pipeline(
|
|
157
|
+
agents: list[Any],
|
|
158
|
+
*,
|
|
159
|
+
name: str = "sequential_pipeline",
|
|
160
|
+
description: str = "Runs specialist agents sequentially, passing results via state.",
|
|
161
|
+
) -> Any:
|
|
162
|
+
"""
|
|
163
|
+
Wrap a list of LlmAgents in a SequentialAgent.
|
|
164
|
+
|
|
165
|
+
Each agent should set ``output_key`` so subsequent agents can read its output
|
|
166
|
+
via {key} template substitution in their instructions.
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
pipeline = build_sequential_pipeline([
|
|
170
|
+
planner_agent, # output_key="plan"
|
|
171
|
+
coder_agent, # reads {plan}, output_key="code"
|
|
172
|
+
reviewer_agent, # reads {code}, output_key="review"
|
|
173
|
+
])
|
|
174
|
+
"""
|
|
175
|
+
_require_workflow()
|
|
176
|
+
return SequentialAgent(name=name, description=description, sub_agents=agents)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
# Parallel Fan-Out
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
def build_parallel_research(
|
|
184
|
+
agents: list[Any],
|
|
185
|
+
*,
|
|
186
|
+
name: str = "parallel_research",
|
|
187
|
+
description: str = "Runs research agents in parallel, collecting results via state.",
|
|
188
|
+
) -> Any:
|
|
189
|
+
"""
|
|
190
|
+
Wrap a list of LlmAgents in a ParallelAgent.
|
|
191
|
+
|
|
192
|
+
Each agent MUST use a distinct ``output_key`` to avoid state key collisions.
|
|
193
|
+
|
|
194
|
+
Example:
|
|
195
|
+
research = build_parallel_research([
|
|
196
|
+
web_researcher, # output_key="web_results"
|
|
197
|
+
code_searcher, # output_key="code_results"
|
|
198
|
+
doc_reader, # output_key="doc_results"
|
|
199
|
+
])
|
|
200
|
+
# Wrap in a SequentialAgent to collect after:
|
|
201
|
+
pipeline = build_sequential_pipeline([research, synthesis_agent])
|
|
202
|
+
"""
|
|
203
|
+
_require_workflow()
|
|
204
|
+
return ParallelAgent(name=name, description=description, sub_agents=agents)
|
|
@@ -215,4 +215,6 @@ def slash_help_lines() -> list[str]:
|
|
|
215
215
|
" Other:",
|
|
216
216
|
" /permissions Show permission / HITL settings",
|
|
217
217
|
" /hooks Show post-turn hook configuration",
|
|
218
|
+
" /kairos How to launch the background parallel job scheduler",
|
|
219
|
+
" /code [on|off] Toggle sandboxed Python executor (ADK BuiltInCodeExecutor)",
|
|
218
220
|
]
|
|
@@ -318,6 +318,88 @@ async def process_repl_slash(
|
|
|
318
318
|
if name in ("exit", "quit"):
|
|
319
319
|
return ReplSlashResult(exit_repl=True)
|
|
320
320
|
|
|
321
|
+
# ── /kairos ───────────────────────────────────────────────────────────────
|
|
322
|
+
if name == "kairos":
|
|
323
|
+
args_s = (sc.args or "").strip()
|
|
324
|
+
out("Kairos — background parallel job scheduler")
|
|
325
|
+
out()
|
|
326
|
+
out("What it does:")
|
|
327
|
+
out(" Runs a long-lived daemon that accepts prompts on stdin and executes")
|
|
328
|
+
out(" each as an isolated GemCode agent job. Jobs run concurrently (up to")
|
|
329
|
+
out(" --concurrency N, default 2) in a priority queue.")
|
|
330
|
+
out()
|
|
331
|
+
out("How to launch (in a separate terminal):")
|
|
332
|
+
out(f" gemcode kairos -C {cfg.project_root}")
|
|
333
|
+
out(" gemcode kairos -C <project> --concurrency 4 --yes")
|
|
334
|
+
out(" gemcode kairos -C <project> --model gemini-2.5-pro --deep-research")
|
|
335
|
+
out()
|
|
336
|
+
out("Options:")
|
|
337
|
+
out(" --concurrency N Max parallel jobs (default: 2)")
|
|
338
|
+
out(" --default-priority N Priority for stdin jobs (higher = runs first, default: 0)")
|
|
339
|
+
out(" --yes Auto-approve mutations (like gemcode --yes)")
|
|
340
|
+
out(" --model <id> Override model for all jobs")
|
|
341
|
+
out(" --model-mode <mode> fast|balanced|quality|auto")
|
|
342
|
+
out(" --deep-research Enable Google Search + URL Context for all jobs")
|
|
343
|
+
out(" --embeddings Enable semantic file search for all jobs")
|
|
344
|
+
out(" --max-llm-calls N Cap model↔tool iterations per job")
|
|
345
|
+
out(" --session <uuid> Share session history with a running gemcode session")
|
|
346
|
+
out()
|
|
347
|
+
out("Tools available to jobs (in addition to normal GemCode tools):")
|
|
348
|
+
out(" kairos_sleep_ms(duration_ms) — pause this job without blocking others")
|
|
349
|
+
out(" kairos_enqueue_prompt(prompt, priority, session_id)")
|
|
350
|
+
out(" — the model can queue MORE jobs itself")
|
|
351
|
+
out()
|
|
352
|
+
out("Use cases:")
|
|
353
|
+
out(" - Process N files in parallel: each file → one job")
|
|
354
|
+
out(" - Polling loop: job sleeps, re-enqueues itself with kairos_enqueue_prompt")
|
|
355
|
+
out(" - Bulk code generation, test runs, or research across many documents")
|
|
356
|
+
out(" - Background work while you continue chatting in this session")
|
|
357
|
+
out()
|
|
358
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
359
|
+
|
|
360
|
+
# ── /code ─────────────────────────────────────────────────────────────────
|
|
361
|
+
if name == "code":
|
|
362
|
+
args_s = (sc.args or "").strip().lower()
|
|
363
|
+
if args_s in ("on", "enable", "1", "true"):
|
|
364
|
+
cfg.enable_code_executor = True
|
|
365
|
+
out("Code executor: ON (sandboxed Python via Gemini built-in executor)")
|
|
366
|
+
out("The agent can now write Python code blocks and execute them safely,")
|
|
367
|
+
out("without requiring bash/shell permission. Best for: math, data, quick tests.")
|
|
368
|
+
out()
|
|
369
|
+
out("Note: the sandbox has no internet/filesystem access — use bash for I/O.")
|
|
370
|
+
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
371
|
+
elif args_s in ("off", "disable", "0", "false"):
|
|
372
|
+
cfg.enable_code_executor = False
|
|
373
|
+
out("Code executor: OFF")
|
|
374
|
+
return ReplSlashResult(skip_model_turn=True, force_rebuild_runner=True)
|
|
375
|
+
else:
|
|
376
|
+
# Status
|
|
377
|
+
status = "ON" if getattr(cfg, "enable_code_executor", False) else "OFF"
|
|
378
|
+
out(f"Code executor: {status}")
|
|
379
|
+
out()
|
|
380
|
+
out("ADK BuiltInCodeExecutor — safe sandboxed Python execution via Gemini API.")
|
|
381
|
+
out()
|
|
382
|
+
out("What it does:")
|
|
383
|
+
out(" When ON, the agent can write Python code blocks directly in its response")
|
|
384
|
+
out(" and the Gemini API executes them in a sandboxed environment. The output")
|
|
385
|
+
out(" (stdout, result) is sent back so the agent can use it for further reasoning.")
|
|
386
|
+
out()
|
|
387
|
+
out("Best for:")
|
|
388
|
+
out(" - Math and numerical calculations")
|
|
389
|
+
out(" - Data processing and transformations")
|
|
390
|
+
out(" - Quick tests of logic or algorithms")
|
|
391
|
+
out(" - Anything that doesn't need filesystem or network access")
|
|
392
|
+
out()
|
|
393
|
+
out("Not for:")
|
|
394
|
+
out(" - Shell commands (use bash)")
|
|
395
|
+
out(" - Reading/writing files (use read_file / write_file)")
|
|
396
|
+
out(" - Internet requests (use web_fetch)")
|
|
397
|
+
out()
|
|
398
|
+
out("Toggle: /code on /code off")
|
|
399
|
+
out()
|
|
400
|
+
out("Supported models: gemini-2.5-flash, gemini-2.5-pro, gemini-3.x and newer.")
|
|
401
|
+
return ReplSlashResult(skip_model_turn=True)
|
|
402
|
+
|
|
321
403
|
# ── /computer ────────────────────────────────────────────────────────────
|
|
322
404
|
if name in ("computer", "browser"):
|
|
323
405
|
args_s = (sc.args or "").strip().lower()
|
|
@@ -30,6 +30,23 @@ def session_db_path(cfg: GemCodeConfig) -> Path:
|
|
|
30
30
|
return cfg.project_root / ".gemcode" / "sessions.sqlite"
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def _build_artifact_service(cfg: GemCodeConfig):
|
|
34
|
+
"""
|
|
35
|
+
Return an ADK ArtifactService for this session, or None if disabled.
|
|
36
|
+
|
|
37
|
+
Uses InMemoryArtifactService so artifacts are available within the session
|
|
38
|
+
without requiring GCS credentials. The agent can save screenshots, generated
|
|
39
|
+
files, large reports, etc. as artifacts to avoid bloating session history.
|
|
40
|
+
"""
|
|
41
|
+
if not getattr(cfg, "enable_artifacts", True):
|
|
42
|
+
return None
|
|
43
|
+
try:
|
|
44
|
+
from google.adk.artifacts import InMemoryArtifactService
|
|
45
|
+
return InMemoryArtifactService()
|
|
46
|
+
except Exception:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
33
50
|
def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None) -> Runner:
|
|
34
51
|
"""Construct Runner + SQLite session service + root LlmAgent."""
|
|
35
52
|
modality_tools = build_modality_extra_tools(cfg)
|
|
@@ -86,7 +103,9 @@ def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None) -> Runner
|
|
|
86
103
|
else:
|
|
87
104
|
memory_service = FileMemoryService(mem_path)
|
|
88
105
|
|
|
89
|
-
|
|
106
|
+
artifact_service = _build_artifact_service(cfg)
|
|
107
|
+
|
|
108
|
+
runner_kwargs: dict = dict(
|
|
90
109
|
app_name="gemcode",
|
|
91
110
|
agent=agent,
|
|
92
111
|
session_service=session_service,
|
|
@@ -94,3 +113,7 @@ def create_runner(cfg: GemCodeConfig, extra_tools: list | None = None) -> Runner
|
|
|
94
113
|
memory_service=memory_service,
|
|
95
114
|
auto_create_session=True,
|
|
96
115
|
)
|
|
116
|
+
if artifact_service is not None:
|
|
117
|
+
runner_kwargs["artifact_service"] = artifact_service
|
|
118
|
+
|
|
119
|
+
return Runner(**runner_kwargs)
|