autobyteus 1.1.0__py3-none-any.whl → 1.1.2__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.
Files changed (103) hide show
  1. autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +1 -1
  2. autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +1 -1
  3. autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +1 -1
  4. autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +1 -1
  5. autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +1 -1
  6. autobyteus/agent/context/__init__.py +0 -5
  7. autobyteus/agent/context/agent_config.py +6 -2
  8. autobyteus/agent/context/agent_context.py +2 -5
  9. autobyteus/agent/context/agent_phase_manager.py +105 -5
  10. autobyteus/agent/context/agent_runtime_state.py +2 -2
  11. autobyteus/agent/context/phases.py +2 -0
  12. autobyteus/agent/events/__init__.py +0 -11
  13. autobyteus/agent/events/agent_events.py +0 -37
  14. autobyteus/agent/events/notifiers.py +25 -7
  15. autobyteus/agent/events/worker_event_dispatcher.py +1 -1
  16. autobyteus/agent/factory/agent_factory.py +6 -2
  17. autobyteus/agent/group/agent_group.py +16 -7
  18. autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +28 -14
  19. autobyteus/agent/handlers/lifecycle_event_logger.py +1 -1
  20. autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +4 -2
  21. autobyteus/agent/handlers/tool_invocation_request_event_handler.py +40 -15
  22. autobyteus/agent/handlers/tool_result_event_handler.py +12 -7
  23. autobyteus/agent/hooks/__init__.py +7 -0
  24. autobyteus/agent/hooks/base_phase_hook.py +11 -2
  25. autobyteus/agent/hooks/hook_definition.py +36 -0
  26. autobyteus/agent/hooks/hook_meta.py +37 -0
  27. autobyteus/agent/hooks/hook_registry.py +118 -0
  28. autobyteus/agent/input_processor/base_user_input_processor.py +6 -3
  29. autobyteus/agent/input_processor/passthrough_input_processor.py +2 -1
  30. autobyteus/agent/input_processor/processor_meta.py +1 -1
  31. autobyteus/agent/input_processor/processor_registry.py +19 -0
  32. autobyteus/agent/llm_response_processor/base_processor.py +6 -3
  33. autobyteus/agent/llm_response_processor/processor_meta.py +1 -1
  34. autobyteus/agent/llm_response_processor/processor_registry.py +19 -0
  35. autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +2 -1
  36. autobyteus/agent/message/context_file_type.py +2 -3
  37. autobyteus/agent/phases/__init__.py +18 -0
  38. autobyteus/agent/phases/discover.py +52 -0
  39. autobyteus/agent/phases/manager.py +265 -0
  40. autobyteus/agent/phases/phase_enum.py +49 -0
  41. autobyteus/agent/phases/transition_decorator.py +40 -0
  42. autobyteus/agent/phases/transition_info.py +33 -0
  43. autobyteus/agent/remote_agent.py +1 -1
  44. autobyteus/agent/runtime/agent_runtime.py +5 -10
  45. autobyteus/agent/runtime/agent_worker.py +62 -19
  46. autobyteus/agent/streaming/agent_event_stream.py +58 -5
  47. autobyteus/agent/streaming/stream_event_payloads.py +24 -13
  48. autobyteus/agent/streaming/stream_events.py +14 -11
  49. autobyteus/agent/system_prompt_processor/base_processor.py +6 -3
  50. autobyteus/agent/system_prompt_processor/processor_meta.py +1 -1
  51. autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +45 -31
  52. autobyteus/agent/tool_invocation.py +29 -3
  53. autobyteus/agent/utils/wait_for_idle.py +1 -1
  54. autobyteus/agent/workspace/__init__.py +2 -0
  55. autobyteus/agent/workspace/base_workspace.py +33 -11
  56. autobyteus/agent/workspace/workspace_config.py +160 -0
  57. autobyteus/agent/workspace/workspace_definition.py +36 -0
  58. autobyteus/agent/workspace/workspace_meta.py +37 -0
  59. autobyteus/agent/workspace/workspace_registry.py +72 -0
  60. autobyteus/cli/__init__.py +4 -3
  61. autobyteus/cli/agent_cli.py +25 -207
  62. autobyteus/cli/cli_display.py +205 -0
  63. autobyteus/events/event_manager.py +2 -1
  64. autobyteus/events/event_types.py +3 -1
  65. autobyteus/llm/api/autobyteus_llm.py +2 -12
  66. autobyteus/llm/api/deepseek_llm.py +11 -173
  67. autobyteus/llm/api/grok_llm.py +11 -172
  68. autobyteus/llm/api/kimi_llm.py +24 -0
  69. autobyteus/llm/api/mistral_llm.py +4 -4
  70. autobyteus/llm/api/ollama_llm.py +2 -2
  71. autobyteus/llm/api/openai_compatible_llm.py +193 -0
  72. autobyteus/llm/api/openai_llm.py +11 -139
  73. autobyteus/llm/extensions/token_usage_tracking_extension.py +11 -1
  74. autobyteus/llm/llm_factory.py +168 -42
  75. autobyteus/llm/models.py +25 -29
  76. autobyteus/llm/ollama_provider.py +6 -2
  77. autobyteus/llm/ollama_provider_resolver.py +44 -0
  78. autobyteus/llm/providers.py +1 -0
  79. autobyteus/llm/token_counter/kimi_token_counter.py +24 -0
  80. autobyteus/llm/token_counter/token_counter_factory.py +3 -0
  81. autobyteus/llm/utils/messages.py +3 -3
  82. autobyteus/tools/__init__.py +2 -0
  83. autobyteus/tools/base_tool.py +7 -1
  84. autobyteus/tools/functional_tool.py +20 -5
  85. autobyteus/tools/mcp/call_handlers/stdio_handler.py +15 -1
  86. autobyteus/tools/mcp/config_service.py +106 -127
  87. autobyteus/tools/mcp/registrar.py +247 -59
  88. autobyteus/tools/mcp/types.py +5 -3
  89. autobyteus/tools/registry/tool_definition.py +8 -1
  90. autobyteus/tools/registry/tool_registry.py +18 -0
  91. autobyteus/tools/tool_category.py +11 -0
  92. autobyteus/tools/tool_meta.py +3 -1
  93. autobyteus/tools/tool_state.py +20 -0
  94. autobyteus/tools/usage/parsers/_json_extractor.py +99 -0
  95. autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +46 -77
  96. autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +87 -96
  97. autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +37 -47
  98. autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +112 -113
  99. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/METADATA +13 -12
  100. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/RECORD +103 -82
  101. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/WHEEL +0 -0
  102. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/licenses/LICENSE +0 -0
  103. {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@ from autobyteus.llm.models import LLMModel
2
2
  from autobyteus.llm.api.ollama_llm import OllamaLLM
3
3
  from autobyteus.llm.providers import LLMProvider
4
4
  from autobyteus.llm.utils.llm_config import LLMConfig, TokenPricingConfig
5
+ from autobyteus.llm.ollama_provider_resolver import OllamaProviderResolver
5
6
  from typing import TYPE_CHECKING
6
7
  import os
7
8
  import logging
@@ -39,7 +40,7 @@ class OllamaModelProvider:
39
40
  try:
40
41
  from autobyteus.llm.llm_factory import LLMFactory # Local import to avoid circular dependency
41
42
 
42
- ollama_host = os.getenv('OLLAMA_HOST', OllamaLLM.DEFAULT_OLLAMA_HOST)
43
+ ollama_host = os.getenv('DEFAULT_OLLAMA_HOST', OllamaLLM.DEFAULT_OLLAMA_HOST)
43
44
 
44
45
  if not OllamaModelProvider.is_valid_url(ollama_host):
45
46
  logger.error(f"Invalid Ollama host URL: {ollama_host}")
@@ -73,11 +74,14 @@ class OllamaModelProvider:
73
74
  model_name = model_info.get('model')
74
75
  if not model_name:
75
76
  continue
77
+
78
+ # Determine the provider based on the model name
79
+ provider = OllamaProviderResolver.resolve(model_name)
76
80
 
77
81
  llm_model = LLMModel(
78
82
  name=model_name,
79
83
  value=model_name,
80
- provider=LLMProvider.OLLAMA,
84
+ provider=provider,
81
85
  llm_class=OllamaLLM,
82
86
  canonical_name=model_name, # Use model_name as the canonical_name
83
87
  default_config=LLMConfig(
@@ -0,0 +1,44 @@
1
+ from autobyteus.llm.providers import LLMProvider
2
+ import logging
3
+
4
+ logger = logging.getLogger(__name__)
5
+
6
+ class OllamaProviderResolver:
7
+ """
8
+ A utility class to resolve the correct LLMProvider for Ollama models
9
+ based on keywords in their names. This helps attribute models to their
10
+ original creators (e.g., Google for 'gemma').
11
+ """
12
+
13
+ # A mapping from keywords to providers. The list is ordered to handle
14
+ # potential overlaps, though current keywords are distinct.
15
+ KEYWORD_PROVIDER_MAP = [
16
+ (['gemma', 'gemini'], LLMProvider.GEMINI),
17
+ (['llama'], LLMProvider.GROQ),
18
+ (['mistral'], LLMProvider.MISTRAL),
19
+ (['deepseek'], LLMProvider.DEEPSEEK),
20
+ ]
21
+
22
+ @staticmethod
23
+ def resolve(model_name: str) -> LLMProvider:
24
+ """
25
+ Resolves the LLMProvider for a given model name from Ollama.
26
+ It checks for keywords in the model name and returns the corresponding
27
+ provider. If no specific provider is found, it defaults to OLLAMA.
28
+
29
+ Args:
30
+ model_name (str): The name of the model discovered from Ollama (e.g., 'gemma:7b').
31
+
32
+ Returns:
33
+ LLMProvider: The resolved provider for the model.
34
+ """
35
+ lower_model_name = model_name.lower()
36
+
37
+ for keywords, provider in OllamaProviderResolver.KEYWORD_PROVIDER_MAP:
38
+ for keyword in keywords:
39
+ if keyword in lower_model_name:
40
+ logger.debug(f"Resolved provider for model '{model_name}' to '{provider.name}' based on keyword '{keyword}'.")
41
+ return provider
42
+
43
+ logger.debug(f"Model '{model_name}' did not match any specific provider keywords. Defaulting to OLLAMA provider.")
44
+ return LLMProvider.OLLAMA
@@ -12,3 +12,4 @@ class LLMProvider(Enum):
12
12
  DEEPSEEK = "deepseek"
13
13
  GROK = "grok"
14
14
  AUTOBYTEUS = "autobyteus"
15
+ KIMI = "kimi"
@@ -0,0 +1,24 @@
1
+ from typing import TYPE_CHECKING
2
+ from autobyteus.llm.token_counter.openai_token_counter import OpenAITokenCounter
3
+ from autobyteus.llm.models import LLMModel
4
+
5
+ if TYPE_CHECKING:
6
+ from autobyteus.llm.base_llm import BaseLLM
7
+
8
+ class KimiTokenCounter(OpenAITokenCounter):
9
+ """
10
+ Token counter for Kimi (Moonshot AI) models. Uses the same token counting implementation as OpenAI.
11
+
12
+ This implementation inherits from OpenAITokenCounter as Kimi uses the same tokenization
13
+ approach as OpenAI's models.
14
+ """
15
+
16
+ def __init__(self, model: LLMModel, llm: 'BaseLLM' = None):
17
+ """
18
+ Initialize the Kimi token counter.
19
+
20
+ Args:
21
+ model (LLMModel): The Kimi model to count tokens for.
22
+ llm (BaseLLM, optional): The LLM instance. Defaults to None.
23
+ """
24
+ super().__init__(model, llm)
@@ -3,6 +3,7 @@ from autobyteus.llm.token_counter.openai_token_counter import OpenAITokenCounter
3
3
  from autobyteus.llm.token_counter.claude_token_counter import ClaudeTokenCounter
4
4
  from autobyteus.llm.token_counter.mistral_token_counter import MistralTokenCounter
5
5
  from autobyteus.llm.token_counter.deepseek_token_counter import DeepSeekTokenCounter
6
+ from autobyteus.llm.token_counter.kimi_token_counter import KimiTokenCounter
6
7
  from autobyteus.llm.token_counter.base_token_counter import BaseTokenCounter
7
8
  from autobyteus.llm.models import LLMModel
8
9
  from autobyteus.llm.providers import LLMProvider
@@ -31,6 +32,8 @@ def get_token_counter(model: LLMModel, llm: 'BaseLLM') -> BaseTokenCounter:
31
32
  return DeepSeekTokenCounter(model, llm)
32
33
  elif model.provider == LLMProvider.GROK:
33
34
  return DeepSeekTokenCounter(model, llm)
35
+ elif model.provider == LLMProvider.KIMI:
36
+ return KimiTokenCounter(model, llm)
34
37
  elif model.provider == LLMProvider.OLLAMA:
35
38
  return OpenAITokenCounter(model, llm)
36
39
  elif model.provider == LLMProvider.GEMINI:
@@ -20,9 +20,9 @@ class Message:
20
20
  self.content = content
21
21
  self.reasoning_content = reasoning_content # Optional field for reasoning content
22
22
 
23
- def to_dict(self) -> Dict[str, Union[str, None]]:
24
- result = {"role": self.role.value, "content": self.content}
25
- if self.reasoning_content is not None:
23
+ def to_dict(self) -> Dict[str, Union[str, List[Dict]]]:
24
+ result: Dict[str, Union[str, List[Dict]]] = {"role": self.role.value, "content": self.content}
25
+ if self.reasoning_content:
26
26
  result["reasoning_content"] = self.reasoning_content
27
27
  return result
28
28
 
@@ -10,6 +10,7 @@ from .base_tool import BaseTool
10
10
  from .functional_tool import tool # The @tool decorator
11
11
  from .parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
12
12
  from .tool_config import ToolConfig # Configuration data object, primarily for class-based tools
13
+ from .tool_category import ToolCategory
13
14
 
14
15
  # --- Re-export specific tools for easier access ---
15
16
 
@@ -47,6 +48,7 @@ __all__ = [
47
48
  "ParameterDefinition",
48
49
  "ParameterType",
49
50
  "ToolConfig",
51
+ "ToolCategory",
50
52
 
51
53
  # Re-exported functional tool instances
52
54
  "ask_user_input",
@@ -9,10 +9,13 @@ from autobyteus.events.event_emitter import EventEmitter
9
9
  from autobyteus.events.event_types import EventType
10
10
 
11
11
  from .tool_meta import ToolMeta
12
+ from .tool_state import ToolState
13
+
12
14
  if TYPE_CHECKING:
13
15
  from autobyteus.agent.context import AgentContext
14
16
  from autobyteus.tools.parameter_schema import ParameterSchema
15
17
  from autobyteus.tools.tool_config import ToolConfig
18
+ from .tool_state import ToolState
16
19
 
17
20
  logger = logging.getLogger('autobyteus')
18
21
 
@@ -25,7 +28,10 @@ class BaseTool(ABC, EventEmitter, metaclass=ToolMeta):
25
28
  self.agent_id: Optional[str] = None
26
29
  # The config is stored primarily for potential use by subclasses or future base features.
27
30
  self._config = config
28
- logger.debug(f"BaseTool instance initializing for potential class {self.__class__.__name__}")
31
+ # Add a dedicated state dictionary for the tool instance
32
+ # CHANGED: Use ToolState class for explicit state management.
33
+ self.tool_state: 'ToolState' = ToolState()
34
+ logger.debug(f"BaseTool instance initializing for potential class {self.__class__.__name__}. tool_state initialized.")
29
35
 
30
36
  @classmethod
31
37
  def get_name(cls) -> str:
@@ -8,6 +8,7 @@ from autobyteus.tools.base_tool import BaseTool
8
8
  from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
9
9
  from autobyteus.tools.tool_config import ToolConfig
10
10
  from autobyteus.tools.registry import default_tool_registry, ToolDefinition
11
+ from autobyteus.tools.tool_category import ToolCategory
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from autobyteus.agent.context import AgentContext
@@ -28,15 +29,20 @@ class FunctionalTool(BaseTool):
28
29
  config_schema: Optional[ParameterSchema],
29
30
  is_async: bool,
30
31
  expects_context: bool,
32
+ expects_tool_state: bool,
31
33
  func_param_names: TypingList[str],
32
34
  instantiation_config: Optional[Dict[str, Any]] = None):
33
35
  super().__init__(config=ToolConfig(params=instantiation_config) if instantiation_config else None)
34
36
  self._original_func = original_func
35
37
  self._is_async = is_async
36
38
  self._expects_context = expects_context
39
+ self._expects_tool_state = expects_tool_state
37
40
  self._func_param_names = func_param_names
38
41
  self._instantiation_config = instantiation_config or {}
39
42
 
43
+ # This instance has its own state dictionary, inherited from BaseTool's __init__
44
+ # self.tool_state: Dict[str, Any] = {} # This is now handled by super().__init__()
45
+
40
46
  # Override instance methods to provide specific schema info
41
47
  self.get_name = lambda: name
42
48
  self.get_description = lambda: description
@@ -65,6 +71,9 @@ class FunctionalTool(BaseTool):
65
71
 
66
72
  if self._expects_context:
67
73
  call_args['context'] = context
74
+
75
+ if self._expects_tool_state:
76
+ call_args['tool_state'] = self.tool_state
68
77
 
69
78
  if self._is_async:
70
79
  return await self._original_func(**call_args)
@@ -143,15 +152,19 @@ def _get_parameter_type_from_hint(py_type: Any, param_name: str) -> Tuple[Parame
143
152
  logger.warning(f"Unmapped type hint {py_type} (actual_type: {actual_type}) for param '{param_name}'. Defaulting to ParameterType.STRING.")
144
153
  return ParameterType.STRING, None
145
154
 
146
- def _parse_signature(sig: inspect.Signature, tool_name: str) -> Tuple[TypingList[str], bool, ParameterSchema]:
155
+ def _parse_signature(sig: inspect.Signature, tool_name: str) -> Tuple[TypingList[str], bool, bool, ParameterSchema]:
147
156
  func_param_names = []
148
157
  expects_context = False
158
+ expects_tool_state = False
149
159
  generated_arg_schema = ParameterSchema()
150
160
 
151
161
  for param_name, param_obj in sig.parameters.items():
152
162
  if param_name == "context":
153
163
  expects_context = True
154
- continue
164
+ continue
165
+ if param_name == "tool_state":
166
+ expects_tool_state = True
167
+ continue
155
168
 
156
169
  func_param_names.append(param_name)
157
170
 
@@ -177,7 +190,7 @@ def _parse_signature(sig: inspect.Signature, tool_name: str) -> Tuple[TypingList
177
190
  )
178
191
  generated_arg_schema.add_parameter(schema_param)
179
192
 
180
- return func_param_names, expects_context, generated_arg_schema
193
+ return func_param_names, expects_context, expects_tool_state, generated_arg_schema
181
194
 
182
195
  # --- The refactored @tool decorator ---
183
196
 
@@ -196,7 +209,7 @@ def tool(
196
209
 
197
210
  sig = inspect.signature(func)
198
211
  is_async = inspect.iscoroutinefunction(func)
199
- func_param_names, expects_context, gen_arg_schema = _parse_signature(sig, tool_name)
212
+ func_param_names, expects_context, expects_tool_state, gen_arg_schema = _parse_signature(sig, tool_name)
200
213
 
201
214
  final_arg_schema = argument_schema if argument_schema is not None else gen_arg_schema
202
215
 
@@ -209,6 +222,7 @@ def tool(
209
222
  config_schema=config_schema,
210
223
  is_async=is_async,
211
224
  expects_context=expects_context,
225
+ expects_tool_state=expects_tool_state,
212
226
  func_param_names=func_param_names,
213
227
  instantiation_config=inst_config.params if inst_config else None
214
228
  )
@@ -221,7 +235,8 @@ def tool(
221
235
  argument_schema=final_arg_schema,
222
236
  config_schema=config_schema,
223
237
  custom_factory=factory,
224
- tool_class=None
238
+ tool_class=None,
239
+ category=ToolCategory.LOCAL
225
240
  )
226
241
  default_tool_registry.register_tool(tool_def)
227
242
 
@@ -1,5 +1,6 @@
1
1
  # file: autobyteus/autobyteus/tools/mcp/call_handlers/stdio_handler.py
2
2
  import logging
3
+ import asyncio
3
4
  from typing import Dict, Any, cast, TYPE_CHECKING
4
5
 
5
6
  from .base_handler import McpCallHandler
@@ -11,6 +12,9 @@ if TYPE_CHECKING:
11
12
 
12
13
  logger = logging.getLogger(__name__)
13
14
 
15
+ # A default timeout for STDIO subprocesses to prevent indefinite hangs.
16
+ DEFAULT_STDIO_TIMEOUT = 30 # seconds
17
+
14
18
  class StdioMcpCallHandler(McpCallHandler):
15
19
  """Handles MCP tool calls over a stateless STDIO transport."""
16
20
 
@@ -23,6 +27,7 @@ class StdioMcpCallHandler(McpCallHandler):
23
27
  """
24
28
  Creates a new subprocess, establishes a session, and executes the
25
29
  requested tool call. It handles 'list_tools' as a special case.
30
+ Includes a timeout to prevent hanging on unresponsive subprocesses.
26
31
  """
27
32
  logger.debug(f"Handling STDIO call to tool '{remote_tool_name}' on server '{config.server_id}'.")
28
33
 
@@ -39,7 +44,8 @@ class StdioMcpCallHandler(McpCallHandler):
39
44
  cwd=stdio_config.cwd
40
45
  )
41
46
 
42
- try:
47
+ async def _perform_call():
48
+ """Inner function to be wrapped by the timeout."""
43
49
  # The stdio_client context manager provides the read/write streams.
44
50
  async with stdio_client(mcp_lib_stdio_params) as (read_stream, write_stream):
45
51
  # The ClientSession is its own context manager that handles initialization.
@@ -54,6 +60,14 @@ class StdioMcpCallHandler(McpCallHandler):
54
60
 
55
61
  logger.debug(f"STDIO call to tool '{remote_tool_name}' on server '{config.server_id}' completed.")
56
62
  return result
63
+
64
+ try:
65
+ return await asyncio.wait_for(_perform_call(), timeout=DEFAULT_STDIO_TIMEOUT)
66
+ except asyncio.TimeoutError:
67
+ error_message = (f"MCP call to '{remote_tool_name}' on server '{config.server_id}' timed out "
68
+ f"after {DEFAULT_STDIO_TIMEOUT} seconds. The subprocess may have hung.")
69
+ logger.error(error_message)
70
+ raise RuntimeError(error_message) from None
57
71
  except Exception as e:
58
72
  logger.error(
59
73
  f"An error occurred during STDIO tool call to '{remote_tool_name}' on server '{config.server_id}': {e}",