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,132 @@
|
|
|
1
|
+
"""Provider error diagnostics shared by CLI and Gateway-backed clients."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from crabcode_core.logging_utils import get_logger
|
|
11
|
+
from crabcode_core.utf8_sanitize import safe_utf8_str
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
_CREDENTIAL_KEYS = {
|
|
16
|
+
"authorization", "proxyauthorization", "apikey", "xapikey", "token",
|
|
17
|
+
"accesstoken", "refreshtoken", "idtoken", "password", "secret",
|
|
18
|
+
"clientsecret", "cookie", "setcookie",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _credential_key(key: str) -> bool:
|
|
23
|
+
return re.sub(r"[^a-z0-9]", "", key.lower()) in _CREDENTIAL_KEYS
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def response_request_id(response: Any) -> str | None:
|
|
27
|
+
headers = getattr(response, "headers", {})
|
|
28
|
+
return next((headers.get(key) for key in (
|
|
29
|
+
"x-request-id", "request-id", "x-amzn-requestid",
|
|
30
|
+
) if headers.get(key)), None)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _exception_details(exc: BaseException) -> list[dict[str, Any]]:
|
|
34
|
+
details = []
|
|
35
|
+
pending = [exc]
|
|
36
|
+
seen: set[int] = set()
|
|
37
|
+
while pending:
|
|
38
|
+
current = pending.pop()
|
|
39
|
+
if id(current) in seen:
|
|
40
|
+
continue
|
|
41
|
+
seen.add(id(current))
|
|
42
|
+
response = getattr(current, "response", None)
|
|
43
|
+
body = getattr(current, "body", None)
|
|
44
|
+
# HTTP errors may contain an envelope beyond SDK exception.body.
|
|
45
|
+
# Only inspect an already-read response; never consume a live stream.
|
|
46
|
+
if response is not None:
|
|
47
|
+
try:
|
|
48
|
+
body = response.text or body
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
details.append({
|
|
52
|
+
"exception": type(current).__name__,
|
|
53
|
+
# SDK messages often repeat body as a Python repr, which would
|
|
54
|
+
# bypass structured credential redaction. The body retains it.
|
|
55
|
+
"message": str(current) if body is None else None,
|
|
56
|
+
"status_code": getattr(current, "status_code", None)
|
|
57
|
+
or getattr(response, "status_code", None),
|
|
58
|
+
"request_id": getattr(current, "request_id", None)
|
|
59
|
+
or response_request_id(response),
|
|
60
|
+
"body": body,
|
|
61
|
+
})
|
|
62
|
+
pending.extend(item for item in (current.__context__, current.__cause__)
|
|
63
|
+
if item is not None)
|
|
64
|
+
return details
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _redact(value: Any, secrets: set[str]) -> Any:
|
|
68
|
+
if hasattr(value, "model_dump"):
|
|
69
|
+
value = value.model_dump(mode="json")
|
|
70
|
+
if isinstance(value, dict):
|
|
71
|
+
return {str(key): "[REDACTED]" if _credential_key(str(key))
|
|
72
|
+
else _redact(item, secrets) for key, item in value.items()}
|
|
73
|
+
if isinstance(value, (list, tuple)):
|
|
74
|
+
return [_redact(item, secrets) for item in value]
|
|
75
|
+
if isinstance(value, bytes):
|
|
76
|
+
value = value.decode("utf-8", "replace")
|
|
77
|
+
if isinstance(value, str):
|
|
78
|
+
# Raw HTTP bodies can be JSON too. Preserve every diagnostic field.
|
|
79
|
+
try:
|
|
80
|
+
parsed = json.loads(value)
|
|
81
|
+
except (ValueError, TypeError):
|
|
82
|
+
parsed = None
|
|
83
|
+
if isinstance(parsed, (dict, list)):
|
|
84
|
+
return _redact(parsed, secrets)
|
|
85
|
+
for secret in sorted(secrets, key=len, reverse=True):
|
|
86
|
+
value = value.replace(secret, "[REDACTED]")
|
|
87
|
+
return re.sub(r"(?i)\bBearer\s+[^\s\"'<>]+", "Bearer [REDACTED]", value)
|
|
88
|
+
return value
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def log_api_error(
|
|
92
|
+
adapter: Any, *, session_id: str | None, model: str, attempt_id: str,
|
|
93
|
+
purpose: str, exception: BaseException | None = None, chunk: Any = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Log the full error body without dumping requests or credential headers.
|
|
96
|
+
|
|
97
|
+
Diagnostics must never replace the original error or change retry behavior.
|
|
98
|
+
Bodies are not truncated; credentials are the only redacted fields.
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
config = getattr(adapter, "config", None)
|
|
102
|
+
secrets = set()
|
|
103
|
+
for name in (getattr(config, "api_key_env", None),
|
|
104
|
+
"OPENAI_API_KEY", "ANTHROPIC_API_KEY"):
|
|
105
|
+
if name and os.environ.get(name):
|
|
106
|
+
secrets.add(os.environ[name])
|
|
107
|
+
for value in (getattr(adapter, "_api_key", None),
|
|
108
|
+
getattr(getattr(adapter, "client", None), "api_key", None)):
|
|
109
|
+
if isinstance(value, str) and value:
|
|
110
|
+
secrets.add(value)
|
|
111
|
+
for key, value in (getattr(config, "http_headers", None) or {}).items():
|
|
112
|
+
if _credential_key(key) and value:
|
|
113
|
+
secrets.add(value)
|
|
114
|
+
if value.lower().startswith("bearer "):
|
|
115
|
+
secrets.add(value[7:])
|
|
116
|
+
details = {
|
|
117
|
+
"session_id": session_id, "model": model,
|
|
118
|
+
"provider": getattr(config, "provider", None) or type(adapter).__name__,
|
|
119
|
+
"attempt_id": attempt_id, "purpose": purpose,
|
|
120
|
+
"errors": _exception_details(exception) if exception is not None else [{
|
|
121
|
+
"message": chunk.error if chunk.error_body is None else None,
|
|
122
|
+
"status_code": chunk.error_status_code,
|
|
123
|
+
"request_id": chunk.error_request_id,
|
|
124
|
+
"body": chunk.error_body,
|
|
125
|
+
}],
|
|
126
|
+
}
|
|
127
|
+
logger.warning("API error details: %s", safe_utf8_str(json.dumps(
|
|
128
|
+
_redact(details, secrets), ensure_ascii=False, default=str,
|
|
129
|
+
)))
|
|
130
|
+
except Exception:
|
|
131
|
+
logger.warning("Unable to serialize API error details: session=%s attempt=%s",
|
|
132
|
+
session_id, attempt_id)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Gemini API adapter — uses Google's OpenAI-compatible endpoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from crabcode_core.api.openai_adapter import OpenAIAdapter
|
|
6
|
+
from crabcode_core.types.config import ApiConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GeminiAdapter(OpenAIAdapter):
|
|
10
|
+
"""Adapter for Google Gemini via its OpenAI-compatible endpoint.
|
|
11
|
+
|
|
12
|
+
Google Gemini has provided an OpenAI-compatible endpoint since late 2024
|
|
13
|
+
(``https://generativelanguage.googleapis.com/v1beta/openai/``), so we
|
|
14
|
+
simply inherit from :class:`OpenAIAdapter` and pre-fill the base URL,
|
|
15
|
+
API key environment variable, and disable thinking (not supported on the
|
|
16
|
+
OpenAI-compatible surface).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config: ApiConfig):
|
|
20
|
+
if not config.base_url:
|
|
21
|
+
config = config.model_copy(update={"base_url": "https://generativelanguage.googleapis.com/v1beta/openai/"})
|
|
22
|
+
if not config.api_key_env:
|
|
23
|
+
config = config.model_copy(update={"api_key_env": "GEMINI_API_KEY"})
|
|
24
|
+
if config.thinking_enabled is True:
|
|
25
|
+
config = config.model_copy(update={"thinking_enabled": False})
|
|
26
|
+
super().__init__(config)
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Known model context window sizes and resolution helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
# Maps model ID (or prefix) to context window size in tokens.
|
|
6
|
+
# Used as fallback when the API doesn't provide model metadata.
|
|
7
|
+
KNOWN_CONTEXT_WINDOWS: dict[str, int] = {
|
|
8
|
+
# Anthropic
|
|
9
|
+
"claude-mythos-5-1": 1_000_000,
|
|
10
|
+
"claude-fable-5-1": 1_000_000,
|
|
11
|
+
"claude-mythos-preview": 1_000_000,
|
|
12
|
+
"claude-mythos-5": 1_000_000,
|
|
13
|
+
"claude-fable-5": 1_000_000,
|
|
14
|
+
"claude-opus-5-5": 1_000_000,
|
|
15
|
+
"claude-sonnet-5-5": 1_000_000,
|
|
16
|
+
"claude-haiku-5-5": 1_000_000,
|
|
17
|
+
"claude-opus-5": 1_000_000,
|
|
18
|
+
"claude-sonnet-5": 1_000_000,
|
|
19
|
+
"claude-opus-4-8": 1_000_000,
|
|
20
|
+
"claude-opus-4-7": 1_000_000,
|
|
21
|
+
"claude-opus-4-6": 1_000_000,
|
|
22
|
+
"claude-sonnet-4-6": 1_000_000,
|
|
23
|
+
"claude-opus-4-5": 200_000,
|
|
24
|
+
"claude-sonnet-4-5": 200_000,
|
|
25
|
+
"claude-haiku-4-5": 200_000,
|
|
26
|
+
"claude-sonnet-4-20250514": 200_000,
|
|
27
|
+
"claude-opus-4-20250514": 200_000,
|
|
28
|
+
"claude-haiku-3-5-20241022": 200_000,
|
|
29
|
+
"claude-3-5-sonnet-20241022": 200_000,
|
|
30
|
+
"claude-3-5-haiku-20241022": 200_000,
|
|
31
|
+
"claude-3-opus-20240229": 200_000,
|
|
32
|
+
"claude-3-sonnet-20240229": 200_000,
|
|
33
|
+
"claude-3-haiku-20240307": 200_000,
|
|
34
|
+
# OpenAI
|
|
35
|
+
"gpt-daybreak-blue-latest": 1_050_000,
|
|
36
|
+
"gpt-daybreak-red-latest": 400_000,
|
|
37
|
+
"gpt-6-astra": 1_050_000,
|
|
38
|
+
"gpt-6.1-sol": 1_050_000,
|
|
39
|
+
"gpt-6-sol": 1_050_000,
|
|
40
|
+
"gpt-6-luna": 1_050_000,
|
|
41
|
+
"gpt-5.6-cyber": 400_000,
|
|
42
|
+
"gpt-5.6-sol": 1_050_000,
|
|
43
|
+
"gpt-5.6-terra": 1_050_000,
|
|
44
|
+
"gpt-5.6-luna": 1_050_000,
|
|
45
|
+
"gpt-5.6": 1_050_000,
|
|
46
|
+
"gpt-5.5-pro": 1_050_000,
|
|
47
|
+
"gpt-5.5": 1_050_000,
|
|
48
|
+
"gpt-5.4-mini": 400_000,
|
|
49
|
+
"gpt-5.4-nano": 400_000,
|
|
50
|
+
"gpt-5.4-pro": 1_050_000,
|
|
51
|
+
"gpt-5.4": 1_050_000,
|
|
52
|
+
"gpt-5.3-chat-latest": 128_000,
|
|
53
|
+
"gpt-5.3-codex": 400_000,
|
|
54
|
+
"gpt-5.2-chat-latest": 128_000,
|
|
55
|
+
"gpt-5.2-codex": 400_000,
|
|
56
|
+
"gpt-5.2-pro": 400_000,
|
|
57
|
+
"gpt-5.2": 400_000,
|
|
58
|
+
"gpt-5.1-chat-latest": 128_000,
|
|
59
|
+
"gpt-5.1-codex-mini": 400_000,
|
|
60
|
+
"gpt-5.1-codex-max": 400_000,
|
|
61
|
+
"gpt-5.1-codex": 400_000,
|
|
62
|
+
"gpt-5.1": 400_000,
|
|
63
|
+
"gpt-5-chat-latest": 128_000,
|
|
64
|
+
"gpt-5-codex": 400_000,
|
|
65
|
+
"gpt-5-mini": 400_000,
|
|
66
|
+
"gpt-5-nano": 400_000,
|
|
67
|
+
"gpt-5-pro": 400_000,
|
|
68
|
+
"gpt-5": 400_000,
|
|
69
|
+
"gpt-4.5-preview": 128_000,
|
|
70
|
+
"gpt-4.1-mini": 1_047_576,
|
|
71
|
+
"gpt-4.1-nano": 1_047_576,
|
|
72
|
+
"gpt-4.1": 1_047_576,
|
|
73
|
+
"gpt-4o-mini-realtime-preview": 16_000,
|
|
74
|
+
"gpt-4o-mini-transcribe": 16_000,
|
|
75
|
+
"gpt-4o-realtime-preview": 32_000,
|
|
76
|
+
"gpt-4o-transcribe-diarize": 16_000,
|
|
77
|
+
"gpt-4o-transcribe": 16_000,
|
|
78
|
+
"gpt-4o-mini": 128_000,
|
|
79
|
+
"gpt-4o": 128_000,
|
|
80
|
+
"gpt-4-turbo": 128_000,
|
|
81
|
+
"gpt-4": 8_192,
|
|
82
|
+
"o1-preview": 128_000,
|
|
83
|
+
"o1-mini": 128_000,
|
|
84
|
+
"o1-pro": 200_000,
|
|
85
|
+
"o1": 200_000,
|
|
86
|
+
"o3-deep-research": 200_000,
|
|
87
|
+
"o3-mini": 200_000,
|
|
88
|
+
"o3-pro": 200_000,
|
|
89
|
+
"o3": 200_000,
|
|
90
|
+
"o4-mini-deep-research": 200_000,
|
|
91
|
+
"o4-mini": 200_000,
|
|
92
|
+
"codex-mini-latest": 200_000,
|
|
93
|
+
# xAI
|
|
94
|
+
"grok-4.7": 500_000,
|
|
95
|
+
"grok-4.20": 1_000_000,
|
|
96
|
+
"grok-4.6": 500_000,
|
|
97
|
+
"grok-4.5": 500_000,
|
|
98
|
+
"grok-4.3": 1_000_000,
|
|
99
|
+
"grok-build-latest": 500_000,
|
|
100
|
+
"grok-build-0.1": 256_000,
|
|
101
|
+
"grok-code-fast": 256_000,
|
|
102
|
+
# DeepSeek
|
|
103
|
+
"deepseek-flash": 1_000_000,
|
|
104
|
+
"deepseek-v4-pro": 1_000_000,
|
|
105
|
+
"deepseek-v4-flash": 1_000_000,
|
|
106
|
+
"deepseek-v4.1": 1_000_000,
|
|
107
|
+
"deep-research-preview-04-2026": 1_048_576,
|
|
108
|
+
"deep-research-max-preview-04-2026": 1_048_576,
|
|
109
|
+
"antigravity-preview-09-2026": 1_048_576,
|
|
110
|
+
"deepseek-v4.1-flash": 1_000_000,
|
|
111
|
+
"deepseek-chat": 128_000,
|
|
112
|
+
"deepseek-reasoner": 128_000,
|
|
113
|
+
# GLM (Zhipu)
|
|
114
|
+
"glm-5.3-flash": 1_000_000,
|
|
115
|
+
"glm-5.3": 1_000_000,
|
|
116
|
+
"glm-5.2": 1_000_000,
|
|
117
|
+
"glm-5.1-fp8": 202_752,
|
|
118
|
+
"glm-5.1": 202_752,
|
|
119
|
+
"glm-4-plus": 128_000,
|
|
120
|
+
"glm-4": 128_000,
|
|
121
|
+
# Other OpenAI-compatible models
|
|
122
|
+
"qwen3.8-max": 1_000_000,
|
|
123
|
+
"qwen3.8-flash": 1_000_000,
|
|
124
|
+
"qwen3.7-plus": 1_000_000,
|
|
125
|
+
"qwen3.7-flash": 1_000_000,
|
|
126
|
+
"qwen3.6-plus": 1_000_000,
|
|
127
|
+
"qwen3.6-flash": 1_000_000,
|
|
128
|
+
"qwen3.5-plus": 1_000_000,
|
|
129
|
+
"qwen3.5-flash": 1_000_000,
|
|
130
|
+
"kimi-k3": 1_000_000,
|
|
131
|
+
"kimi-k2.7-code": 262_144,
|
|
132
|
+
"kimi-k2.6": 262_144,
|
|
133
|
+
"kimi-k2.5": 262_144,
|
|
134
|
+
"moonshot-v1-128k": 128_000,
|
|
135
|
+
"moonshot-v1-32k": 32_000,
|
|
136
|
+
"moonshot-v1-8k": 8_000,
|
|
137
|
+
"MiniMax-M3": 1_000_000,
|
|
138
|
+
"MiniMax-M2.7-highspeed": 204_800,
|
|
139
|
+
"MiniMax-M2.7": 204_800,
|
|
140
|
+
"minimax-m2.7": 204_800,
|
|
141
|
+
"skyclaw-v1-lite": 1_000_000,
|
|
142
|
+
"skyclaw-v1": 1_000_000,
|
|
143
|
+
# Meta
|
|
144
|
+
"Llama-4-Maverick-17B-128E-Instruct": 1_000_000,
|
|
145
|
+
"Llama-4-Scout-17B-16E-Instruct": 10_000_000,
|
|
146
|
+
"Llama-3.3": 128_000,
|
|
147
|
+
"Llama-3.2": 128_000,
|
|
148
|
+
"Llama-3.1": 128_000,
|
|
149
|
+
"Llama-3": 8_000,
|
|
150
|
+
"Llama-2": 4_000,
|
|
151
|
+
"Llama-1": 2_048,
|
|
152
|
+
"Llama-4-Maverick-17B-128E-Instruct": 1_000_000,
|
|
153
|
+
"Llama-4-Scout-17B-16E-Instruct": 10_000_000,
|
|
154
|
+
"muse-spark-1.3-contributor": 1_000_000,
|
|
155
|
+
"muse-spark-1.3": 1_000_000,
|
|
156
|
+
"mimo-v2.6-pro": 1_000_000,
|
|
157
|
+
"mimo-v2.6-pro-ultraspeed": 1_000_000,
|
|
158
|
+
"mimo-v2.6-flash": 1_000_000,
|
|
159
|
+
"mimo-v2.5": 1_000_000,
|
|
160
|
+
"mimo-v2.5-pro": 1_000_000,
|
|
161
|
+
"mimo-v2.5-asr": 8_000,
|
|
162
|
+
"mimo-v2.5-tts": 8_000,
|
|
163
|
+
"mimo-v2-flash": 256_000,
|
|
164
|
+
"mimo-v2-pro": 1_000_000,
|
|
165
|
+
"mimo-v2-omni": 256_000,
|
|
166
|
+
"mimo-7b": 32_768,
|
|
167
|
+
"IFM/K2-Horizon-375B-A23B": 524_288,
|
|
168
|
+
"IFM/K2-Horizon-MoVA-36B-A4B": 524_288,
|
|
169
|
+
"IFM/K2-Horizon-32B": 524_288,
|
|
170
|
+
"IFM/K2-Horizon-7B-Uno": 524_288,
|
|
171
|
+
"IFM/K2-Horizon-7B": 524_288,
|
|
172
|
+
"IFM/K2-Horizon-3.7B": 524_288,
|
|
173
|
+
"IFM/K2-Horizon-0.9B": 131_072,
|
|
174
|
+
"thinkingmachines/Inkling": 1_048_576,
|
|
175
|
+
"thinkingmachines/Inkling-Small": 1_048_576,
|
|
176
|
+
"inkling": 1_048_576,
|
|
177
|
+
"inkling-small": 1_048_576,
|
|
178
|
+
"NVIDIA-Nemotron-3-Ultra-550B-A55B": 1_000_000,
|
|
179
|
+
"NVIDIA-Nemotron-3.5-Lightning-30B-A3B": 1_000_000,
|
|
180
|
+
"NVIDIA-Nemotron-3-Nano-30B-A3B": 1_000_000,
|
|
181
|
+
"Nemotron-Cascade-2-30B-A3B": 1_000_000,
|
|
182
|
+
"NVIDIA-Nemotron-Nano-12B-v2-VL": 128_000,
|
|
183
|
+
"NVIDIA-Nemotron-Nano-12B-v2": 128_000,
|
|
184
|
+
"Llama-3.1-Nemotron-Nano-4B-v1.1": 131_072,
|
|
185
|
+
"Nemotron-H-4B-Instruct-128K": 128_000,
|
|
186
|
+
"Nemotron-Mini-4B-Instruct": 4_096,
|
|
187
|
+
"Nemotron-4-340B-Instruct": 4_096,
|
|
188
|
+
"nemotron-3-8b-base-4k": 4_096,
|
|
189
|
+
# Mistral
|
|
190
|
+
"mistral-medium-3-5": 256_000,
|
|
191
|
+
"mistral-medium-3": 256_000,
|
|
192
|
+
"mistral-medium-latest": 256_000,
|
|
193
|
+
"mistral-small-2603": 256_000,
|
|
194
|
+
"mistral-small-latest": 256_000,
|
|
195
|
+
"mistral-large-2512": 256_000,
|
|
196
|
+
"mistral-large-latest": 256_000,
|
|
197
|
+
"Mixtral-8x22B-Instruct-v0.1": 64_000,
|
|
198
|
+
"Mixtral-8x22B-v0.1": 64_000,
|
|
199
|
+
"Mixtral-8x7B-Instruct-v0.1": 32_000,
|
|
200
|
+
"Mixtral-8x7B-v0.1": 32_000,
|
|
201
|
+
"Mixtral-8x22B-Instruct-v0.1": 64_000,
|
|
202
|
+
"Mixtral-8x22B-v0.1": 64_000,
|
|
203
|
+
"Mixtral-8x7B-Instruct-v0.1": 32_000,
|
|
204
|
+
"Mixtral-8x7B-v0.1": 32_000,
|
|
205
|
+
"open-mixtral-8x22b": 64_000,
|
|
206
|
+
"open-mixtral-8x7b": 32_000,
|
|
207
|
+
# Ollama
|
|
208
|
+
"qwen3:32b": 128_000,
|
|
209
|
+
"qwen2.5-coder:32b": 128_000,
|
|
210
|
+
"deepseek-coder-v2": 128_000,
|
|
211
|
+
"llama3.1:8b": 128_000,
|
|
212
|
+
"codellama": 16_000,
|
|
213
|
+
"mistral": 32_000,
|
|
214
|
+
"mixtral": 32_000,
|
|
215
|
+
# Gemini
|
|
216
|
+
"gemini-3.8-live-extended-thinking": 131_072,
|
|
217
|
+
"gemini-3.8-live": 131_072,
|
|
218
|
+
"gemini-3.8-flash": 1_048_576,
|
|
219
|
+
"gemini-3.8-flash-tts": 1_048_576,
|
|
220
|
+
"gemini-3.8-flash-lite-tts": 1_048_576,
|
|
221
|
+
"gemini-nano-banana-2.1": 1_048_576,
|
|
222
|
+
"gemini-3.7-flash": 1_048_576,
|
|
223
|
+
"gemini-3.6-flash": 1_048_576,
|
|
224
|
+
"gemini-3.5-flash-lite": 1_048_576,
|
|
225
|
+
"gemini-3.5-flash": 1_048_576,
|
|
226
|
+
"gemini-3.1-pro": 1_048_576,
|
|
227
|
+
"gemini-3.1-flash-lite": 1_048_576,
|
|
228
|
+
"gemini-3-flash": 1_048_576,
|
|
229
|
+
"gemini-2.5-pro": 1_048_576,
|
|
230
|
+
"gemini-2.5-flash-lite": 1_048_576,
|
|
231
|
+
"gemini-2.5-flash": 1_048_576,
|
|
232
|
+
"gemini-2.0-flash-lite": 1_048_576,
|
|
233
|
+
"gemini-2.0-flash": 1_048_576,
|
|
234
|
+
"gemini-1.5-pro": 2_097_152,
|
|
235
|
+
"gemini-1.5-flash": 1_048_576,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
DEFAULT_CONTEXT_WINDOW = 200_000
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def lookup_context_window(model: str | None) -> int | None:
|
|
242
|
+
"""Look up the context window for a model ID.
|
|
243
|
+
|
|
244
|
+
Tries exact match first, then prefix matching for versioned model IDs
|
|
245
|
+
(e.g. "gpt-4o-2024-11-20" falls back to "gpt-4o").
|
|
246
|
+
"""
|
|
247
|
+
if not model:
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
if model in KNOWN_CONTEXT_WINDOWS:
|
|
251
|
+
return KNOWN_CONTEXT_WINDOWS[model]
|
|
252
|
+
|
|
253
|
+
for known_model in sorted(KNOWN_CONTEXT_WINDOWS, key=len, reverse=True):
|
|
254
|
+
if model.startswith((f"{known_model}-", f"{known_model}:")):
|
|
255
|
+
return KNOWN_CONTEXT_WINDOWS[known_model]
|
|
256
|
+
|
|
257
|
+
return None
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Shared HTTP routing policy. Never fall back from a proxy to direct access."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import ssl
|
|
5
|
+
from typing import Any
|
|
6
|
+
from urllib.parse import urlsplit
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def http_options(config: Any) -> dict[str, Any]:
|
|
12
|
+
mode = getattr(config, "network_mode", "inherit")
|
|
13
|
+
if mode == "inherit":
|
|
14
|
+
return {}
|
|
15
|
+
if mode == "direct":
|
|
16
|
+
return {"trust_env": False}
|
|
17
|
+
proxy = getattr(config, "proxy_url", None)
|
|
18
|
+
try:
|
|
19
|
+
parsed = urlsplit(proxy or "")
|
|
20
|
+
valid = parsed.scheme in {"http", "https"} and bool(parsed.hostname) and parsed.port != 0
|
|
21
|
+
valid = valid and not parsed.username and not parsed.password
|
|
22
|
+
except ValueError:
|
|
23
|
+
valid = False
|
|
24
|
+
if not valid:
|
|
25
|
+
raise ValueError("Proxy mode requires an HTTP(S) proxy URL without embedded credentials")
|
|
26
|
+
return {"trust_env": False, "proxy": proxy}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def request_timeout(config: Any) -> httpx.Timeout:
|
|
30
|
+
timeout = float(getattr(config, "timeout", 300))
|
|
31
|
+
return httpx.Timeout(timeout, connect=min(timeout, 10.0))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def sdk_options(config: Any) -> dict[str, Any]:
|
|
35
|
+
# The query loop owns retry budgets; nested SDK retries hide progress.
|
|
36
|
+
options: dict[str, Any] = {"max_retries": 0, "timeout": request_timeout(config)}
|
|
37
|
+
routing = http_options(config)
|
|
38
|
+
if routing:
|
|
39
|
+
options["http_client"] = httpx.AsyncClient(**routing, timeout=request_timeout(config))
|
|
40
|
+
return options
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def certificate_failure(exc: BaseException) -> bool:
|
|
44
|
+
seen: set[int] = set()
|
|
45
|
+
pending = [exc]
|
|
46
|
+
while pending:
|
|
47
|
+
current = pending.pop()
|
|
48
|
+
if id(current) in seen:
|
|
49
|
+
continue
|
|
50
|
+
seen.add(id(current))
|
|
51
|
+
if isinstance(current, ssl.SSLCertVerificationError) or "CERTIFICATE_VERIFY_FAILED" in str(current):
|
|
52
|
+
return True
|
|
53
|
+
pending.extend(item for item in (current.__cause__, current.__context__) if item is not None)
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def network_error_message(exc: BaseException, config: Any) -> str:
|
|
58
|
+
if certificate_failure(exc):
|
|
59
|
+
return "模型连接证书校验失败,请检查证书或网络拦截;未自动重试"
|
|
60
|
+
mode = getattr(config, "network_mode", "inherit")
|
|
61
|
+
route = {"inherit": "继承进程代理环境", "direct": "直连", "proxy": "指定代理"}.get(mode, mode)
|
|
62
|
+
return f"模型连接中断({type(exc).__name__},{route})。请检查网络或代理状态后重试;更改系统环境变量需重启 Gateway。"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Ollama API adapter — delegates to OpenAIAdapter with Ollama defaults."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from crabcode_core.api.openai_adapter import OpenAIAdapter
|
|
6
|
+
from crabcode_core.types.config import ApiConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OllamaAdapter(OpenAIAdapter):
|
|
10
|
+
"""Adapter for Ollama via its OpenAI-compatible Chat Completions API.
|
|
11
|
+
|
|
12
|
+
Ollama natively exposes an OpenAI-compatible endpoint at
|
|
13
|
+
``http://localhost:11434/v1`` and does not require a real API key,
|
|
14
|
+
but the OpenAI SDK still expects *some* value for ``api_key``.
|
|
15
|
+
This adapter pre-fills those defaults and disables extended thinking
|
|
16
|
+
(local models do not support it).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config: ApiConfig):
|
|
20
|
+
# Pre-fill Ollama defaults before handing off to OpenAIAdapter
|
|
21
|
+
if not config.base_url:
|
|
22
|
+
config = config.model_copy(update={"base_url": "http://localhost:11434/v1"})
|
|
23
|
+
if not config.api_key_env:
|
|
24
|
+
config = config.model_copy(update={"api_key_env": "OLLAMA_API_KEY"})
|
|
25
|
+
if config.thinking_enabled is True:
|
|
26
|
+
config = config.model_copy(update={"thinking_enabled": False})
|
|
27
|
+
super().__init__(config)
|