sourcebot 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 (110) hide show
  1. sourcebot/__init__.py +9 -0
  2. sourcebot/__main__.py +17 -0
  3. sourcebot/bus/__init__.py +4 -0
  4. sourcebot/bus/channel_adapter.py +21 -0
  5. sourcebot/bus/event_bus.py +15 -0
  6. sourcebot/bus/message_models.py +33 -0
  7. sourcebot/bus/outbound_dispatcher.py +15 -0
  8. sourcebot/bus/session_manager.py +20 -0
  9. sourcebot/cli/commands/core/__init__.py +3 -0
  10. sourcebot/cli/commands/core/command_line.py +26 -0
  11. sourcebot/cli/commands/init_commands/__init__.py +3 -0
  12. sourcebot/cli/commands/init_commands/init_global_config.py +30 -0
  13. sourcebot/cli/commands/init_commands/init_workspace_config.py +18 -0
  14. sourcebot/cli/commands/run_commands/__init__.py +3 -0
  15. sourcebot/cli/commands/run_commands/command_line_tool.py +345 -0
  16. sourcebot/cli/commands/run_commands/safe_runner.py +47 -0
  17. sourcebot/cli/main.py +28 -0
  18. sourcebot/config/__init__.py +15 -0
  19. sourcebot/config/base.py +13 -0
  20. sourcebot/config/config_manager.py +367 -0
  21. sourcebot/config/exceptions.py +4 -0
  22. sourcebot/config/global_config.py +55 -0
  23. sourcebot/config/provider_config.py +62 -0
  24. sourcebot/config/workspace_config.py +106 -0
  25. sourcebot/context/__init__.py +5 -0
  26. sourcebot/context/context_builder.py +78 -0
  27. sourcebot/context/identity.py +19 -0
  28. sourcebot/context/message_builder.py +154 -0
  29. sourcebot/context/skill/__init__.py +7 -0
  30. sourcebot/context/skill/skill.py +11 -0
  31. sourcebot/context/skill/skill_context.py +10 -0
  32. sourcebot/context/skill/skill_loader.py +57 -0
  33. sourcebot/context/skill/skill_metadata.py +27 -0
  34. sourcebot/context/skill/skill_requirements.py +25 -0
  35. sourcebot/context/skill/skill_summary.py +31 -0
  36. sourcebot/conversation/__init__.py +2 -0
  37. sourcebot/conversation/service.py +191 -0
  38. sourcebot/docker_sandbox/__init__.py +3 -0
  39. sourcebot/docker_sandbox/docker_sandbox.py +113 -0
  40. sourcebot/llm/__init__.py +3 -0
  41. sourcebot/llm/anthropic/__init__.py +2 -0
  42. sourcebot/llm/anthropic/adapter.py +30 -0
  43. sourcebot/llm/anthropic/anthropic_llm_client.py +38 -0
  44. sourcebot/llm/anthropic/converter.py +59 -0
  45. sourcebot/llm/core/adapter.py +16 -0
  46. sourcebot/llm/core/client.py +16 -0
  47. sourcebot/llm/core/delta.py +12 -0
  48. sourcebot/llm/core/message.py +53 -0
  49. sourcebot/llm/core/message_converter.py +33 -0
  50. sourcebot/llm/core/response.py +30 -0
  51. sourcebot/llm/core/tool.py +7 -0
  52. sourcebot/llm/core/tool_converter.py +30 -0
  53. sourcebot/llm/core/tool_delta_aggregator.py +38 -0
  54. sourcebot/llm/llm_client_factory.py +13 -0
  55. sourcebot/llm/openai/__init__.py +2 -0
  56. sourcebot/llm/openai/adapter.py +27 -0
  57. sourcebot/llm/openai/converter.py +53 -0
  58. sourcebot/llm/openai/openai_llm_client.py +47 -0
  59. sourcebot/logging/__init__.py +3 -0
  60. sourcebot/logging/setup.py +33 -0
  61. sourcebot/memory/__init__.py +5 -0
  62. sourcebot/memory/file_store.py +23 -0
  63. sourcebot/memory/llm_consolidator.py +79 -0
  64. sourcebot/memory/service.py +116 -0
  65. sourcebot/memory/window_policy.py +36 -0
  66. sourcebot/prompt/__init__.py +4 -0
  67. sourcebot/prompt/deeomposer_prompt.py +420 -0
  68. sourcebot/prompt/identity_prompt.py +98 -0
  69. sourcebot/prompt/subagent_prompt.py +25 -0
  70. sourcebot/runtime/__init__.py +3 -0
  71. sourcebot/runtime/agent/__init__.py +3 -0
  72. sourcebot/runtime/agent/agent.py +130 -0
  73. sourcebot/runtime/agent/agent_factory.py +83 -0
  74. sourcebot/runtime/dag/planner/__init__.py +3 -0
  75. sourcebot/runtime/dag/planner/dag_planner.py +26 -0
  76. sourcebot/runtime/dag/planner/execution_scheduler.py +35 -0
  77. sourcebot/runtime/dag/planner/parallelism_optimizer.py +44 -0
  78. sourcebot/runtime/dag/planner/task_decomposer.py +37 -0
  79. sourcebot/runtime/dag/scheduler/__init__.py +3 -0
  80. sourcebot/runtime/dag/scheduler/dag_scheduler.py +319 -0
  81. sourcebot/runtime/dag/scheduler/retry_policy.py +27 -0
  82. sourcebot/runtime/dag/scheduler/run_store.py +58 -0
  83. sourcebot/runtime/dag/scheduler/state_store.py +40 -0
  84. sourcebot/runtime/dag/scheduler/task_graph.py +29 -0
  85. sourcebot/runtime/init_system.py +182 -0
  86. sourcebot/runtime/tool_executor.py +30 -0
  87. sourcebot/security/policy.py +23 -0
  88. sourcebot/session/__init__.py +4 -0
  89. sourcebot/session/jsonl_repository.py +142 -0
  90. sourcebot/session/repository.py +19 -0
  91. sourcebot/session/service.py +44 -0
  92. sourcebot/session/session.py +53 -0
  93. sourcebot/storage/__init__.py +3 -0
  94. sourcebot/storage/rules_loader.py +72 -0
  95. sourcebot/storage/skill_storage.py +51 -0
  96. sourcebot/tools/__init__.py +7 -0
  97. sourcebot/tools/base.py +182 -0
  98. sourcebot/tools/registry.py +81 -0
  99. sourcebot/tools/rule_detail.py +70 -0
  100. sourcebot/tools/rule_list.py +57 -0
  101. sourcebot/tools/shell.py +93 -0
  102. sourcebot/tools/skill_detail.py +61 -0
  103. sourcebot/tools/skill_list.py +68 -0
  104. sourcebot/utils/__init__.py +2 -0
  105. sourcebot/utils/output.py +79 -0
  106. sourcebot-0.1.0.dist-info/METADATA +318 -0
  107. sourcebot-0.1.0.dist-info/RECORD +110 -0
  108. sourcebot-0.1.0.dist-info/WHEEL +5 -0
  109. sourcebot-0.1.0.dist-info/entry_points.txt +2 -0
  110. sourcebot-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,13 @@
1
+ # src/sourcebot/config/base.py
2
+ from pydantic import BaseModel, ConfigDict
3
+ from typing import Any, Dict
4
+
5
+
6
+ class Base(BaseModel):
7
+ """Basic configuration class"""
8
+ model_config = ConfigDict(
9
+ extra = "forbid",
10
+ validate_assignment = True,
11
+ arbitrary_types_allowed = False,
12
+ populate_by_name = True,
13
+ )
@@ -0,0 +1,367 @@
1
+ # sourcebot/config/config_manager.py
2
+ import json
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Optional, Dict, Any, Union
6
+ from datetime import datetime
7
+
8
+ from .exceptions import ConfigError
9
+ from .global_config import GlobalConfig
10
+ from .workspace_config import WorkspaceConfig, ModelConfig
11
+ from .provider_config import ProvidersConfig
12
+
13
+
14
+ class ConfigManager:
15
+ """
16
+ Configuration Manager - Separate global configuration and workspace configuration
17
+ """
18
+
19
+ def __init__(self, app_name: str = "sourcebot"):
20
+ """
21
+ Initialize Configuration Manager
22
+
23
+ Args:
24
+ app_name: Application Name
25
+ """
26
+ self.app_name = app_name
27
+ self.app_root_path = Path.home() / f".{app_name}"
28
+ self.global_config_path = self.app_root_path / "config.json"
29
+ self.workspace_config_name = f"{app_name}.workspace.json"
30
+
31
+ self.app_root_path.mkdir(parents=True, exist_ok=True)
32
+
33
+ self._global_config: Optional[GlobalConfig] = None
34
+ self._workspace_config: Optional[WorkspaceConfig] = None
35
+ self._current_workspace_path: Optional[Path] = None
36
+
37
+ # Global Configuration Management
38
+
39
+ def init_global_config(self, force: bool = False) -> GlobalConfig:
40
+ """
41
+ Initialize global configuration (only once)
42
+
43
+ Args:
44
+ force: Whether to force re-initialization
45
+
46
+ Returns:
47
+ Global configuration object
48
+ """
49
+ if not force and self.global_config_path.exists():
50
+ return self.load_global_config()
51
+
52
+ config = GlobalConfig()
53
+
54
+ self.save_global_config(config)
55
+ print(f"✅ Global configuration has been initialized: {self.global_config_path}")
56
+
57
+ self._global_config = config
58
+ return config
59
+
60
+ def load_global_config(self) -> GlobalConfig:
61
+ """
62
+ Load global configuration
63
+
64
+ Returns:
65
+ Global configuration object
66
+ """
67
+ if self._global_config is not None:
68
+ return self._global_config
69
+
70
+ if not self.global_config_path.exists():
71
+ return self.init_global_config()
72
+
73
+ try:
74
+ with open(self.global_config_path, 'r', encoding='utf-8') as f:
75
+ data = json.load(f)
76
+
77
+ config = GlobalConfig.model_validate(data)
78
+ self._global_config = config
79
+ return config
80
+
81
+ except Exception as e:
82
+ raise ConfigError(f"Failed to load global configuration: {e}")
83
+
84
+ def save_global_config(self, config: Optional[GlobalConfig] = None):
85
+ """
86
+ Save global configuration
87
+
88
+ Args:
89
+ config: Configuration object; saves cached configuration when None is selected.
90
+ """
91
+ config = config or self._global_config
92
+ if config is None:
93
+ raise ConfigError("No global configuration can be saved")
94
+
95
+ config.update_timestamp()
96
+
97
+ try:
98
+ self.global_config_path.parent.mkdir(parents=True, exist_ok=True)
99
+
100
+ with open(self.global_config_path, 'w', encoding='utf-8') as f:
101
+ json.dump(config.model_dump(exclude_none=True), f, indent=2, ensure_ascii=False)
102
+
103
+ self._global_config = config
104
+ print(f"✅ Global configuration has been saved")
105
+
106
+ except Exception as e:
107
+ raise ConfigError(f"Failed to save global configuration: {e}")
108
+
109
+ # Provider Configuration Management
110
+
111
+ def set_provider_config(self, provider: str, api_key: str, api_base: Optional[str] = None, **kwargs):
112
+ """
113
+ Configure Provider
114
+
115
+ Args:
116
+ Provider: Provider name
117
+ API_key: API key
118
+ API_base: API base URL
119
+ **Kwargs: Other configurations
120
+ """
121
+ global_config = self.load_global_config()
122
+
123
+ provider_config = getattr(global_config.providers, provider, None)
124
+ if provider_config is None:
125
+ raise ConfigError(f"Unsupported providers: {provider}")
126
+
127
+ provider_config.api_key = api_key
128
+ if api_base:
129
+ provider_config.api_base = api_base
130
+ for key, value in kwargs.items():
131
+ if hasattr(provider_config, key):
132
+ setattr(provider_config, key, value)
133
+
134
+ self.save_global_config(global_config)
135
+ print(f"✅ {provider} configuration has been updated")
136
+
137
+ def get_provider_config(self, provider: str):
138
+ """Get provider configuration"""
139
+ global_config = self.load_global_config()
140
+ return getattr(global_config.providers, provider, None)
141
+
142
+ def list_providers(self, only_configured: bool = False) -> Dict[str, Any]:
143
+ """List all providers"""
144
+ global_config = self.load_global_config()
145
+
146
+ if only_configured:
147
+ return {name: config for name, config in global_config.providers if config.is_configured()}
148
+ else:
149
+ return {name: config for name, config in global_config.providers}
150
+
151
+ # Workspace Configuration Management
152
+
153
+ def find_workspace_config(self, start_path: Optional[Path] = None) -> Optional[Path]:
154
+ """
155
+ Locate workspace configuration file
156
+
157
+ Args:
158
+ start_path: Starting path
159
+
160
+ Returns:
161
+ Configuration file path
162
+ """
163
+ if start_path is None:
164
+ start_path = Path.cwd()
165
+
166
+ start_path = start_path.absolute()
167
+
168
+ current = start_path
169
+ while current != current.parent:
170
+ config_file = current / self.workspace_config_name
171
+ if config_file.exists():
172
+ return config_file
173
+ current = current.parent
174
+
175
+ return None
176
+
177
+ def load_workspace_config(self, workspace_path: Optional[Path] = None) -> Optional[WorkspaceConfig]:
178
+ """
179
+ Load workspace configuration
180
+
181
+ Args:
182
+ workspace_path: Workspace path
183
+ Returns:
184
+ Workspace configuration object; returns None if not found.
185
+ """
186
+ config_path = self.find_workspace_config(workspace_path)
187
+ if not config_path:
188
+ return None
189
+
190
+ try:
191
+ with open(config_path, 'r', encoding='utf-8') as f:
192
+ data = json.load(f)
193
+
194
+ config = WorkspaceConfig.model_validate(data)
195
+ self._workspace_config = config
196
+ self._current_workspace_path = config_path.parent
197
+ return config
198
+
199
+ except Exception as e:
200
+ raise ConfigError(f"Failed to load workspace configuration: {e}")
201
+
202
+ def init_workspace_config(self, project_name: str, project_path: Optional[Path] = None,
203
+ default_model: Optional[str] = None) -> WorkspaceConfig:
204
+ """
205
+ Initialize workspace configuration
206
+
207
+ Args:
208
+ project_name: Project name
209
+ project_path: Project path
210
+ default_model: Default model name
211
+ Returns:
212
+ Workspace configuration object
213
+ """
214
+ if project_path is None:
215
+ project_path = Path.cwd()
216
+ else:
217
+ project_path = Path(project_path).expanduser().resolve()
218
+
219
+ project_path.mkdir(parents=True, exist_ok=True)
220
+ config_path = project_path / self.workspace_config_name
221
+ if config_path.exists():
222
+ response = input(f"The workspace configuration already exists: {config_path}\nShould it be overwritten? (y/N): ")
223
+ if response.lower() != 'y':
224
+ return self.load_workspace_config(project_path)
225
+
226
+ config = WorkspaceConfig(
227
+ project_name=project_name,
228
+ project_root=str(project_path)
229
+ )
230
+
231
+ if default_model:
232
+ global_config = self.load_global_config()
233
+ config.add_model(
234
+ name="default",
235
+ provider=global_config.default_provider,
236
+ model=default_model
237
+ )
238
+ config.default_model = "default"
239
+
240
+ self.save_workspace_config(config, project_path)
241
+
242
+ self._workspace_config = config
243
+ self._current_workspace_path = project_path
244
+
245
+ return config
246
+
247
+ def save_workspace_config(self, config: WorkspaceConfig, project_path: Optional[Path] = None):
248
+ """
249
+ Save workspace configuration
250
+ Args:
251
+ config: Workspace configuration
252
+ project_path: Project path
253
+ """
254
+ if project_path is None:
255
+ project_path = Path(config.project_root)
256
+ else:
257
+ project_path = Path(project_path).expanduser().resolve()
258
+
259
+ project_path.mkdir(parents=True, exist_ok=True)
260
+
261
+ config_path = project_path / self.workspace_config_name
262
+
263
+ try:
264
+ with open(config_path, 'w', encoding='utf-8') as f:
265
+ json.dump(config.model_dump(exclude_none=True), f, indent=2, ensure_ascii=False)
266
+
267
+ print(f"✅ Workspace configuration has been saved: {config_path}")
268
+
269
+ except Exception as e:
270
+ raise ConfigError(f"Failed to save workspace configuration: {e}")
271
+
272
+ # Model Configuration Management
273
+
274
+ def add_model_to_workspace(self, name: str, provider: str, model: str,
275
+ workspace_path: Optional[Path] = None, **kwargs):
276
+ """
277
+ Add a model to the workspace configuration
278
+
279
+ Args:
280
+ name: Model name
281
+ provider: Provider name
282
+ model: Model name
283
+ workspace_path: Workspace path
284
+ **kwargs: Other model parameters
285
+ """
286
+ global_config = self.load_global_config()
287
+ provider_config = getattr(global_config.providers, provider, None)
288
+ if not provider_config or not provider_config.is_configured():
289
+ print(f"Warning: Provider {provider} has not set the API key in the global configuration.")
290
+
291
+ workspace_config = self.load_workspace_config(workspace_path)
292
+ if not workspace_config:
293
+ if workspace_path is None:
294
+ workspace_path = Path.cwd()
295
+ workspace_config = self.init_workspace_config(
296
+ project_name=workspace_path.name,
297
+ project_path=workspace_path
298
+ )
299
+
300
+ workspace_config.add_model(name, provider, model, **kwargs)
301
+
302
+ if workspace_config.default_model == "default" and name != "default":
303
+ workspace_config.default_model = name
304
+
305
+ self.save_workspace_config(workspace_config)
306
+ print(f"✅ The model {name} ({provider}/{model}) has been added to the workspace.")
307
+
308
+
309
+ def get_model_for_workspace(self, model_name: Optional[str] = None,
310
+ workspace_path: Optional[Path] = None) -> Dict[str, Any]:
311
+ """
312
+ Retrieves the workspace model configuration (including the global API key)
313
+ Args:
314
+ model_name: Model name; default model is used if None
315
+ workspace_path: Workspace path
316
+ Returns:
317
+ A dictionary containing the complete configuration (model parameters + API key, etc.)
318
+ """
319
+
320
+ workspace_config = self.load_workspace_config(workspace_path)
321
+ if not workspace_config:
322
+ raise ConfigError("Workspace configuration not found, please initialize first.")
323
+
324
+ model_config = workspace_config.get_model_config(model_name)
325
+ if not model_config:
326
+ raise ConfigError(f"The model does not exist: {model_name or workspace_config.default_model}")
327
+
328
+ global_config = self.load_global_config()
329
+ provider_config = global_config.get_provider_config(model_config.provider)
330
+
331
+ if not provider_config or not provider_config.is_configured():
332
+ raise ConfigError(f"The provider {model_config.provider} does not have an API key configured.")
333
+
334
+ result = {
335
+ "provider": model_config.provider,
336
+ "model": model_config.model,
337
+ "api_key": provider_config.api_key,
338
+ "api_base": provider_config.api_base,
339
+ "temperature": model_config.temperature,
340
+ "max_tokens": model_config.max_tokens,
341
+ "top_p": model_config.top_p,
342
+ }
343
+
344
+ if provider_config.extra_headers:
345
+ result["extra_headers"] = provider_config.extra_headers
346
+
347
+ return result
348
+
349
+ # Tools and Methods
350
+
351
+ @property
352
+ def current_workspace(self) -> Optional[WorkspaceConfig]:
353
+ """Get current workspace configuration"""
354
+ if self._workspace_config is None:
355
+ self._workspace_config = self.load_workspace_config()
356
+ return self._workspace_config
357
+
358
+ @property
359
+ def in_workspace(self) -> bool:
360
+ return self.current_workspace is not None
361
+
362
+ def get_workspace_path(self) -> Optional[Path]:
363
+ """Get the current workspace path"""
364
+ if self._current_workspace_path:
365
+ return self._current_workspace_path
366
+ config_path = self.find_workspace_config()
367
+ return config_path.parent if config_path else None
@@ -0,0 +1,4 @@
1
+ # sourcebot/config/exceptions.py
2
+ class ConfigError(Exception):
3
+ """config error"""
4
+ pass
@@ -0,0 +1,55 @@
1
+ # sourcebot/config/global_config.py
2
+ from pathlib import Path
3
+ from pydantic import Field
4
+ from typing import Optional, Dict, Any
5
+ from datetime import datetime
6
+
7
+ from .base import Base
8
+ from .provider_config import ProvidersConfig
9
+
10
+
11
+ class GlobalConfig(Base):
12
+ """Global configuration - initialized only once, saved in ~/.sourcebot/config.json"""
13
+
14
+ version: str = Field(default="1.0.0", description="Configuration version")
15
+ created_at: str = Field(default_factory=lambda: datetime.now().isoformat(), description="Creation time")
16
+ updated_at: str = Field(default_factory=lambda: datetime.now().isoformat(), description="Update time")
17
+
18
+ providers: ProvidersConfig = Field(default_factory=ProvidersConfig, description="LLM provider configuration")
19
+
20
+ default_provider: str = Field(default="dashscope", description="Default provider")
21
+ default_model: str = Field(default="qwen3.5-plus", description="Default model")
22
+
23
+ cache_dir: str = Field(default=str(Path.home() / ".cache" / "sourcebot"), description="Cache directory")
24
+ log_level: str = Field(default="info", description="Log levels")
25
+
26
+ def update_timestamp(self):
27
+ """Update timestamp"""
28
+ self.updated_at = datetime.now().isoformat()
29
+
30
+ def get_provider_config(self, provider_name: str) -> Optional[Any]:
31
+ """Get the configuration of the specified provider"""
32
+ return getattr(self.providers, provider_name, None)
33
+
34
+ def is_provider_configured(self, provider_name: str) -> bool:
35
+ """Check if the provider has been configured"""
36
+ provider = self.get_provider_config(provider_name)
37
+ return provider is not None and provider.is_configured()
38
+
39
+ class Config:
40
+ json_schema_extra = {
41
+ "example": {
42
+ "version": "1.0.0",
43
+ "default_provider": "openai",
44
+ "default_model": "gpt-4",
45
+ "providers": {
46
+ "openai": {
47
+ "api_key": "sk-...",
48
+ "api_base": "https://api.openai.com/v1"
49
+ },
50
+ "anthropic": {
51
+ "api_key": "sk-ant-..."
52
+ }
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,62 @@
1
+ # src/sourcebot/config/provider_config.py
2
+ from pydantic import Field
3
+ from typing import Optional, Dict
4
+ from .base import Base
5
+
6
+
7
+ class ProviderConfig(Base):
8
+ """LLM provider configuration."""
9
+
10
+ api_key: str = Field(default="", description="API key")
11
+ api_base: str = Field(default="", description="API base URL")
12
+ temperature: float = 0.7
13
+ extra_headers: Optional[Dict[str, str]] = Field(default=None, description="Custom request headers")
14
+ timeout: Optional[int] = Field(default=None, description="Timeout (seconds)")
15
+ max_retries: int = Field(default=3, description="Maximum number of retries")
16
+
17
+ def is_configured(self) -> bool:
18
+ """Check if it is configured"""
19
+ return bool(self.api_key)
20
+
21
+
22
+ class ProvidersConfig(Base):
23
+ """Configuration for LLM providers."""
24
+
25
+ custom: ProviderConfig = Field(default_factory=ProviderConfig, description="Custom OpenAI compatible endpoints")
26
+ anthropic: ProviderConfig = Field(default_factory=ProviderConfig, description="Anthropic Claude")
27
+ openai: ProviderConfig = Field(default_factory=ProviderConfig, description="OpenAI")
28
+ openrouter: ProviderConfig = Field(default_factory=ProviderConfig, description="OpenRouter")
29
+
30
+
31
+ deepseek: ProviderConfig = Field(default_factory=ProviderConfig, description="DeepSeek")
32
+ zhipu: ProviderConfig = Field(default_factory=ProviderConfig, description="zhipu")
33
+ dashscope: ProviderConfig = Field(default_factory=ProviderConfig, description="dashscope")
34
+ moonshot: ProviderConfig = Field(default_factory=ProviderConfig, description="Moonshot AI")
35
+ minimax: ProviderConfig = Field(default_factory=ProviderConfig, description="MiniMax")
36
+
37
+ aihubmix: ProviderConfig = Field(default_factory=ProviderConfig, description="AiHubMix API gateway")
38
+ siliconflow: ProviderConfig = Field(default_factory=ProviderConfig, description="SiliconFlow")
39
+ volcengine: ProviderConfig = Field(default_factory=ProviderConfig, description="volcengine")
40
+
41
+
42
+ groq: ProviderConfig = Field(default_factory=ProviderConfig, description="Groq")
43
+ vllm: ProviderConfig = Field(default_factory=ProviderConfig, description="vLLM")
44
+ gemini: ProviderConfig = Field(default_factory=ProviderConfig, description="Google Gemini")
45
+
46
+ # OAuth
47
+ openai_codex: ProviderConfig = Field(default_factory=ProviderConfig, description="OpenAI Codex (OAuth)")
48
+ github_copilot: ProviderConfig = Field(default_factory=ProviderConfig, description="Github Copilot (OAuth)")
49
+
50
+ def get_provider(self, name: str) -> ProviderConfig:
51
+ """Retrieve provider configuration for a specified name"""
52
+ return getattr(self, name, None)
53
+
54
+ def list_configured(self) -> list[str]:
55
+ """List all configured providers"""
56
+ return [name for name, provider in self if provider.is_configured()]
57
+
58
+ def __iter__(self):
59
+ """Iterate through all providers"""
60
+ for field_name in self.model_fields:
61
+ if field_name not in ['model_config']:
62
+ yield field_name, getattr(self, field_name)
@@ -0,0 +1,106 @@
1
+ # sourcebot/config/workspace_config.py
2
+ from pathlib import Path
3
+ from pydantic import Field, field_validator
4
+ from typing import Optional, List, Dict, Any
5
+ from datetime import datetime
6
+
7
+ from .base import Base
8
+
9
+
10
+ class ModelConfig(Base):
11
+ """Model configuration"""
12
+ provider: str = Field(..., description="Provider name")
13
+ model: str = Field(..., description="Model name")
14
+ temperature: float = Field(default=0.7, ge=0.0, le=2.0, description="Temperature")
15
+ max_tokens: int = Field(default=2000, gt=0, description="Maximum tokens")
16
+ top_p: float = Field(default=1.0, ge=0.0, le=1.0, description="Top-p")
17
+
18
+ @field_validator('provider')
19
+ def validate_provider(cls, v):
20
+ """Validate provider name"""
21
+ # Only basic validation here, specific availability is determined by global configuration
22
+ if not v or not isinstance(v, str):
23
+ raise ValueError("Provider name must be a non-empty string")
24
+ return v.lower()
25
+
26
+
27
+ class WorkspaceConfig(Base):
28
+ """Workspace configuration - saved independently for each project"""
29
+
30
+ # Project information
31
+ project_name: str = Field(..., description="Project name")
32
+ project_root: str = Field(..., description="Project root directory")
33
+ project_rules: str = Field("", description="Project rules keywords")
34
+ project_skill: str = Field("", description="Project skill keywords")
35
+ docker_image: str = Field("python:3.11-slim", description="docker image")
36
+ created_at: str = Field(default_factory=lambda: datetime.now().isoformat(), description="Creation time")
37
+
38
+ # Model configuration
39
+ models: Dict[str, ModelConfig] = Field(
40
+ default_factory=lambda: {
41
+ "main_agent": ModelConfig(
42
+ provider = "dashscope",
43
+ model = "qwen3.5-plus",
44
+ temperature = 0.7,
45
+ max_tokens = 2000,
46
+ top_p = 1.0
47
+ ),
48
+ "sub_agent": ModelConfig(
49
+ provider = "dashscope",
50
+ model = "qwen3.5-plus",
51
+ temperature = 0.7,
52
+ max_tokens = 2000,
53
+ top_p = 1.0
54
+ )
55
+ },
56
+
57
+ )
58
+
59
+ # Workspace settings
60
+ context_files: List[str] = Field(default_factory=list, description="Context file patterns")
61
+ ignore_patterns: List[str] = Field(
62
+ default_factory=lambda: ["**/__pycache__", "**/.git", "**/.idea", "**/*.pyc"],
63
+ description="Ignore file patterns"
64
+ )
65
+ max_file_size: int = Field(default=10 * 1024 * 1024, description="Maximum file size (bytes)")
66
+
67
+ # Custom settings
68
+ settings: Dict[str, Any] = Field(default_factory=dict, description="Custom settings")
69
+
70
+ def get_model_config(self, model_name: Optional[str] = None) -> Optional[ModelConfig]:
71
+ """Get model configuration"""
72
+ if model_name is None:
73
+ model_name = self.default_model
74
+ return self.models.get(model_name)
75
+
76
+ def add_model(self, name: str, provider: str, model: str, **kwargs):
77
+ """Add model configuration"""
78
+ self.models[name] = ModelConfig(provider=provider, model=model, **kwargs)
79
+
80
+ @property
81
+ def root_path(self) -> Path:
82
+ """Get project root directory path"""
83
+ return Path(self.project_root).expanduser().resolve()
84
+
85
+ class Config:
86
+ json_schema_extra = {
87
+ "example": {
88
+ "project_name": "my-ai-project",
89
+ "project_root": "/home/user/projects/my-ai-project",
90
+ "project_rules": "python",
91
+ "project_skill": "python",
92
+ "docker_image": "python:3.11-slim",
93
+ "models": {
94
+ "gpt4": {
95
+ "provider": "openai",
96
+ "model": "gpt-4",
97
+ "temperature": 0.7
98
+ },
99
+ "claude": { # TODO: Anthropic compatible
100
+ "provider": "anthropic",
101
+ "model": "claude-3-opus-20240229",
102
+ "temperature": 0.5
103
+ }
104
+ }
105
+ }
106
+ }
@@ -0,0 +1,5 @@
1
+ from sourcebot.context.context_builder import ContextBuilder
2
+ from sourcebot.context.identity import get_identity
3
+ from sourcebot.context.message_builder import MessageBuilder
4
+ # from sourcebot.context.rules_loader import RulesLoader
5
+ __all__ = ["ContextBuilder", "get_identity", "MessageBuilder"]