feat: Add LSP plugin unit tests and frontend common initialization library
Some checks failed
CI / Determine matrix (push) Has been cancelled
CI / Sanitizer (ASan+UBSan) / ubuntu-24.04 (push) Has been cancelled
CI / Coverage (gcovr) / ubuntu-24.04 (push) Has been cancelled
CI / ${{ matrix.os }} / ${{ matrix.build_type }} (push) Has been cancelled

- Introduced `dstalk_lsp_plugin_test` for testing LSP plugin functionalities including `lsp_trim`, `lsp_frame_message`, and `lsp_parse_content_length`.
- Created `dstalk_frontend_common` static library to encapsulate shared initialization logic for frontend components (CLI, GUI, Web).
- Implemented configuration file discovery and service querying in `dstalk_frontend_init`.
- Added internal headers for LSP and Anthropic plugins to facilitate unit testing.
- Established a mailroom system for asynchronous message passing between stateless agents, enhancing coordination and context management.
This commit is contained in:
2026-06-01 08:51:40 +08:00
parent 8faa02c3d5
commit c0af9c65c7
17 changed files with 1235 additions and 69 deletions

View File

@@ -0,0 +1,124 @@
// ============================================================================
// dstalk_frontend_common — 实现
// ============================================================================
#include "dstalk_frontend_common.hpp"
#include <cstdio>
#include <cstdlib>
#include <cstring>
// ---- 配置文件发现 ----
static const char* discover_config(int argc, char* argv[],
const char* default_cfg,
const char* const* skip_flags)
{
// 1) 从 argv 中查找首个非标志参数
if (argc >= 2 && argv) {
for (int i = 1; i < argc; ++i) {
bool is_skip = false;
if (skip_flags) {
for (int k = 0; skip_flags[k]; ++k) {
if (std::strcmp(argv[i], skip_flags[k]) == 0) {
is_skip = true;
// 若该标志有值参数则多跳一个
if (i + 1 < argc && argv[i + 1][0] != '-') ++i;
break;
}
}
}
if (!is_skip) return argv[i];
}
}
// 2) 回退:尝试默认配置文件
if (!default_cfg || default_cfg[0] == '\0') return nullptr;
const char* candidates[] = { default_cfg, nullptr };
for (int i = 0; candidates[i]; ++i) {
FILE* f = nullptr;
#ifdef _WIN32
if (fopen_s(&f, candidates[i], "r") == 0 && f) {
#else
f = std::fopen(candidates[i], "r");
if (f) {
#endif
std::fclose(f);
return candidates[i];
}
}
return nullptr;
}
// ---- 主初始化 ----
int dstalk_frontend_init(FrontendServices& svc,
int argc, char* argv[],
const char* default_cfg,
const char* const* skip_flags)
{
// (1) 发现并加载配置
const char* cfg = discover_config(argc, argv, default_cfg, skip_flags);
if (dstalk_init(cfg) != 0) {
std::fprintf(stderr, "[dstalk] 初始化失败\n");
return 1;
}
// (2) 查询 AI 服务
const char* provider = dstalk_config_get("ai.provider");
if (!provider || provider[0] == '\0') provider = "ai.deepseek";
svc.provider = provider;
svc.ai = static_cast<const dstalk_ai_service_t*>(
dstalk_service_query(provider, 1));
svc.session = static_cast<const dstalk_session_service_t*>(
dstalk_service_query("session", 1));
svc.file_io = static_cast<const dstalk_file_io_service_t*>(
dstalk_service_query("file_io", 1));
svc.tools = static_cast<const dstalk_tools_service_t*>(
dstalk_service_query("tools", 1));
const dstalk_context_service_t* ctx_svc =
static_cast<const dstalk_context_service_t*>(
dstalk_service_query("context", 1));
(void)ctx_svc; // 不强制使用,保留以备将来使用
if (!svc.ai) {
std::fprintf(stderr, "[dstalk] AI 服务未找到(请检查插件目录)\n");
return 2;
}
if (!svc.session) {
std::fprintf(stderr, "[dstalk] Session 服务未找到\n");
return 3;
}
// (3) 配置 AI 服务的默认值
const char* base_url = dstalk_config_get("api.base_url");
const char* api_key = dstalk_config_get("api.api_key");
const char* model = dstalk_config_get("api.model");
if (!base_url || base_url[0] == '\0') base_url = "https://api.deepseek.com/v1";
if (!model || model[0] == '\0') model = "deepseek-v4-pro";
svc.base_url = base_url;
svc.api_key = api_key ? api_key : "";
svc.model = model;
svc.ai->configure(provider, base_url,
api_key ? api_key : "",
model, 4096, 0.7);
svc.initialized = true;
return 0;
}
// ---- 会话历史回放 ----
int dstalk_frontend_replay_history(FrontendServices& svc)
{
if (!svc.session) return 0;
int count = 0;
const dstalk_message_t* msgs = svc.session->history(&count);
return count; // 调用方自行遍历并重建前端消息列表
}