aidev-bkplugin 2.2.4__tar.gz → 2.4.0__tar.gz
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.
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/PKG-INFO +2 -2
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/apps.py +4 -0
- aidev_bkplugin-2.4.0/aidev_bkplugin/celery_runtime.py +76 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/settings.py +9 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/views/session.py +33 -22
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/pyproject.toml +2 -2
- aidev_bkplugin-2.4.0/tests/test_celery_runtime.py +60 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/views/test_session_pv_files_views.py +94 -19
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/constants.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/enums.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/management/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/management/commands/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/management/commands/upgrade_sessions.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/migrations/0001_initial.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/migrations/0002_event_delivery.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/migrations/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/models.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/openapi/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/openapi/urls.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/openapi/views.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/apigw/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/apigw/permissions.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/checkpoint/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/checkpoint/bk_django_saver.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/drf/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/drf/authentication.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/drf/decorators.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/drf/exception.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/drf/renderers.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/permissions.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/private/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/private/permissions.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/private/urls.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/private/views.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/serializers/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/serializers/agent.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/serializers/chat_completion.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/serializers/llm.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/serializers/user_operation.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/agent_bkplugin.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/agent_builder.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/agent_config.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/agent_execution.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/agent_helpers.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/agent_session.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/approval_resume.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/chat_tracing.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/database_event_bus.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/event_resource_manager.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/event_tracing.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/execution.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/llm.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/metric_runtime.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/otel_metrics.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/user_operation.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/tasks.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/urls.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/utils.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/views/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/views/agent.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/views/base.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/views/chat.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/views/chat_group.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/views/flow_agent.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/views/llm.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/views/user_operation.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/readme.md +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/apps.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/conftest.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/database_events/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/database_events/conftest.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/database_events/test_bus.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/database_events/test_resource_manager.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/database_events/test_tracing.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/packages/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/packages/drf/test_renderers_trace.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/packages/test_bk_django_saver.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/packages/test_bk_django_saver_async.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/private/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/private/test_private_views.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/agent/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/agent/conftest.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/agent/test_agent_config_fetcher.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/agent/test_builder.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/agent/test_session_manager.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_agent_completion.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_agui_handler.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_approval_resume.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_approval_resume_tracing.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_bkplugin.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_chat_tracing.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_event_tracing.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_execution.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_llm.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_otel_metrics.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_resume_consumer.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_thread_chat_completion.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_user_operation.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/settings.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/test_apps.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/test_constants.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/test_management_metrics.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/views/__init__.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/views/test_agent_view.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/views/test_chat_agent_code_propagation.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/views/test_chat_completion_serializer.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/views/test_chat_flow_agent.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/views/test_chat_stream_status.py +0 -0
- {aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/views/test_session_view.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: aidev-bkplugin
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: AiDev Agent Bkplugin builtin functions
|
|
5
5
|
Requires-Python: <3.12,>=3.11
|
|
6
|
-
Requires-Dist: aidev-agent>=2.
|
|
6
|
+
Requires-Dist: aidev-agent>=2.4.0
|
|
7
7
|
Requires-Dist: blueapps<5.0,>=4.15.1
|
|
8
8
|
Requires-Dist: django<5.0,>=4.2.28
|
|
9
9
|
Provides-Extra: opentelemetry
|
|
@@ -8,6 +8,7 @@ from aidev_agent.utils.module_loading import import_string
|
|
|
8
8
|
from django.apps import AppConfig
|
|
9
9
|
from django.conf import settings
|
|
10
10
|
|
|
11
|
+
from aidev_bkplugin.celery_runtime import install_celery_exit_on_broker_loss
|
|
11
12
|
from aidev_bkplugin.services.metric_runtime import set_metric_service
|
|
12
13
|
|
|
13
14
|
try:
|
|
@@ -224,6 +225,9 @@ class AgentConfig(AppConfig):
|
|
|
224
225
|
if custom_resource_manager:
|
|
225
226
|
resource_manager.replace_defaults(import_string(custom_resource_manager)())
|
|
226
227
|
|
|
228
|
+
# Celery worker 断连退出,避免 consumers=0 假活。
|
|
229
|
+
install_celery_exit_on_broker_loss()
|
|
230
|
+
|
|
227
231
|
# 初始化 OpenTelemetry
|
|
228
232
|
init_bk_aidev_agent_otel()
|
|
229
233
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Celery worker 与 broker 断连后的退出策略。
|
|
3
|
+
|
|
4
|
+
默认 Celery 会无限重连。重连失败或 consumers 未恢复时进程仍活着,
|
|
5
|
+
队列 ``consumers=0``,任务堆积,表现为服务假死。
|
|
6
|
+
连上之后禁止重连,让 worker 以非 0 退出,由 PaaS 拉起新进程。
|
|
7
|
+
启动阶段仍允许重试,避免 broker 尚未就绪时直接起不来。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# Django settings / Celery 5 namespace 两套名字都写,兼容 blueapps 是否带 CELERY_ 前缀。
|
|
20
|
+
BROKER_LOSS_EXIT_SETTINGS = {
|
|
21
|
+
"BROKER_CONNECTION_RETRY": False,
|
|
22
|
+
"BROKER_CONNECTION_RETRY_ON_STARTUP": True,
|
|
23
|
+
"WORKER_CANCEL_LONG_RUNNING_TASKS_ON_CONNECTION_LOSS": True,
|
|
24
|
+
"CELERY_BROKER_CONNECTION_RETRY": False,
|
|
25
|
+
"CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP": True,
|
|
26
|
+
"CELERY_WORKER_CANCEL_LONG_RUNNING_TASKS_ON_CONNECTION_LOSS": True,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_celery_worker(argv: list[str] | None = None) -> bool:
|
|
31
|
+
"""判断当前进程是否为 Celery worker(不含 beat)。"""
|
|
32
|
+
args = list(argv if argv is not None else sys.argv)
|
|
33
|
+
if not args:
|
|
34
|
+
return False
|
|
35
|
+
program = Path(args[0]).name.lower()
|
|
36
|
+
rest = args[1:]
|
|
37
|
+
if program in {"celery", "celery.exe"}:
|
|
38
|
+
return "worker" in rest
|
|
39
|
+
if rest and rest[0] == "celery":
|
|
40
|
+
return "worker" in rest[1:]
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def apply_celery_exit_on_broker_loss(app: Any) -> None:
|
|
45
|
+
"""Disable post-connect broker reconnect so a lost consumer shuts the worker down."""
|
|
46
|
+
conf = app.conf
|
|
47
|
+
conf.broker_connection_retry = False
|
|
48
|
+
conf.broker_connection_retry_on_startup = True
|
|
49
|
+
conf.worker_cancel_long_running_tasks_on_connection_loss = True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def install_celery_exit_on_broker_loss(argv: list[str] | None = None) -> bool:
|
|
53
|
+
"""Worker 进程里关掉连上之后的 broker 重连。非 worker 或未安装 Celery 时跳过。"""
|
|
54
|
+
if not is_celery_worker(argv):
|
|
55
|
+
return False
|
|
56
|
+
try:
|
|
57
|
+
from celery import current_app
|
|
58
|
+
except ImportError:
|
|
59
|
+
logger.warning("[aidev_bkplugin] celery is not installed; skip broker-loss exit hook")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
apps = [current_app]
|
|
63
|
+
try:
|
|
64
|
+
from blueapps.core.celery import app as blueapps_app
|
|
65
|
+
except ImportError:
|
|
66
|
+
blueapps_app = None
|
|
67
|
+
if blueapps_app is not None and blueapps_app is not current_app:
|
|
68
|
+
apps.append(blueapps_app)
|
|
69
|
+
|
|
70
|
+
for app in apps:
|
|
71
|
+
apply_celery_exit_on_broker_loss(app)
|
|
72
|
+
logger.warning(
|
|
73
|
+
"[aidev_bkplugin] Celery broker reconnect after connect is disabled; "
|
|
74
|
+
"worker will exit on connection loss so the platform can restart it"
|
|
75
|
+
)
|
|
76
|
+
return True
|
|
@@ -37,6 +37,15 @@ RUN_VER = "ieod" if os.environ.get("BKPAAS_ENGINE_REGION", "default") == "ieod"
|
|
|
37
37
|
# OAuth 认证配置
|
|
38
38
|
BKAUTH_BACKEND_TYPE = "bk_ticket" if RUN_VER == "ieod" else "bk_token"
|
|
39
39
|
|
|
40
|
+
# Celery worker 连上 broker 后禁止假活重连;断连以非 0 退出,由 PaaS 拉起新 worker。
|
|
41
|
+
# 启动阶段仍允许重试。Django 无 namespace 与 CELERY_ 前缀两套都写,兼容 blueapps。
|
|
42
|
+
BROKER_CONNECTION_RETRY = False
|
|
43
|
+
BROKER_CONNECTION_RETRY_ON_STARTUP = True
|
|
44
|
+
WORKER_CANCEL_LONG_RUNNING_TASKS_ON_CONNECTION_LOSS = True
|
|
45
|
+
CELERY_BROKER_CONNECTION_RETRY = False
|
|
46
|
+
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
|
|
47
|
+
CELERY_WORKER_CANCEL_LONG_RUNNING_TASKS_ON_CONNECTION_LOSS = True
|
|
48
|
+
|
|
40
49
|
# 仅社区版需要配置,其它版本已由开发框架处理
|
|
41
50
|
if BKAUTH_BACKEND_TYPE == 'bk_token':
|
|
42
51
|
OAUTH_COOKIES_PARAMS = {"bk_token": "bk_token", "bk_uid": "bk_uid"}
|
|
@@ -11,9 +11,11 @@ from aidev_agent.services.sandbox_pv_files import (
|
|
|
11
11
|
SandboxFileInvalidRequestError,
|
|
12
12
|
SandboxFileNotFoundError,
|
|
13
13
|
SandboxPvFileService,
|
|
14
|
+
SandboxVolumeNotFoundError,
|
|
14
15
|
fill_user_image_urls,
|
|
15
16
|
iter_user_images_missing_url,
|
|
16
17
|
validate_session_upload_files,
|
|
18
|
+
validate_session_upload_stats,
|
|
17
19
|
)
|
|
18
20
|
from bkapi_client_core.exceptions import HTTPResponseError
|
|
19
21
|
from blueapps.core.exceptions import ClientBlueException, ResourceNotFound, ServerBlueException
|
|
@@ -236,11 +238,10 @@ class ChatSessionViewSet(PluginViewSet):
|
|
|
236
238
|
def pv_files(self, request, pk, **kwargs):
|
|
237
239
|
self._check_session_owner(request, pk, require_access=False)
|
|
238
240
|
svc = self._make_pv_file_service(request)
|
|
239
|
-
params = request.query_params
|
|
240
241
|
try:
|
|
241
242
|
data = svc.list_files(
|
|
242
243
|
session_code=pk,
|
|
243
|
-
path=
|
|
244
|
+
path=request.query_params.get("path"),
|
|
244
245
|
since=None,
|
|
245
246
|
until=None,
|
|
246
247
|
)
|
|
@@ -248,14 +249,29 @@ class ChatSessionViewSet(PluginViewSet):
|
|
|
248
249
|
self._raise_pv_exc(exc)
|
|
249
250
|
return Response(data=data)
|
|
250
251
|
|
|
252
|
+
@pv_files.mapping.delete
|
|
253
|
+
def delete_pv_file(self, request, pk, **kwargs):
|
|
254
|
+
self._check_session_owner(request, pk, require_access=True)
|
|
255
|
+
try:
|
|
256
|
+
self._make_pv_file_service(request).delete_file(
|
|
257
|
+
session_code=pk, path=request.query_params.get("path")
|
|
258
|
+
)
|
|
259
|
+
# 幂等只覆盖「文件不存在」;会话根本没有 PV 是真错误,不能当删除成功返回 200
|
|
260
|
+
except SandboxVolumeNotFoundError as exc:
|
|
261
|
+
self._raise_pv_exc(exc)
|
|
262
|
+
except SandboxFileNotFoundError:
|
|
263
|
+
pass
|
|
264
|
+
except SandboxFileError as exc:
|
|
265
|
+
self._raise_pv_exc(exc)
|
|
266
|
+
return Response()
|
|
267
|
+
|
|
251
268
|
@action(["GET"], url_path="pv_files/stat", detail=True)
|
|
252
269
|
def pv_files_stat(self, request, pk, **kwargs):
|
|
253
270
|
self._check_session_owner(request, pk, require_access=False)
|
|
254
|
-
path = request.query_params.get("path", "")
|
|
255
|
-
if not path:
|
|
256
|
-
raise ClientBlueException(message="path is required")
|
|
257
271
|
try:
|
|
258
|
-
data = self._make_pv_file_service(request).stat_file(
|
|
272
|
+
data = self._make_pv_file_service(request).stat_file(
|
|
273
|
+
session_code=pk, path=request.query_params.get("path")
|
|
274
|
+
)
|
|
259
275
|
except SandboxFileError as exc:
|
|
260
276
|
self._raise_pv_exc(exc)
|
|
261
277
|
return Response(data=data)
|
|
@@ -263,13 +279,10 @@ class ChatSessionViewSet(PluginViewSet):
|
|
|
263
279
|
@action(["GET"], url_path="pv_files/preview", detail=True)
|
|
264
280
|
def pv_files_preview(self, request, pk, **kwargs):
|
|
265
281
|
self._check_session_owner(request, pk, require_access=False)
|
|
266
|
-
path = request.query_params.get("path", "")
|
|
267
|
-
if not path:
|
|
268
|
-
raise ClientBlueException(message="path is required")
|
|
269
282
|
max_bytes = _parse_positive_int(request.query_params.get("max_bytes"), 65536)
|
|
270
283
|
try:
|
|
271
284
|
content, truncated = self._make_pv_file_service(request).preview_file(
|
|
272
|
-
session_code=pk, path=path, max_bytes=max_bytes
|
|
285
|
+
session_code=pk, path=request.query_params.get("path"), max_bytes=max_bytes
|
|
273
286
|
)
|
|
274
287
|
except SandboxFileError as exc:
|
|
275
288
|
self._raise_pv_exc(exc)
|
|
@@ -280,13 +293,10 @@ class ChatSessionViewSet(PluginViewSet):
|
|
|
280
293
|
@action(["GET"], url_path="pv_files/download_url", detail=True)
|
|
281
294
|
def pv_files_download_url(self, request, pk, **kwargs):
|
|
282
295
|
self._check_session_owner(request, pk, require_access=False)
|
|
283
|
-
path = request.query_params.get("path", "")
|
|
284
|
-
if not path:
|
|
285
|
-
raise ClientBlueException(message="path is required")
|
|
286
296
|
expires_in = _parse_positive_int(request.query_params.get("expires_in"), 600)
|
|
287
297
|
try:
|
|
288
298
|
data = self._make_pv_file_service(request).get_download_url(
|
|
289
|
-
session_code=pk, path=path, expires_in=expires_in
|
|
299
|
+
session_code=pk, path=request.query_params.get("path"), expires_in=expires_in
|
|
290
300
|
)
|
|
291
301
|
except SandboxFileError as exc:
|
|
292
302
|
self._raise_pv_exc(exc)
|
|
@@ -302,15 +312,16 @@ class ChatSessionViewSet(PluginViewSet):
|
|
|
302
312
|
"""批量上传文件到会话 PV。"""
|
|
303
313
|
self._check_session_owner(request, pk, require_access=True)
|
|
304
314
|
uploaded_files = request.FILES.getlist("files")
|
|
305
|
-
files = [
|
|
306
|
-
{
|
|
307
|
-
"name": upload_file.name,
|
|
308
|
-
"content": upload_file.read(),
|
|
309
|
-
"mime_type": upload_file.content_type or "application/octet-stream",
|
|
310
|
-
}
|
|
311
|
-
for upload_file in uploaded_files
|
|
312
|
-
]
|
|
313
315
|
try:
|
|
316
|
+
validate_session_upload_stats(uploaded_files)
|
|
317
|
+
files = [
|
|
318
|
+
{
|
|
319
|
+
"name": upload_file.name,
|
|
320
|
+
"content": upload_file.read(),
|
|
321
|
+
"mime_type": upload_file.content_type or "application/octet-stream",
|
|
322
|
+
}
|
|
323
|
+
for upload_file in uploaded_files
|
|
324
|
+
]
|
|
314
325
|
validate_session_upload_files(files)
|
|
315
326
|
except SandboxFileError as exc:
|
|
316
327
|
self._raise_pv_exc(exc)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aidev-bkplugin"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.4.0"
|
|
4
4
|
description = "AiDev Agent Bkplugin builtin functions"
|
|
5
5
|
readme = "readme.md"
|
|
6
6
|
requires-python = "<3.12,>=3.11"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"aidev-agent>=2.
|
|
8
|
+
"aidev-agent>=2.4.0",
|
|
9
9
|
"blueapps>=4.15.1, <5.0",
|
|
10
10
|
"django>=4.2.28,<5.0",
|
|
11
11
|
]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from types import ModuleType, SimpleNamespace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from aidev_bkplugin import settings as plugin_settings
|
|
7
|
+
from aidev_bkplugin.celery_runtime import (
|
|
8
|
+
BROKER_LOSS_EXIT_SETTINGS,
|
|
9
|
+
apply_celery_exit_on_broker_loss,
|
|
10
|
+
install_celery_exit_on_broker_loss,
|
|
11
|
+
is_celery_worker,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.parametrize(
|
|
16
|
+
("argv", "expected"),
|
|
17
|
+
[
|
|
18
|
+
(["celery", "-A", "blueapps.core.celery", "worker", "-P", "threads"], True),
|
|
19
|
+
(["/usr/local/bin/celery", "-A", "test_app", "worker"], True),
|
|
20
|
+
(["manage.py", "celery", "worker", "-B"], True),
|
|
21
|
+
(["bin/manage.py", "celery", "-A", "test_app", "worker"], True),
|
|
22
|
+
(["celery", "-A", "blueapps.core.celery", "beat"], False),
|
|
23
|
+
(["manage.py", "celery", "beat", "-l", "info"], False),
|
|
24
|
+
(["gunicorn", "wsgi:application"], False),
|
|
25
|
+
(["manage.py", "runserver"], False),
|
|
26
|
+
([], False),
|
|
27
|
+
],
|
|
28
|
+
)
|
|
29
|
+
def test_is_celery_worker_detects_worker_only(argv, expected):
|
|
30
|
+
assert is_celery_worker(argv) is expected
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_apply_celery_exit_on_broker_loss_disables_reconnect_after_connect():
|
|
34
|
+
app = SimpleNamespace(conf=SimpleNamespace())
|
|
35
|
+
|
|
36
|
+
apply_celery_exit_on_broker_loss(app)
|
|
37
|
+
|
|
38
|
+
assert app.conf.broker_connection_retry is False
|
|
39
|
+
assert app.conf.broker_connection_retry_on_startup is True
|
|
40
|
+
assert app.conf.worker_cancel_long_running_tasks_on_connection_loss is True
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_install_applies_when_celery_is_available(mocker):
|
|
44
|
+
app = SimpleNamespace(conf=SimpleNamespace())
|
|
45
|
+
celery_mod = ModuleType("celery")
|
|
46
|
+
celery_mod.current_app = app
|
|
47
|
+
mocker.patch.dict("sys.modules", {"celery": celery_mod})
|
|
48
|
+
|
|
49
|
+
assert install_celery_exit_on_broker_loss(["celery", "-A", "app", "worker"]) is True
|
|
50
|
+
assert app.conf.broker_connection_retry is False
|
|
51
|
+
assert app.conf.broker_connection_retry_on_startup is True
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_plugin_settings_disable_broker_reconnect_after_connect():
|
|
55
|
+
for name, value in BROKER_LOSS_EXIT_SETTINGS.items():
|
|
56
|
+
assert getattr(plugin_settings, name) is value
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_install_skips_non_worker_process():
|
|
60
|
+
assert install_celery_exit_on_broker_loss(["gunicorn", "wsgi:application"]) is False
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
覆盖点:
|
|
5
5
|
- 构造沙箱文件 Service 时正确注入 PluginResourceManager + executor_info
|
|
6
|
-
-
|
|
6
|
+
- 6 个 action 参数透传(GET list / DELETE / stat / preview / download_url / upload)
|
|
7
7
|
- 上传会话归属校验与沙箱文件异常映射
|
|
8
8
|
- 沙箱文件异常 → blueapps 异常映射(404 / 400 / 500)
|
|
9
9
|
- preview 返回 HttpResponse(text/plain) + X-Truncated 头透传
|
|
@@ -60,6 +60,7 @@ from aidev_agent.services.sandbox_pv_files import ( # noqa: E402
|
|
|
60
60
|
SandboxFileInvalidRequestError,
|
|
61
61
|
SandboxFileNotFoundError,
|
|
62
62
|
SandboxFileServerError,
|
|
63
|
+
SandboxVolumeNotFoundError,
|
|
63
64
|
)
|
|
64
65
|
from aidev_bkplugin.views import session as session_mod # noqa: E402
|
|
65
66
|
|
|
@@ -203,6 +204,7 @@ class TestPvFilesGet:
|
|
|
203
204
|
instance.list_files.return_value = {"count": 1, "results": [{"path": "a.txt"}]}
|
|
204
205
|
response = view.pv_files(_request({"path": "sub/"}), pk="s1")
|
|
205
206
|
assert response.data == {"count": 1, "results": [{"path": "a.txt"}]}
|
|
207
|
+
# 视图只透传原始入参,归一化由 Service 负责
|
|
206
208
|
instance.list_files.assert_called_once_with(session_code="s1", path="sub/", since=None, until=None)
|
|
207
209
|
|
|
208
210
|
def test_list_translates_not_found(self, view, mock_svc):
|
|
@@ -282,6 +284,53 @@ class TestCheckSessionOwner:
|
|
|
282
284
|
fake_client.api.retrieve_chat_session.assert_not_called()
|
|
283
285
|
|
|
284
286
|
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
# pv_files DELETE
|
|
289
|
+
# ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class TestPvFilesDelete:
|
|
293
|
+
def test_delete_forwards_path_and_returns_ok(self, view, mock_svc):
|
|
294
|
+
instance, _ = mock_svc
|
|
295
|
+
instance.delete_file.return_value = None
|
|
296
|
+
response = view.delete_pv_file(_request({"path": "files/a.txt"}, method="DELETE"), pk="s1")
|
|
297
|
+
assert response.status_code == 200
|
|
298
|
+
instance.delete_file.assert_called_once_with(session_code="s1", path="files/a.txt")
|
|
299
|
+
fake_client.api.retrieve_chat_session.assert_called_once()
|
|
300
|
+
|
|
301
|
+
def test_delete_invalid_path_is_not_idempotent(self, view, mock_svc):
|
|
302
|
+
"""非法路径由 Service 判定;幂等分支不能把它一起吞成 200。"""
|
|
303
|
+
from blueapps.core.exceptions import ClientBlueException
|
|
304
|
+
|
|
305
|
+
instance, _ = mock_svc
|
|
306
|
+
instance.delete_file.side_effect = SandboxFileInvalidArgumentError("invalid path")
|
|
307
|
+
with pytest.raises(ClientBlueException) as excinfo:
|
|
308
|
+
view.delete_pv_file(_request({"path": "files/../secret.txt"}, method="DELETE"), pk="s1")
|
|
309
|
+
assert excinfo.value.STATUS_CODE == 400
|
|
310
|
+
|
|
311
|
+
def test_delete_not_found_is_idempotent(self, view, mock_svc):
|
|
312
|
+
instance, _ = mock_svc
|
|
313
|
+
instance.delete_file.side_effect = SandboxFileNotFoundError("nf")
|
|
314
|
+
response = view.delete_pv_file(_request({"path": "files/gone.txt"}, method="DELETE"), pk="s1")
|
|
315
|
+
assert response.status_code == 200
|
|
316
|
+
|
|
317
|
+
def test_delete_missing_volume_is_not_idempotent(self, view, mock_svc):
|
|
318
|
+
"""会话没有 PV 不属于「文件已删掉」,不能被幂等吞成 200。"""
|
|
319
|
+
from blueapps.core.exceptions import ResourceNotFound
|
|
320
|
+
|
|
321
|
+
instance, _ = mock_svc
|
|
322
|
+
instance.delete_file.side_effect = SandboxVolumeNotFoundError("session s1 未初始化沙箱 PersistentVolume")
|
|
323
|
+
with pytest.raises(ResourceNotFound):
|
|
324
|
+
view.delete_pv_file(_request({"path": "files/a.txt"}, method="DELETE"), pk="s1")
|
|
325
|
+
|
|
326
|
+
def test_delete_checks_owner(self, view, mock_svc):
|
|
327
|
+
from blueapps.core.exceptions import ClientBlueException
|
|
328
|
+
|
|
329
|
+
fake_client.api.retrieve_chat_session.side_effect = TestCheckSessionOwner._make_http_error(403)
|
|
330
|
+
with pytest.raises(ClientBlueException):
|
|
331
|
+
view.delete_pv_file(_request({"path": "files/a.txt"}, method="DELETE"), pk="s1")
|
|
332
|
+
|
|
333
|
+
|
|
285
334
|
# ---------------------------------------------------------------------------
|
|
286
335
|
# pv_files_stat
|
|
287
336
|
# ---------------------------------------------------------------------------
|
|
@@ -295,12 +344,6 @@ class TestPvFilesStat:
|
|
|
295
344
|
assert response.data == {"exists": True, "size": 42}
|
|
296
345
|
instance.stat_file.assert_called_once_with(session_code="s1", path="x.txt")
|
|
297
346
|
|
|
298
|
-
def test_stat_requires_path(self, view, mock_svc):
|
|
299
|
-
from blueapps.core.exceptions import ClientBlueException
|
|
300
|
-
|
|
301
|
-
with pytest.raises(ClientBlueException):
|
|
302
|
-
view.pv_files_stat(_request({}), pk="s1")
|
|
303
|
-
|
|
304
347
|
def test_stat_translates_invalid_arg(self, view, mock_svc):
|
|
305
348
|
from blueapps.core.exceptions import ClientBlueException
|
|
306
349
|
|
|
@@ -354,12 +397,6 @@ class TestPvFilesPreview:
|
|
|
354
397
|
view.pv_files_preview(_request({"path": "x.bin"}), pk="s1")
|
|
355
398
|
assert excinfo.value.STATUS_CODE == 400
|
|
356
399
|
|
|
357
|
-
def test_preview_requires_path(self, view, mock_svc):
|
|
358
|
-
from blueapps.core.exceptions import ClientBlueException
|
|
359
|
-
|
|
360
|
-
with pytest.raises(ClientBlueException):
|
|
361
|
-
view.pv_files_preview(_request({}), pk="s1")
|
|
362
|
-
|
|
363
400
|
|
|
364
401
|
# ---------------------------------------------------------------------------
|
|
365
402
|
# pv_files_download_url
|
|
@@ -389,12 +426,6 @@ class TestPvFilesDownloadUrl:
|
|
|
389
426
|
view.pv_files_download_url(_request({"path": "x.txt"}), pk="s1")
|
|
390
427
|
assert excinfo.value.STATUS_CODE == 500
|
|
391
428
|
|
|
392
|
-
def test_download_url_requires_path(self, view, mock_svc):
|
|
393
|
-
from blueapps.core.exceptions import ClientBlueException
|
|
394
|
-
|
|
395
|
-
with pytest.raises(ClientBlueException):
|
|
396
|
-
view.pv_files_download_url(_request({}), pk="s1")
|
|
397
|
-
|
|
398
429
|
|
|
399
430
|
class TestPvFilesUpload:
|
|
400
431
|
def test_upload_forwards_files_to_local_service(self, view, mock_svc):
|
|
@@ -432,6 +463,50 @@ class TestPvFilesUpload:
|
|
|
432
463
|
instance.upload_files.assert_not_called()
|
|
433
464
|
session_mod.PluginResourceManager.return_value.get_client.return_value.api.retrieve_latest_skill_version_image.assert_not_called()
|
|
434
465
|
|
|
466
|
+
def test_upload_rejects_oversized_file_without_reading(self, view, mock_svc):
|
|
467
|
+
from aidev_agent.services.sandbox_pv_files import MAX_SESSION_UPLOAD_FILE_SIZE
|
|
468
|
+
from blueapps.core.exceptions import ClientBlueException
|
|
469
|
+
|
|
470
|
+
instance, _ = mock_svc
|
|
471
|
+
uploaded_file = SimpleNamespace(
|
|
472
|
+
name="big.txt",
|
|
473
|
+
size=MAX_SESSION_UPLOAD_FILE_SIZE + 1,
|
|
474
|
+
content_type="text/plain",
|
|
475
|
+
read=MagicMock(side_effect=AssertionError("oversized file must not be read")),
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
with pytest.raises(ClientBlueException) as excinfo:
|
|
479
|
+
view.pv_files_upload(_request(method="POST", files=[uploaded_file]), pk="s1")
|
|
480
|
+
|
|
481
|
+
assert str(MAX_SESSION_UPLOAD_FILE_SIZE) in excinfo.value.message
|
|
482
|
+
assert excinfo.value.STATUS_CODE == 400
|
|
483
|
+
uploaded_file.read.assert_not_called()
|
|
484
|
+
instance.upload_files.assert_not_called()
|
|
485
|
+
|
|
486
|
+
def test_upload_rejects_too_many_files_without_reading(self, view, mock_svc):
|
|
487
|
+
from aidev_agent.services.sandbox_pv_files import MAX_SESSION_UPLOAD_FILES
|
|
488
|
+
from blueapps.core.exceptions import ClientBlueException
|
|
489
|
+
|
|
490
|
+
instance, _ = mock_svc
|
|
491
|
+
uploaded_files = [
|
|
492
|
+
SimpleNamespace(
|
|
493
|
+
name=f"f{index}.txt",
|
|
494
|
+
size=1,
|
|
495
|
+
content_type="text/plain",
|
|
496
|
+
read=MagicMock(side_effect=AssertionError("too many files must not be read")),
|
|
497
|
+
)
|
|
498
|
+
for index in range(MAX_SESSION_UPLOAD_FILES + 1)
|
|
499
|
+
]
|
|
500
|
+
|
|
501
|
+
with pytest.raises(ClientBlueException) as excinfo:
|
|
502
|
+
view.pv_files_upload(_request(method="POST", files=uploaded_files), pk="s1")
|
|
503
|
+
|
|
504
|
+
assert str(MAX_SESSION_UPLOAD_FILES) in excinfo.value.message
|
|
505
|
+
assert excinfo.value.STATUS_CODE == 400
|
|
506
|
+
for uploaded_file in uploaded_files:
|
|
507
|
+
uploaded_file.read.assert_not_called()
|
|
508
|
+
instance.upload_files.assert_not_called()
|
|
509
|
+
|
|
435
510
|
|
|
436
511
|
# ---------------------------------------------------------------------------
|
|
437
512
|
# 异常映射:SandboxFileError 基类兜底为 ServerBlueException
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/management/commands/__init__.py
RENAMED
|
File without changes
|
{aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/management/commands/upgrade_sessions.py
RENAMED
|
File without changes
|
|
File without changes
|
{aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/migrations/0002_event_delivery.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/checkpoint/__init__.py
RENAMED
|
File without changes
|
{aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/packages/checkpoint/bk_django_saver.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/aidev_bkplugin/services/event_resource_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/database_events/test_resource_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/agent/test_agent_config_fetcher.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/services/test_approval_resume_tracing.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/views/test_chat_agent_code_propagation.py
RENAMED
|
File without changes
|
{aidev_bkplugin-2.2.4 → aidev_bkplugin-2.4.0}/tests/views/test_chat_completion_serializer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|