agent-yaml 0.1.0__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.
- agent_yaml/__init__.py +8 -0
- agent_yaml/adapters/__init__.py +95 -0
- agent_yaml/adapters/base.py +55 -0
- agent_yaml/adapters/claude.py +144 -0
- agent_yaml/adapters/codex.py +112 -0
- agent_yaml/adapters/gemini.py +141 -0
- agent_yaml/adapters/opencode.py +147 -0
- agent_yaml/adapters/qwen.py +160 -0
- agent_yaml/cli.py +973 -0
- agent_yaml/ensure.py +115 -0
- agent_yaml/events.py +26 -0
- agent_yaml/executor.py +868 -0
- agent_yaml/graph.py +292 -0
- agent_yaml/includes.py +222 -0
- agent_yaml/loops.py +3252 -0
- agent_yaml/parser.py +52 -0
- agent_yaml/skip_conditions.py +287 -0
- agent_yaml/task_validators.py +571 -0
- agent_yaml/utils.py +338 -0
- agent_yaml/validator.py +827 -0
- agent_yaml/variables.py +330 -0
- agent_yaml/web/__init__.py +1 -0
- agent_yaml/web/app.py +281 -0
- agent_yaml/web/db.py +413 -0
- agent_yaml/web/runtime.py +207 -0
- agent_yaml/web/scheduler.py +81 -0
- agent_yaml/web/static/app.js +565 -0
- agent_yaml/web/static/index.html +138 -0
- agent_yaml/web/static/styles.css +814 -0
- agent_yaml/web/static/webgl.js +636 -0
- agent_yaml/when_conditions.py +127 -0
- agent_yaml-0.1.0.dist-info/METADATA +311 -0
- agent_yaml-0.1.0.dist-info/RECORD +37 -0
- agent_yaml-0.1.0.dist-info/WHEEL +5 -0
- agent_yaml-0.1.0.dist-info/entry_points.txt +2 -0
- agent_yaml-0.1.0.dist-info/licenses/LICENSE +21 -0
- agent_yaml-0.1.0.dist-info/top_level.txt +1 -0
agent_yaml/__init__.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI adapter modules for different AI agent tools.
|
|
3
|
+
|
|
4
|
+
Provides tool-specific adapters that translate generic workflow configurations
|
|
5
|
+
into tool-specific CLI invocations for OpenCode, Claude, Gemini, Qwen, and Codex.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .base import BaseAdapter
|
|
9
|
+
from .claude import ClaudeAdapter
|
|
10
|
+
from .codex import CodexAdapter
|
|
11
|
+
from .gemini import GeminiAdapter
|
|
12
|
+
from .opencode import OpenCodeAdapter
|
|
13
|
+
from .qwen import QwenAdapter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class UnsupportedCLIError(Exception):
|
|
17
|
+
"""Exception raised when an unsupported or unknown CLI agent is specified."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CLINotFoundError(Exception):
|
|
23
|
+
"""Exception raised when the CLI executable is not found in PATH."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_adapter(cli_agent_name: str) -> BaseAdapter:
|
|
29
|
+
"""
|
|
30
|
+
Factory function to get the appropriate adapter for a CLI agent.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
cli_agent_name: Name of the CLI agent (opencode, claude, gemini, qwen, codex)
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
BaseAdapter: Instance of the appropriate adapter class
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
UnsupportedCLIError: If the CLI agent name is not recognized
|
|
40
|
+
CLINotFoundError: If the CLI executable is not found in PATH
|
|
41
|
+
|
|
42
|
+
Reference:
|
|
43
|
+
spec/ADAPTER_SPEC.md lines 627-792 for capability detection and translation logic
|
|
44
|
+
"""
|
|
45
|
+
# Normalize to lowercase for case-insensitive matching
|
|
46
|
+
cli_name = cli_agent_name.lower().strip()
|
|
47
|
+
|
|
48
|
+
# Map CLI agent names to adapter classes
|
|
49
|
+
adapter_map = {
|
|
50
|
+
"opencode": OpenCodeAdapter,
|
|
51
|
+
"claude": ClaudeAdapter,
|
|
52
|
+
"gemini": GeminiAdapter,
|
|
53
|
+
"qwen": QwenAdapter,
|
|
54
|
+
"codex": CodexAdapter,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Check if CLI agent is supported
|
|
58
|
+
if cli_name not in adapter_map:
|
|
59
|
+
supported_agents = ", ".join(sorted(adapter_map.keys()))
|
|
60
|
+
raise UnsupportedCLIError(
|
|
61
|
+
f"Unsupported CLI agent: '{cli_agent_name}'. Supported agents are: {supported_agents}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Create adapter instance
|
|
65
|
+
adapter_class = adapter_map[cli_name]
|
|
66
|
+
adapter = adapter_class()
|
|
67
|
+
|
|
68
|
+
# Validate that CLI executable is available
|
|
69
|
+
if not adapter.check_available():
|
|
70
|
+
# Get the expected executable name based on the adapter
|
|
71
|
+
if cli_name == "claude":
|
|
72
|
+
executable_hint = "claude-code or claude"
|
|
73
|
+
else:
|
|
74
|
+
executable_hint = cli_name
|
|
75
|
+
|
|
76
|
+
raise CLINotFoundError(
|
|
77
|
+
f"CLI executable not found for '{cli_agent_name}'. "
|
|
78
|
+
f"Please ensure '{executable_hint}' is installed and available in PATH. "
|
|
79
|
+
f"Run 'which {executable_hint.split()[0]}' to verify installation."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return adapter
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
__all__ = [
|
|
86
|
+
"BaseAdapter",
|
|
87
|
+
"ClaudeAdapter",
|
|
88
|
+
"GeminiAdapter",
|
|
89
|
+
"QwenAdapter",
|
|
90
|
+
"CodexAdapter",
|
|
91
|
+
"OpenCodeAdapter",
|
|
92
|
+
"UnsupportedCLIError",
|
|
93
|
+
"CLINotFoundError",
|
|
94
|
+
"get_adapter",
|
|
95
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base adapter abstract class.
|
|
3
|
+
|
|
4
|
+
Defines the interface that all CLI adapters must implement.
|
|
5
|
+
Provides common functionality for command building and subprocess execution.
|
|
6
|
+
|
|
7
|
+
All adapters translate generic configurations to tool-specific CLI arguments:
|
|
8
|
+
- skip-confirmations → tool-specific flag
|
|
9
|
+
- additional-dirs → tool-specific workspace/directory flag
|
|
10
|
+
- timeout → subprocess timeout (not CLI arg)
|
|
11
|
+
- mcp-config-file → tool-specific MCP configuration
|
|
12
|
+
|
|
13
|
+
Reference: spec/ADAPTER_SPEC.md for complete mapping specifications.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseAdapter(ABC):
|
|
20
|
+
"""Abstract base class for CLI adapters."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, tool_name, tool_config=None):
|
|
23
|
+
"""
|
|
24
|
+
Initialize adapter.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
tool_name: Name of the CLI tool (claude, gemini, qwen, codex)
|
|
28
|
+
tool_config: Optional tool-specific configuration
|
|
29
|
+
"""
|
|
30
|
+
self.tool_name = tool_name
|
|
31
|
+
self.tool_config = tool_config or {}
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def build_command(self, task_config, prompt):
|
|
35
|
+
"""
|
|
36
|
+
Build CLI command from task configuration.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
task_config: Task configuration dict
|
|
40
|
+
prompt: Interpolated prompt string
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
list: Command as list of strings (argv format)
|
|
44
|
+
"""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def check_available(self):
|
|
49
|
+
"""
|
|
50
|
+
Check if CLI tool is available in PATH.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
bool: True if tool is available
|
|
54
|
+
"""
|
|
55
|
+
pass
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Claude CLI adapter.
|
|
3
|
+
|
|
4
|
+
Maps generic configurations to Claude-specific CLI arguments:
|
|
5
|
+
- skip-confirmations: true → --dangerously-skip-permissions
|
|
6
|
+
- additional-dirs: ["/tmp"] → --add-dir="/tmp"
|
|
7
|
+
- timeout: 3600 → subprocess timeout (not CLI arg)
|
|
8
|
+
- mcp-config-file: "..." → --mcp-config "..."
|
|
9
|
+
- tools: "default" (default) → --tools "default" (enable all tools in print mode)
|
|
10
|
+
- output-format: "stream-json" → --output-format "stream-json" (streaming output)
|
|
11
|
+
|
|
12
|
+
CLI tool: claude-code or claude (detection via which)
|
|
13
|
+
|
|
14
|
+
Default behavior: Uses --print mode with tools enabled for non-interactive agentic execution.
|
|
15
|
+
|
|
16
|
+
Reference: spec/ADAPTER_SPEC.md for complete Claude mappings.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import shutil
|
|
20
|
+
|
|
21
|
+
from .base import BaseAdapter
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ClaudeAdapter(BaseAdapter):
|
|
25
|
+
"""Adapter for Claude CLI (claude-code)."""
|
|
26
|
+
|
|
27
|
+
# Model name translation mapping
|
|
28
|
+
MODEL_MAPPING = {
|
|
29
|
+
"sonnet-4.5": "claude-sonnet-4-5-20250929",
|
|
30
|
+
"opus-4": "claude-opus-4",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def __init__(self, tool_config=None):
|
|
34
|
+
"""Initialize Claude adapter."""
|
|
35
|
+
super().__init__("claude", tool_config)
|
|
36
|
+
|
|
37
|
+
def build_command(self, task_config, prompt):
|
|
38
|
+
"""
|
|
39
|
+
Build Claude CLI command from task configuration.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
task_config: Task configuration dict
|
|
43
|
+
prompt: Interpolated prompt string
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
list: Command as list of strings (argv format)
|
|
47
|
+
"""
|
|
48
|
+
# Start with base command - check both "claude" and "claude-code"
|
|
49
|
+
cmd = []
|
|
50
|
+
if shutil.which("claude-code"):
|
|
51
|
+
cmd.append("claude-code")
|
|
52
|
+
elif shutil.which("claude"):
|
|
53
|
+
cmd.append("claude")
|
|
54
|
+
else:
|
|
55
|
+
cmd.append("claude") # Default, will fail in check_available
|
|
56
|
+
|
|
57
|
+
# Process config options in order per spec
|
|
58
|
+
|
|
59
|
+
# 1. skip-confirmations: true → --dangerously-skip-permissions
|
|
60
|
+
if task_config.get("skip-confirmations"):
|
|
61
|
+
cmd.append("--dangerously-skip-permissions")
|
|
62
|
+
|
|
63
|
+
# 2. additional-dirs: ["/tmp"] → --add-dir="/tmp" (repeat for each)
|
|
64
|
+
additional_dirs = task_config.get("additional-dirs", [])
|
|
65
|
+
for directory in additional_dirs:
|
|
66
|
+
cmd.append(f"--add-dir={directory}")
|
|
67
|
+
|
|
68
|
+
# 3. model: "sonnet-4.5" → --model "claude-sonnet-4-5-20250929" (with translation)
|
|
69
|
+
model = task_config.get("model")
|
|
70
|
+
if model:
|
|
71
|
+
# Translate generic model name to Claude-specific identifier
|
|
72
|
+
translated_model = self.MODEL_MAPPING.get(model, model)
|
|
73
|
+
cmd.extend(["--model", translated_model])
|
|
74
|
+
|
|
75
|
+
# 4. debug: true → --debug
|
|
76
|
+
if task_config.get("debug"):
|
|
77
|
+
cmd.append("--debug")
|
|
78
|
+
|
|
79
|
+
# 5. tools: "default" → --tools "default" (enable tools in print mode)
|
|
80
|
+
# Default to "default" which enables all tools for agentic execution
|
|
81
|
+
tools = task_config.get("tools", "default")
|
|
82
|
+
if tools:
|
|
83
|
+
cmd.extend(["--tools", tools])
|
|
84
|
+
|
|
85
|
+
# 6. output-format: "stream-json" → --output-format "stream-json"
|
|
86
|
+
# Default to "text" for simplicity, use "stream-json" in debug mode for streaming
|
|
87
|
+
output_format = task_config.get("output-format")
|
|
88
|
+
if output_format is None:
|
|
89
|
+
# Auto-select: stream-json in debug mode, text otherwise
|
|
90
|
+
output_format = "stream-json" if task_config.get("debug") else "text"
|
|
91
|
+
|
|
92
|
+
# stream-json requires --verbose flag
|
|
93
|
+
if output_format == "stream-json":
|
|
94
|
+
cmd.append("--verbose")
|
|
95
|
+
cmd.extend(["--output-format", "stream-json"])
|
|
96
|
+
elif output_format != "text":
|
|
97
|
+
cmd.extend(["--output-format", output_format])
|
|
98
|
+
|
|
99
|
+
# 7. session-id: "id" → --session-id "id"
|
|
100
|
+
session_id = task_config.get("session-id")
|
|
101
|
+
if session_id:
|
|
102
|
+
cmd.extend(["--session-id", session_id])
|
|
103
|
+
|
|
104
|
+
# 8. mcp-servers: [notion] → --allowed-mcp-server-names notion
|
|
105
|
+
mcp_servers = task_config.get("mcp-servers", [])
|
|
106
|
+
if mcp_servers:
|
|
107
|
+
cmd.append("--allowed-mcp-server-names")
|
|
108
|
+
cmd.extend(mcp_servers)
|
|
109
|
+
|
|
110
|
+
# 9. mcp-config-file: "path" → --mcp-config "path"
|
|
111
|
+
mcp_config_file = task_config.get("mcp-config-file")
|
|
112
|
+
if mcp_config_file:
|
|
113
|
+
cmd.extend(["--mcp-config", mcp_config_file])
|
|
114
|
+
|
|
115
|
+
# 10. permissions: ["plan"] → --permission-mode plan
|
|
116
|
+
permissions = task_config.get("permissions", [])
|
|
117
|
+
if permissions:
|
|
118
|
+
# Take first permission mode
|
|
119
|
+
cmd.extend(["--permission-mode", permissions[0]])
|
|
120
|
+
|
|
121
|
+
# 11. raw-args: [...] → append directly at end
|
|
122
|
+
raw_args = task_config.get("raw-args", [])
|
|
123
|
+
if raw_args:
|
|
124
|
+
cmd.extend(raw_args)
|
|
125
|
+
|
|
126
|
+
# 12. timeout: 3600 → store but DON'T add to CLI args (used for subprocess timeout)
|
|
127
|
+
# Note: timeout is handled by executor, not part of CLI command
|
|
128
|
+
|
|
129
|
+
# 13. Add prompt in print mode (non-interactive agentic execution)
|
|
130
|
+
# Always use --print mode with tools enabled for reliable non-interactive execution
|
|
131
|
+
cmd.extend(["--print", prompt])
|
|
132
|
+
|
|
133
|
+
return cmd
|
|
134
|
+
|
|
135
|
+
def check_available(self):
|
|
136
|
+
"""
|
|
137
|
+
Check if Claude CLI is available in PATH.
|
|
138
|
+
|
|
139
|
+
Checks for both "claude" and "claude-code" executables.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
bool: True if tool is available
|
|
143
|
+
"""
|
|
144
|
+
return shutil.which("claude-code") is not None or shutil.which("claude") is not None
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Codex CLI adapter.
|
|
3
|
+
|
|
4
|
+
Maps generic configurations to Codex-specific CLI arguments:
|
|
5
|
+
- skip-confirmations: true → --full-auto
|
|
6
|
+
- additional-dirs: ["/tmp"] → -C /tmp (ONLY first directory, warn if multiple)
|
|
7
|
+
- model: pass through as-is (no translation)
|
|
8
|
+
- debug: true → -c debug=true (config style, NOT --debug)
|
|
9
|
+
- permissions: ["on-failure"] → -a on-failure
|
|
10
|
+
- web-search: true → --search
|
|
11
|
+
- PROMPT: DIRECT argument at end (NO flag)
|
|
12
|
+
|
|
13
|
+
Unsupported features (log warning):
|
|
14
|
+
- output-format
|
|
15
|
+
|
|
16
|
+
CLI tool: codex exec (uses exec subcommand for non-interactive execution)
|
|
17
|
+
|
|
18
|
+
Reference: spec/ADAPTER_SPEC.md for complete Codex mappings.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
import shutil
|
|
23
|
+
|
|
24
|
+
from .base import BaseAdapter
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CodexAdapter(BaseAdapter):
|
|
30
|
+
"""Adapter for Codex CLI."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, tool_config=None):
|
|
33
|
+
"""Initialize Codex adapter."""
|
|
34
|
+
super().__init__("codex", tool_config)
|
|
35
|
+
|
|
36
|
+
def build_command(self, task_config, prompt):
|
|
37
|
+
"""
|
|
38
|
+
Build Codex CLI command from task configuration.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
task_config: Task configuration dict
|
|
42
|
+
prompt: Interpolated prompt string
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
list: Command as list of strings (argv format)
|
|
46
|
+
"""
|
|
47
|
+
# Start with base command - use 'exec' subcommand for non-interactive execution
|
|
48
|
+
cmd = ["codex", "exec"]
|
|
49
|
+
|
|
50
|
+
# Always add --skip-git-repo-check to allow running in any directory
|
|
51
|
+
cmd.append("--skip-git-repo-check")
|
|
52
|
+
|
|
53
|
+
# Process config options in order per spec
|
|
54
|
+
|
|
55
|
+
# 1. skip-confirmations: true → --full-auto
|
|
56
|
+
if task_config.get("skip-confirmations"):
|
|
57
|
+
cmd.append("--full-auto")
|
|
58
|
+
|
|
59
|
+
# 2. additional-dirs: ["/tmp"] → -C /tmp (ONLY first directory)
|
|
60
|
+
additional_dirs = task_config.get("additional-dirs", [])
|
|
61
|
+
if additional_dirs:
|
|
62
|
+
# Codex only supports one directory
|
|
63
|
+
cmd.extend(["-C", additional_dirs[0]])
|
|
64
|
+
if len(additional_dirs) > 1:
|
|
65
|
+
logger.warning(
|
|
66
|
+
f"Codex only supports one directory. Using first directory '{additional_dirs[0]}'. "
|
|
67
|
+
f"Additional directories will be ignored: {additional_dirs[1:]}"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# 3. model: pass through as-is (no translation)
|
|
71
|
+
model = task_config.get("model")
|
|
72
|
+
if model:
|
|
73
|
+
cmd.extend(["--model", model])
|
|
74
|
+
|
|
75
|
+
# 4. debug: true → -c debug=true (config style)
|
|
76
|
+
if task_config.get("debug"):
|
|
77
|
+
cmd.extend(["-c", "debug=true"])
|
|
78
|
+
|
|
79
|
+
# 5. output-format: NOT SUPPORTED
|
|
80
|
+
if task_config.get("output-format"):
|
|
81
|
+
logger.warning("Codex does not support output-format configuration")
|
|
82
|
+
|
|
83
|
+
# 6. permissions: ["on-failure"] → -a on-failure
|
|
84
|
+
permissions = task_config.get("permissions", [])
|
|
85
|
+
if permissions:
|
|
86
|
+
# Take first permission mode
|
|
87
|
+
cmd.extend(["-a", permissions[0]])
|
|
88
|
+
|
|
89
|
+
# 7. web-search: true → --search
|
|
90
|
+
if task_config.get("web-search"):
|
|
91
|
+
cmd.append("--search")
|
|
92
|
+
|
|
93
|
+
# 8. raw-args: passthrough (no translation)
|
|
94
|
+
raw_args = task_config.get("raw-args", [])
|
|
95
|
+
if raw_args:
|
|
96
|
+
cmd.extend(raw_args)
|
|
97
|
+
|
|
98
|
+
# 9. Prompt goes at the end as DIRECT argument (NO flag)
|
|
99
|
+
cmd.append(prompt)
|
|
100
|
+
|
|
101
|
+
return cmd
|
|
102
|
+
|
|
103
|
+
def check_available(self):
|
|
104
|
+
"""
|
|
105
|
+
Check if Codex CLI is available in PATH.
|
|
106
|
+
|
|
107
|
+
Checks for "codex" executable.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
bool: True if tool is available
|
|
111
|
+
"""
|
|
112
|
+
return shutil.which("codex") is not None
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gemini CLI adapter.
|
|
3
|
+
|
|
4
|
+
Maps generic configurations to Gemini-specific CLI arguments:
|
|
5
|
+
- skip-confirmations: true → --yolo or --approval-mode yolo
|
|
6
|
+
- additional-dirs: ["/tmp"] → --include-directories /tmp (space-separated)
|
|
7
|
+
- model: pass through as-is (no translation)
|
|
8
|
+
- permissions: ["plan"] → --approval-mode plan
|
|
9
|
+
- mcp-servers: [notion] → --allowed-mcp-server-names notion
|
|
10
|
+
- extensions: [...] → --extensions ...
|
|
11
|
+
- checkpointing: true → --checkpointing
|
|
12
|
+
- screen-reader: true → --screen-reader
|
|
13
|
+
- telemetry: true → --telemetry
|
|
14
|
+
- web-search: true → BUILT-IN (no flag needed)
|
|
15
|
+
- output-format: "json" → --output-format json
|
|
16
|
+
|
|
17
|
+
Unsupported features (log warning):
|
|
18
|
+
- session-id
|
|
19
|
+
- mcp-config-file
|
|
20
|
+
|
|
21
|
+
CLI tool: gemini
|
|
22
|
+
|
|
23
|
+
Reference: spec/ADAPTER_SPEC.md for complete Gemini mappings.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import shutil
|
|
28
|
+
|
|
29
|
+
from .base import BaseAdapter
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class GeminiAdapter(BaseAdapter):
|
|
35
|
+
"""Adapter for Gemini CLI."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, tool_config=None):
|
|
38
|
+
"""Initialize Gemini adapter."""
|
|
39
|
+
super().__init__("gemini", tool_config)
|
|
40
|
+
|
|
41
|
+
def build_command(self, task_config, prompt):
|
|
42
|
+
"""
|
|
43
|
+
Build Gemini CLI command from task configuration.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
task_config: Task configuration dict
|
|
47
|
+
prompt: Interpolated prompt string
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
list: Command as list of strings (argv format)
|
|
51
|
+
"""
|
|
52
|
+
# Start with base command
|
|
53
|
+
cmd = ["gemini"]
|
|
54
|
+
|
|
55
|
+
# Process config options in order per spec
|
|
56
|
+
|
|
57
|
+
# 1. skip-confirmations: true → --yolo
|
|
58
|
+
if task_config.get("skip-confirmations"):
|
|
59
|
+
cmd.append("--yolo")
|
|
60
|
+
|
|
61
|
+
# 2. additional-dirs: ["/tmp"] → --include-directories /tmp (space-separated)
|
|
62
|
+
additional_dirs = task_config.get("additional-dirs", [])
|
|
63
|
+
if additional_dirs:
|
|
64
|
+
cmd.append("--include-directories")
|
|
65
|
+
cmd.extend(additional_dirs)
|
|
66
|
+
|
|
67
|
+
# 3. model: pass through as-is (no translation)
|
|
68
|
+
model = task_config.get("model")
|
|
69
|
+
if model:
|
|
70
|
+
cmd.extend(["--model", model])
|
|
71
|
+
|
|
72
|
+
# 4. debug: true → --debug
|
|
73
|
+
if task_config.get("debug"):
|
|
74
|
+
cmd.append("--debug")
|
|
75
|
+
|
|
76
|
+
# 5. output-format: "json" → --output-format json
|
|
77
|
+
output_format = task_config.get("output-format")
|
|
78
|
+
if output_format:
|
|
79
|
+
cmd.extend(["--output-format", output_format])
|
|
80
|
+
|
|
81
|
+
# 6. session-id: NOT SUPPORTED
|
|
82
|
+
if task_config.get("session-id"):
|
|
83
|
+
logger.warning("Gemini does not support session-id configuration")
|
|
84
|
+
|
|
85
|
+
# 7. mcp-servers: [notion] → --allowed-mcp-server-names notion
|
|
86
|
+
mcp_servers = task_config.get("mcp-servers", [])
|
|
87
|
+
if mcp_servers:
|
|
88
|
+
cmd.append("--allowed-mcp-server-names")
|
|
89
|
+
cmd.extend(mcp_servers)
|
|
90
|
+
|
|
91
|
+
# 8. mcp-config-file: NOT SUPPORTED
|
|
92
|
+
if task_config.get("mcp-config-file"):
|
|
93
|
+
logger.warning("Gemini does not support mcp-config-file configuration")
|
|
94
|
+
|
|
95
|
+
# 9. permissions: ["plan"] → --approval-mode plan
|
|
96
|
+
permissions = task_config.get("permissions", [])
|
|
97
|
+
if permissions:
|
|
98
|
+
# Take first permission mode
|
|
99
|
+
cmd.extend(["--approval-mode", permissions[0]])
|
|
100
|
+
|
|
101
|
+
# 10. extensions: [...] → --extensions ...
|
|
102
|
+
extensions = task_config.get("extensions", [])
|
|
103
|
+
if extensions:
|
|
104
|
+
cmd.append("--extensions")
|
|
105
|
+
cmd.extend(extensions)
|
|
106
|
+
|
|
107
|
+
# 11. checkpointing: true → --checkpointing
|
|
108
|
+
if task_config.get("checkpointing"):
|
|
109
|
+
cmd.append("--checkpointing")
|
|
110
|
+
|
|
111
|
+
# 12. telemetry: true → --telemetry
|
|
112
|
+
if task_config.get("telemetry"):
|
|
113
|
+
cmd.append("--telemetry")
|
|
114
|
+
|
|
115
|
+
# 13. screen-reader: true → --screen-reader
|
|
116
|
+
if task_config.get("screen-reader"):
|
|
117
|
+
cmd.append("--screen-reader")
|
|
118
|
+
|
|
119
|
+
# 14. web-search: BUILT-IN (no flag needed)
|
|
120
|
+
# web-search is built-in for Gemini, don't add any flag
|
|
121
|
+
|
|
122
|
+
# 15. raw-args: passthrough (no translation)
|
|
123
|
+
raw_args = task_config.get("raw-args", [])
|
|
124
|
+
if raw_args:
|
|
125
|
+
cmd.extend(raw_args)
|
|
126
|
+
|
|
127
|
+
# 16. Prompt goes at the end with -p flag
|
|
128
|
+
cmd.extend(["-p", prompt])
|
|
129
|
+
|
|
130
|
+
return cmd
|
|
131
|
+
|
|
132
|
+
def check_available(self):
|
|
133
|
+
"""
|
|
134
|
+
Check if Gemini CLI is available in PATH.
|
|
135
|
+
|
|
136
|
+
Checks for "gemini" executable.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
bool: True if tool is available
|
|
140
|
+
"""
|
|
141
|
+
return shutil.which("gemini") is not None
|