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.
Files changed (205) hide show
  1. crabcode-0.1.6.dist-info/METADATA +63 -0
  2. crabcode-0.1.6.dist-info/RECORD +205 -0
  3. crabcode-0.1.6.dist-info/WHEEL +4 -0
  4. crabcode-0.1.6.dist-info/entry_points.txt +2 -0
  5. crabcode_cli/__init__.py +7 -0
  6. crabcode_cli/__main__.py +4 -0
  7. crabcode_cli/_native/crabcode-computer-use-host.exe +0 -0
  8. crabcode_cli/_native/runtime.json +6 -0
  9. crabcode_cli/app.py +776 -0
  10. crabcode_cli/banner.py +39 -0
  11. crabcode_cli/computer_use.py +286 -0
  12. crabcode_cli/model_manager.py +649 -0
  13. crabcode_cli/model_picker.py +168 -0
  14. crabcode_cli/pipe.py +148 -0
  15. crabcode_cli/render/__init__.py +0 -0
  16. crabcode_cli/render/messages.py +19 -0
  17. crabcode_cli/repl.py +4841 -0
  18. crabcode_cli/session_picker.py +239 -0
  19. crabcode_core/__init__.py +7 -0
  20. crabcode_core/_version.py +3 -0
  21. crabcode_core/_windows_process.py +68 -0
  22. crabcode_core/agent_manager.py +1731 -0
  23. crabcode_core/api/__init__.py +20 -0
  24. crabcode_core/api/anthropic_adapter.py +648 -0
  25. crabcode_core/api/azure_adapter.py +69 -0
  26. crabcode_core/api/base.py +233 -0
  27. crabcode_core/api/codex_adapter.py +1327 -0
  28. crabcode_core/api/error_logging.py +132 -0
  29. crabcode_core/api/gemini_adapter.py +26 -0
  30. crabcode_core/api/model_info.py +257 -0
  31. crabcode_core/api/network.py +62 -0
  32. crabcode_core/api/ollama_adapter.py +27 -0
  33. crabcode_core/api/openai_adapter.py +323 -0
  34. crabcode_core/api/registry.py +58 -0
  35. crabcode_core/command_line.py +18 -0
  36. crabcode_core/compact/__init__.py +17 -0
  37. crabcode_core/compact/compact.py +514 -0
  38. crabcode_core/compact/context_tokens.py +165 -0
  39. crabcode_core/computer_use_mode.py +38 -0
  40. crabcode_core/computer_use_observation.py +144 -0
  41. crabcode_core/config/__init__.py +3 -0
  42. crabcode_core/config/claudemd.py +61 -0
  43. crabcode_core/config/manager.py +237 -0
  44. crabcode_core/config/model_catalog.py +256 -0
  45. crabcode_core/events.py +4896 -0
  46. crabcode_core/file_lock.py +81 -0
  47. crabcode_core/filesystem.py +22 -0
  48. crabcode_core/goal.py +149 -0
  49. crabcode_core/hooks/__init__.py +5 -0
  50. crabcode_core/hooks/manager.py +290 -0
  51. crabcode_core/image_io.py +111 -0
  52. crabcode_core/io_worker.py +211 -0
  53. crabcode_core/logging_utils.py +168 -0
  54. crabcode_core/lsp/__init__.py +6 -0
  55. crabcode_core/lsp/client.py +786 -0
  56. crabcode_core/lsp/diagnostics.py +150 -0
  57. crabcode_core/lsp/manager.py +583 -0
  58. crabcode_core/mcp/__init__.py +4 -0
  59. crabcode_core/mcp/client.py +169 -0
  60. crabcode_core/mcp/config.py +57 -0
  61. crabcode_core/path_validation.py +34 -0
  62. crabcode_core/paths.py +24 -0
  63. crabcode_core/peer/__init__.py +10 -0
  64. crabcode_core/peer/runtime.py +518 -0
  65. crabcode_core/permissions/__init__.py +7 -0
  66. crabcode_core/permissions/ai_reviewer.py +184 -0
  67. crabcode_core/permissions/manager.py +187 -0
  68. crabcode_core/plan/__init__.py +5 -0
  69. crabcode_core/plan/executor.py +270 -0
  70. crabcode_core/plan/types.py +169 -0
  71. crabcode_core/prompts/__init__.py +0 -0
  72. crabcode_core/prompts/blocks.py +10 -0
  73. crabcode_core/prompts/context.py +229 -0
  74. crabcode_core/prompts/library.py +92 -0
  75. crabcode_core/prompts/profile.py +79 -0
  76. crabcode_core/prompts/system.py +356 -0
  77. crabcode_core/prompts/templates.py +104 -0
  78. crabcode_core/query/__init__.py +0 -0
  79. crabcode_core/query/computer_use_history.py +83 -0
  80. crabcode_core/query/loop.py +2446 -0
  81. crabcode_core/query/retry.py +116 -0
  82. crabcode_core/schedule/__init__.py +13 -0
  83. crabcode_core/schedule/manager.py +631 -0
  84. crabcode_core/schedule/models.py +204 -0
  85. crabcode_core/schedule/store.py +558 -0
  86. crabcode_core/schedule/timing.py +287 -0
  87. crabcode_core/session/__init__.py +12 -0
  88. crabcode_core/session/export.py +141 -0
  89. crabcode_core/session/meta_db.py +684 -0
  90. crabcode_core/session/storage.py +2032 -0
  91. crabcode_core/session/title_gen.py +52 -0
  92. crabcode_core/skills/__init__.py +6 -0
  93. crabcode_core/skills/context.py +23 -0
  94. crabcode_core/skills/loader.py +214 -0
  95. crabcode_core/skills/matcher.py +203 -0
  96. crabcode_core/snapshot/__init__.py +21 -0
  97. crabcode_core/snapshot/snapshot.py +436 -0
  98. crabcode_core/snapshot/tracker.py +359 -0
  99. crabcode_core/subprocess_utils.py +260 -0
  100. crabcode_core/team/__init__.py +27 -0
  101. crabcode_core/team/bridge.py +66 -0
  102. crabcode_core/team/inbox.py +237 -0
  103. crabcode_core/team/manager.py +1660 -0
  104. crabcode_core/team/message_bus.py +821 -0
  105. crabcode_core/team/models.py +191 -0
  106. crabcode_core/team/recovery.py +192 -0
  107. crabcode_core/text_io.py +52 -0
  108. crabcode_core/tools/__init__.py +112 -0
  109. crabcode_core/tools/_input_helpers.py +39 -0
  110. crabcode_core/tools/_input_schema.py +130 -0
  111. crabcode_core/tools/agent.py +413 -0
  112. crabcode_core/tools/apply_patch.py +587 -0
  113. crabcode_core/tools/ask_user.py +122 -0
  114. crabcode_core/tools/bash.py +154 -0
  115. crabcode_core/tools/browser.py +678 -0
  116. crabcode_core/tools/checklist.py +241 -0
  117. crabcode_core/tools/checkpoint.py +78 -0
  118. crabcode_core/tools/computer_use.py +599 -0
  119. crabcode_core/tools/diff_utils.py +123 -0
  120. crabcode_core/tools/file_edit.py +240 -0
  121. crabcode_core/tools/file_read.py +187 -0
  122. crabcode_core/tools/file_write.py +154 -0
  123. crabcode_core/tools/glob.py +123 -0
  124. crabcode_core/tools/goal.py +134 -0
  125. crabcode_core/tools/grep.py +247 -0
  126. crabcode_core/tools/image.py +173 -0
  127. crabcode_core/tools/image_generate.py +115 -0
  128. crabcode_core/tools/lint.py +388 -0
  129. crabcode_core/tools/loading.py +187 -0
  130. crabcode_core/tools/memory.py +456 -0
  131. crabcode_core/tools/monitor.py +677 -0
  132. crabcode_core/tools/peer.py +103 -0
  133. crabcode_core/tools/revert.py +90 -0
  134. crabcode_core/tools/schedule.py +683 -0
  135. crabcode_core/tools/skill.py +108 -0
  136. crabcode_core/tools/switch_mode.py +213 -0
  137. crabcode_core/tools/team.py +329 -0
  138. crabcode_core/tools/tool_search.py +64 -0
  139. crabcode_core/tools/web_search.py +444 -0
  140. crabcode_core/turn_details.py +64 -0
  141. crabcode_core/types/__init__.py +95 -0
  142. crabcode_core/types/config.py +434 -0
  143. crabcode_core/types/event.py +334 -0
  144. crabcode_core/types/message.py +263 -0
  145. crabcode_core/types/tool.py +208 -0
  146. crabcode_core/usage.py +289 -0
  147. crabcode_core/utf8_sanitize.py +29 -0
  148. crabcode_debugger/__init__.py +5 -0
  149. crabcode_debugger/adapters.py +332 -0
  150. crabcode_debugger/dap.py +304 -0
  151. crabcode_debugger/memory.py +1806 -0
  152. crabcode_debugger/process.py +707 -0
  153. crabcode_debugger/runtime.py +58 -0
  154. crabcode_debugger/sessions.py +387 -0
  155. crabcode_debugger/tool.py +731 -0
  156. crabcode_gateway/__init__.py +18 -0
  157. crabcode_gateway/acp/__init__.py +13 -0
  158. crabcode_gateway/acp/agent.py +1581 -0
  159. crabcode_gateway/acp/session.py +114 -0
  160. crabcode_gateway/acp/transport.py +39 -0
  161. crabcode_gateway/acp/types.py +90 -0
  162. crabcode_gateway/adapter.py +30 -0
  163. crabcode_gateway/auth.py +139 -0
  164. crabcode_gateway/computer_use.py +364 -0
  165. crabcode_gateway/document_engine.py +637 -0
  166. crabcode_gateway/document_worker.py +301 -0
  167. crabcode_gateway/event_bus.py +655 -0
  168. crabcode_gateway/grpc/__init__.py +0 -0
  169. crabcode_gateway/grpc/crabcode_pb2.py +70 -0
  170. crabcode_gateway/grpc/crabcode_pb2_grpc.py +474 -0
  171. crabcode_gateway/grpc/proto/crabcode.proto +160 -0
  172. crabcode_gateway/grpc/server.py +765 -0
  173. crabcode_gateway/middleware.py +344 -0
  174. crabcode_gateway/protocol.py +17 -0
  175. crabcode_gateway/routes/__init__.py +0 -0
  176. crabcode_gateway/routes/agent.py +392 -0
  177. crabcode_gateway/routes/auth.py +150 -0
  178. crabcode_gateway/routes/computer_use.py +101 -0
  179. crabcode_gateway/routes/config.py +1698 -0
  180. crabcode_gateway/routes/document.py +1810 -0
  181. crabcode_gateway/routes/event.py +3801 -0
  182. crabcode_gateway/routes/git_review.py +232 -0
  183. crabcode_gateway/routes/health.py +14 -0
  184. crabcode_gateway/routes/peer.py +78 -0
  185. crabcode_gateway/routes/permission.py +121 -0
  186. crabcode_gateway/routes/schedule.py +300 -0
  187. crabcode_gateway/routes/session.py +1496 -0
  188. crabcode_gateway/routes/snapshot.py +211 -0
  189. crabcode_gateway/routes/tasks.py +318 -0
  190. crabcode_gateway/routes/team.py +392 -0
  191. crabcode_gateway/routes/usage.py +49 -0
  192. crabcode_gateway/routes/workspace.py +299 -0
  193. crabcode_gateway/safe_file.py +63 -0
  194. crabcode_gateway/schemas.py +1820 -0
  195. crabcode_gateway/server.py +514 -0
  196. crabcode_gateway/session_registry.py +36 -0
  197. crabcode_gateway/task_registry.py +708 -0
  198. crabcode_gateway/turn_details.py +60 -0
  199. crabcode_search/__init__.py +11 -0
  200. crabcode_search/background.py +368 -0
  201. crabcode_search/chunker.py +517 -0
  202. crabcode_search/embedder.py +497 -0
  203. crabcode_search/indexer.py +215 -0
  204. crabcode_search/store.py +222 -0
  205. crabcode_search/tool.py +268 -0
@@ -0,0 +1,69 @@
1
+ """Azure OpenAI API adapter — inherits from OpenAIAdapter with Azure-specific client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any
7
+
8
+ from crabcode_core.api.base import ModelConfig
9
+ from crabcode_core.api.openai_adapter import OpenAIAdapter
10
+ from crabcode_core.types.config import ApiConfig
11
+
12
+
13
+ class AzureOpenAIAdapter(OpenAIAdapter):
14
+ """Adapter for Azure OpenAI endpoints.
15
+
16
+ Azure OpenAI uses *deployment names* instead of model names. The
17
+ ``config.model`` value is treated as the Azure deployment name.
18
+
19
+ Required environment variables (or equivalent config):
20
+ - AZURE_OPENAI_API_KEY (or config.api_key_env)
21
+ - AZURE_OPENAI_ENDPOINT
22
+ - AZURE_OPENAI_API_VERSION (defaults to ``2024-10-21``)
23
+ - AZURE_OPENAI_DEPLOYMENT (optional; overrides config.model)
24
+ """
25
+
26
+ def __init__(self, config: ApiConfig):
27
+ # Azure deployment name: env override takes precedence
28
+ self._deployment_override = os.environ.get("AZURE_OPENAI_DEPLOYMENT")
29
+ super().__init__(config)
30
+
31
+ def _create_client(self, config: ApiConfig) -> Any:
32
+ """Create an AsyncAzureOpenAI client."""
33
+ import openai
34
+
35
+ # --- API key ---
36
+ api_key: str | None = None
37
+ if config.api_key_env:
38
+ api_key = os.environ.get(config.api_key_env)
39
+ if not api_key:
40
+ api_key = os.environ.get("AZURE_OPENAI_API_KEY")
41
+
42
+ # --- Azure endpoint ---
43
+ azure_endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT")
44
+ if not azure_endpoint and config.base_url:
45
+ azure_endpoint = config.base_url
46
+
47
+ # --- API version ---
48
+ api_version = os.environ.get("AZURE_OPENAI_API_VERSION", "2024-10-21")
49
+
50
+ kwargs: dict[str, Any] = {}
51
+ if api_key:
52
+ kwargs["api_key"] = api_key
53
+ if azure_endpoint:
54
+ kwargs["azure_endpoint"] = azure_endpoint
55
+ kwargs["api_version"] = api_version
56
+ if config.http_headers:
57
+ kwargs["default_headers"] = config.http_headers
58
+
59
+ from crabcode_core.api.network import sdk_options
60
+ return openai.AsyncAzureOpenAI(**kwargs, **sdk_options(config))
61
+
62
+ def _resolve_model(self, config: ModelConfig) -> str:
63
+ """Azure uses deployment names instead of model names."""
64
+ return (
65
+ self._deployment_override
66
+ or config.model
67
+ or self.config.model
68
+ or "gpt-4o"
69
+ )
@@ -0,0 +1,233 @@
1
+ """Abstract base for all API adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from dataclasses import dataclass, field
7
+ import time
8
+ from typing import Any, AsyncGenerator
9
+
10
+ import httpx
11
+
12
+ from crabcode_core.types.message import Message
13
+
14
+
15
+ def _raw_usage_field(raw: Any, *keys: str) -> tuple[Any, bool]:
16
+ for key in keys:
17
+ if isinstance(raw, dict):
18
+ if key in raw and raw[key] is not None:
19
+ return raw[key], True
20
+ else:
21
+ value = getattr(raw, key, None)
22
+ if value is not None:
23
+ return value, True
24
+ return None, False
25
+
26
+
27
+ def usage_int_field(raw: Any, *keys: str) -> tuple[int, bool]:
28
+ value, present = _raw_usage_field(raw, *keys)
29
+ if not present:
30
+ return 0, False
31
+ try:
32
+ count = int(value)
33
+ if isinstance(value, bool) or count < 0 or (isinstance(value, float) and value != count):
34
+ return 0, False
35
+ return count, True
36
+ except (TypeError, ValueError, OverflowError):
37
+ return 0, False
38
+
39
+
40
+ def normalize_openai_usage(raw: Any) -> dict[str, int]:
41
+ """Normalize Chat Completions and Responses usage fields."""
42
+ usage: dict[str, int] = {}
43
+ input_tokens, has_input = usage_int_field(raw, "input_tokens", "prompt_tokens")
44
+ output_tokens, has_output = usage_int_field(raw, "output_tokens", "completion_tokens")
45
+ if has_input:
46
+ usage["input_tokens"] = input_tokens
47
+ usage["total_input_tokens"] = input_tokens
48
+ if has_output:
49
+ usage["output_tokens"] = output_tokens
50
+ total_tokens, has_total = usage_int_field(raw, "total_tokens")
51
+ if has_total:
52
+ usage["total_tokens"] = total_tokens
53
+
54
+ details, has_details = _raw_usage_field(
55
+ raw,
56
+ "input_tokens_details",
57
+ "prompt_tokens_details",
58
+ )
59
+ if has_details:
60
+ cached_tokens, has_cached = usage_int_field(details, "cached_tokens")
61
+ if has_cached:
62
+ usage["cache_read_tokens"] = cached_tokens
63
+ output_details, has_output_details = _raw_usage_field(
64
+ raw, "output_tokens_details", "completion_tokens_details",
65
+ )
66
+ if has_output_details:
67
+ reasoning_tokens, has_reasoning = usage_int_field(output_details, "reasoning_tokens")
68
+ if has_reasoning:
69
+ usage["reasoning_tokens"] = reasoning_tokens
70
+ return usage
71
+
72
+
73
+ @dataclass
74
+ class ModelConfig:
75
+ """Configuration for a single API call."""
76
+ model: str = "claude-sonnet-4-20250514"
77
+ max_tokens: int = 16384
78
+ thinking_enabled: bool = True
79
+ thinking_budget: int = 10000
80
+ temperature: float | None = None
81
+ stop_sequences: list[str] | None = None
82
+ timeout: int = 300 # seconds
83
+ context_window: int = 0 # 0 means unknown / not resolved yet
84
+ reasoning_effort: str | None = None # call-level override for Responses-style APIs
85
+
86
+
87
+ @dataclass
88
+ class StreamChunk:
89
+ """A single chunk from the streaming API response.
90
+
91
+ Each chunk can carry text, tool use data, thinking, or metadata.
92
+ """
93
+ type: str # text, thinking, tool lifecycle, message lifecycle, or error
94
+ text: str = ""
95
+ tool_use_id: str = ""
96
+ tool_name: str = ""
97
+ tool_input_json: str = ""
98
+ usage: dict[str, Any] = field(default_factory=dict)
99
+ stop_reason: str = ""
100
+ error: str = ""
101
+ # Responses-style adapters expose item completion so the query loop can
102
+ # checkpoint only durable output before replaying a dropped stream.
103
+ item_id: str = ""
104
+ item_type: str = ""
105
+ # Adapter-authoritative retry metadata. ``None`` leaves classification to
106
+ # the generic query loop.
107
+ retryable: bool | None = None
108
+ connection_failed: bool = False
109
+ retry_after: float | None = None
110
+ model_id: str = "" # provider-returned model identity, when available
111
+ # Internal diagnostics only; never forwarded as UI events or conversation content.
112
+ error_body: Any = None
113
+ error_status_code: int | None = None
114
+ error_request_id: str | None = None
115
+
116
+
117
+ class APIAdapter(ABC):
118
+ """Abstract interface for LLM API backends.
119
+
120
+ All adapters translate between CrabCode's internal Anthropic-style
121
+ message format and the provider's native format.
122
+ """
123
+
124
+ config: Any # ApiConfig — set by concrete subclasses
125
+ emits_response_item_events: bool = False
126
+
127
+ @abstractmethod
128
+ async def stream_message(
129
+ self,
130
+ messages: list[Message],
131
+ system: list[str],
132
+ tools: list[dict[str, Any]],
133
+ config: ModelConfig,
134
+ ) -> AsyncGenerator[StreamChunk, None]:
135
+ """Send messages and stream back response chunks."""
136
+ ...
137
+ yield # pragma: no cover
138
+
139
+ @abstractmethod
140
+ async def count_tokens(
141
+ self,
142
+ messages: list[Message],
143
+ system: list[str],
144
+ ) -> int:
145
+ """Estimate token count for a message list."""
146
+ ...
147
+
148
+ async def resolve_context_window(self) -> int:
149
+ """Resolve the effective context window size for the current model.
150
+
151
+ Priority: config.context_window (user override)
152
+ -> API query (Anthropic Models API)
153
+ -> built-in lookup table
154
+ -> DEFAULT_CONTEXT_WINDOW
155
+ """
156
+ from crabcode_core.api.model_info import DEFAULT_CONTEXT_WINDOW, lookup_context_window
157
+
158
+ if hasattr(self, "config") and getattr(self.config, "context_window", None):
159
+ return self.config.context_window
160
+
161
+ model = getattr(self.config, "model", None) if hasattr(self, "config") else None
162
+ looked_up = lookup_context_window(model)
163
+ if looked_up is not None:
164
+ return looked_up
165
+
166
+ return DEFAULT_CONTEXT_WINDOW
167
+
168
+ async def count_input_tokens(
169
+ self, messages: list[Message], system: list[str],
170
+ tools: list[dict[str, Any]], config: ModelConfig,
171
+ ) -> int | None:
172
+ """Count the complete input on the server, or return None if unsupported.
173
+
174
+ Unlike legacy count_tokens(), this method must never return a local
175
+ estimate. The query loop bounds its latency and handles failures.
176
+ """
177
+ return None
178
+
179
+ def try_switch_fallback_transport(self) -> bool:
180
+ """Switch a failed WebSocket session to HTTP when supported.
181
+
182
+ CrabCode adapters currently stream over HTTP, so the default is a
183
+ no-op. Keeping the hook in the shared contract preserves retry
184
+ state-machine ordering for adapters that add WebSocket transport.
185
+ """
186
+ return False
187
+
188
+ async def reset_network_client(self) -> None:
189
+ """Replace a failed SDK connection pool without changing its routing policy."""
190
+ import asyncio
191
+ from crabcode_core.api.network import http_options, request_timeout
192
+
193
+ client = getattr(self, "client", None)
194
+ copy = getattr(client, "with_options", None)
195
+ if not callable(copy):
196
+ return
197
+ transport = httpx.AsyncClient(timeout=request_timeout(self.config), **http_options(self.config))
198
+ try:
199
+ replacement = copy(http_client=transport, max_retries=0)
200
+ except BaseException:
201
+ await transport.aclose()
202
+ raise
203
+ self.client = replacement
204
+ try:
205
+ await asyncio.wait_for(client.close(), timeout=2)
206
+ except Exception:
207
+ pass
208
+
209
+ async def _count_tokens_http(
210
+ self, url: str, payload: dict[str, Any], headers: dict[str, str],
211
+ ) -> int | None:
212
+ """Probe compatible endpoints without retries or repeated failure delays."""
213
+ if time.monotonic() < getattr(self, "_token_count_retry_at", 0):
214
+ return None
215
+ # Keep the cooldown even if the caller's overall deadline cancels HTTP.
216
+ self._token_count_retry_at = time.monotonic() + 300
217
+ try:
218
+ from crabcode_core.api.network import http_options
219
+ async with httpx.AsyncClient(timeout=3.0, **http_options(getattr(self, "config", None))) as client:
220
+ response = await client.post(url, json=payload, headers=headers)
221
+ if response.status_code in {404, 405, 501}:
222
+ self._token_count_retry_at = float("inf")
223
+ return None
224
+ response.raise_for_status()
225
+ tokens, valid = usage_int_field(response.json(), "input_tokens")
226
+ if valid and tokens > 0:
227
+ self._token_count_retry_at = 0
228
+ return tokens
229
+ except (httpx.HTTPError, ValueError, TypeError):
230
+ pass
231
+ # A broken/unsupported counting route must not break normal generation.
232
+ self._token_count_retry_at = time.monotonic() + 300
233
+ return None