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,323 @@
1
+ """OpenAI API adapter — translates between internal Anthropic-style messages and OpenAI format."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ from typing import Any, AsyncGenerator
8
+
9
+ from crabcode_core.api.base import (
10
+ APIAdapter,
11
+ ModelConfig,
12
+ StreamChunk,
13
+ normalize_openai_usage,
14
+ )
15
+ from crabcode_core.types.config import ApiConfig
16
+ from crabcode_core.utf8_sanitize import safe_utf8_json_tree, safe_utf8_str
17
+ from crabcode_core.types.message import (
18
+ Message,
19
+ MessageRole,
20
+ TextBlock,
21
+ ToolResultBlock,
22
+ ToolUseBlock,
23
+ ThinkingBlock,
24
+ ImageBlock,
25
+ )
26
+
27
+
28
+ def _messages_to_openai(
29
+ messages: list[Message],
30
+ system: list[str],
31
+ pass_reasoning_content: bool = False,
32
+ ) -> list[dict[str, Any]]:
33
+ """Convert internal messages + system to OpenAI chat format."""
34
+ result: list[dict[str, Any]] = []
35
+ pending_tool_ids: set[str] = set()
36
+ deferred_images: list[dict[str, Any]] = []
37
+
38
+ def require_complete_image_batch() -> None:
39
+ if deferred_images:
40
+ raise ValueError(
41
+ "Cannot send tool images: missing tool results for "
42
+ + ", ".join(sorted(pending_tool_ids))
43
+ )
44
+
45
+ system_text = "\n\n".join(s for s in system if s)
46
+ if system_text:
47
+ result.append({"role": "system", "content": system_text})
48
+
49
+ for msg in messages:
50
+ if msg.role == MessageRole.SYSTEM:
51
+ continue
52
+
53
+ if isinstance(msg.content, str):
54
+ require_complete_image_batch()
55
+ result.append({"role": msg.role.value, "content": msg.content})
56
+ continue
57
+
58
+ text_parts: list[str] = []
59
+ reasoning_parts: list[str] = []
60
+ tool_calls: list[dict[str, Any]] = []
61
+ tool_results: list[dict[str, Any]] = []
62
+
63
+ for block in msg.content:
64
+ if isinstance(block, TextBlock):
65
+ text_parts.append(block.text)
66
+ elif isinstance(block, ToolUseBlock):
67
+ tool_calls.append({
68
+ "id": block.id,
69
+ "type": "function",
70
+ "function": {
71
+ "name": block.name,
72
+ "arguments": json.dumps(block.input),
73
+ },
74
+ })
75
+ elif isinstance(block, ToolResultBlock):
76
+ tool_results.append({
77
+ "role": "tool",
78
+ "tool_call_id": block.tool_use_id,
79
+ "content": block.content,
80
+ })
81
+ elif isinstance(block, ThinkingBlock):
82
+ reasoning_parts.append(block.thinking)
83
+
84
+ if msg.role == MessageRole.ASSISTANT:
85
+ require_complete_image_batch()
86
+ pending_tool_ids = {call["id"] for call in tool_calls}
87
+ entry: dict[str, Any] = {"role": "assistant"}
88
+ if text_parts:
89
+ entry["content"] = "".join(text_parts)
90
+ elif tool_calls:
91
+ # OpenAI expects explicit null when the turn is tool-only.
92
+ entry["content"] = None
93
+ if tool_calls:
94
+ entry["tool_calls"] = tool_calls
95
+ if pass_reasoning_content and reasoning_parts:
96
+ entry["reasoning_content"] = "".join(reasoning_parts)
97
+ result.append(entry)
98
+ elif msg.role == MessageRole.USER:
99
+ if not tool_results:
100
+ require_complete_image_batch()
101
+ if tool_results:
102
+ result.extend(tool_results)
103
+ pending_tool_ids.difference_update(
104
+ item["tool_call_id"] for item in tool_results
105
+ )
106
+
107
+ # Chat Completions requires all tool results before the next user
108
+ # message. Hold tool images until the entire call batch is closed.
109
+ has_images = isinstance(msg.content, list) and any(
110
+ isinstance(b, ImageBlock) for b in msg.content
111
+ )
112
+ if has_images:
113
+ content_parts: list[dict[str, Any]] = []
114
+ for block in msg.content if isinstance(msg.content, list) else []:
115
+ if isinstance(block, TextBlock):
116
+ content_parts.append({"type": "text", "text": block.text})
117
+ elif isinstance(block, ImageBlock):
118
+ # Build data URL from base64 source
119
+ source = block.source
120
+ if source.get("type") == "base64":
121
+ data_url = f"data:{source.get('media_type', 'image/png')};base64,{source.get('data', '')}"
122
+ else:
123
+ data_url = source.get("url", "")
124
+ content_parts.append({
125
+ "type": "image_url",
126
+ "image_url": {"url": data_url},
127
+ })
128
+ if content_parts:
129
+ image_message = {"role": "user", "content": content_parts}
130
+ if tool_results:
131
+ deferred_images.append(image_message)
132
+ else:
133
+ result.append(image_message)
134
+ elif text_parts and not tool_results:
135
+ result.append({"role": "user", "content": "".join(text_parts)})
136
+
137
+ if not pending_tool_ids:
138
+ result.extend(deferred_images)
139
+ deferred_images.clear()
140
+
141
+ require_complete_image_batch()
142
+ return safe_utf8_json_tree(result)
143
+
144
+
145
+ def _tools_to_openai(tools: list[dict[str, Any]]) -> list[dict[str, Any]]:
146
+ """Convert tool schemas to OpenAI function format."""
147
+ result = []
148
+ for tool in tools:
149
+ schema = tool.get("input_schema", {"type": "object", "properties": {}})
150
+ result.append({
151
+ "type": "function",
152
+ "function": {
153
+ "name": tool["name"],
154
+ "description": tool.get("description", ""),
155
+ "parameters": schema,
156
+ },
157
+ })
158
+ return safe_utf8_json_tree(result)
159
+
160
+
161
+ class OpenAIAdapter(APIAdapter):
162
+ """Adapter for OpenAI-compatible APIs.
163
+
164
+ Designed to be subclassed by providers that use an OpenAI-compatible
165
+ chat completions surface (Azure, Ollama, Gemini, etc.). Subclasses
166
+ can override :meth:`_create_client` and :meth:`_resolve_model` to
167
+ customise client construction and model/deployment name resolution.
168
+ """
169
+
170
+ def __init__(self, config: ApiConfig):
171
+ self.config = config
172
+ self.client = self._create_client(config)
173
+
174
+ def _create_client(self, config: ApiConfig) -> Any:
175
+ """Create the async OpenAI client. Override in subclasses."""
176
+ import openai
177
+
178
+ api_key: str | None = None
179
+ if config.api_key_env:
180
+ api_key = os.environ.get(config.api_key_env)
181
+ if not api_key:
182
+ api_key = os.environ.get("OPENAI_API_KEY")
183
+
184
+ kwargs: dict[str, Any] = {}
185
+ if api_key:
186
+ kwargs["api_key"] = api_key
187
+ if config.base_url:
188
+ kwargs["base_url"] = config.base_url
189
+ if config.http_headers:
190
+ kwargs["default_headers"] = config.http_headers
191
+
192
+ from crabcode_core.api.network import sdk_options
193
+ return openai.AsyncOpenAI(**kwargs, **sdk_options(config))
194
+
195
+ def _resolve_model(self, config: ModelConfig) -> str:
196
+ """Resolve the model name for the API call. Override in subclasses."""
197
+ return config.model or self.config.model or "gpt-4o"
198
+
199
+ async def stream_message(
200
+ self,
201
+ messages: list[Message],
202
+ system: list[str],
203
+ tools: list[dict[str, Any]],
204
+ config: ModelConfig,
205
+ ) -> AsyncGenerator[StreamChunk, None]:
206
+ model = self._resolve_model(config)
207
+
208
+ params: dict[str, Any] = {
209
+ "model": model,
210
+ "max_tokens": config.max_tokens,
211
+ "messages": _messages_to_openai(
212
+ messages,
213
+ system,
214
+ pass_reasoning_content=self.config.pass_reasoning_content,
215
+ ),
216
+ "stream": True,
217
+ "stream_options": {"include_usage": True},
218
+ }
219
+
220
+ api_tools = _tools_to_openai(tools)
221
+ if api_tools:
222
+ params["tools"] = api_tools
223
+
224
+ if config.temperature is not None:
225
+ params["temperature"] = config.temperature
226
+
227
+ extra_body = safe_utf8_json_tree(dict(self.config.extra_body or {}))
228
+ if extra_body:
229
+ params["extra_body"] = extra_body
230
+
231
+ tool_call_buffers: dict[int, dict[str, str]] = {}
232
+
233
+ stream = await self.client.chat.completions.create(**params)
234
+ async for chunk in stream:
235
+ if not chunk.choices:
236
+ if chunk.usage:
237
+ yield StreamChunk(
238
+ type="message_delta",
239
+ usage=normalize_openai_usage(chunk.usage),
240
+ model_id=str(getattr(chunk, "model", "") or ""),
241
+ )
242
+ continue
243
+
244
+ delta = chunk.choices[0].delta
245
+ finish_reason = chunk.choices[0].finish_reason
246
+
247
+ reasoning_content = getattr(delta, "reasoning_content", None)
248
+ if reasoning_content:
249
+ yield StreamChunk(
250
+ type="thinking",
251
+ text=safe_utf8_str(reasoning_content),
252
+ )
253
+
254
+ if delta.content:
255
+ yield StreamChunk(type="text", text=safe_utf8_str(delta.content))
256
+
257
+ if delta.tool_calls:
258
+ for tc in delta.tool_calls:
259
+ idx = tc.index
260
+ if idx not in tool_call_buffers:
261
+ tool_call_buffers[idx] = {
262
+ "id": tc.id or "",
263
+ "name": "",
264
+ "arguments": "",
265
+ }
266
+ buf = tool_call_buffers[idx]
267
+ if tc.id:
268
+ buf["id"] = tc.id
269
+ if tc.function:
270
+ if tc.function.name:
271
+ buf["name"] = tc.function.name
272
+ yield StreamChunk(
273
+ type="tool_use_start",
274
+ tool_use_id=buf["id"],
275
+ tool_name=buf["name"],
276
+ )
277
+ if tc.function.arguments:
278
+ buf["arguments"] += tc.function.arguments
279
+ yield StreamChunk(
280
+ type="tool_use_delta",
281
+ tool_use_id=buf["id"],
282
+ tool_input_json=tc.function.arguments,
283
+ )
284
+
285
+ if finish_reason == "tool_calls":
286
+ for buf in tool_call_buffers.values():
287
+ yield StreamChunk(
288
+ type="tool_use_end",
289
+ tool_use_id=buf["id"],
290
+ tool_name=buf["name"],
291
+ tool_input_json=buf["arguments"],
292
+ )
293
+ tool_call_buffers.clear()
294
+
295
+ if finish_reason:
296
+ # In particular, length is a completed but truncated response.
297
+ # Preserve it so the query loop reports output_limit instead
298
+ # of mistaking the end of the iterator for a successful turn.
299
+ # Keep consuming the stream for the separate usage-only chunk.
300
+ usage = normalize_openai_usage(chunk.usage) if chunk.usage else {}
301
+ yield StreamChunk(
302
+ type="message_stop",
303
+ stop_reason={"stop": "end_turn", "tool_calls": "tool_use"}.get(
304
+ finish_reason, finish_reason,
305
+ ),
306
+ usage=usage,
307
+ model_id=str(getattr(chunk, "model", "") or ""),
308
+ )
309
+
310
+ async def count_tokens(
311
+ self,
312
+ messages: list[Message],
313
+ system: list[str],
314
+ ) -> int:
315
+ total = sum(len(s) for s in system)
316
+ for msg in messages:
317
+ if isinstance(msg.content, str):
318
+ total += len(msg.content)
319
+ else:
320
+ for block in msg.content:
321
+ if isinstance(block, TextBlock):
322
+ total += len(block.text)
323
+ return total // 4
@@ -0,0 +1,58 @@
1
+ """API adapter factory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from crabcode_core.api.base import APIAdapter
6
+ from crabcode_core.types.config import ApiConfig
7
+
8
+
9
+ def create_adapter(config: ApiConfig) -> APIAdapter:
10
+ """Create an API adapter based on configuration."""
11
+ provider = config.provider or "anthropic"
12
+
13
+ if provider == "anthropic":
14
+ from crabcode_core.api.anthropic_adapter import AnthropicAdapter
15
+ return AnthropicAdapter(config)
16
+ elif provider == "bedrock":
17
+ from crabcode_core.api.anthropic_adapter import BedrockAdapter
18
+ return BedrockAdapter(config)
19
+ elif provider == "vertex":
20
+ from crabcode_core.api.anthropic_adapter import VertexAdapter
21
+ return VertexAdapter(config)
22
+ elif provider == "openai":
23
+ from crabcode_core.api.openai_adapter import OpenAIAdapter
24
+ return OpenAIAdapter(config)
25
+ elif provider == "azure":
26
+ from crabcode_core.api.azure_adapter import AzureOpenAIAdapter
27
+ return AzureOpenAIAdapter(config)
28
+ elif provider == "gemini":
29
+ from crabcode_core.api.gemini_adapter import GeminiAdapter
30
+ return GeminiAdapter(config)
31
+ elif provider == "ollama":
32
+ from crabcode_core.api.ollama_adapter import OllamaAdapter
33
+ return OllamaAdapter(config)
34
+ elif provider == "codex":
35
+ from crabcode_core.api.codex_adapter import CodexAdapter
36
+ return CodexAdapter(config)
37
+ elif provider == "router":
38
+ fmt = config.format or "openai"
39
+ if fmt == "anthropic":
40
+ from crabcode_core.api.anthropic_adapter import AnthropicAdapter
41
+ return AnthropicAdapter(config)
42
+ elif fmt == "codex":
43
+ from crabcode_core.api.codex_adapter import CodexAdapter
44
+ return CodexAdapter(config)
45
+ elif fmt == "ollama":
46
+ from crabcode_core.api.ollama_adapter import OllamaAdapter
47
+ return OllamaAdapter(config)
48
+ elif fmt == "gemini":
49
+ from crabcode_core.api.gemini_adapter import GeminiAdapter
50
+ return GeminiAdapter(config)
51
+ elif fmt == "azure":
52
+ from crabcode_core.api.azure_adapter import AzureOpenAIAdapter
53
+ return AzureOpenAIAdapter(config)
54
+ else:
55
+ from crabcode_core.api.openai_adapter import OpenAIAdapter
56
+ return OpenAIAdapter(config)
57
+ else:
58
+ raise ValueError(f"Unknown API provider: {provider}")
@@ -0,0 +1,18 @@
1
+ """Slash-command argument parsing, preserving Windows path separators."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shlex
7
+
8
+
9
+ def split_command_arguments(value: str) -> list[str]:
10
+ if os.name != "nt":
11
+ return shlex.split(value)
12
+ lexer = shlex.shlex(value, posix=True)
13
+ lexer.whitespace_split = True
14
+ lexer.commenters = ""
15
+ # Slash commands are not a shell: backslashes in paths are literal,
16
+ # including a trailing separator inside quotes. Both quote styles work.
17
+ lexer.escape = ""
18
+ return list(lexer)
@@ -0,0 +1,17 @@
1
+ from crabcode_core.compact.compact import (
2
+ compact_summary_text,
3
+ compact_conversation,
4
+ compaction_input_limit,
5
+ conversation_turn_starts,
6
+ estimate_token_count,
7
+ should_auto_compact,
8
+ )
9
+
10
+ __all__ = [
11
+ "compact_conversation",
12
+ "compact_summary_text",
13
+ "compaction_input_limit",
14
+ "conversation_turn_starts",
15
+ "estimate_token_count",
16
+ "should_auto_compact",
17
+ ]