crabcode 0.1.6__py3-none-win_amd64.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.
- crabcode-0.1.6.dist-info/METADATA +63 -0
- crabcode-0.1.6.dist-info/RECORD +205 -0
- crabcode-0.1.6.dist-info/WHEEL +4 -0
- crabcode-0.1.6.dist-info/entry_points.txt +2 -0
- crabcode_cli/__init__.py +7 -0
- crabcode_cli/__main__.py +4 -0
- crabcode_cli/_native/crabcode-computer-use-host.exe +0 -0
- crabcode_cli/_native/runtime.json +6 -0
- crabcode_cli/app.py +776 -0
- crabcode_cli/banner.py +39 -0
- crabcode_cli/computer_use.py +286 -0
- crabcode_cli/model_manager.py +649 -0
- crabcode_cli/model_picker.py +168 -0
- crabcode_cli/pipe.py +148 -0
- crabcode_cli/render/__init__.py +0 -0
- crabcode_cli/render/messages.py +19 -0
- crabcode_cli/repl.py +4841 -0
- crabcode_cli/session_picker.py +239 -0
- crabcode_core/__init__.py +7 -0
- crabcode_core/_version.py +3 -0
- crabcode_core/_windows_process.py +68 -0
- crabcode_core/agent_manager.py +1731 -0
- crabcode_core/api/__init__.py +20 -0
- crabcode_core/api/anthropic_adapter.py +648 -0
- crabcode_core/api/azure_adapter.py +69 -0
- crabcode_core/api/base.py +233 -0
- crabcode_core/api/codex_adapter.py +1327 -0
- crabcode_core/api/error_logging.py +132 -0
- crabcode_core/api/gemini_adapter.py +26 -0
- crabcode_core/api/model_info.py +257 -0
- crabcode_core/api/network.py +62 -0
- crabcode_core/api/ollama_adapter.py +27 -0
- crabcode_core/api/openai_adapter.py +323 -0
- crabcode_core/api/registry.py +58 -0
- crabcode_core/command_line.py +18 -0
- crabcode_core/compact/__init__.py +17 -0
- crabcode_core/compact/compact.py +514 -0
- crabcode_core/compact/context_tokens.py +165 -0
- crabcode_core/computer_use_mode.py +38 -0
- crabcode_core/computer_use_observation.py +144 -0
- crabcode_core/config/__init__.py +3 -0
- crabcode_core/config/claudemd.py +61 -0
- crabcode_core/config/manager.py +237 -0
- crabcode_core/config/model_catalog.py +256 -0
- crabcode_core/events.py +4896 -0
- crabcode_core/file_lock.py +81 -0
- crabcode_core/filesystem.py +22 -0
- crabcode_core/goal.py +149 -0
- crabcode_core/hooks/__init__.py +5 -0
- crabcode_core/hooks/manager.py +290 -0
- crabcode_core/image_io.py +111 -0
- crabcode_core/io_worker.py +211 -0
- crabcode_core/logging_utils.py +168 -0
- crabcode_core/lsp/__init__.py +6 -0
- crabcode_core/lsp/client.py +786 -0
- crabcode_core/lsp/diagnostics.py +150 -0
- crabcode_core/lsp/manager.py +583 -0
- crabcode_core/mcp/__init__.py +4 -0
- crabcode_core/mcp/client.py +169 -0
- crabcode_core/mcp/config.py +57 -0
- crabcode_core/path_validation.py +34 -0
- crabcode_core/paths.py +24 -0
- crabcode_core/peer/__init__.py +10 -0
- crabcode_core/peer/runtime.py +518 -0
- crabcode_core/permissions/__init__.py +7 -0
- crabcode_core/permissions/ai_reviewer.py +184 -0
- crabcode_core/permissions/manager.py +187 -0
- crabcode_core/plan/__init__.py +5 -0
- crabcode_core/plan/executor.py +270 -0
- crabcode_core/plan/types.py +169 -0
- crabcode_core/prompts/__init__.py +0 -0
- crabcode_core/prompts/blocks.py +10 -0
- crabcode_core/prompts/context.py +229 -0
- crabcode_core/prompts/library.py +92 -0
- crabcode_core/prompts/profile.py +79 -0
- crabcode_core/prompts/system.py +356 -0
- crabcode_core/prompts/templates.py +104 -0
- crabcode_core/query/__init__.py +0 -0
- crabcode_core/query/computer_use_history.py +83 -0
- crabcode_core/query/loop.py +2446 -0
- crabcode_core/query/retry.py +116 -0
- crabcode_core/schedule/__init__.py +13 -0
- crabcode_core/schedule/manager.py +631 -0
- crabcode_core/schedule/models.py +204 -0
- crabcode_core/schedule/store.py +558 -0
- crabcode_core/schedule/timing.py +287 -0
- crabcode_core/session/__init__.py +12 -0
- crabcode_core/session/export.py +141 -0
- crabcode_core/session/meta_db.py +684 -0
- crabcode_core/session/storage.py +2032 -0
- crabcode_core/session/title_gen.py +52 -0
- crabcode_core/skills/__init__.py +6 -0
- crabcode_core/skills/context.py +23 -0
- crabcode_core/skills/loader.py +214 -0
- crabcode_core/skills/matcher.py +203 -0
- crabcode_core/snapshot/__init__.py +21 -0
- crabcode_core/snapshot/snapshot.py +436 -0
- crabcode_core/snapshot/tracker.py +359 -0
- crabcode_core/subprocess_utils.py +260 -0
- crabcode_core/team/__init__.py +27 -0
- crabcode_core/team/bridge.py +66 -0
- crabcode_core/team/inbox.py +237 -0
- crabcode_core/team/manager.py +1660 -0
- crabcode_core/team/message_bus.py +821 -0
- crabcode_core/team/models.py +191 -0
- crabcode_core/team/recovery.py +192 -0
- crabcode_core/text_io.py +52 -0
- crabcode_core/tools/__init__.py +112 -0
- crabcode_core/tools/_input_helpers.py +39 -0
- crabcode_core/tools/_input_schema.py +130 -0
- crabcode_core/tools/agent.py +413 -0
- crabcode_core/tools/apply_patch.py +587 -0
- crabcode_core/tools/ask_user.py +122 -0
- crabcode_core/tools/bash.py +154 -0
- crabcode_core/tools/browser.py +678 -0
- crabcode_core/tools/checklist.py +241 -0
- crabcode_core/tools/checkpoint.py +78 -0
- crabcode_core/tools/computer_use.py +599 -0
- crabcode_core/tools/diff_utils.py +123 -0
- crabcode_core/tools/file_edit.py +240 -0
- crabcode_core/tools/file_read.py +187 -0
- crabcode_core/tools/file_write.py +154 -0
- crabcode_core/tools/glob.py +123 -0
- crabcode_core/tools/goal.py +134 -0
- crabcode_core/tools/grep.py +247 -0
- crabcode_core/tools/image.py +173 -0
- crabcode_core/tools/image_generate.py +115 -0
- crabcode_core/tools/lint.py +388 -0
- crabcode_core/tools/loading.py +187 -0
- crabcode_core/tools/memory.py +456 -0
- crabcode_core/tools/monitor.py +677 -0
- crabcode_core/tools/peer.py +103 -0
- crabcode_core/tools/revert.py +90 -0
- crabcode_core/tools/schedule.py +683 -0
- crabcode_core/tools/skill.py +108 -0
- crabcode_core/tools/switch_mode.py +213 -0
- crabcode_core/tools/team.py +329 -0
- crabcode_core/tools/tool_search.py +64 -0
- crabcode_core/tools/web_search.py +444 -0
- crabcode_core/turn_details.py +64 -0
- crabcode_core/types/__init__.py +95 -0
- crabcode_core/types/config.py +434 -0
- crabcode_core/types/event.py +334 -0
- crabcode_core/types/message.py +263 -0
- crabcode_core/types/tool.py +208 -0
- crabcode_core/usage.py +289 -0
- crabcode_core/utf8_sanitize.py +29 -0
- crabcode_debugger/__init__.py +5 -0
- crabcode_debugger/adapters.py +332 -0
- crabcode_debugger/dap.py +304 -0
- crabcode_debugger/memory.py +1806 -0
- crabcode_debugger/process.py +707 -0
- crabcode_debugger/runtime.py +58 -0
- crabcode_debugger/sessions.py +387 -0
- crabcode_debugger/tool.py +731 -0
- crabcode_gateway/__init__.py +18 -0
- crabcode_gateway/acp/__init__.py +13 -0
- crabcode_gateway/acp/agent.py +1581 -0
- crabcode_gateway/acp/session.py +114 -0
- crabcode_gateway/acp/transport.py +39 -0
- crabcode_gateway/acp/types.py +90 -0
- crabcode_gateway/adapter.py +30 -0
- crabcode_gateway/auth.py +139 -0
- crabcode_gateway/computer_use.py +364 -0
- crabcode_gateway/document_engine.py +637 -0
- crabcode_gateway/document_worker.py +301 -0
- crabcode_gateway/event_bus.py +655 -0
- crabcode_gateway/grpc/__init__.py +0 -0
- crabcode_gateway/grpc/crabcode_pb2.py +70 -0
- crabcode_gateway/grpc/crabcode_pb2_grpc.py +474 -0
- crabcode_gateway/grpc/proto/crabcode.proto +160 -0
- crabcode_gateway/grpc/server.py +765 -0
- crabcode_gateway/middleware.py +344 -0
- crabcode_gateway/protocol.py +17 -0
- crabcode_gateway/routes/__init__.py +0 -0
- crabcode_gateway/routes/agent.py +392 -0
- crabcode_gateway/routes/auth.py +150 -0
- crabcode_gateway/routes/computer_use.py +101 -0
- crabcode_gateway/routes/config.py +1698 -0
- crabcode_gateway/routes/document.py +1810 -0
- crabcode_gateway/routes/event.py +3801 -0
- crabcode_gateway/routes/git_review.py +232 -0
- crabcode_gateway/routes/health.py +14 -0
- crabcode_gateway/routes/peer.py +78 -0
- crabcode_gateway/routes/permission.py +121 -0
- crabcode_gateway/routes/schedule.py +300 -0
- crabcode_gateway/routes/session.py +1496 -0
- crabcode_gateway/routes/snapshot.py +211 -0
- crabcode_gateway/routes/tasks.py +318 -0
- crabcode_gateway/routes/team.py +392 -0
- crabcode_gateway/routes/usage.py +49 -0
- crabcode_gateway/routes/workspace.py +299 -0
- crabcode_gateway/safe_file.py +63 -0
- crabcode_gateway/schemas.py +1820 -0
- crabcode_gateway/server.py +514 -0
- crabcode_gateway/session_registry.py +36 -0
- crabcode_gateway/task_registry.py +708 -0
- crabcode_gateway/turn_details.py +60 -0
- crabcode_search/__init__.py +11 -0
- crabcode_search/background.py +368 -0
- crabcode_search/chunker.py +517 -0
- crabcode_search/embedder.py +497 -0
- crabcode_search/indexer.py +215 -0
- crabcode_search/store.py +222 -0
- crabcode_search/tool.py +268 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""Configuration types for CrabCode settings."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
DEFAULT_FILESYSTEM_TIMEOUT = 3600.0
|
|
12
|
+
DEFAULT_COMPACT_BUFFER_TOKENS = 20_000
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
ReasoningEffort = Literal[
|
|
16
|
+
"none", "minimal", "low", "medium", "high", "xhigh", "max"
|
|
17
|
+
]
|
|
18
|
+
REASONING_EFFORT_LEVELS: tuple[ReasoningEffort, ...] = (
|
|
19
|
+
"none",
|
|
20
|
+
"minimal",
|
|
21
|
+
"low",
|
|
22
|
+
"medium",
|
|
23
|
+
"high",
|
|
24
|
+
"xhigh",
|
|
25
|
+
"max",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PermissionRule(BaseModel):
|
|
30
|
+
"""A single permission rule for tool access control."""
|
|
31
|
+
tool: str
|
|
32
|
+
path: str | None = None
|
|
33
|
+
command: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AiReviewSettings(BaseModel):
|
|
37
|
+
"""Settings for AI-assisted tool permission review."""
|
|
38
|
+
|
|
39
|
+
model: str | None = None
|
|
40
|
+
decisions: list[Literal["allow", "ask", "deny"]] = Field(
|
|
41
|
+
default_factory=lambda: ["allow", "ask"]
|
|
42
|
+
)
|
|
43
|
+
fallback: Literal["allow", "ask", "deny"] = "ask"
|
|
44
|
+
timeout: int = 30
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PermissionsSettings(BaseModel):
|
|
48
|
+
allow: list[PermissionRule] = Field(default_factory=list)
|
|
49
|
+
deny: list[PermissionRule] = Field(default_factory=list)
|
|
50
|
+
ask: list[PermissionRule] = Field(default_factory=list)
|
|
51
|
+
# None means "not explicitly configured". PermissionManager still falls
|
|
52
|
+
# back to ask mode, while config layering can distinguish an omitted value
|
|
53
|
+
# from an explicit override.
|
|
54
|
+
default_mode: str | None = None
|
|
55
|
+
additional_directories: list[str] = Field(default_factory=list)
|
|
56
|
+
# Deprecated compatibility alias for default_mode="run_everything".
|
|
57
|
+
run_everything: bool = False
|
|
58
|
+
ai_review: AiReviewSettings = Field(default_factory=AiReviewSettings)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class McpServerConfig(BaseModel):
|
|
62
|
+
command: list[str] | None = None
|
|
63
|
+
url: str | None = None
|
|
64
|
+
env: dict[str, str] = Field(default_factory=dict)
|
|
65
|
+
disabled: bool = False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class LspServerConfig(BaseModel):
|
|
69
|
+
"""Configuration for a single LSP server.
|
|
70
|
+
|
|
71
|
+
Users can customize built-in LSP servers or add new ones in settings.json,
|
|
72
|
+
following the same pattern as OpenCode's opencode.json LSP configuration.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
command: list[str] = Field(default_factory=list)
|
|
76
|
+
extensions: list[str] = Field(default_factory=list)
|
|
77
|
+
env: dict[str, str] = Field(default_factory=dict)
|
|
78
|
+
initialization: dict[str, Any] = Field(default_factory=dict)
|
|
79
|
+
disabled: bool = False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ApiConfig(BaseModel):
|
|
83
|
+
"""API backend configuration."""
|
|
84
|
+
# Optional shared configuration entry. This is metadata used while
|
|
85
|
+
# loading ``CrabCodeSettings`` and is retained on the resolved config so
|
|
86
|
+
# callers can still identify its source group.
|
|
87
|
+
group: str | None = None
|
|
88
|
+
provider: str | None = None # anthropic | openai | codex | ollama | gemini | azure | bedrock | vertex | router
|
|
89
|
+
model: str | None = None
|
|
90
|
+
base_url: str | None = None
|
|
91
|
+
network_mode: Literal["inherit", "direct", "proxy"] = "inherit"
|
|
92
|
+
proxy_url: str | None = None
|
|
93
|
+
|
|
94
|
+
@field_validator("proxy_url")
|
|
95
|
+
@classmethod
|
|
96
|
+
def validate_proxy_url(cls, value: str | None) -> str | None:
|
|
97
|
+
if not value:
|
|
98
|
+
return None
|
|
99
|
+
from urllib.parse import urlsplit
|
|
100
|
+
try:
|
|
101
|
+
parsed = urlsplit(value)
|
|
102
|
+
valid = parsed.scheme in {"http", "https"} and bool(parsed.hostname) and parsed.port != 0
|
|
103
|
+
valid = valid and not parsed.username and not parsed.password
|
|
104
|
+
except ValueError:
|
|
105
|
+
valid = False
|
|
106
|
+
if not valid:
|
|
107
|
+
raise ValueError("Use an HTTP(S) proxy URL without embedded credentials")
|
|
108
|
+
return value
|
|
109
|
+
|
|
110
|
+
api_key_env: str | None = None
|
|
111
|
+
codex_auth_path: str | None = None
|
|
112
|
+
# Opt in to native Responses image generation on API-key/custom Codex
|
|
113
|
+
# endpoints. Official Codex OAuth exposes it automatically.
|
|
114
|
+
image_generation_enabled: bool = False
|
|
115
|
+
http_headers: dict[str, str] = Field(default_factory=dict)
|
|
116
|
+
format: str | None = None # anthropic | openai | codex | ollama | gemini | azure (for routers)
|
|
117
|
+
max_tokens: int = 16384
|
|
118
|
+
thinking_enabled: bool = True
|
|
119
|
+
thinking_budget: int = 10000
|
|
120
|
+
pass_reasoning_content: bool = False
|
|
121
|
+
anthropic_stream_transport: Literal["auto", "sdk", "httpx"] = "auto"
|
|
122
|
+
reasoning_effort: ReasoningEffort | None = None
|
|
123
|
+
timeout: int = 300 # seconds, for API calls
|
|
124
|
+
# HTTP request-establishment retries for transport failures and 5xx,
|
|
125
|
+
# excluding 429. This budget is separate from stream reconnects.
|
|
126
|
+
request_max_retries: int = Field(default=4, ge=0, le=100)
|
|
127
|
+
# Stream reconnect budget (default 3, hard cap 100).
|
|
128
|
+
max_retries: int = Field(default=3, ge=0, le=100)
|
|
129
|
+
# Explicit opt-in for a separate unbounded 5s..60s connection retry path.
|
|
130
|
+
unbounded_connection_retries: bool = False
|
|
131
|
+
context_window: int | None = None # override auto-detected context window
|
|
132
|
+
prompt_cache_key: str | None = None # OpenAI Responses API prompt cache routing key
|
|
133
|
+
prompt_caching: Literal["auto", "enabled", "disabled"] = "auto"
|
|
134
|
+
prompt_cache_retention: Literal["in_memory", "24h"] | None = None
|
|
135
|
+
extra_body: dict[str, Any] = Field(default_factory=dict)
|
|
136
|
+
azure_endpoint: str | None = None # Azure OpenAI endpoint URL
|
|
137
|
+
azure_api_version: str | None = None # Azure API version
|
|
138
|
+
azure_deployment: str | None = None # Azure deployment name (if model field is not used for this)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class AgentSettings(BaseModel):
|
|
142
|
+
"""Settings for the built-in Agent (sub-agent) tool."""
|
|
143
|
+
max_turns: int = 10
|
|
144
|
+
timeout: int = 300
|
|
145
|
+
max_output_chars: int = 12000
|
|
146
|
+
stream_send_input_output: bool = False
|
|
147
|
+
max_concurrency: int = 4
|
|
148
|
+
max_depth: int = 2
|
|
149
|
+
max_active_agents_per_run: int = 16
|
|
150
|
+
types: dict[str, "AgentTypeConfig"] = Field(default_factory=dict)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class AgentTypeConfig(BaseModel):
|
|
154
|
+
"""Settings for a specific sub-agent type."""
|
|
155
|
+
model_profile: str | None = None
|
|
156
|
+
allowed_tools: list[str] = Field(default_factory=list)
|
|
157
|
+
prompt: str | None = None
|
|
158
|
+
enable_lsp: bool = True
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class DisplaySettings(BaseModel):
|
|
162
|
+
"""Settings for tool result display in the terminal."""
|
|
163
|
+
default_max_lines: int = 50
|
|
164
|
+
tool_max_lines: dict[str, int] = {}
|
|
165
|
+
max_chars: int = 50_000
|
|
166
|
+
|
|
167
|
+
# Built-in defaults merged under tool_max_lines overrides
|
|
168
|
+
_TOOL_DEFAULTS: dict[str, int] = {
|
|
169
|
+
"Agent": 120,
|
|
170
|
+
"Bash": 60,
|
|
171
|
+
"apply_patch": 120,
|
|
172
|
+
"Grep": 50,
|
|
173
|
+
"Glob": 30,
|
|
174
|
+
"Read": 80,
|
|
175
|
+
"Lint": 60,
|
|
176
|
+
"WebSearch": 50,
|
|
177
|
+
"Browser": 60,
|
|
178
|
+
"CodebaseSearch": 50,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
def get_max_lines(self, tool_name: str) -> int:
|
|
182
|
+
"""Return max display lines for a tool, considering overrides."""
|
|
183
|
+
if tool_name in self.tool_max_lines:
|
|
184
|
+
return self.tool_max_lines[tool_name]
|
|
185
|
+
return self._TOOL_DEFAULTS.get(tool_name, self.default_max_lines)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class LoggingSettings(BaseModel):
|
|
189
|
+
"""Settings for runtime logging."""
|
|
190
|
+
|
|
191
|
+
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "WARNING"
|
|
192
|
+
file: str | None = None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class TeamSettings(BaseModel):
|
|
196
|
+
"""Settings for Agent Teams feature."""
|
|
197
|
+
|
|
198
|
+
max_teammates: int = Field(default=8, gt=0)
|
|
199
|
+
backpressure_queue_size: int = Field(default=100, gt=0)
|
|
200
|
+
max_message_size_bytes: int = Field(default=10_000, gt=0)
|
|
201
|
+
inbox_dir: str | None = None
|
|
202
|
+
bridge_policy: str = "deny" # deny | allow_tagged | allow_all
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class CrossSessionSettings(BaseModel):
|
|
206
|
+
"""Settings for messaging between independent local sessions."""
|
|
207
|
+
|
|
208
|
+
enabled: bool = True
|
|
209
|
+
name: str | None = None
|
|
210
|
+
inbound: Literal["auto", "accept", "hold", "refuse"] = "auto"
|
|
211
|
+
registry_dir: str | None = None
|
|
212
|
+
queue_size: int = Field(default=50, gt=0)
|
|
213
|
+
max_message_size_bytes: int = Field(default=10_000, gt=0)
|
|
214
|
+
connect_timeout_seconds: float = Field(default=3.0, gt=0)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class ScheduleSettings(BaseModel):
|
|
218
|
+
"""Settings for the Schedule (cron/interval/once) subsystem."""
|
|
219
|
+
|
|
220
|
+
enabled: bool = True
|
|
221
|
+
max_concurrent_jobs: int = 4
|
|
222
|
+
default_timeout: int = 600 # seconds
|
|
223
|
+
max_runs_per_job: int | None = None # None = unlimited
|
|
224
|
+
persist: bool = True # persist jobs across restarts
|
|
225
|
+
log_retention_days: int = 30
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class SnapshotSettings(BaseModel):
|
|
229
|
+
"""Controls optional file-system snapshots used by Bash and Checkpoint."""
|
|
230
|
+
|
|
231
|
+
enabled: bool = True
|
|
232
|
+
# The scanner stops as soon as this cumulative size is exceeded. This is
|
|
233
|
+
# deliberately a safety ceiling rather than an exact disk-usage report.
|
|
234
|
+
max_size_mb: int = Field(default=1024, ge=1, le=1_048_576)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class ComputerUseSettings(BaseModel):
|
|
238
|
+
"""Controls how a connected Computer Use host operates the GUI."""
|
|
239
|
+
|
|
240
|
+
target_scope: Literal["app_window", "desktop"] = "app_window"
|
|
241
|
+
delivery_policy: Literal["strict_background", "allow_foreground"] = "allow_foreground"
|
|
242
|
+
|
|
243
|
+
@model_validator(mode="before")
|
|
244
|
+
@classmethod
|
|
245
|
+
def migrate_mode(cls, value: Any) -> Any:
|
|
246
|
+
if isinstance(value, dict) and "mode" in value:
|
|
247
|
+
value = dict(value)
|
|
248
|
+
mode = value.pop("mode")
|
|
249
|
+
if mode not in ("background_app", "foreground_desktop"):
|
|
250
|
+
raise ValueError("Unknown Computer Use mode")
|
|
251
|
+
value.setdefault("target_scope", "desktop" if mode == "foreground_desktop" else "app_window")
|
|
252
|
+
return value
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def mode(self) -> str:
|
|
256
|
+
"""Legacy routing alias. It never overrides foreground permission."""
|
|
257
|
+
return "foreground_desktop" if self.target_scope == "desktop" else "background_app"
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class GatewaySecuritySettings(BaseModel):
|
|
261
|
+
"""Authentication settings for the HTTP/WebSocket/gRPC gateway."""
|
|
262
|
+
|
|
263
|
+
mode: Literal["none", "password", "publickey", "mixed"] = "none"
|
|
264
|
+
# A plain password is accepted for local development; deployments should
|
|
265
|
+
# prefer password_hash or the CLI/environment instead.
|
|
266
|
+
password: str | None = None
|
|
267
|
+
password_hash: str | None = None
|
|
268
|
+
jwt_secret: str | None = None
|
|
269
|
+
authorized_keys: str = "~/.ssh/authorized_keys"
|
|
270
|
+
token_ttl_seconds: int = Field(default=900, gt=0, le=86_400)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class GatewayWorkspaceSettings(BaseModel):
|
|
274
|
+
"""Directories the Gateway may expose through workspace discovery APIs."""
|
|
275
|
+
|
|
276
|
+
browse_roots: list[str] = Field(default_factory=list)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class GatewaySettings(BaseModel):
|
|
280
|
+
security: GatewaySecuritySettings = Field(default_factory=GatewaySecuritySettings)
|
|
281
|
+
workspace: GatewayWorkspaceSettings = Field(default_factory=GatewayWorkspaceSettings)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class ToolLoadingSettings(BaseModel):
|
|
285
|
+
mode: Literal["eager", "discovery"] = "discovery"
|
|
286
|
+
pinned_tools: list[str] = Field(default_factory=list)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class PromptTemplateConfig(BaseModel):
|
|
290
|
+
"""A named system-prompt template. Blank sections mean the built-in default."""
|
|
291
|
+
|
|
292
|
+
id: str
|
|
293
|
+
name: str
|
|
294
|
+
sections: dict[str, str] = Field(default_factory=dict)
|
|
295
|
+
|
|
296
|
+
@field_validator("id", "name")
|
|
297
|
+
@classmethod
|
|
298
|
+
def _required_name(cls, value: str) -> str:
|
|
299
|
+
text = value.strip()
|
|
300
|
+
if not text:
|
|
301
|
+
raise ValueError("must not be blank")
|
|
302
|
+
if len(text) > 80:
|
|
303
|
+
raise ValueError("is too long")
|
|
304
|
+
return text
|
|
305
|
+
|
|
306
|
+
@field_validator("sections", mode="before")
|
|
307
|
+
@classmethod
|
|
308
|
+
def _section_text(cls, value: Any) -> dict[str, str]:
|
|
309
|
+
if not isinstance(value, dict):
|
|
310
|
+
return {}
|
|
311
|
+
cleaned: dict[str, str] = {}
|
|
312
|
+
for key, item in value.items():
|
|
313
|
+
if isinstance(key, str) and isinstance(item, str):
|
|
314
|
+
cleaned[key] = item
|
|
315
|
+
return cleaned
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class UserAppendPromptConfig(BaseModel):
|
|
319
|
+
"""A saved prompt that can be appended to the user message sent to the model."""
|
|
320
|
+
|
|
321
|
+
id: str
|
|
322
|
+
text: str
|
|
323
|
+
enabled: bool = False
|
|
324
|
+
|
|
325
|
+
@field_validator("id", "text")
|
|
326
|
+
@classmethod
|
|
327
|
+
def _required_text(cls, value: str) -> str:
|
|
328
|
+
text = value.strip()
|
|
329
|
+
if not text:
|
|
330
|
+
raise ValueError("must not be blank")
|
|
331
|
+
if len(text) > 8000:
|
|
332
|
+
raise ValueError("is too long")
|
|
333
|
+
return text
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class CrabCodeSettings(BaseModel):
|
|
337
|
+
"""Full settings.json schema."""
|
|
338
|
+
follow_up_mode: Literal["queue", "steer"] = "queue"
|
|
339
|
+
permissions: PermissionsSettings = Field(default_factory=PermissionsSettings)
|
|
340
|
+
env: dict[str, str] = Field(default_factory=dict)
|
|
341
|
+
mcp_servers: dict[str, McpServerConfig] = Field(default_factory=dict)
|
|
342
|
+
api: ApiConfig = Field(default_factory=ApiConfig)
|
|
343
|
+
# Shared API settings inherited by named models. A model's explicitly
|
|
344
|
+
# configured fields take precedence over the referenced group.
|
|
345
|
+
groups: dict[str, ApiConfig] = Field(default_factory=dict)
|
|
346
|
+
models: dict[str, ApiConfig] = Field(default_factory=dict)
|
|
347
|
+
default_model: str | None = None
|
|
348
|
+
hooks: dict[str, list[dict[str, Any]]] = Field(default_factory=dict)
|
|
349
|
+
auto_compact_enabled: bool = True
|
|
350
|
+
compact_buffer_tokens: int = Field(default=DEFAULT_COMPACT_BUFFER_TOKENS, ge=0, strict=True)
|
|
351
|
+
max_context_length: int | None = None
|
|
352
|
+
language: str | None = None
|
|
353
|
+
output_style: str | None = None
|
|
354
|
+
prompt_profile: dict[str, Any] | None = None
|
|
355
|
+
prompt_templates: list[PromptTemplateConfig] = Field(default_factory=list)
|
|
356
|
+
active_prompt_template: str | None = None
|
|
357
|
+
user_append_prompts: list[UserAppendPromptConfig] = Field(default_factory=list)
|
|
358
|
+
extra_tools: list[str] = Field(default_factory=list)
|
|
359
|
+
tool_loading: ToolLoadingSettings = Field(default_factory=ToolLoadingSettings)
|
|
360
|
+
ultra_mode: bool = False
|
|
361
|
+
tool_call_timeout: float | None = None
|
|
362
|
+
filesystem_timeout: float | None = Field(
|
|
363
|
+
default=DEFAULT_FILESYSTEM_TIMEOUT,
|
|
364
|
+
gt=0,
|
|
365
|
+
allow_inf_nan=False,
|
|
366
|
+
description="Default timeout in seconds for Read, apply_patch, Write, Edit, Glob, Grep, and Bash; null disables it.",
|
|
367
|
+
)
|
|
368
|
+
tool_settings: dict[str, dict[str, Any]] = Field(default_factory=dict)
|
|
369
|
+
agent: AgentSettings = Field(default_factory=AgentSettings)
|
|
370
|
+
team: TeamSettings = Field(default_factory=TeamSettings)
|
|
371
|
+
cross_session: CrossSessionSettings = Field(default_factory=CrossSessionSettings)
|
|
372
|
+
schedule: ScheduleSettings = Field(default_factory=ScheduleSettings)
|
|
373
|
+
snapshot: SnapshotSettings = Field(default_factory=SnapshotSettings)
|
|
374
|
+
computer_use: ComputerUseSettings = Field(default_factory=ComputerUseSettings)
|
|
375
|
+
gateway: GatewaySettings = Field(default_factory=GatewaySettings)
|
|
376
|
+
display: DisplaySettings = Field(default_factory=DisplaySettings)
|
|
377
|
+
logging: LoggingSettings = Field(default_factory=LoggingSettings)
|
|
378
|
+
lsp: dict[str, LspServerConfig] | bool = Field(default_factory=dict)
|
|
379
|
+
|
|
380
|
+
model_config = {"extra": "allow"}
|
|
381
|
+
|
|
382
|
+
@field_validator("active_prompt_template", mode="before")
|
|
383
|
+
@classmethod
|
|
384
|
+
def _blank_active_prompt_template(cls, value: Any) -> str | None:
|
|
385
|
+
if value is None:
|
|
386
|
+
return None
|
|
387
|
+
if isinstance(value, str):
|
|
388
|
+
text = value.strip()
|
|
389
|
+
return text or None
|
|
390
|
+
return value
|
|
391
|
+
|
|
392
|
+
@model_validator(mode="after")
|
|
393
|
+
def _resolve_model_groups(self) -> "CrabCodeSettings":
|
|
394
|
+
"""Expand group settings into named models after validation.
|
|
395
|
+
|
|
396
|
+
Pydantic populates omitted fields with defaults, so using a normal
|
|
397
|
+
dictionary merge would incorrectly override group values (for example
|
|
398
|
+
an omitted ``thinking_enabled`` would look like ``True``). The
|
|
399
|
+
per-model ``model_fields_set`` records which fields the user actually
|
|
400
|
+
supplied and lets us inherit only the omitted fields.
|
|
401
|
+
"""
|
|
402
|
+
if not self.groups or not self.models:
|
|
403
|
+
return self
|
|
404
|
+
|
|
405
|
+
resolved: dict[str, ApiConfig] = {}
|
|
406
|
+
for name, model_config in self.models.items():
|
|
407
|
+
group_name = model_config.group
|
|
408
|
+
group_config = self.groups.get(group_name) if group_name else None
|
|
409
|
+
if group_config is None:
|
|
410
|
+
# Keep unknown groups non-fatal so a typo does not discard all
|
|
411
|
+
# otherwise valid settings; the model remains usable on its
|
|
412
|
+
# own and callers can report the missing group if desired.
|
|
413
|
+
resolved[name] = model_config
|
|
414
|
+
continue
|
|
415
|
+
|
|
416
|
+
inherited = group_config.model_copy(deep=True)
|
|
417
|
+
for field_name in ApiConfig.model_fields:
|
|
418
|
+
if field_name in model_config.model_fields_set:
|
|
419
|
+
setattr(
|
|
420
|
+
inherited,
|
|
421
|
+
field_name,
|
|
422
|
+
deepcopy(getattr(model_config, field_name)),
|
|
423
|
+
)
|
|
424
|
+
resolved[name] = inherited
|
|
425
|
+
|
|
426
|
+
self.models = resolved
|
|
427
|
+
return self
|
|
428
|
+
|
|
429
|
+
def get_api_config(self, model_name: str | None = None) -> ApiConfig:
|
|
430
|
+
"""Return the ApiConfig for a named model, falling back to the default api config."""
|
|
431
|
+
name = model_name or self.default_model
|
|
432
|
+
if name and name in self.models:
|
|
433
|
+
return self.models[name]
|
|
434
|
+
return self.api
|