netra-zen 1.0.7__py3-none-any.whl → 1.0.9__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.
@@ -1,351 +1,351 @@
1
- #!/usr/bin/env python3
2
- """
3
- Base Agent Interface for Extensible Coding Agent Support
4
-
5
- This module provides the foundation for supporting multiple coding agents
6
- beyond Claude Code, enabling zen to orchestrate various AI coding assistants.
7
-
8
- Design Goals:
9
- - Simple interface for easy integration
10
- - Consistent token/cost tracking across agents
11
- - Minimal overhead for basic agents
12
- - Extensible for complex agent features
13
- """
14
-
15
- from abc import ABC, abstractmethod
16
- from dataclasses import dataclass
17
- from typing import Dict, List, Optional, Any, AsyncIterator
18
- from pathlib import Path
19
- import subprocess
20
- import asyncio
21
-
22
-
23
- @dataclass
24
- class AgentConfig:
25
- """Configuration for any coding agent"""
26
- name: str
27
- command: str
28
- description: Optional[str] = None
29
- allowed_tools: List[str] = None
30
- output_format: str = "text"
31
- workspace_dir: Optional[Path] = None
32
- session_id: Optional[str] = None
33
- environment_vars: Dict[str, str] = None
34
- timeout: int = 300
35
-
36
- def __post_init__(self):
37
- if self.description is None:
38
- self.description = f"Execute {self.command}"
39
- if self.environment_vars is None:
40
- self.environment_vars = {}
41
-
42
-
43
- @dataclass
44
- class AgentUsageMetrics:
45
- """Standardized usage metrics across all agents"""
46
- total_tokens: int = 0
47
- input_tokens: int = 0
48
- output_tokens: int = 0
49
- cached_tokens: int = 0
50
- tool_calls: int = 0
51
- api_calls: int = 0
52
- total_cost_usd: Optional[float] = None
53
- model_used: str = "unknown"
54
-
55
- # Agent-specific metrics (extensible)
56
- agent_specific: Dict[str, Any] = None
57
-
58
- def __post_init__(self):
59
- if self.agent_specific is None:
60
- self.agent_specific = {}
61
-
62
-
63
- @dataclass
64
- class AgentStatus:
65
- """Execution status for any coding agent"""
66
- name: str
67
- status: str = "pending" # pending, running, completed, failed
68
- start_time: Optional[float] = None
69
- end_time: Optional[float] = None
70
- pid: Optional[int] = None
71
- output: str = ""
72
- error: str = ""
73
- metrics: AgentUsageMetrics = None
74
-
75
- def __post_init__(self):
76
- if self.metrics is None:
77
- self.metrics = AgentUsageMetrics()
78
-
79
-
80
- class BaseCodingAgent(ABC):
81
- """
82
- Abstract base class for all coding agents.
83
-
84
- Provides the interface that all coding agents must implement
85
- to be compatible with the zen orchestrator.
86
- """
87
-
88
- def __init__(self, config: AgentConfig):
89
- self.config = config
90
- self.status = AgentStatus(name=config.name)
91
-
92
- @abstractmethod
93
- def build_command(self) -> List[str]:
94
- """
95
- Build the command line arguments for executing this agent.
96
-
97
- Returns:
98
- List of command line arguments
99
- """
100
- pass
101
-
102
- @abstractmethod
103
- async def execute(self) -> bool:
104
- """
105
- Execute the agent and return success status.
106
-
107
- Returns:
108
- True if execution was successful, False otherwise
109
- """
110
- pass
111
-
112
- @abstractmethod
113
- def parse_output_line(self, line: str) -> bool:
114
- """
115
- Parse a single line of output to extract metrics.
116
-
117
- Args:
118
- line: Single line from agent output
119
-
120
- Returns:
121
- True if line was parsed successfully, False otherwise
122
- """
123
- pass
124
-
125
- @abstractmethod
126
- def calculate_cost(self) -> float:
127
- """
128
- Calculate the total cost for this agent's execution.
129
-
130
- Returns:
131
- Total cost in USD
132
- """
133
- pass
134
-
135
- def get_agent_type(self) -> str:
136
- """Return the type identifier for this agent"""
137
- return self.__class__.__name__.lower().replace('agent', '')
138
-
139
- def get_metrics(self) -> AgentUsageMetrics:
140
- """Get current usage metrics"""
141
- return self.status.metrics
142
-
143
- def get_status(self) -> AgentStatus:
144
- """Get current execution status"""
145
- return self.status
146
-
147
- def supports_feature(self, feature: str) -> bool:
148
- """Check if agent supports a specific feature"""
149
- # Override in subclasses to declare supported features
150
- return False
151
-
152
-
153
- class ClaudeCodeAgent(BaseCodingAgent):
154
- """
155
- Claude Code agent implementation.
156
-
157
- Provides compatibility with the existing Claude Code orchestrator
158
- through the new agent interface.
159
- """
160
-
161
- def build_command(self) -> List[str]:
162
- """Build Claude Code command"""
163
- # Use existing logic from ClaudeInstanceOrchestrator.build_claude_command
164
- cmd = ["claude", "-p", self.config.command]
165
-
166
- if self.config.output_format and self.config.output_format != "text":
167
- cmd.append(f"--output-format={self.config.output_format}")
168
-
169
- if self.config.allowed_tools:
170
- cmd.append(f"--allowedTools={','.join(self.config.allowed_tools)}")
171
-
172
- if self.config.session_id:
173
- cmd.extend(["--session-id", self.config.session_id])
174
-
175
- return cmd
176
-
177
- async def execute(self) -> bool:
178
- """Execute Claude Code with async process handling"""
179
- try:
180
- cmd = self.build_command()
181
- self.status.start_time = asyncio.get_event_loop().time()
182
- self.status.status = "running"
183
-
184
- # Create async process
185
- process = await asyncio.create_subprocess_exec(
186
- *cmd,
187
- stdout=asyncio.subprocess.PIPE,
188
- stderr=asyncio.subprocess.PIPE,
189
- cwd=self.config.workspace_dir
190
- )
191
-
192
- self.status.pid = process.pid
193
-
194
- # Stream output and parse in real-time
195
- async def read_stream(stream, is_stdout=True):
196
- while True:
197
- line = await stream.readline()
198
- if not line:
199
- break
200
- line_str = line.decode().strip()
201
-
202
- if is_stdout:
203
- self.status.output += line_str + "\n"
204
- self.parse_output_line(line_str)
205
- else:
206
- self.status.error += line_str + "\n"
207
-
208
- # Read both streams concurrently
209
- await asyncio.gather(
210
- read_stream(process.stdout, True),
211
- read_stream(process.stderr, False),
212
- return_exceptions=True
213
- )
214
-
215
- # Wait for process completion
216
- returncode = await process.wait()
217
- self.status.end_time = asyncio.get_event_loop().time()
218
-
219
- if returncode == 0:
220
- self.status.status = "completed"
221
- return True
222
- else:
223
- self.status.status = "failed"
224
- return False
225
-
226
- except Exception as e:
227
- self.status.status = "failed"
228
- self.status.error = str(e)
229
- return False
230
-
231
- def parse_output_line(self, line: str) -> bool:
232
- """Parse Claude Code JSON output"""
233
- # Use existing logic from ClaudeInstanceOrchestrator._parse_token_usage
234
- try:
235
- import json
236
- if line.startswith('{'):
237
- data = json.loads(line)
238
- if 'usage' in data:
239
- usage = data['usage']
240
- self.status.metrics.input_tokens = max(
241
- self.status.metrics.input_tokens,
242
- int(usage.get('input_tokens', 0))
243
- )
244
- self.status.metrics.output_tokens = max(
245
- self.status.metrics.output_tokens,
246
- int(usage.get('output_tokens', 0))
247
- )
248
- self.status.metrics.total_tokens = max(
249
- self.status.metrics.total_tokens,
250
- int(usage.get('total_tokens', 0))
251
- )
252
- return True
253
- except (json.JSONDecodeError, ValueError, KeyError):
254
- pass
255
- return False
256
-
257
- def calculate_cost(self) -> float:
258
- """Calculate cost using Claude pricing"""
259
- # Use existing logic or new pricing engine
260
- if self.status.metrics.total_cost_usd is not None:
261
- return self.status.metrics.total_cost_usd
262
-
263
- # Fallback calculation
264
- input_cost = (self.status.metrics.input_tokens / 1_000_000) * 3.00
265
- output_cost = (self.status.metrics.output_tokens / 1_000_000) * 15.00
266
- return input_cost + output_cost
267
-
268
- def supports_feature(self, feature: str) -> bool:
269
- """Claude Code supported features"""
270
- supported = ['streaming', 'json_output', 'tools', 'sessions', 'real_time_metrics']
271
- return feature in supported
272
-
273
-
274
- # Example of another agent implementation
275
- class ContinueAgent(BaseCodingAgent):
276
- """
277
- Example implementation for Continue.dev agent.
278
-
279
- This demonstrates how to add support for other coding agents.
280
- """
281
-
282
- def build_command(self) -> List[str]:
283
- """Build Continue.dev command"""
284
- return ["continue", self.config.command]
285
-
286
- async def execute(self) -> bool:
287
- """Execute Continue.dev agent"""
288
- # Placeholder implementation
289
- # Real implementation would integrate with Continue.dev API
290
- self.status.status = "completed"
291
- return True
292
-
293
- def parse_output_line(self, line: str) -> bool:
294
- """Parse Continue.dev output"""
295
- # Placeholder - implement Continue.dev specific parsing
296
- return False
297
-
298
- def calculate_cost(self) -> float:
299
- """Calculate Continue.dev costs"""
300
- # Continue.dev may have different pricing model
301
- return 0.0
302
-
303
- def supports_feature(self, feature: str) -> bool:
304
- """Continue.dev supported features"""
305
- supported = ['autocomplete', 'chat', 'refactoring']
306
- return feature in supported
307
-
308
-
309
- # Agent factory for easy creation
310
- class AgentFactory:
311
- """Factory for creating appropriate agent instances"""
312
-
313
- _agent_types = {
314
- 'claude': ClaudeCodeAgent,
315
- 'continue': ContinueAgent,
316
- # Add more agents here as they're implemented
317
- }
318
-
319
- @classmethod
320
- def create_agent(cls, agent_type: str, config: AgentConfig) -> BaseCodingAgent:
321
- """
322
- Create an agent instance of the specified type.
323
-
324
- Args:
325
- agent_type: Type of agent to create (e.g., 'claude', 'continue')
326
- config: Agent configuration
327
-
328
- Returns:
329
- Agent instance
330
-
331
- Raises:
332
- ValueError: If agent type is not supported
333
- """
334
- if agent_type not in cls._agent_types:
335
- raise ValueError(f"Unsupported agent type: {agent_type}. "
336
- f"Supported types: {list(cls._agent_types.keys())}")
337
-
338
- agent_class = cls._agent_types[agent_type]
339
- return agent_class(config)
340
-
341
- @classmethod
342
- def get_supported_agents(cls) -> List[str]:
343
- """Get list of supported agent types"""
344
- return list(cls._agent_types.keys())
345
-
346
- @classmethod
347
- def register_agent(cls, agent_type: str, agent_class: type):
348
- """Register a new agent type"""
349
- if not issubclass(agent_class, BaseCodingAgent):
350
- raise ValueError("Agent class must inherit from BaseCodingAgent")
1
+ #!/usr/bin/env python3
2
+ """
3
+ Base Agent Interface for Extensible Coding Agent Support
4
+
5
+ This module provides the foundation for supporting multiple coding agents
6
+ beyond Claude Code, enabling zen to orchestrate various AI coding assistants.
7
+
8
+ Design Goals:
9
+ - Simple interface for easy integration
10
+ - Consistent token/cost tracking across agents
11
+ - Minimal overhead for basic agents
12
+ - Extensible for complex agent features
13
+ """
14
+
15
+ from abc import ABC, abstractmethod
16
+ from dataclasses import dataclass
17
+ from typing import Dict, List, Optional, Any, AsyncIterator
18
+ from pathlib import Path
19
+ import subprocess
20
+ import asyncio
21
+
22
+
23
+ @dataclass
24
+ class AgentConfig:
25
+ """Configuration for any coding agent"""
26
+ name: str
27
+ command: str
28
+ description: Optional[str] = None
29
+ allowed_tools: List[str] = None
30
+ output_format: str = "text"
31
+ workspace_dir: Optional[Path] = None
32
+ session_id: Optional[str] = None
33
+ environment_vars: Dict[str, str] = None
34
+ timeout: int = 300
35
+
36
+ def __post_init__(self):
37
+ if self.description is None:
38
+ self.description = f"Execute {self.command}"
39
+ if self.environment_vars is None:
40
+ self.environment_vars = {}
41
+
42
+
43
+ @dataclass
44
+ class AgentUsageMetrics:
45
+ """Standardized usage metrics across all agents"""
46
+ total_tokens: int = 0
47
+ input_tokens: int = 0
48
+ output_tokens: int = 0
49
+ cached_tokens: int = 0
50
+ tool_calls: int = 0
51
+ api_calls: int = 0
52
+ total_cost_usd: Optional[float] = None
53
+ model_used: str = "unknown"
54
+
55
+ # Agent-specific metrics (extensible)
56
+ agent_specific: Dict[str, Any] = None
57
+
58
+ def __post_init__(self):
59
+ if self.agent_specific is None:
60
+ self.agent_specific = {}
61
+
62
+
63
+ @dataclass
64
+ class AgentStatus:
65
+ """Execution status for any coding agent"""
66
+ name: str
67
+ status: str = "pending" # pending, running, completed, failed
68
+ start_time: Optional[float] = None
69
+ end_time: Optional[float] = None
70
+ pid: Optional[int] = None
71
+ output: str = ""
72
+ error: str = ""
73
+ metrics: AgentUsageMetrics = None
74
+
75
+ def __post_init__(self):
76
+ if self.metrics is None:
77
+ self.metrics = AgentUsageMetrics()
78
+
79
+
80
+ class BaseCodingAgent(ABC):
81
+ """
82
+ Abstract base class for all coding agents.
83
+
84
+ Provides the interface that all coding agents must implement
85
+ to be compatible with the zen orchestrator.
86
+ """
87
+
88
+ def __init__(self, config: AgentConfig):
89
+ self.config = config
90
+ self.status = AgentStatus(name=config.name)
91
+
92
+ @abstractmethod
93
+ def build_command(self) -> List[str]:
94
+ """
95
+ Build the command line arguments for executing this agent.
96
+
97
+ Returns:
98
+ List of command line arguments
99
+ """
100
+ pass
101
+
102
+ @abstractmethod
103
+ async def execute(self) -> bool:
104
+ """
105
+ Execute the agent and return success status.
106
+
107
+ Returns:
108
+ True if execution was successful, False otherwise
109
+ """
110
+ pass
111
+
112
+ @abstractmethod
113
+ def parse_output_line(self, line: str) -> bool:
114
+ """
115
+ Parse a single line of output to extract metrics.
116
+
117
+ Args:
118
+ line: Single line from agent output
119
+
120
+ Returns:
121
+ True if line was parsed successfully, False otherwise
122
+ """
123
+ pass
124
+
125
+ @abstractmethod
126
+ def calculate_cost(self) -> float:
127
+ """
128
+ Calculate the total cost for this agent's execution.
129
+
130
+ Returns:
131
+ Total cost in USD
132
+ """
133
+ pass
134
+
135
+ def get_agent_type(self) -> str:
136
+ """Return the type identifier for this agent"""
137
+ return self.__class__.__name__.lower().replace('agent', '')
138
+
139
+ def get_metrics(self) -> AgentUsageMetrics:
140
+ """Get current usage metrics"""
141
+ return self.status.metrics
142
+
143
+ def get_status(self) -> AgentStatus:
144
+ """Get current execution status"""
145
+ return self.status
146
+
147
+ def supports_feature(self, feature: str) -> bool:
148
+ """Check if agent supports a specific feature"""
149
+ # Override in subclasses to declare supported features
150
+ return False
151
+
152
+
153
+ class ClaudeCodeAgent(BaseCodingAgent):
154
+ """
155
+ Claude Code agent implementation.
156
+
157
+ Provides compatibility with the existing Claude Code orchestrator
158
+ through the new agent interface.
159
+ """
160
+
161
+ def build_command(self) -> List[str]:
162
+ """Build Claude Code command"""
163
+ # Use existing logic from ClaudeInstanceOrchestrator.build_claude_command
164
+ cmd = ["claude", "-p", self.config.command]
165
+
166
+ if self.config.output_format and self.config.output_format != "text":
167
+ cmd.append(f"--output-format={self.config.output_format}")
168
+
169
+ if self.config.allowed_tools:
170
+ cmd.append(f"--allowedTools={','.join(self.config.allowed_tools)}")
171
+
172
+ if self.config.session_id:
173
+ cmd.extend(["--session-id", self.config.session_id])
174
+
175
+ return cmd
176
+
177
+ async def execute(self) -> bool:
178
+ """Execute Claude Code with async process handling"""
179
+ try:
180
+ cmd = self.build_command()
181
+ self.status.start_time = asyncio.get_event_loop().time()
182
+ self.status.status = "running"
183
+
184
+ # Create async process
185
+ process = await asyncio.create_subprocess_exec(
186
+ *cmd,
187
+ stdout=asyncio.subprocess.PIPE,
188
+ stderr=asyncio.subprocess.PIPE,
189
+ cwd=self.config.workspace_dir
190
+ )
191
+
192
+ self.status.pid = process.pid
193
+
194
+ # Stream output and parse in real-time
195
+ async def read_stream(stream, is_stdout=True):
196
+ while True:
197
+ line = await stream.readline()
198
+ if not line:
199
+ break
200
+ line_str = line.decode().strip()
201
+
202
+ if is_stdout:
203
+ self.status.output += line_str + "\n"
204
+ self.parse_output_line(line_str)
205
+ else:
206
+ self.status.error += line_str + "\n"
207
+
208
+ # Read both streams concurrently
209
+ await asyncio.gather(
210
+ read_stream(process.stdout, True),
211
+ read_stream(process.stderr, False),
212
+ return_exceptions=True
213
+ )
214
+
215
+ # Wait for process completion
216
+ returncode = await process.wait()
217
+ self.status.end_time = asyncio.get_event_loop().time()
218
+
219
+ if returncode == 0:
220
+ self.status.status = "completed"
221
+ return True
222
+ else:
223
+ self.status.status = "failed"
224
+ return False
225
+
226
+ except Exception as e:
227
+ self.status.status = "failed"
228
+ self.status.error = str(e)
229
+ return False
230
+
231
+ def parse_output_line(self, line: str) -> bool:
232
+ """Parse Claude Code JSON output"""
233
+ # Use existing logic from ClaudeInstanceOrchestrator._parse_token_usage
234
+ try:
235
+ import json
236
+ if line.startswith('{'):
237
+ data = json.loads(line)
238
+ if 'usage' in data:
239
+ usage = data['usage']
240
+ self.status.metrics.input_tokens = max(
241
+ self.status.metrics.input_tokens,
242
+ int(usage.get('input_tokens', 0))
243
+ )
244
+ self.status.metrics.output_tokens = max(
245
+ self.status.metrics.output_tokens,
246
+ int(usage.get('output_tokens', 0))
247
+ )
248
+ self.status.metrics.total_tokens = max(
249
+ self.status.metrics.total_tokens,
250
+ int(usage.get('total_tokens', 0))
251
+ )
252
+ return True
253
+ except (json.JSONDecodeError, ValueError, KeyError):
254
+ pass
255
+ return False
256
+
257
+ def calculate_cost(self) -> float:
258
+ """Calculate cost using Claude pricing"""
259
+ # Use existing logic or new pricing engine
260
+ if self.status.metrics.total_cost_usd is not None:
261
+ return self.status.metrics.total_cost_usd
262
+
263
+ # Fallback calculation
264
+ input_cost = (self.status.metrics.input_tokens / 1_000_000) * 3.00
265
+ output_cost = (self.status.metrics.output_tokens / 1_000_000) * 15.00
266
+ return input_cost + output_cost
267
+
268
+ def supports_feature(self, feature: str) -> bool:
269
+ """Claude Code supported features"""
270
+ supported = ['streaming', 'json_output', 'tools', 'sessions', 'real_time_metrics']
271
+ return feature in supported
272
+
273
+
274
+ # Example of another agent implementation
275
+ class ContinueAgent(BaseCodingAgent):
276
+ """
277
+ Example implementation for Continue.dev agent.
278
+
279
+ This demonstrates how to add support for other coding agents.
280
+ """
281
+
282
+ def build_command(self) -> List[str]:
283
+ """Build Continue.dev command"""
284
+ return ["continue", self.config.command]
285
+
286
+ async def execute(self) -> bool:
287
+ """Execute Continue.dev agent"""
288
+ # Placeholder implementation
289
+ # Real implementation would integrate with Continue.dev API
290
+ self.status.status = "completed"
291
+ return True
292
+
293
+ def parse_output_line(self, line: str) -> bool:
294
+ """Parse Continue.dev output"""
295
+ # Placeholder - implement Continue.dev specific parsing
296
+ return False
297
+
298
+ def calculate_cost(self) -> float:
299
+ """Calculate Continue.dev costs"""
300
+ # Continue.dev may have different pricing model
301
+ return 0.0
302
+
303
+ def supports_feature(self, feature: str) -> bool:
304
+ """Continue.dev supported features"""
305
+ supported = ['autocomplete', 'chat', 'refactoring']
306
+ return feature in supported
307
+
308
+
309
+ # Agent factory for easy creation
310
+ class AgentFactory:
311
+ """Factory for creating appropriate agent instances"""
312
+
313
+ _agent_types = {
314
+ 'claude': ClaudeCodeAgent,
315
+ 'continue': ContinueAgent,
316
+ # Add more agents here as they're implemented
317
+ }
318
+
319
+ @classmethod
320
+ def create_agent(cls, agent_type: str, config: AgentConfig) -> BaseCodingAgent:
321
+ """
322
+ Create an agent instance of the specified type.
323
+
324
+ Args:
325
+ agent_type: Type of agent to create (e.g., 'claude', 'continue')
326
+ config: Agent configuration
327
+
328
+ Returns:
329
+ Agent instance
330
+
331
+ Raises:
332
+ ValueError: If agent type is not supported
333
+ """
334
+ if agent_type not in cls._agent_types:
335
+ raise ValueError(f"Unsupported agent type: {agent_type}. "
336
+ f"Supported types: {list(cls._agent_types.keys())}")
337
+
338
+ agent_class = cls._agent_types[agent_type]
339
+ return agent_class(config)
340
+
341
+ @classmethod
342
+ def get_supported_agents(cls) -> List[str]:
343
+ """Get list of supported agent types"""
344
+ return list(cls._agent_types.keys())
345
+
346
+ @classmethod
347
+ def register_agent(cls, agent_type: str, agent_class: type):
348
+ """Register a new agent type"""
349
+ if not issubclass(agent_class, BaseCodingAgent):
350
+ raise ValueError("Agent class must inherit from BaseCodingAgent")
351
351
  cls._agent_types[agent_type] = agent_class