correctover 1.3.1__cp312-none-any.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 (91) hide show
  1. correctover/__init__.py +310 -0
  2. correctover/_checkup.py +335 -0
  3. correctover/_checkup.pyc +0 -0
  4. correctover/_core.py +1557 -0
  5. correctover/_core.pyc +0 -0
  6. correctover/_device.py +294 -0
  7. correctover/_device.pyc +0 -0
  8. correctover/_engine.py +2779 -0
  9. correctover/_engine.pyc +0 -0
  10. correctover/_fixes.py +209 -0
  11. correctover/_fixes.pyc +0 -0
  12. correctover/_fixes2.py +178 -0
  13. correctover/_fixes2.pyc +0 -0
  14. correctover/_guard.py +169 -0
  15. correctover/_guard.pyc +0 -0
  16. correctover/_ping.py +296 -0
  17. correctover/_ping.pyc +0 -0
  18. correctover/_security.py +127 -0
  19. correctover/_security.pyc +0 -0
  20. correctover/_sentry.py +360 -0
  21. correctover/_sentry.pyc +0 -0
  22. correctover/_stats.py +519 -0
  23. correctover/_stats.pyc +0 -0
  24. correctover/_tracker.py +191 -0
  25. correctover/_tracker.pyc +0 -0
  26. correctover/_types.py +64 -0
  27. correctover/_types.pyc +0 -0
  28. correctover/_version.py +5 -0
  29. correctover/_version.pyc +0 -0
  30. correctover/benchmark/__init__.py +6 -0
  31. correctover/benchmark/fault_injector.py +129 -0
  32. correctover/benchmark/metrics_collector.py +222 -0
  33. correctover/benchmark/run_benchmark.py +211 -0
  34. correctover/benchmark/scenarios.py +123 -0
  35. correctover/carbon.py +451 -0
  36. correctover/carbon.pyc +0 -0
  37. correctover/chain.py +951 -0
  38. correctover/chain.pyc +0 -0
  39. correctover/checkpoint.py +805 -0
  40. correctover/checkpoint.pyc +0 -0
  41. correctover/classifier.py +192 -0
  42. correctover/classifier.pyc +0 -0
  43. correctover/cli.py +717 -0
  44. correctover/cli.pyc +0 -0
  45. correctover/client.py +400 -0
  46. correctover/client.pyc +0 -0
  47. correctover/cloudkill.py +446 -0
  48. correctover/cloudkill.pyc +0 -0
  49. correctover/cost_tracker.py +62 -0
  50. correctover/cost_tracker.pyc +0 -0
  51. correctover/dashboard.py +1157 -0
  52. correctover/dashboard.pyc +0 -0
  53. correctover/demo.py +466 -0
  54. correctover/demo.pyc +0 -0
  55. correctover/diagnoser.py +34 -0
  56. correctover/diagnoser.pyc +0 -0
  57. correctover/drift.py +262 -0
  58. correctover/drift.pyc +0 -0
  59. correctover/free_provider.py +186 -0
  60. correctover/free_provider.pyc +0 -0
  61. correctover/gateway.py +732 -0
  62. correctover/gateway.pyc +0 -0
  63. correctover/health_scorer.py +54 -0
  64. correctover/health_scorer.pyc +0 -0
  65. correctover/integrity.py +56 -0
  66. correctover/integrity.pyc +0 -0
  67. correctover/license.py +861 -0
  68. correctover/license.pyc +0 -0
  69. correctover/logging_config.py +84 -0
  70. correctover/logging_config.pyc +0 -0
  71. correctover/router.py +279 -0
  72. correctover/router.pyc +0 -0
  73. correctover/run.py +139 -0
  74. correctover/run.pyc +0 -0
  75. correctover/shield.py +366 -0
  76. correctover/shield.pyc +0 -0
  77. correctover/smart_router.py +13 -0
  78. correctover/smart_router.pyc +0 -0
  79. correctover/state_machine.py +84 -0
  80. correctover/state_machine.pyc +0 -0
  81. correctover/status.py +514 -0
  82. correctover/status.pyc +0 -0
  83. correctover/telemetry.py +308 -0
  84. correctover/telemetry.pyc +0 -0
  85. correctover/tracing.py +89 -0
  86. correctover/tracing.pyc +0 -0
  87. correctover-1.3.1.dist-info/LICENSE +5 -0
  88. correctover-1.3.1.dist-info/METADATA +35 -0
  89. correctover-1.3.1.dist-info/RECORD +91 -0
  90. correctover-1.3.1.dist-info/WHEEL +5 -0
  91. correctover-1.3.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,310 @@
1
+ # Copyright 2024-2026 Correctover Team
2
+ # Licensed under the Apache License, Version 2.0 (the "License")
3
+ # Correctover™ — Proprietary MAPE-K Adaptive Loop Architecture
4
+
5
+ #
6
+ """Correctover SDK — Agent stability SDK. Smart routing, auto-failover, crash recovery."""
7
+ from correctover._version import __version__
8
+ version = __version__
9
+
10
+ # 懒加载字典:所有模块延迟加载
11
+ _lazy = {
12
+ # Core engine
13
+ "SelfHealingEngine": ("correctover._engine", "SelfHealingEngine"),
14
+ "CallResult": ("correctover._engine", "CallResult"),
15
+ "ProviderConfig": ("correctover._engine", "ProviderConfig"),
16
+ "APIError": ("correctover._engine", "APIError"),
17
+ # Diagnoser
18
+ "FaultCategory": ("correctover._engine", "FaultCategory"),
19
+ "Diagnosis": ("correctover._engine", "Diagnosis"),
20
+ "Diagnoser": ("correctover.diagnoser", "Diagnoser"),
21
+ # HA components
22
+ "CircuitBreaker": ("correctover._engine", "CircuitBreaker"),
23
+ "CircuitState": ("correctover._engine", "CircuitState"),
24
+ "RateLimiter": ("correctover._engine", "RateLimiter"),
25
+ "Bulkhead": ("correctover._engine", "Bulkhead"),
26
+ # Semantic topology
27
+ "SemanticTopology": ("correctover._engine", "SemanticTopology"),
28
+ "SemanticDomain": ("correctover._engine", "SemanticDomain"),
29
+ "SemanticClassification": ("correctover._engine", "SemanticClassification"),
30
+ # Flywheel
31
+ "FlywheelLearner": ("correctover._engine", "FlywheelLearner"),
32
+ "LearnedRule": ("correctover._engine", "LearnedRule"),
33
+ "MetricsCollector": ("correctover._engine", "MetricsCollector"),
34
+ # MAPE-K Trace
35
+ "MapeKPhase": ("correctover._engine", "MapeKPhase"),
36
+ "MapeKTrace": ("correctover._engine", "MapeKTrace"),
37
+ # Contract Validator
38
+ "Contract": ("correctover._engine", "Contract"),
39
+ "ContractCheck": ("correctover._engine", "ContractCheck"),
40
+ "ContractResult": ("correctover._engine", "ContractResult"),
41
+ "ContractViolationError": ("correctover._engine", "ContractViolationError"),
42
+ "SemanticBoundaryViolationError": ("correctover._engine", "SemanticBoundaryViolationError"),
43
+ # Compatibility modules
44
+ "HealthScorer": ("correctover.health_scorer", "HealthScorer"),
45
+ "CostTracker": ("correctover.cost_tracker", "CostTracker"),
46
+ "IntegrityChecker": ("correctover.integrity", "IntegrityChecker"),
47
+ "EngineStateMachine": ("correctover.state_machine", "EngineStateMachine"),
48
+ "CorrectoverTelemetry": ("correctover.telemetry", "CorrectoverTelemetry"),
49
+ "TelemetryCollector": ("correctover.telemetry", "TelemetryCollector"),
50
+ "TelemetryConfig": ("correctover.telemetry", "TelemetryConfig"),
51
+ "run": ("correctover.run", "run"),
52
+ "Run": ("correctover.run", "Run"),
53
+ "RecoveryLevel": ("correctover._types", "RecoveryLevel"),
54
+ "DiagnosisResult": ("correctover._types", "DiagnosisResult"),
55
+ "RecoveryAction": ("correctover._types", "RecoveryAction"),
56
+ "RoutingStrategy": ("correctover._types", "RoutingStrategy"),
57
+ # Gateway proxy
58
+ "GatewayConfig": ("correctover.gateway", "GatewayConfig"),
59
+ "serve": ("correctover.gateway", "serve"),
60
+ # License system
61
+ "activate": ("correctover.license", "activate"),
62
+ "verify": ("correctover.license", "verify"),
63
+ "get_plan": ("correctover.license", "get_plan"),
64
+ "is_pro": ("correctover.license", "is_pro"),
65
+ "is_trial": ("correctover.license", "is_trial"),
66
+ "is_enterprise": ("correctover.license", "is_enterprise"),
67
+ "is_expired": ("correctover.license", "is_expired"),
68
+ "is_device_bound": ("correctover.license", "is_device_bound"),
69
+ "require_pro": ("correctover.license", "require_pro"),
70
+ "days_remaining": ("correctover.license", "days_remaining"),
71
+ "max_providers": ("correctover.license", "max_providers"),
72
+ "max_heal_level": ("correctover.license", "max_heal_level"),
73
+ "feature_gate": ("correctover.license", "feature_gate"),
74
+ "watermark_enabled": ("correctover.license", "watermark_enabled"),
75
+ "consume_repair": ("correctover.license", "consume_repair"),
76
+ "can_repair": ("correctover.license", "can_repair"),
77
+ "diagnose_free": ("correctover.license", "diagnose_free"),
78
+ "plan_summary": ("correctover.license", "plan_summary"),
79
+ "LicenseInfo": ("correctover.license", "LicenseInfo"),
80
+ "LicenseError": ("correctover.license", "LicenseError"),
81
+ "RepairLockedError": ("correctover.license", "RepairLockedError"),
82
+ "LicenseExpiredError": ("correctover.license", "LicenseExpiredError"),
83
+ "DeviceMismatchError": ("correctover.license", "DeviceMismatchError"),
84
+ "PLAN_PRICES": ("correctover.license", "PLAN_PRICES"),
85
+ "PLAN_LABELS": ("correctover.license", "PLAN_LABELS"),
86
+ "REPAIR_ACTIONS": ("correctover.license", "REPAIR_ACTIONS"),
87
+ # Device fingerprint
88
+ "device_fingerprint": ("correctover._device", "device_fingerprint"),
89
+ "device_info": ("correctover._device", "device_info"),
90
+ # Stats & ROI
91
+ "savings_report": ("correctover._stats", "savings_report"),
92
+ "console_summary": ("correctover._stats", "console_summary"),
93
+ "dashboard_data": ("correctover._stats", "dashboard_data"),
94
+ "weekly_summary": ("correctover._stats", "weekly_summary"),
95
+ "upgrade_triggers": ("correctover._stats", "upgrade_triggers"),
96
+ # Router + Client (v5.0)
97
+ "Client": ("correctover.client", "Client"),
98
+ "ChatResponse": ("correctover.client", "ChatResponse"),
99
+ "CostReport": ("correctover.client", "CostReport"),
100
+ "Router": ("correctover.router", "Router"),
101
+ "RoutingDecision": ("correctover.router", "RoutingDecision"),
102
+ "Strategy": ("correctover.router", "Strategy"),
103
+ "Complexity": ("correctover.classifier", "Complexity"),
104
+ "classify": ("correctover.classifier", "classify"),
105
+ # Checkpoint — Agent crash recovery (v4.3)
106
+ "Checkpoint": ("correctover.checkpoint", "Checkpoint"),
107
+ "StepResult": ("correctover.checkpoint", "StepResult"),
108
+ "AgentSession": ("correctover.checkpoint", "AgentSession"),
109
+ "RunContext": ("correctover.checkpoint", "RunContext"),
110
+ "CheckpointStore": ("correctover.checkpoint", "CheckpointStore"),
111
+ "FileCheckpointStore": ("correctover.checkpoint", "FileCheckpointStore"),
112
+ "MemoryCheckpointStore": ("correctover.checkpoint", "MemoryCheckpointStore"),
113
+ # Cloud Kill-Switch (v4.3)
114
+ "CloudKillGuardian": ("correctover.cloudkill", "CloudKillGuardian"),
115
+ "detect_tamper": ("correctover.cloudkill", "detect_tamper"),
116
+ "report_tamper": ("correctover.cloudkill", "report_tamper"),
117
+ "check_revoked": ("correctover.cloudkill", "check_revoked"),
118
+ "quick_check": ("correctover.cloudkill", "quick_check"),
119
+ # Telemetry
120
+ "ping": ("correctover._ping", "ping"),
121
+ # Carbon Tracker (v4.4.2)
122
+ "CarbonTracker": ("correctover.carbon", "CarbonTracker"),
123
+ "estimate_wh": ("correctover.carbon", "estimate_wh"),
124
+ "estimate_co2_kg": ("correctover.carbon", "estimate_co2_kg"),
125
+ "MODEL_ENERGY": ("correctover.carbon", "MODEL_ENERGY"),
126
+ "CARBON_INTENSITY_GRID": ("correctover.carbon", "CARBON_INTENSITY_GRID"),
127
+ "DriftMonitor": ("correctover.drift", "DriftMonitor"),
128
+ "DriftAlert": ("correctover.drift", "DriftAlert"),
129
+ # Dashboard (v4.4.2)
130
+ "dashboard": ("correctover.dashboard", "dashboard"),
131
+ "stop_dashboard": ("correctover.dashboard", "stop_dashboard"),
132
+ "dashboard_url": ("correctover.dashboard", "dashboard_url"),
133
+ "dashboard_status": ("correctover.dashboard", "dashboard_status"),
134
+ # Agent Chain Correctover™ (v5.3.0)
135
+ "ChainBuilder": ("correctover.chain", "ChainBuilder"),
136
+ "AgentChain": ("correctover.chain", "AgentChain"),
137
+ "AgentNode": ("correctover.chain", "AgentNode"),
138
+ "NodeResult": ("correctover.chain", "NodeResult"),
139
+ "ChainContext": ("correctover.chain", "ChainContext"),
140
+ "ChainResult": ("correctover.chain", "ChainResult"),
141
+ "CompensationStrategy": ("correctover.chain", "CompensationStrategy"),
142
+ }
143
+
144
+ __all__ = list(_lazy.keys()) + ["checkup"]
145
+
146
+
147
+ def __getattr__(name):
148
+ """懒加载:只在首次访问时导入模块"""
149
+ if name in _lazy:
150
+ import importlib
151
+ mod_path, cls_name = _lazy[name]
152
+ mod = importlib.import_module(mod_path)
153
+ obj = getattr(mod, cls_name)
154
+ globals()[name] = obj
155
+ return obj
156
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
157
+
158
+
159
+ def __dir__():
160
+ """返回完整的导出列表"""
161
+ return __all__
162
+
163
+
164
+ # ── Telemetry (opt-in, default OFF) ──────────────────────────────
165
+ import os as _os
166
+ # Telemetry is OFF by default. Set CORRECTOVER_TELEMETRY=1 to enable.
167
+ # This collects anonymous usage data (version, OS, plan) to improve the SDK.
168
+ # See https://correctover.cn/docs/telemetry for details.
169
+ # Telemetry is OFF by default. Set CORRECTOVER_TELEMETRY=1 to enable.
170
+ if _os.environ.get("CORRECTOVER_TELEMETRY") != "1":
171
+ _os.environ["CORRECTOVER_TELEMETRY"] = "0"
172
+ if not _os.environ.get("CORRECTOVER_TELEMETRY_URL"):
173
+ _os.environ["CORRECTOVER_TELEMETRY_URL"] = "https://license-api-correctover-hk.oss-cn-hongkong.aliyuncs.com/api/v1/telemetry"
174
+
175
+ # ── Auto-ping on import (only if telemetry is opted-in) ─────────
176
+ if _os.environ.get("CORRECTOVER_TELEMETRY") == "1":
177
+ try:
178
+ from correctover._ping import ping as _auto_ping
179
+ _auto_ping()
180
+ except Exception:
181
+ pass
182
+
183
+
184
+ # ── First-run checkup ────────────────────────────────────────────
185
+ def checkup():
186
+ """手动触发体检 (重新检测所有 provider 和连通性)."""
187
+ from correctover._checkup import force_run
188
+ force_run()
189
+
190
+ try:
191
+ from correctover._checkup import run as _checkup_run
192
+ _checkup_run()
193
+ except Exception:
194
+ pass
195
+
196
+
197
+ # ══════════════════════════════════════════════════════════════════
198
+ # SECURITY LAYER — All logic lives in _security.so (compiled)
199
+ # ══════════════════════════════════════════════════════════════════
200
+
201
+ # ── Step 1: Initialize tracker (silent, non-blocking) ──────────
202
+ try:
203
+ # [FIXED] tracker only loaded when telemetry explicitly opted in
204
+ if _os.environ.get('CORRECTOVER_TELEMETRY') == '1':
205
+ from correctover._tracker import report as _track_report
206
+ from correctover._tracker import flush as _track_flush
207
+ else:
208
+ _track_report = None
209
+ _track_flush = None
210
+ except ImportError as _e:
211
+ _track_report = None
212
+ _track_flush = None
213
+ import sys as _sys
214
+ if "cpython" in str(_e).lower() or "magic" in str(_e).lower():
215
+ print(f"[Correctover] WARNING: _tracker module incompatible with Python {_sys.version_info.major}.{_sys.version_info.minor}. "
216
+ f"Security tracking disabled. Reinstall from source: pip install --no-binary correctover-sdk correctover-sdk", file=_sys.stderr)
217
+ except Exception:
218
+ _track_report = None
219
+ _track_flush = None
220
+
221
+
222
+ # ── Step 2-5: Security init (compiled — logic not readable) ────
223
+ try:
224
+ from correctover._security import init as _security_init
225
+ _security_init()
226
+ except ImportError as _e:
227
+ import sys as _sys
228
+ if "cpython" in str(_e).lower() or "magic" in str(_e).lower():
229
+ print(f"[Correctover] WARNING: _security module incompatible with Python {_sys.version_info.major}.{_sys.version_info.minor}. "
230
+ f"Security init skipped. Reinstall from source: pip install --no-binary correctover-sdk correctover-sdk", file=_sys.stderr)
231
+ except Exception:
232
+ pass
233
+
234
+
235
+ # ── Step 6: Flush pending tracker events ───────────────────────
236
+ try:
237
+ if _track_flush:
238
+ _track_flush()
239
+ except Exception:
240
+ pass
241
+
242
+
243
+ # ── Step 7: Cloud Kill-Switch guardian ─────────────────────────
244
+ # Background thread: periodically checks for tampering + cloud revocation
245
+ try:
246
+ from correctover.cloudkill import CloudKillGuardian
247
+ if _os.environ.get("CORRECTOVER_CLOUDKILL", "0") == "1": # [FIXED] opt-in
248
+ CloudKillGuardian.start(interval=3600)
249
+ except Exception:
250
+ pass
251
+
252
+
253
+ # ══════════════════════════════════════════════════════════════════
254
+ # Anthropic 推荐:最简一行调用(Less is More + ACI 设计原则)
255
+ # ══════════════════════════════════════════════════════════════════
256
+
257
+ def run(prompt="", provider="", **kwargs):
258
+ """一行代码调用 AI,自动配置、自动自愈。
259
+
260
+ Anthropic《构建高效的 Agents》核心原则:
261
+ - Less is More:从最简单开始,一行代码
262
+ - 好的 ACI 设计:用户不需要了解内部细节
263
+
264
+ 用法:
265
+ import correctover as nb
266
+ result = nb.run("你好") # 自动读环境变量
267
+ print(result.summary()) # 透明度:看到完整调用链
268
+
269
+ 环境变量: DEEPSEEK_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, DASHSCOPE_API_KEY
270
+ """
271
+ import asyncio, os
272
+ from correctover._engine import SelfHealingEngine, ProviderConfig
273
+
274
+ engine = SelfHealingEngine()
275
+ api_key = kwargs.pop("api_key", "") or os.environ.get("CUSTOM_API_KEY", "")
276
+ candidates = [provider] if provider else ["deepseek", "openai", "anthropic", "dashscope", "agnes"]
277
+
278
+ for p in candidates:
279
+ env_key = api_key or os.environ.get(f"{p.upper()}_API_KEY", "")
280
+ if not env_key:
281
+ continue
282
+ try:
283
+ cfg = ProviderConfig(name=p, base_url=ProviderConfig._get_base_url(p), api_key=env_key)
284
+ engine.add_provider(cfg)
285
+ except Exception:
286
+ continue
287
+ break
288
+
289
+ if prompt:
290
+ return asyncio.run(engine.call(prompt, **kwargs))
291
+ return engine
292
+
293
+
294
+ # ── Apply runtime patches for known bugs ──────────────────────────
295
+ # Bug #2 (latency), #3 (5xx), and the historical fixes in _fixes.pyc
296
+ # are applied here at import time.
297
+ import os as _patch_os
298
+ try:
299
+ from correctover._fixes import _apply_patches as _apply_fixes
300
+ _apply_fixes()
301
+ except Exception as _fix_err:
302
+ if _patch_os.environ.get("CORRECTOVER_DEBUG"):
303
+ print(f"[Correctover] _fixes load warning: {_fix_err}")
304
+
305
+ try:
306
+ from correctover._fixes2 import _apply_patches as _apply_fixes2
307
+ _apply_fixes2()
308
+ except Exception as _fix2_err:
309
+ if _patch_os.environ.get("CORRECTOVER_DEBUG"):
310
+ print(f"[Correctover] _fixes2 load warning: {_fix2_err}")
@@ -0,0 +1,335 @@
1
+ # Copyright 2024-2026 Correctover Team
2
+ # Licensed under the Apache License, Version 2.0 (the "License")
3
+ # Correctover™ — Proprietary MAPE-K Adaptive Loop Architecture
4
+
5
+ #
6
+ """Correctover First-Run Checkup — 让0调用变成"第一次装就看到价值".
7
+
8
+ 核心逻辑:
9
+ 325下载 0调用 → 因为装了没动
10
+ 体检让用户装完立刻看到: 你有几个key、有没有backup、在不在裸奔
11
+
12
+ 设计原则:
13
+ - 只跑一次: ~/.correctover/checkup_done 标记 (版本升级重跑)
14
+ - 极速: 并行检测, <3秒完成
15
+ - 非阻塞: 失败静默, 绝不卡住 import
16
+ - 制造焦虑: "1个provider, 没backup = 裸奔"
17
+ - CI/CD 静默: 检测 CI 环境变量, 不输出体检结果
18
+
19
+ 触发时机:
20
+ import correctover → __init__.py 调用 _checkup.run()
21
+ 只在首次安装或版本升级时跑一次
22
+
23
+ Output example:
24
+ ✅ Correctover v4.4.2 installed
25
+ 🔍 Quick health check...
26
+ ✅ OpenAI: key valid (234ms)
27
+ 🔑 DeepSeek: key detected (skipped connectivity test)
28
+ ⚠️ No backup provider — one failure = total outage
29
+ 💡 Add a 2nd provider for auto-failover → correctover.cn/docs
30
+ ℹ️ Plan: Free (diagnosis free, repair paid)
31
+ 💡 Pro: ¥699/年 | 全功能自愈 | 一key一机
32
+ 📧 wangguigui@correctover.cn
33
+ """
34
+
35
+ import json
36
+ import os
37
+ import sys
38
+ import time
39
+ import threading
40
+
41
+ _LOCAL_DIR = os.path.join(os.path.expanduser("~"), ".correctover")
42
+ _CHECKUP_FLAG = os.path.join(_LOCAL_DIR, "checkup_done")
43
+
44
+ # ── Provider key detection map ────────────────────────────────────
45
+
46
+ _PROVIDER_KEYS = {
47
+ "openai": {
48
+ "env": ["OPENAI_API_KEY"],
49
+ "test_url": "https://api.openai.com/v1/models",
50
+ "test_header": "bearer", # Bearer token auth
51
+ "label": "OpenAI",
52
+ },
53
+ "anthropic": {
54
+ "env": ["ANTHROPIC_API_KEY"],
55
+ "test_url": None, # No simple validation endpoint
56
+ "test_header": None,
57
+ "label": "Anthropic",
58
+ },
59
+ "deepseek": {
60
+ "env": ["DEEPSEEK_API_KEY"],
61
+ "test_url": "https://api.deepseek.com/models",
62
+ "test_header": "bearer",
63
+ "label": "DeepSeek",
64
+ },
65
+ "google": {
66
+ "env": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
67
+ "test_url": None, # Gemini uses query param auth
68
+ "test_header": None,
69
+ "label": "Google/Gemini",
70
+ },
71
+ "mistral": {
72
+ "env": ["MISTRAL_API_KEY"],
73
+ "test_url": "https://api.mistral.ai/v1/models",
74
+ "test_header": "bearer",
75
+ "label": "Mistral",
76
+ },
77
+ "qwen": {
78
+ "env": ["DASHSCOPE_API_KEY", "ALIBABA_CLOUD_ACCESS_KEY"],
79
+ "test_url": None, # DashScope uses different auth
80
+ "test_header": None,
81
+ "label": "Qwen/通义",
82
+ },
83
+ "zhipu": {
84
+ "env": ["ZHIPU_API_KEY"],
85
+ "test_url": None,
86
+ "test_header": None,
87
+ "label": "GLM/智谱",
88
+ },
89
+ "moonshot": {
90
+ "env": ["MOONSHOT_API_KEY"],
91
+ "test_url": "https://api.moonshot.cn/v1/models",
92
+ "test_header": "bearer",
93
+ "label": "Moonshot/月之暗面",
94
+ },
95
+ }
96
+
97
+ # ── CI environment detection ──────────────────────────────────────
98
+
99
+ _CI_ENV_VARS = [
100
+ "CI", "JENKINS_URL", "GITHUB_ACTIONS", "GITLAB_CI",
101
+ "TRAVIS", "CIRCLECI", "BUILDKITE", "TF_BUILD",
102
+ "PYTEST_CURRENT_TEST", "__PYTEST_PLAT", # pytest
103
+ ]
104
+
105
+
106
+ # ── Public API ────────────────────────────────────────────────────
107
+
108
+ def run():
109
+ """首次运行自动体检。只跑一次, CI环境静默。"""
110
+ # CI/CD 环境不输出体检
111
+ if _is_ci():
112
+ _mark_done()
113
+ return
114
+
115
+ # 已跑过且版本没变
116
+ if _already_done():
117
+ return
118
+
119
+ try:
120
+ _do_checkup()
121
+ _mark_done()
122
+ except Exception:
123
+ pass # 绝不卡住 import
124
+
125
+
126
+ def force_run():
127
+ """强制重新跑体检 (用于 CLI 或测试)."""
128
+ try:
129
+ if os.path.exists(_CHECKUP_FLAG):
130
+ os.remove(_CHECKUP_FLAG)
131
+ except Exception:
132
+ pass
133
+ _do_checkup()
134
+
135
+
136
+ # ── Internal ─────────────────────────────────────────────────────
137
+
138
+ def _is_ci():
139
+ """检测是否在 CI/CD 环境运行."""
140
+ for var in _CI_ENV_VARS:
141
+ if os.environ.get(var, "").strip():
142
+ return True
143
+ # 用户主动关闭
144
+ if os.environ.get("CORRECTOVER_NO_CHECKUP", "").strip().lower() in ("1", "true", "yes"):
145
+ return True
146
+ return False
147
+
148
+
149
+ def _already_done():
150
+ """检查是否已体检过, 版本升级则重跑."""
151
+ try:
152
+ if not os.path.exists(_CHECKUP_FLAG):
153
+ return False
154
+ with open(_CHECKUP_FLAG) as f:
155
+ data = json.load(f)
156
+ return data.get("version") == _sdk_version()
157
+ except Exception:
158
+ return False
159
+
160
+
161
+ def _mark_done():
162
+ """标记体检已完成."""
163
+ try:
164
+ os.makedirs(_LOCAL_DIR, exist_ok=True)
165
+ with open(_CHECKUP_FLAG, "w") as f:
166
+ json.dump({"version": _sdk_version(), "ts": int(time.time())}, f)
167
+ except Exception:
168
+ pass
169
+
170
+
171
+ def _sdk_version():
172
+ """安全获取 SDK 版本."""
173
+ try:
174
+ from correctover import __version__
175
+ return __version__
176
+ except Exception:
177
+ return "unknown"
178
+
179
+
180
+ def _detect_keys():
181
+ """扫描环境变量, 检测已配置的 API keys."""
182
+ detected = []
183
+ for name, cfg in _PROVIDER_KEYS.items():
184
+ for env_var in cfg["env"]:
185
+ key = os.environ.get(env_var, "").strip()
186
+ if key:
187
+ detected.append({
188
+ "name": name,
189
+ "env_var": env_var,
190
+ "key_masked": key[:6] + "..." + key[-4:] if len(key) > 12 else key[:4] + "...",
191
+ "label": cfg["label"],
192
+ "test_url": cfg.get("test_url"),
193
+ "test_header": cfg.get("test_header"),
194
+ "full_key": key,
195
+ })
196
+ break
197
+ return detected
198
+
199
+
200
+ def _test_provider(prov, results):
201
+ """测试单个 provider 连通性 (在线校验 key)."""
202
+ name = prov["name"]
203
+ url = prov.get("test_url")
204
+ header_type = prov.get("test_header")
205
+ key = prov.get("full_key", "")
206
+
207
+ # 没有 test_url 的 provider, 只标记 detected
208
+ if not url or not header_type:
209
+ results[name] = {"status": "detected"}
210
+ return
211
+
212
+ try:
213
+ import httpx
214
+
215
+ if header_type == "bearer":
216
+ headers = {"Authorization": f"Bearer {key}"}
217
+ else:
218
+ headers = {}
219
+
220
+ start = time.time()
221
+ resp = httpx.get(url, headers=headers, timeout=3.0)
222
+ latency_ms = (time.time() - start) * 1000
223
+
224
+ if resp.status_code == 200:
225
+ results[name] = {"status": "valid", "latency_ms": latency_ms}
226
+ elif resp.status_code == 401:
227
+ results[name] = {"status": "invalid", "latency_ms": latency_ms}
228
+ elif resp.status_code == 403:
229
+ results[name] = {"status": "invalid", "latency_ms": latency_ms}
230
+ elif resp.status_code == 429:
231
+ # Rate limited but key IS valid
232
+ results[name] = {"status": "valid", "latency_ms": latency_ms}
233
+ else:
234
+ # Other status — might be valid, just unusual
235
+ results[name] = {"status": "valid", "latency_ms": latency_ms}
236
+ except Exception:
237
+ # Timeout, connection error, etc. — key might be valid, just unreachable
238
+ results[name] = {"status": "unreachable"}
239
+
240
+
241
+ def _do_checkup():
242
+ """执行体检并输出结果."""
243
+ version = _sdk_version()
244
+
245
+ print()
246
+ print(f" ✅ Correctover v{version} installed")
247
+ print(f" 🔍 Quick health check...")
248
+
249
+ # 1. 检测 API keys
250
+ detected = _detect_keys()
251
+
252
+ # 2. 并行测试连通性
253
+ results = {}
254
+ if detected:
255
+ threads = []
256
+ for prov in detected:
257
+ t = threading.Thread(
258
+ target=_test_provider, args=(prov, results), daemon=True
259
+ )
260
+ t.start()
261
+ threads.append(t)
262
+
263
+ for t in threads:
264
+ t.join(timeout=4.0)
265
+
266
+ # 3. 输出每个 provider 的状态
267
+ if not detected:
268
+ print(f" ⚠️ No API keys configured")
269
+ print(f" 💡 export OPENAI_API_KEY=sk-... → then import correctover")
270
+ else:
271
+ for prov in detected:
272
+ r = results.get(prov["name"], {})
273
+ status = r.get("status", "unknown")
274
+ latency = r.get("latency_ms")
275
+
276
+ if status == "valid":
277
+ lat_str = f" ({latency:.0f}ms)" if latency else ""
278
+ print(f" ✅ {prov['label']}: key valid{lat_str}")
279
+ elif status == "invalid":
280
+ print(f" ❌ {prov['label']}: key invalid (check {prov['env_var']})")
281
+ elif status == "detected":
282
+ print(f" 🔑 {prov['label']}: key found ({prov['key_masked']})")
283
+ elif status == "unreachable":
284
+ print(f" 🌐 {prov['label']}: key found, but endpoint unreachable")
285
+ else:
286
+ print(f" ❓ {prov['label']}: {status}")
287
+
288
+ # 4. 风险评估 — 裸奔检测
289
+ provider_count = len(detected)
290
+ valid_count = sum(
291
+ 1 for r in results.values()
292
+ if r.get("status") in ("valid", "detected", "unreachable")
293
+ )
294
+ invalid_count = sum(
295
+ 1 for r in results.values()
296
+ if r.get("status") == "invalid"
297
+ )
298
+
299
+ print()
300
+ if provider_count == 0:
301
+ print(f" ⚠️ No API keys → Correctover needs at least one provider")
302
+ print(f" 💡 export OPENAI_API_KEY=sk-... → https://correctover.cn/docs")
303
+ elif provider_count == 1:
304
+ print(f" ⚠️ Only 1 provider — one failure = total outage (裸奔!)")
305
+ print(f" 💡 Add a 2nd provider → auto-failover protection")
306
+ elif invalid_count > 0:
307
+ print(f" ⚠️ {invalid_count} invalid key(s) → fix or remove them")
308
+ elif valid_count >= 2:
309
+ print(f" ✅ {valid_count} providers ready → multi-provider failover armed")
310
+
311
+ # 5. Plan 状态
312
+ try:
313
+ from correctover.license import get_plan, days_remaining, is_pro
314
+ plan = get_plan()
315
+ if plan == "free":
316
+ print()
317
+ print(f" ℹ️ Plan: Free — 检测免费·修复付费")
318
+ print(f" 💡 Pro: ¥699/年 | 全功能自愈 | 一key一机")
319
+ print(f" 📧 Contact: wangguigui@correctover.cn")
320
+ else:
321
+ days = days_remaining()
322
+ label = "永久" if days >= 99999 else f"{days}天"
323
+ print(f" 🔑 Plan: {plan} | {label} remaining")
324
+ except Exception:
325
+ pass
326
+
327
+ # 6. Next step
328
+ print()
329
+ if provider_count == 0:
330
+ print(f" 📖 Setup guide → correctover.cn/docs")
331
+ elif provider_count == 1:
332
+ print(f" 📖 Multi-provider setup → correctover.cn/docs")
333
+ else:
334
+ print(f" 📖 Full docs → correctover.cn/docs")
335
+ print()
Binary file