kolega-code 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.
Files changed (171) hide show
  1. kolega_code/__init__.py +151 -0
  2. kolega_code/agent/__init__.py +42 -0
  3. kolega_code/agent/baseagent.py +998 -0
  4. kolega_code/agent/browseragent.py +123 -0
  5. kolega_code/agent/coder.py +157 -0
  6. kolega_code/agent/common.py +41 -0
  7. kolega_code/agent/compression.py +81 -0
  8. kolega_code/agent/context.py +112 -0
  9. kolega_code/agent/conversation.py +408 -0
  10. kolega_code/agent/generalagent.py +146 -0
  11. kolega_code/agent/investigationagent.py +123 -0
  12. kolega_code/agent/planningagent.py +187 -0
  13. kolega_code/agent/prompt_provider.py +196 -0
  14. kolega_code/agent/prompt_templates/agents/browser.j2 +102 -0
  15. kolega_code/agent/prompt_templates/agents/coder_cli_mode.j2 +127 -0
  16. kolega_code/agent/prompt_templates/agents/general.j2 +68 -0
  17. kolega_code/agent/prompt_templates/agents/investigation.j2 +72 -0
  18. kolega_code/agent/prompt_templates/common/frontend_guidance.md +36 -0
  19. kolega_code/agent/prompt_templates/common/kolega_md_instructions.md +14 -0
  20. kolega_code/agent/prompt_templates/environment_variables/workspace_env_vars.md +11 -0
  21. kolega_code/agent/prompt_templates/template_guidance/expo-template.md +379 -0
  22. kolega_code/agent/prompt_templates/template_guidance/html-website-template.md +3 -0
  23. kolega_code/agent/prompt_templates/template_guidance/mern-stack-template.md +3 -0
  24. kolega_code/agent/prompt_templates/template_guidance/react-vite-shadcdn-template.md +182 -0
  25. kolega_code/agent/prompts.py +192 -0
  26. kolega_code/agent/tests/__init__.py +0 -0
  27. kolega_code/agent/tests/llm/__init__.py +0 -0
  28. kolega_code/agent/tests/llm/test_anthropic_token_counting.py +633 -0
  29. kolega_code/agent/tests/llm/test_billing_openai_cache.py +74 -0
  30. kolega_code/agent/tests/llm/test_client.py +773 -0
  31. kolega_code/agent/tests/llm/test_dashscope_mapping.py +32 -0
  32. kolega_code/agent/tests/llm/test_error_boundary.py +322 -0
  33. kolega_code/agent/tests/llm/test_exceptions.py +249 -0
  34. kolega_code/agent/tests/llm/test_instrumented_client.py +536 -0
  35. kolega_code/agent/tests/llm/test_instrumented_client_integration.py +547 -0
  36. kolega_code/agent/tests/llm/test_langfuse_normalization.py +39 -0
  37. kolega_code/agent/tests/llm/test_model_specs.py +17 -0
  38. kolega_code/agent/tests/llm/test_openai_cached_tokens.py +58 -0
  39. kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +74 -0
  40. kolega_code/agent/tests/llm/test_openai_message_conversion.py +30 -0
  41. kolega_code/agent/tests/llm/test_openai_token_counting.py +687 -0
  42. kolega_code/agent/tests/llm/test_tool_execution_ids.py +193 -0
  43. kolega_code/agent/tests/services/__init__.py +1 -0
  44. kolega_code/agent/tests/services/test_browser.py +447 -0
  45. kolega_code/agent/tests/services/test_browser_parity.py +353 -0
  46. kolega_code/agent/tests/services/test_file_system.py +699 -0
  47. kolega_code/agent/tests/services/test_sandbox_terminal_input.py +98 -0
  48. kolega_code/agent/tests/services/test_terminal.py +154 -0
  49. kolega_code/agent/tests/services/test_terminal_command_tracking.py +385 -0
  50. kolega_code/agent/tests/services/test_terminal_state_serializer.py +262 -0
  51. kolega_code/agent/tests/test_agent_tools_inventory.py +267 -0
  52. kolega_code/agent/tests/test_base_agent.py +1942 -0
  53. kolega_code/agent/tests/test_coder_attachments.py +330 -0
  54. kolega_code/agent/tests/test_coder_prompt_extensions.py +61 -0
  55. kolega_code/agent/tests/test_commands.py +179 -0
  56. kolega_code/agent/tests/test_duplicate_tool_results.py +556 -0
  57. kolega_code/agent/tests/test_empty_message_handling.py +48 -0
  58. kolega_code/agent/tests/test_general_agent.py +242 -0
  59. kolega_code/agent/tests/test_html.py +320 -0
  60. kolega_code/agent/tests/test_parallel_tool_calls.py +291 -0
  61. kolega_code/agent/tests/test_planning_agent.py +227 -0
  62. kolega_code/agent/tests/test_prompt_provider.py +271 -0
  63. kolega_code/agent/tests/test_tool_registry.py +102 -0
  64. kolega_code/agent/tests/test_tools.py +549 -0
  65. kolega_code/agent/tests/tool_backend/__init__.py +0 -0
  66. kolega_code/agent/tests/tool_backend/test_agent_tool.py +356 -0
  67. kolega_code/agent/tests/tool_backend/test_base_tool.py +147 -0
  68. kolega_code/agent/tests/tool_backend/test_browser_tool.py +335 -0
  69. kolega_code/agent/tests/tool_backend/test_build_tool.py +93 -0
  70. kolega_code/agent/tests/tool_backend/test_create_file_tool.py +115 -0
  71. kolega_code/agent/tests/tool_backend/test_glob_tool.py +196 -0
  72. kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +230 -0
  73. kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +292 -0
  74. kolega_code/agent/tests/tool_backend/test_read_file_tool.py +173 -0
  75. kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +115 -0
  76. kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +141 -0
  77. kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +174 -0
  78. kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +228 -0
  79. kolega_code/agent/tests/tool_backend/test_terminal_tool.py +482 -0
  80. kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +189 -0
  81. kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +445 -0
  82. kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +194 -0
  83. kolega_code/agent/tool_backend/agent_tool.py +414 -0
  84. kolega_code/agent/tool_backend/apply_edit_tool.py +98 -0
  85. kolega_code/agent/tool_backend/apply_patch_tool.py +514 -0
  86. kolega_code/agent/tool_backend/base_tool.py +217 -0
  87. kolega_code/agent/tool_backend/browser_tool.py +271 -0
  88. kolega_code/agent/tool_backend/build_tool.py +93 -0
  89. kolega_code/agent/tool_backend/create_file_tool.py +52 -0
  90. kolega_code/agent/tool_backend/glob_tool.py +323 -0
  91. kolega_code/agent/tool_backend/list_directory_tool.py +300 -0
  92. kolega_code/agent/tool_backend/memory_tool.py +79 -0
  93. kolega_code/agent/tool_backend/read_file_tool.py +119 -0
  94. kolega_code/agent/tool_backend/replace_entire_file_tool.py +40 -0
  95. kolega_code/agent/tool_backend/replace_lines_tool.py +97 -0
  96. kolega_code/agent/tool_backend/search_and_replace_tool.py +146 -0
  97. kolega_code/agent/tool_backend/search_codebase_tool.py +377 -0
  98. kolega_code/agent/tool_backend/streaming_tool.py +47 -0
  99. kolega_code/agent/tool_backend/terminal_tool.py +643 -0
  100. kolega_code/agent/tool_backend/think_hard_tool.py +211 -0
  101. kolega_code/agent/tool_backend/web_fetch_tool.py +205 -0
  102. kolega_code/agent/tools.py +1704 -0
  103. kolega_code/agent/utils/commands.py +94 -0
  104. kolega_code/cli/__init__.py +1 -0
  105. kolega_code/cli/app.py +2756 -0
  106. kolega_code/cli/config.py +280 -0
  107. kolega_code/cli/connection.py +49 -0
  108. kolega_code/cli/file_index.py +147 -0
  109. kolega_code/cli/main.py +564 -0
  110. kolega_code/cli/mentions.py +155 -0
  111. kolega_code/cli/messages.py +89 -0
  112. kolega_code/cli/provider_registry.py +96 -0
  113. kolega_code/cli/session_store.py +207 -0
  114. kolega_code/cli/settings.py +87 -0
  115. kolega_code/cli/skills.py +409 -0
  116. kolega_code/cli/slash_commands.py +108 -0
  117. kolega_code/cli/tests/__init__.py +1 -0
  118. kolega_code/cli/tests/test_app.py +4251 -0
  119. kolega_code/cli/tests/test_cli_config.py +171 -0
  120. kolega_code/cli/tests/test_connection.py +26 -0
  121. kolega_code/cli/tests/test_file_index.py +103 -0
  122. kolega_code/cli/tests/test_main.py +455 -0
  123. kolega_code/cli/tests/test_mentions.py +108 -0
  124. kolega_code/cli/tests/test_session_store.py +67 -0
  125. kolega_code/cli/tests/test_settings.py +62 -0
  126. kolega_code/cli/tests/test_skills.py +157 -0
  127. kolega_code/cli/tests/test_slash_commands.py +88 -0
  128. kolega_code/cli/theme.py +180 -0
  129. kolega_code/config.py +154 -0
  130. kolega_code/events.py +202 -0
  131. kolega_code/llm/client.py +300 -0
  132. kolega_code/llm/exceptions.py +285 -0
  133. kolega_code/llm/instrumented_client.py +520 -0
  134. kolega_code/llm/models.py +1368 -0
  135. kolega_code/llm/providers/__init__.py +0 -0
  136. kolega_code/llm/providers/anthropic.py +387 -0
  137. kolega_code/llm/providers/base.py +71 -0
  138. kolega_code/llm/providers/google.py +157 -0
  139. kolega_code/llm/providers/models.py +37 -0
  140. kolega_code/llm/providers/openai.py +363 -0
  141. kolega_code/llm/ratelimit.py +40 -0
  142. kolega_code/llm/specs.py +67 -0
  143. kolega_code/llm/tool_execution_ids.py +18 -0
  144. kolega_code/models/__init__.py +9 -0
  145. kolega_code/models/sandbox_terminal_state.py +47 -0
  146. kolega_code/runtime.py +50 -0
  147. kolega_code/sandbox/README.md +200 -0
  148. kolega_code/sandbox/__init__.py +21 -0
  149. kolega_code/sandbox/async_filesystem.py +475 -0
  150. kolega_code/sandbox/base.py +297 -0
  151. kolega_code/sandbox/browser.py +25 -0
  152. kolega_code/sandbox/event_loop.py +43 -0
  153. kolega_code/sandbox/filesystem.py +341 -0
  154. kolega_code/sandbox/local.py +118 -0
  155. kolega_code/sandbox/serializer.py +175 -0
  156. kolega_code/sandbox/terminal.py +868 -0
  157. kolega_code/sandbox/utils.py +216 -0
  158. kolega_code/services/base.py +255 -0
  159. kolega_code/services/browser.py +444 -0
  160. kolega_code/services/file_system.py +749 -0
  161. kolega_code/services/html.py +221 -0
  162. kolega_code/services/terminal.py +903 -0
  163. kolega_code/tools/__init__.py +22 -0
  164. kolega_code/tools/core.py +33 -0
  165. kolega_code/tools/definitions.py +81 -0
  166. kolega_code/tools/registry.py +73 -0
  167. kolega_code-0.1.0.dist-info/METADATA +157 -0
  168. kolega_code-0.1.0.dist-info/RECORD +171 -0
  169. kolega_code-0.1.0.dist-info/WHEEL +4 -0
  170. kolega_code-0.1.0.dist-info/entry_points.txt +2 -0
  171. kolega_code-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,123 @@
1
+ from pathlib import Path
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ from .baseagent import BaseAgent
5
+ from kolega_code.config import AgentConfig
6
+ from kolega_code.events import AgentConnectionManager
7
+ from kolega_code.llm.models import Message, TextBlock
8
+ from .prompt_provider import AgentType, PromptExtension
9
+ from .tools import ToolCollection
10
+
11
+
12
+ class BrowserAgent(BaseAgent):
13
+ """
14
+ An AI coding agent that operates within a workspace to assist with programming tasks.
15
+
16
+ The agent has access to the project filesystem and can perform coding operations
17
+ like reading, analyzing, and modifying code files.
18
+ """
19
+
20
+ agent_name = "browser-agent"
21
+
22
+ def __init__(
23
+ self,
24
+ project_path: str | Path,
25
+ workspace_id: str,
26
+ thread_id: str,
27
+ connection_manager: AgentConnectionManager,
28
+ config: AgentConfig,
29
+ sub_agent: bool = True,
30
+ filesystem=None,
31
+ terminal_manager=None,
32
+ browser_manager=None,
33
+ langfuse_client=None,
34
+ user_id: Optional[str] = None,
35
+ user_email: Optional[str] = None,
36
+ project_template_slug: Optional[str] = None,
37
+ protected_files: Optional[List[str]] = None,
38
+ agent_mode: Optional["AgentMode"] = None,
39
+ workspace_env_var_descriptions: Optional[Dict[str, str]] = None,
40
+ workspace_memories: Optional[List[str]] = None,
41
+ prompt_extensions: Optional[List[PromptExtension]] = None,
42
+ tool_extensions: Optional[List[Any]] = None,
43
+ usage_recorder: Optional[Any] = None,
44
+ sub_agent_recorder: Optional[Any] = None,
45
+ ) -> None:
46
+ """
47
+ Initialize a new BrowserAgent instance.
48
+
49
+ Args:
50
+ project_path: File system path to the project root directory
51
+ workspace_id: Identifier for the workspace
52
+ thread_id: Identifier for the thread
53
+ connection_manager: Manager for handling agent connections
54
+ config: Agent configuration
55
+ sub_agent: Whether this agent is a sub-agent of another agent
56
+ filesystem: Optional filesystem implementation
57
+ terminal_manager: Optional terminal manager implementation
58
+ browser_manager: Optional browser manager implementation
59
+ langfuse_client: Optional Langfuse client for LLM observability
60
+ user_id: Optional ID of user who created this job
61
+ user_email: Optional email of user who created this job
62
+ project_template_slug: Optional slug of the project template being used
63
+ protected_files: Optional list of file basenames protected from edits in vibe mode
64
+ agent_mode: Optional agent mode (not used for BrowserAgent)
65
+ workspace_env_var_descriptions: Optional mapping of workspace environment variable descriptions
66
+ workspace_memories: Optional list of workspace memories to inject into prompts
67
+ prompt_extensions: Host-provided prompt sections for app-specific context
68
+ tool_extensions: Host-provided tool providers for app-specific tools
69
+ usage_recorder: Optional callback for recording normalized LLM usage
70
+ sub_agent_recorder: Optional callback for persisting sub-agent conversation state
71
+ """
72
+ super().__init__(
73
+ project_path,
74
+ workspace_id,
75
+ thread_id,
76
+ connection_manager,
77
+ config,
78
+ sub_agent=sub_agent,
79
+ filesystem=filesystem,
80
+ terminal_manager=terminal_manager,
81
+ browser_manager=browser_manager,
82
+ langfuse_client=langfuse_client,
83
+ user_id=user_id,
84
+ user_email=user_email,
85
+ project_template_slug=project_template_slug,
86
+ protected_files=protected_files,
87
+ agent_mode=agent_mode,
88
+ workspace_env_var_descriptions=workspace_env_var_descriptions,
89
+ workspace_memories=workspace_memories,
90
+ prompt_extensions=prompt_extensions,
91
+ tool_extensions=tool_extensions,
92
+ usage_recorder=usage_recorder,
93
+ sub_agent_recorder=sub_agent_recorder,
94
+ )
95
+
96
+ self.tool_collection = ToolCollection(
97
+ self.project_path,
98
+ self.workspace_id,
99
+ self.thread_id,
100
+ self.connection_manager,
101
+ self.config,
102
+ caller=self,
103
+ browser_only=True,
104
+ filesystem=self.filesystem,
105
+ terminal_manager=self.terminal_manager,
106
+ browser_manager=self.browser_manager,
107
+ langfuse_client=self.langfuse_client,
108
+ tool_extensions=self.tool_extensions,
109
+ )
110
+
111
+ self._initialize_system_prompt()
112
+
113
+ def _initialize_system_prompt(self):
114
+ """Initialize system prompt using PromptProvider."""
115
+ # Generate prompt using the shared prompt provider
116
+ prompt_text = self.prompt_provider.get_system_prompt(
117
+ agent_type=AgentType.BROWSER,
118
+ mode=self.agent_mode,
119
+ template_slug=self.project_template_slug,
120
+ prompt_extensions=self.prompt_extensions,
121
+ context=self.build_prompt_context(),
122
+ )
123
+ self.system_prompt = Message(role="system", content=[TextBlock(text=prompt_text)])
@@ -0,0 +1,157 @@
1
+ from pathlib import Path
2
+ from typing import Any, Dict, List, Optional
3
+
4
+ from .baseagent import BaseAgent
5
+ from .common import LogMixin
6
+ from kolega_code.config import AgentConfig
7
+ from kolega_code.events import AgentConnectionManager
8
+ from kolega_code.llm.models import Message, TextBlock
9
+ from .prompt_provider import AgentType, AgentMode, PromptExtension, PromptProvider
10
+ from .tools import ToolCollection, ToolCollectionConfig
11
+ from .utils.commands import CommandProcessor
12
+
13
+
14
+ @CommandProcessor.process_commands
15
+ class CoderAgent(BaseAgent, LogMixin):
16
+ """
17
+ An AI coding agent that operates within a workspace to assist with programming tasks.
18
+
19
+ The agent has access to the project filesystem and can perform coding operations
20
+ like reading, analyzing, and modifying code files.
21
+ """
22
+
23
+ agent_name = "coder"
24
+
25
+ def __init__(
26
+ self,
27
+ project_path: str | Path,
28
+ workspace_id: str,
29
+ thread_id: str,
30
+ connection_manager: AgentConnectionManager,
31
+ config: AgentConfig,
32
+ sub_agent: bool = False,
33
+ filesystem=None,
34
+ terminal_manager=None,
35
+ browser_manager=None,
36
+ langfuse_client=None,
37
+ user_id: Optional[str] = None,
38
+ user_email: Optional[str] = None,
39
+ project_template_slug: Optional[str] = None,
40
+ protected_files: Optional[List[str]] = None,
41
+ agent_mode: Optional[AgentMode] = None,
42
+ workspace_env_var_descriptions: Optional[Dict[str, str]] = None,
43
+ workspace_memories: Optional[List[str]] = None,
44
+ prompt_provider: Optional[PromptProvider] = None,
45
+ prompt_extensions: Optional[List[PromptExtension]] = None,
46
+ tool_extensions: Optional[List[Any]] = None,
47
+ usage_recorder: Optional[Any] = None,
48
+ sub_agent_recorder: Optional[Any] = None,
49
+ ) -> None:
50
+ """
51
+ Initialize a new CoderAgent instance.
52
+
53
+ Args:
54
+ project_path: File system path to the project root directory
55
+ workspace_id: Identifier for the workspace
56
+ thread_id: Identifier for the thread
57
+ connection_manager: Manager for handling agent connections
58
+ config: Agent configuration settings
59
+ sub_agent: Whether this is a sub-agent (default: False)
60
+ filesystem: File system implementation (optional)
61
+ terminal_manager: Terminal manager implementation (optional)
62
+ browser_manager: Browser manager implementation (optional)
63
+ langfuse_client: Optional Langfuse client for LLM observability
64
+ user_id: Optional ID of user who created this job
65
+ user_email: Optional email of user who created this job
66
+ project_template_slug: Optional slug of the project template being used
67
+ protected_files: Optional list of file basenames protected from edits in vibe mode
68
+ agent_mode: Optional agent mode (CLI, VIBE, CODE, or FIX)
69
+ workspace_env_var_descriptions: Optional mapping of workspace environment variable descriptions
70
+ workspace_memories: Optional list of workspace memories to inject into prompts
71
+ prompt_provider: Optional host-configured prompt provider
72
+ prompt_extensions: Host-provided prompt sections for app-specific context
73
+ tool_extensions: Host-provided tool providers for app-specific tools
74
+ usage_recorder: Optional callback for recording normalized LLM usage
75
+ sub_agent_recorder: Optional callback for persisting sub-agent conversation state
76
+ """
77
+ # Call parent constructor
78
+ super().__init__(
79
+ project_path,
80
+ workspace_id,
81
+ thread_id,
82
+ connection_manager,
83
+ config,
84
+ sub_agent,
85
+ filesystem=filesystem,
86
+ terminal_manager=terminal_manager,
87
+ browser_manager=browser_manager,
88
+ langfuse_client=langfuse_client,
89
+ user_id=user_id,
90
+ user_email=user_email,
91
+ project_template_slug=project_template_slug,
92
+ protected_files=protected_files,
93
+ agent_mode=agent_mode,
94
+ workspace_env_var_descriptions=workspace_env_var_descriptions,
95
+ workspace_memories=workspace_memories,
96
+ prompt_provider=prompt_provider,
97
+ prompt_extensions=prompt_extensions,
98
+ tool_extensions=tool_extensions,
99
+ usage_recorder=usage_recorder,
100
+ sub_agent_recorder=sub_agent_recorder,
101
+ )
102
+
103
+ # Configure tool collection with custom coder agent tools
104
+ tool_exclusions = [
105
+ "read_memory",
106
+ "write_memory",
107
+ "execute_terminal_command",
108
+ "replace_lines",
109
+ "apply_patch",
110
+ "edit_file",
111
+ "get_tool_list",
112
+ "log_error",
113
+ "log_info",
114
+ "run_command", # Disabled: unreliable completion detection, use run_command_tracked instead
115
+ # Exclude task-specific dispatch tools since coder shouldn't call itself or other agents
116
+ "dispatch_coding_agent",
117
+ ]
118
+ mode_value = self.agent_mode.value if isinstance(self.agent_mode, AgentMode) else self.agent_mode
119
+ if mode_value == AgentMode.CLI.value:
120
+ tool_exclusions.extend(["build_backend", "build_frontend"])
121
+ if sub_agent:
122
+ # A dispatched coder must not fan out into further sub-agents
123
+ tool_exclusions.append("dispatch_general_agent")
124
+
125
+ tool_config = ToolCollectionConfig(
126
+ custom_tool_groups=["coder_agent_tools"],
127
+ tool_exclusions=tool_exclusions,
128
+ )
129
+
130
+ self.tool_collection = ToolCollection(
131
+ self.project_path,
132
+ self.workspace_id,
133
+ self.thread_id,
134
+ self.connection_manager,
135
+ self.config,
136
+ caller=self,
137
+ tool_config=tool_config,
138
+ filesystem=self.filesystem,
139
+ terminal_manager=self.terminal_manager,
140
+ browser_manager=self.browser_manager,
141
+ langfuse_client=self.langfuse_client,
142
+ tool_extensions=self.tool_extensions,
143
+ )
144
+
145
+ self._initialize_system_prompt()
146
+
147
+ def _initialize_system_prompt(self):
148
+ """Initialize system prompt using PromptProvider."""
149
+ # Generate prompt using the shared prompt provider
150
+ prompt_text = self.prompt_provider.get_system_prompt(
151
+ agent_type=AgentType.CODER,
152
+ mode=self.agent_mode,
153
+ template_slug=self.project_template_slug,
154
+ prompt_extensions=self.prompt_extensions,
155
+ context=self.build_prompt_context(),
156
+ )
157
+ self.system_prompt = Message(role="system", content=[TextBlock(text=prompt_text)])
@@ -0,0 +1,41 @@
1
+ from kolega_code.events import AgentEvent
2
+
3
+
4
+ class LogMixin:
5
+ """
6
+ A mixin class providing logging functionality to agents.
7
+
8
+ This mixin expects the implementing class to have a connection_manager attribute,
9
+ a workspace_id attribute, and a thread_id attribute.
10
+ """
11
+
12
+ async def log_info(self, message: str, sender: str = "agent") -> None:
13
+ """
14
+ Log an informational message to the logs panel.
15
+
16
+ Args:
17
+ message: The information message to log
18
+ """
19
+ log_event = AgentEvent(event_type="log_message", sender=sender, content={"text": message, "level": "info"})
20
+
21
+ await self.connection_manager.broadcast_event(log_event, self.workspace_id, self.thread_id)
22
+
23
+ async def log_error(self, message: str, sender: str = "agent") -> None:
24
+ """
25
+ Log an error message to the logs panel.
26
+
27
+ Args:
28
+ message: The error message to log
29
+ """
30
+ log_event = AgentEvent(event_type="log_message", sender=sender, content={"text": message, "level": "error"})
31
+ await self.connection_manager.broadcast_event(log_event, self.workspace_id, self.thread_id)
32
+
33
+ async def log_warning(self, message: str, sender: str = "agent") -> None:
34
+ """
35
+ Log a warning message to the logs panel.
36
+
37
+ Args:
38
+ message: The warning message to log
39
+ """
40
+ log_event = AgentEvent(event_type="log_message", sender=sender, content={"text": message, "level": "warning"})
41
+ await self.connection_manager.broadcast_event(log_event, self.workspace_id, self.thread_id)
@@ -0,0 +1,81 @@
1
+ """History compression: summarize a conversation when it outgrows the context window."""
2
+
3
+ import logging
4
+ from typing import Awaitable, Callable, Optional
5
+
6
+ from .conversation import Conversation
7
+ from kolega_code.llm.models import Message, MessageHistory, TextBlock
8
+ from .prompts import (
9
+ COMPRESSION_SUMMARY_SYSTEM_PROMPT,
10
+ COMPRESSION_SUMMARY_USER_PROMPT_TEMPLATE,
11
+ )
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ LogCallback = Callable[[str], Awaitable[None]]
16
+
17
+
18
+ class HistoryCompressor:
19
+ """Summarizes a conversation non-destructively when it crosses the budget threshold."""
20
+
21
+ MIN_MESSAGES_TO_COMPRESS = 5
22
+
23
+ def __init__(self, threshold: float = 0.8) -> None:
24
+ # Fraction of the model context window above which compression kicks in
25
+ self.threshold = threshold
26
+
27
+ def over_budget(self, input_tokens: int, model_context_length: int) -> bool:
28
+ return input_tokens > model_context_length * self.threshold
29
+
30
+ async def summarize(
31
+ self,
32
+ conversation: Conversation,
33
+ *,
34
+ llm,
35
+ model: str,
36
+ max_completion_tokens: int,
37
+ temperature: float,
38
+ thinking,
39
+ on_info: Optional[LogCallback] = None,
40
+ on_error: Optional[LogCallback] = None,
41
+ ) -> bool:
42
+ """
43
+ Non-destructively summarize the conversation and mark a compression boundary.
44
+
45
+ Returns True if a summary was recorded.
46
+ """
47
+ if not conversation.history or len(conversation.history) < self.MIN_MESSAGES_TO_COMPRESS:
48
+ return False
49
+
50
+ if on_info:
51
+ await on_info("Compressing message history...")
52
+
53
+ try:
54
+ conversation_markdown = conversation.history.get_markdown_conversation()
55
+ user_prompt_filled = COMPRESSION_SUMMARY_USER_PROMPT_TEMPLATE.replace("{HISTORY}", conversation_markdown)
56
+
57
+ messages = MessageHistory([Message(role="user", content=[TextBlock(text=user_prompt_filled)])])
58
+ system_message = Message(role="system", content=[TextBlock(text=COMPRESSION_SUMMARY_SYSTEM_PROMPT)])
59
+
60
+ response = await llm.generate(
61
+ messages=messages,
62
+ system=system_message,
63
+ temperature=temperature,
64
+ model=model,
65
+ max_completion_tokens=max_completion_tokens,
66
+ thinking=thinking,
67
+ )
68
+
69
+ summary = response.get_text_content()
70
+ conversation.record_compression(Message(role="user", content=[TextBlock(text=summary)]))
71
+
72
+ if on_info:
73
+ await on_info("Message history compressed (non-destructive).")
74
+ return True
75
+
76
+ except Exception as e:
77
+ if on_error:
78
+ await on_error(f"Failed to compress message history: {str(e)}")
79
+ else:
80
+ logger.error("Failed to compress message history: %s", e)
81
+ return False
@@ -0,0 +1,112 @@
1
+ """AgentContext: everything an agent needs, grouped by concern.
2
+
3
+ Replaces the long flat constructor signature on BaseAgent. Hosts build one
4
+ AgentContext and hand it to any agent class; the legacy keyword signature
5
+ remains supported on BaseAgent and converts to an AgentContext internally.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ from langfuse import Langfuse
13
+
14
+ from kolega_code.config import AgentConfig
15
+ from kolega_code.events import AgentConnectionManager
16
+ from kolega_code.llm.client import LLMClient
17
+ from kolega_code.llm.instrumented_client import InstrumentedLLMClient
18
+ from .prompt_provider import AgentMode, PromptExtension, PromptProvider
19
+ from kolega_code.services.base import BrowserManager, TerminalManager
20
+ from kolega_code.services.browser import PlaywrightBrowserManager
21
+ from kolega_code.services.file_system import FileSystem, LocalFileSystem
22
+ from kolega_code.services.terminal import LocalTerminalManager
23
+
24
+
25
+ @dataclass
26
+ class WorkspaceInfo:
27
+ """Identity and content of the workspace the agent operates in."""
28
+
29
+ project_path: Path
30
+ workspace_id: str
31
+ thread_id: str
32
+ project_template_slug: Optional[str] = None
33
+ protected_files: List[str] = field(default_factory=list)
34
+ env_var_descriptions: Dict[str, str] = field(default_factory=dict)
35
+ memories: List[str] = field(default_factory=list)
36
+
37
+ def __post_init__(self) -> None:
38
+ if isinstance(self.project_path, str):
39
+ self.project_path = Path(self.project_path)
40
+
41
+
42
+ @dataclass
43
+ class AgentServices:
44
+ """The environment abstractions the agent works through."""
45
+
46
+ filesystem: FileSystem
47
+ terminal_manager: TerminalManager
48
+ browser_manager: BrowserManager
49
+
50
+ @classmethod
51
+ def local(cls, workspace: WorkspaceInfo, connection_manager: AgentConnectionManager) -> "AgentServices":
52
+ """Default local-machine services rooted at the workspace project path."""
53
+ return cls(
54
+ filesystem=LocalFileSystem(root_path=workspace.project_path),
55
+ terminal_manager=LocalTerminalManager(workspace.workspace_id, workspace.thread_id, connection_manager),
56
+ browser_manager=PlaywrightBrowserManager(),
57
+ )
58
+
59
+
60
+ @dataclass
61
+ class Telemetry:
62
+ """Observability and usage-recording hooks provided by the host."""
63
+
64
+ langfuse_client: Optional[Langfuse] = None
65
+ user_id: Optional[str] = None
66
+ user_email: Optional[str] = None
67
+ usage_recorder: Optional[Any] = None
68
+ sub_agent_recorder: Optional[Any] = None
69
+
70
+
71
+ @dataclass
72
+ class AgentContext:
73
+ """Everything an agent needs to run, grouped by concern."""
74
+
75
+ workspace: WorkspaceInfo
76
+ config: AgentConfig
77
+ connection_manager: AgentConnectionManager
78
+ services: AgentServices
79
+ telemetry: Telemetry = field(default_factory=Telemetry)
80
+ agent_mode: Optional[AgentMode] = None
81
+ prompt_provider: Optional[PromptProvider] = None
82
+ prompt_extensions: List[PromptExtension] = field(default_factory=list)
83
+ tool_extensions: List[Any] = field(default_factory=list)
84
+
85
+ def create_llm_client(self, agent_name: str) -> LLMClient:
86
+ """Create the LLM client, instrumented when a Langfuse client is available."""
87
+ model_config = self.config.long_context_config
88
+
89
+ if self.telemetry.langfuse_client:
90
+ return InstrumentedLLMClient(
91
+ provider=model_config.provider,
92
+ api_key=self.config.get_api_key(model_config.provider),
93
+ max_retries=model_config.rate_limits.max_retries,
94
+ requests_per_minute=model_config.rate_limits.requests_per_minute,
95
+ tokens_per_minute=model_config.rate_limits.tokens_per_minute,
96
+ langfuse_client=self.telemetry.langfuse_client,
97
+ workspace_id=self.workspace.workspace_id,
98
+ thread_id=self.workspace.thread_id,
99
+ agent_type=agent_name,
100
+ environment=self.config.environment,
101
+ user_id=self.telemetry.user_id,
102
+ user_email=self.telemetry.user_email,
103
+ usage_recorder=self.telemetry.usage_recorder,
104
+ )
105
+
106
+ return LLMClient(
107
+ provider=model_config.provider,
108
+ api_key=self.config.get_api_key(model_config.provider),
109
+ max_retries=model_config.rate_limits.max_retries,
110
+ requests_per_minute=model_config.rate_limits.requests_per_minute,
111
+ tokens_per_minute=model_config.rate_limits.tokens_per_minute,
112
+ )