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,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
|