devlake-mcp 0.4.1__py3-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.
- devlake_mcp/__init__.py +7 -0
- devlake_mcp/__main__.py +10 -0
- devlake_mcp/cli.py +794 -0
- devlake_mcp/client.py +474 -0
- devlake_mcp/compat.py +165 -0
- devlake_mcp/config.py +204 -0
- devlake_mcp/constants.py +161 -0
- devlake_mcp/enums.py +58 -0
- devlake_mcp/generation_manager.py +296 -0
- devlake_mcp/git_utils.py +489 -0
- devlake_mcp/hooks/__init__.py +49 -0
- devlake_mcp/hooks/hook_utils.py +246 -0
- devlake_mcp/hooks/post_tool_use.py +325 -0
- devlake_mcp/hooks/pre_tool_use.py +110 -0
- devlake_mcp/hooks/record_session.py +183 -0
- devlake_mcp/hooks/session_start.py +81 -0
- devlake_mcp/hooks/stop.py +275 -0
- devlake_mcp/hooks/transcript_utils.py +547 -0
- devlake_mcp/hooks/user_prompt_submit.py +204 -0
- devlake_mcp/logging_config.py +202 -0
- devlake_mcp/retry_queue.py +556 -0
- devlake_mcp/server.py +664 -0
- devlake_mcp/session_manager.py +444 -0
- devlake_mcp/utils.py +225 -0
- devlake_mcp/version_utils.py +174 -0
- devlake_mcp-0.4.1.dist-info/METADATA +541 -0
- devlake_mcp-0.4.1.dist-info/RECORD +31 -0
- devlake_mcp-0.4.1.dist-info/WHEEL +5 -0
- devlake_mcp-0.4.1.dist-info/entry_points.txt +3 -0
- devlake_mcp-0.4.1.dist-info/licenses/LICENSE +21 -0
- devlake_mcp-0.4.1.dist-info/top_level.txt +1 -0
devlake_mcp/client.py
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DevLake API 客户端
|
|
3
|
+
|
|
4
|
+
提供与 DevLake REST API 交互的基础功能。
|
|
5
|
+
|
|
6
|
+
改进:
|
|
7
|
+
- 使用 requests 库替代 urllib(更简洁、更强大)
|
|
8
|
+
- 完善的错误处理(针对不同HTTP状态码和业务错误)
|
|
9
|
+
- 完整的类型注解
|
|
10
|
+
- 自动连接池管理
|
|
11
|
+
|
|
12
|
+
错误处理:
|
|
13
|
+
- HTTP 层错误:网络连接、超时、HTTP 4xx/5xx
|
|
14
|
+
- 业务层错误:HTTP 200 但 success=false
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import requests
|
|
18
|
+
from typing import Any, Dict, List, Optional
|
|
19
|
+
from requests.exceptions import RequestException, Timeout, ConnectionError as ReqConnectionError
|
|
20
|
+
|
|
21
|
+
from .config import DevLakeConfig
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ============================================================================
|
|
25
|
+
# 异常类定义
|
|
26
|
+
# ============================================================================
|
|
27
|
+
|
|
28
|
+
class DevLakeAPIError(Exception):
|
|
29
|
+
"""DevLake API 错误基类"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DevLakeConnectionError(DevLakeAPIError):
|
|
34
|
+
"""连接错误(网络不可达、DNS解析失败等)"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DevLakeTimeoutError(DevLakeAPIError):
|
|
39
|
+
"""请求超时错误"""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DevLakeAuthError(DevLakeAPIError):
|
|
44
|
+
"""认证失败错误(401/403)"""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DevLakeNotFoundError(DevLakeAPIError):
|
|
49
|
+
"""资源不存在错误(404)"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DevLakeValidationError(DevLakeAPIError):
|
|
54
|
+
"""请求参数验证错误(400)"""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DevLakeServerError(DevLakeAPIError):
|
|
59
|
+
"""服务器错误(5xx)"""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class DevLakeBusinessError(DevLakeAPIError):
|
|
64
|
+
"""业务逻辑错误(HTTP 200 但业务状态异常)"""
|
|
65
|
+
def __init__(self, message: str, code: Optional[int] = None, response_data: Optional[Dict[str, Any]] = None):
|
|
66
|
+
super().__init__(message)
|
|
67
|
+
self.code = code
|
|
68
|
+
self.response_data = response_data
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class DevLakeClient:
|
|
72
|
+
"""
|
|
73
|
+
DevLake API 客户端
|
|
74
|
+
|
|
75
|
+
提供与 DevLake REST API 交互的基础功能,包括:
|
|
76
|
+
- GET/POST/PUT/PATCH/DELETE 请求
|
|
77
|
+
- 自动错误处理(HTTP层 + 业务层)
|
|
78
|
+
- 响应解析
|
|
79
|
+
- 自动连接池管理
|
|
80
|
+
|
|
81
|
+
改进:
|
|
82
|
+
- 使用 requests.Session 实现连接池复用
|
|
83
|
+
- 更精细的错误分类和处理(区分HTTP错误和业务错误)
|
|
84
|
+
- 完整的类型注解
|
|
85
|
+
|
|
86
|
+
错误层次:
|
|
87
|
+
1. HTTP 层错误 (4xx/5xx): 网络传输层面的错误
|
|
88
|
+
- DevLakeConnectionError: 连接失败
|
|
89
|
+
- DevLakeTimeoutError: 请求超时
|
|
90
|
+
- DevLakeAuthError: 认证失败 (401/403)
|
|
91
|
+
- DevLakeNotFoundError: 资源不存在 (404)
|
|
92
|
+
- DevLakeValidationError: 参数错误 (400)
|
|
93
|
+
- DevLakeServerError: 服务器错误 (5xx)
|
|
94
|
+
|
|
95
|
+
2. 业务层错误 (HTTP 200 但 success=false): 应用逻辑层面的错误
|
|
96
|
+
- DevLakeBusinessError: 业务逻辑错误,如字段缺失、数据验证失败等
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, config: Optional[DevLakeConfig] = None):
|
|
100
|
+
"""
|
|
101
|
+
初始化客户端
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
config: DevLake 配置,如果为 None 则从环境变量加载
|
|
105
|
+
|
|
106
|
+
注意:
|
|
107
|
+
推荐使用 context manager 来确保资源正确释放:
|
|
108
|
+
|
|
109
|
+
with DevLakeClient() as client:
|
|
110
|
+
client.post('/api/sessions', data)
|
|
111
|
+
"""
|
|
112
|
+
self.config = config or DevLakeConfig.from_env()
|
|
113
|
+
|
|
114
|
+
# 创建 requests Session(连接池复用)
|
|
115
|
+
self.session = requests.Session()
|
|
116
|
+
self.session.headers.update(self.config.get_headers())
|
|
117
|
+
self._closed = False
|
|
118
|
+
|
|
119
|
+
def _make_request(
|
|
120
|
+
self,
|
|
121
|
+
method: str,
|
|
122
|
+
path: str,
|
|
123
|
+
data: Optional[Dict[str, Any]] = None,
|
|
124
|
+
params: Optional[Dict[str, Any]] = None
|
|
125
|
+
) -> Dict[str, Any]:
|
|
126
|
+
"""
|
|
127
|
+
发起 HTTP 请求(使用 requests 库)
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
method: HTTP 方法(GET, POST, PUT, PATCH, DELETE)
|
|
131
|
+
path: API 路径(如 /api/connections)
|
|
132
|
+
data: 请求体数据(自动转换为JSON)
|
|
133
|
+
params: URL 查询参数
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dict[str, Any]: 响应 JSON 数据
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
DevLakeConnectionError: 连接失败
|
|
140
|
+
DevLakeTimeoutError: 请求超时
|
|
141
|
+
DevLakeAuthError: 认证失败
|
|
142
|
+
DevLakeNotFoundError: 资源不存在
|
|
143
|
+
DevLakeValidationError: 验证失败
|
|
144
|
+
DevLakeServerError: 服务器错误
|
|
145
|
+
DevLakeBusinessError: 业务逻辑错误(HTTP 200 但业务状态异常)
|
|
146
|
+
DevLakeAPIError: 其他 API 错误
|
|
147
|
+
"""
|
|
148
|
+
# 构建完整 URL
|
|
149
|
+
url = f"{self.config.base_url}{path}"
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
# 发起请求
|
|
153
|
+
response = self.session.request(
|
|
154
|
+
method=method,
|
|
155
|
+
url=url,
|
|
156
|
+
json=data, # requests 会自动序列化为 JSON
|
|
157
|
+
params=params,
|
|
158
|
+
timeout=self.config.timeout,
|
|
159
|
+
verify=self.config.verify_ssl
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# 检查 HTTP 状态码
|
|
163
|
+
response.raise_for_status()
|
|
164
|
+
|
|
165
|
+
# 解析响应
|
|
166
|
+
if not response.text:
|
|
167
|
+
return {}
|
|
168
|
+
|
|
169
|
+
result = response.json()
|
|
170
|
+
|
|
171
|
+
# 检查业务层错误(HTTP 200 但业务状态异常)
|
|
172
|
+
# DevLake API 统一响应格式: {"success": true/false, "error": "...", "data": {...}}
|
|
173
|
+
if isinstance(result, dict) and result.get('success') is False:
|
|
174
|
+
error_msg = result.get('error', '业务逻辑错误')
|
|
175
|
+
error_code = result.get('code') # 可选的错误码
|
|
176
|
+
raise DevLakeBusinessError(
|
|
177
|
+
f"业务错误: {error_msg}",
|
|
178
|
+
code=error_code,
|
|
179
|
+
response_data=result
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
except Timeout as e:
|
|
185
|
+
raise DevLakeTimeoutError(
|
|
186
|
+
f"请求超时({self.config.timeout}秒): {url}"
|
|
187
|
+
) from e
|
|
188
|
+
|
|
189
|
+
except ReqConnectionError as e:
|
|
190
|
+
raise DevLakeConnectionError(
|
|
191
|
+
f"连接失败: {url}\n原因: {str(e)}"
|
|
192
|
+
) from e
|
|
193
|
+
|
|
194
|
+
except requests.HTTPError as e:
|
|
195
|
+
# 获取错误详情
|
|
196
|
+
error_body = ""
|
|
197
|
+
try:
|
|
198
|
+
error_body = e.response.text if e.response else ""
|
|
199
|
+
except Exception:
|
|
200
|
+
error_body = "<无法读取错误详情>"
|
|
201
|
+
|
|
202
|
+
# 优先从 response 对象获取状态码
|
|
203
|
+
status_code = e.response.status_code if e.response else 0
|
|
204
|
+
|
|
205
|
+
# 如果 response 为 None,尝试从错误消息中提取状态码
|
|
206
|
+
# 例如: "400 Client Error: BAD REQUEST"
|
|
207
|
+
if status_code == 0:
|
|
208
|
+
import re
|
|
209
|
+
error_str = str(e)
|
|
210
|
+
match = re.match(r'^(\d{3})\s', error_str)
|
|
211
|
+
if match:
|
|
212
|
+
status_code = int(match.group(1))
|
|
213
|
+
|
|
214
|
+
# 根据状态码抛出不同的异常
|
|
215
|
+
if status_code == 0:
|
|
216
|
+
# 没有收到响应(response 为 None),说明请求根本没发出去或者被中断
|
|
217
|
+
raise DevLakeAPIError(
|
|
218
|
+
f"HTTP 请求失败,未收到响应\n"
|
|
219
|
+
f"URL: {url}\n"
|
|
220
|
+
f"方法: {method}\n"
|
|
221
|
+
f"原始错误: {str(e)}"
|
|
222
|
+
) from e
|
|
223
|
+
elif status_code == 400:
|
|
224
|
+
# 添加请求数据到错误信息中,帮助调试
|
|
225
|
+
import json
|
|
226
|
+
request_data = json.dumps(data, ensure_ascii=False, indent=2) if data else "无"
|
|
227
|
+
raise DevLakeValidationError(
|
|
228
|
+
f"请求参数错误(400 Bad Request)\n"
|
|
229
|
+
f"URL: {url}\n"
|
|
230
|
+
f"请求数据: {request_data}\n"
|
|
231
|
+
f"服务器响应: {error_body}"
|
|
232
|
+
) from e
|
|
233
|
+
elif status_code == 401:
|
|
234
|
+
raise DevLakeAuthError(
|
|
235
|
+
f"认证失败,请检查 API Token"
|
|
236
|
+
) from e
|
|
237
|
+
elif status_code == 403:
|
|
238
|
+
raise DevLakeAuthError(
|
|
239
|
+
f"权限不足,无法访问资源: {path}"
|
|
240
|
+
) from e
|
|
241
|
+
elif status_code == 404:
|
|
242
|
+
raise DevLakeNotFoundError(
|
|
243
|
+
f"资源不存在: {path}"
|
|
244
|
+
) from e
|
|
245
|
+
elif status_code >= 500:
|
|
246
|
+
raise DevLakeServerError(
|
|
247
|
+
f"服务器错误({status_code}): {error_body}"
|
|
248
|
+
) from e
|
|
249
|
+
else:
|
|
250
|
+
raise DevLakeAPIError(
|
|
251
|
+
f"API错误({status_code}): {error_body}"
|
|
252
|
+
) from e
|
|
253
|
+
|
|
254
|
+
except RequestException as e:
|
|
255
|
+
raise DevLakeAPIError(
|
|
256
|
+
f"请求失败: {str(e)}"
|
|
257
|
+
) from e
|
|
258
|
+
|
|
259
|
+
def close(self):
|
|
260
|
+
"""
|
|
261
|
+
关闭 session 释放连接池资源
|
|
262
|
+
|
|
263
|
+
在不使用 context manager 时,建议手动调用此方法释放资源。
|
|
264
|
+
"""
|
|
265
|
+
if not self._closed:
|
|
266
|
+
self.session.close()
|
|
267
|
+
self._closed = True
|
|
268
|
+
|
|
269
|
+
def __enter__(self):
|
|
270
|
+
"""Context manager 进入"""
|
|
271
|
+
return self
|
|
272
|
+
|
|
273
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
274
|
+
"""Context manager 退出,确保资源释放"""
|
|
275
|
+
self.close()
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
def __del__(self):
|
|
279
|
+
"""析构函数,确保资源最终被释放"""
|
|
280
|
+
try:
|
|
281
|
+
self.close()
|
|
282
|
+
except Exception:
|
|
283
|
+
# 析构时忽略所有异常
|
|
284
|
+
pass
|
|
285
|
+
|
|
286
|
+
def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
287
|
+
"""
|
|
288
|
+
发起 GET 请求
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
path: API 路径
|
|
292
|
+
params: URL 查询参数
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Dict[str, Any]: 响应数据
|
|
296
|
+
"""
|
|
297
|
+
return self._make_request("GET", path, params=params)
|
|
298
|
+
|
|
299
|
+
def post(self, path: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
300
|
+
"""
|
|
301
|
+
发起 POST 请求
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
path: API 路径
|
|
305
|
+
data: 请求体数据
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Dict[str, Any]: 响应数据
|
|
309
|
+
"""
|
|
310
|
+
return self._make_request("POST", path, data=data)
|
|
311
|
+
|
|
312
|
+
def put(self, path: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
313
|
+
"""
|
|
314
|
+
发起 PUT 请求
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
path: API 路径
|
|
318
|
+
data: 请求体数据
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
Dict[str, Any]: 响应数据
|
|
322
|
+
"""
|
|
323
|
+
return self._make_request("PUT", path, data=data)
|
|
324
|
+
|
|
325
|
+
def patch(self, path: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
326
|
+
"""
|
|
327
|
+
发起 PATCH 请求
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
path: API 路径
|
|
331
|
+
data: 请求体数据
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Dict[str, Any]: 响应数据
|
|
335
|
+
"""
|
|
336
|
+
return self._make_request("PATCH", path, data=data)
|
|
337
|
+
|
|
338
|
+
def delete(self, path: str) -> Dict[str, Any]:
|
|
339
|
+
"""
|
|
340
|
+
发起 DELETE 请求
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
path: API 路径
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Dict[str, Any]: 响应数据
|
|
347
|
+
"""
|
|
348
|
+
return self._make_request("DELETE", path)
|
|
349
|
+
|
|
350
|
+
def health_check(self) -> Dict[str, Any]:
|
|
351
|
+
"""
|
|
352
|
+
健康检查
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
Dict[str, Any]: 健康状态信息
|
|
356
|
+
"""
|
|
357
|
+
try:
|
|
358
|
+
# DevLake 的健康检查端点
|
|
359
|
+
response = self.get("/api/ping")
|
|
360
|
+
return {
|
|
361
|
+
"status": "healthy",
|
|
362
|
+
"message": "DevLake API is accessible",
|
|
363
|
+
"base_url": self.config.base_url,
|
|
364
|
+
"response": response
|
|
365
|
+
}
|
|
366
|
+
except Exception as e:
|
|
367
|
+
return {
|
|
368
|
+
"status": "unhealthy",
|
|
369
|
+
"message": str(e),
|
|
370
|
+
"base_url": self.config.base_url
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
# ========================================================================
|
|
374
|
+
# AI Coding API 便捷方法(用于 Hooks)
|
|
375
|
+
# ========================================================================
|
|
376
|
+
|
|
377
|
+
def create_session(self, session_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
378
|
+
"""
|
|
379
|
+
创建 AI 编码会话
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
session_data: 会话数据(包含 session_id, user_name, git_repo_path 等)
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
Dict[str, Any]: 创建的会话数据
|
|
386
|
+
"""
|
|
387
|
+
return self.post("/api/ai-coding/sessions", session_data)
|
|
388
|
+
|
|
389
|
+
def update_session(self, session_id: str, update_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
390
|
+
"""
|
|
391
|
+
更新 AI 编码会话
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
session_id: 会话 ID
|
|
395
|
+
update_data: 更新数据
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Dict[str, Any]: 更新后的会话数据
|
|
399
|
+
"""
|
|
400
|
+
return self.patch(f"/api/ai-coding/sessions/{session_id}", update_data)
|
|
401
|
+
|
|
402
|
+
def increment_session_rounds(self, session_id: str) -> Dict[str, Any]:
|
|
403
|
+
"""
|
|
404
|
+
增加会话的对话轮数(conversation_rounds)
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
session_id: 会话 ID
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Dict[str, Any]: 更新后的会话数据
|
|
411
|
+
"""
|
|
412
|
+
return self.patch(f"/api/ai-coding/sessions/{session_id}/increment-rounds", {})
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def create_prompt(self, prompt_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
416
|
+
"""
|
|
417
|
+
创建 Prompt 记录
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
prompt_data: Prompt 数据(包含 session_id, prompt_uuid, prompt_content 等)
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Dict[str, Any]: 创建的 Prompt 数据
|
|
424
|
+
"""
|
|
425
|
+
return self.post("/api/ai-coding/prompts", prompt_data)
|
|
426
|
+
|
|
427
|
+
def get_prompt(self, prompt_uuid: str) -> Dict[str, Any]:
|
|
428
|
+
"""
|
|
429
|
+
获取 Prompt 记录
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
prompt_uuid: Prompt UUID(使用 generation_id)
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
Dict[str, Any]: Prompt 数据
|
|
436
|
+
"""
|
|
437
|
+
return self.get(f"/api/ai-coding/prompts/{prompt_uuid}")
|
|
438
|
+
|
|
439
|
+
def update_prompt(self, prompt_uuid: str, update_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
440
|
+
"""
|
|
441
|
+
更新 Prompt 记录
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
prompt_uuid: Prompt UUID(使用 generation_id)
|
|
445
|
+
update_data: 更新数据(如 response_content, status, loop_count 等)
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Dict[str, Any]: 更新后的 Prompt 数据
|
|
449
|
+
"""
|
|
450
|
+
return self.patch(f"/api/ai-coding/prompts/{prompt_uuid}", update_data)
|
|
451
|
+
|
|
452
|
+
def create_file_changes(self, changes: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
453
|
+
"""
|
|
454
|
+
批量创建文件变更记录
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
changes: 文件变更列表
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Dict[str, Any]: 创建结果
|
|
461
|
+
"""
|
|
462
|
+
return self.post("/api/ai-coding/file-changes", {"changes": changes})
|
|
463
|
+
|
|
464
|
+
def create_transcript(self, transcript_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
465
|
+
"""
|
|
466
|
+
创建 Transcript 记录
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
transcript_data: Transcript 数据(包含 session_id, transcript_content 等)
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
Dict[str, Any]: 创建的 Transcript 数据
|
|
473
|
+
"""
|
|
474
|
+
return self.post("/api/ai-coding/transcripts", transcript_data)
|
devlake_mcp/compat.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
兼容性检测模块
|
|
3
|
+
|
|
4
|
+
提供 Python 版本检测和功能可用性检查,实现渐进式功能支持:
|
|
5
|
+
- Python 3.9:仅支持 Hooks 模式
|
|
6
|
+
- Python 3.10+:完整支持(Hooks + MCP Server)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import warnings
|
|
11
|
+
from typing import Optional, Any
|
|
12
|
+
|
|
13
|
+
# 检测 Python 版本
|
|
14
|
+
PYTHON_VERSION = sys.version_info
|
|
15
|
+
HAS_MCP_SUPPORT = PYTHON_VERSION >= (3, 10)
|
|
16
|
+
|
|
17
|
+
# 尝试导入 fastmcp(仅在 Python 3.10+ 时)
|
|
18
|
+
MCP_AVAILABLE = False
|
|
19
|
+
FastMCP: Optional[Any] = None
|
|
20
|
+
|
|
21
|
+
if HAS_MCP_SUPPORT:
|
|
22
|
+
try:
|
|
23
|
+
from fastmcp import FastMCP
|
|
24
|
+
MCP_AVAILABLE = True
|
|
25
|
+
except ImportError:
|
|
26
|
+
MCP_AVAILABLE = False
|
|
27
|
+
warnings.warn(
|
|
28
|
+
"fastmcp 未安装。MCP 功能已禁用。\n"
|
|
29
|
+
"安装方式: pip install 'devlake-mcp[mcp]'",
|
|
30
|
+
ImportWarning,
|
|
31
|
+
stacklevel=2
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_version_info() -> dict:
|
|
36
|
+
"""
|
|
37
|
+
获取版本和功能支持信息
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
dict: {
|
|
41
|
+
"python_version": "3.10.19",
|
|
42
|
+
"python_version_tuple": (3, 10, 19),
|
|
43
|
+
"mcp_supported": True, # Python 版本是否支持 MCP
|
|
44
|
+
"mcp_available": True, # fastmcp 是否已安装
|
|
45
|
+
"fastmcp_version": "2.13.0.2", # fastmcp 版本(如果已安装)
|
|
46
|
+
"features": {
|
|
47
|
+
"hooks": True, # Hooks 模式(所有版本都支持)
|
|
48
|
+
"mcp_server": True # MCP Server 模式
|
|
49
|
+
},
|
|
50
|
+
"recommended_action": "..." # 推荐操作
|
|
51
|
+
}
|
|
52
|
+
"""
|
|
53
|
+
python_version_str = f"{PYTHON_VERSION.major}.{PYTHON_VERSION.minor}.{PYTHON_VERSION.micro}"
|
|
54
|
+
|
|
55
|
+
# 获取 fastmcp 版本(如果可用)
|
|
56
|
+
fastmcp_version = None
|
|
57
|
+
if MCP_AVAILABLE:
|
|
58
|
+
try:
|
|
59
|
+
import fastmcp
|
|
60
|
+
fastmcp_version = getattr(fastmcp, '__version__', 'unknown')
|
|
61
|
+
except Exception:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
# 判断推荐操作
|
|
65
|
+
if MCP_AVAILABLE:
|
|
66
|
+
recommended_action = "✓ 所有功能可用"
|
|
67
|
+
elif not HAS_MCP_SUPPORT:
|
|
68
|
+
recommended_action = "升级到 Python 3.10+ 以使用 MCP 功能"
|
|
69
|
+
else:
|
|
70
|
+
recommended_action = "安装完整功能: pip install 'devlake-mcp[mcp]'"
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"python_version": python_version_str,
|
|
74
|
+
"python_version_tuple": (PYTHON_VERSION.major, PYTHON_VERSION.minor, PYTHON_VERSION.micro),
|
|
75
|
+
"mcp_supported": HAS_MCP_SUPPORT,
|
|
76
|
+
"mcp_available": MCP_AVAILABLE,
|
|
77
|
+
"fastmcp_version": fastmcp_version,
|
|
78
|
+
"features": {
|
|
79
|
+
"hooks": True, # Hooks 模式所有版本都支持
|
|
80
|
+
"mcp_server": MCP_AVAILABLE, # MCP Server 需要 fastmcp
|
|
81
|
+
},
|
|
82
|
+
"recommended_action": recommended_action
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def check_mcp_available() -> bool:
|
|
87
|
+
"""
|
|
88
|
+
检查 MCP 功能是否可用
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
bool: MCP 功能是否可用(fastmcp 已安装且 Python >= 3.10)
|
|
92
|
+
"""
|
|
93
|
+
return MCP_AVAILABLE
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_compatibility_warnings() -> list[str]:
|
|
97
|
+
"""
|
|
98
|
+
获取兼容性警告信息
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
list[str]: 警告信息列表
|
|
102
|
+
"""
|
|
103
|
+
warnings_list = []
|
|
104
|
+
|
|
105
|
+
if not HAS_MCP_SUPPORT:
|
|
106
|
+
warnings_list.append(
|
|
107
|
+
f"⚠ Python {PYTHON_VERSION.major}.{PYTHON_VERSION.minor} detected. "
|
|
108
|
+
f"MCP 功能需要 Python 3.10+"
|
|
109
|
+
)
|
|
110
|
+
warnings_list.append(
|
|
111
|
+
"ℹ Hooks 模式可用于 Python 3.9"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
elif not MCP_AVAILABLE:
|
|
115
|
+
warnings_list.append(
|
|
116
|
+
"⚠ fastmcp 未安装。MCP Server 功能不可用。"
|
|
117
|
+
)
|
|
118
|
+
warnings_list.append(
|
|
119
|
+
"ℹ 安装完整功能: pip install 'devlake-mcp[mcp]'"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return warnings_list
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def print_compatibility_info(verbose: bool = False):
|
|
126
|
+
"""
|
|
127
|
+
打印兼容性信息
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
verbose: 是否显示详细信息
|
|
131
|
+
"""
|
|
132
|
+
info = get_version_info()
|
|
133
|
+
|
|
134
|
+
print("=" * 60)
|
|
135
|
+
print("DevLake MCP - 版本信息")
|
|
136
|
+
print("=" * 60)
|
|
137
|
+
print(f"Python 版本: {info['python_version']}")
|
|
138
|
+
|
|
139
|
+
if info['mcp_available']:
|
|
140
|
+
print(f"✓ MCP Server: 已启用 (FastMCP {info['fastmcp_version']})")
|
|
141
|
+
elif info['mcp_supported']:
|
|
142
|
+
print("✗ MCP Server: 未安装 (需要 fastmcp)")
|
|
143
|
+
else:
|
|
144
|
+
print("✗ MCP Server: 不支持 (需要 Python 3.10+)")
|
|
145
|
+
|
|
146
|
+
print(f"✓ Hooks 模式: 可用")
|
|
147
|
+
print()
|
|
148
|
+
|
|
149
|
+
# 显示警告
|
|
150
|
+
warnings_list = get_compatibility_warnings()
|
|
151
|
+
if warnings_list:
|
|
152
|
+
print("注意事项:")
|
|
153
|
+
for warning in warnings_list:
|
|
154
|
+
print(f" {warning}")
|
|
155
|
+
print()
|
|
156
|
+
|
|
157
|
+
# 显示推荐操作
|
|
158
|
+
print(f"建议: {info['recommended_action']}")
|
|
159
|
+
print("=" * 60)
|
|
160
|
+
|
|
161
|
+
if verbose:
|
|
162
|
+
print("\n功能详情:")
|
|
163
|
+
print(f" - Hooks 模式: {'✓' if info['features']['hooks'] else '✗'}")
|
|
164
|
+
print(f" - MCP Server: {'✓' if info['features']['mcp_server'] else '✗'}")
|
|
165
|
+
print()
|