feat: Add LSP plugin unit tests and frontend common initialization library
Some checks failed
Some checks failed
- 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:
17
dstalk_frontend_common/CMakeLists.txt
Normal file
17
dstalk_frontend_common/CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
# ============================================================
|
||||
# dstalk_frontend_common — 前端公共初始化静态库
|
||||
# ============================================================
|
||||
|
||||
add_library(dstalk_frontend_common STATIC
|
||||
src/frontend_common.cpp
|
||||
)
|
||||
|
||||
target_include_directories(dstalk_frontend_common
|
||||
PUBLIC include
|
||||
)
|
||||
|
||||
target_link_libraries(dstalk_frontend_common
|
||||
PUBLIC dstalk
|
||||
)
|
||||
|
||||
target_compile_features(dstalk_frontend_common PUBLIC cxx_std_20)
|
||||
65
dstalk_frontend_common/include/dstalk_frontend_common.hpp
Normal file
65
dstalk_frontend_common/include/dstalk_frontend_common.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
// ============================================================================
|
||||
// dstalk_frontend_common — 前端公共初始化模块
|
||||
// ============================================================================
|
||||
// 提供所有前端(CLI / GUI / Web)共享的启动逻辑:
|
||||
// - 配置文件发现(argv / 默认路径 / 平台 fopen)
|
||||
// - dstalk_init() 调用
|
||||
// - 常用服务查询(ai, session, file_io, tools, context)
|
||||
// - AI 服务默认配置(从 config 读取,带 fallback)
|
||||
// ============================================================================
|
||||
|
||||
#ifndef DSTALK_FRONTEND_COMMON_HPP
|
||||
#define DSTALK_FRONTEND_COMMON_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "dstalk/dstalk_host.h"
|
||||
|
||||
struct FrontendServices {
|
||||
const dstalk_ai_service_t* ai = nullptr;
|
||||
const dstalk_session_service_t* session = nullptr;
|
||||
const dstalk_file_io_service_t* file_io = nullptr;
|
||||
const dstalk_tools_service_t* tools = nullptr;
|
||||
|
||||
std::string provider; // "ai.deepseek" / "ai.openai" / "ai.anthropic"
|
||||
std::string model; // e.g. "deepseek-v4-pro"
|
||||
std::string base_url; // e.g. "https://api.deepseek.com/v1"
|
||||
std::string api_key;
|
||||
|
||||
// 是否已成功初始化 dstalk 核心
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
// ---- 前端公共初始化 ----
|
||||
//
|
||||
// 功能:
|
||||
// 1. 发现配置文件:优先 argv[1](跳过已知标志),其次 default_config(如 "config.toml")
|
||||
// 2. 调用 dstalk_init(config_path)
|
||||
// 3. 查询常用插件服务(ai / session / file_io / tools)
|
||||
// 4. 用 dstalk_config_get 读取 api.* 键并调用 ai->configure() 设置默认值
|
||||
//
|
||||
// 参数:
|
||||
// svc - [out] 填入查询到的服务指针和配置信息
|
||||
// argc/argv - 命令行参数(可为 0/nullptr,例如 GUI 没有命令行参数)
|
||||
// default_cfg- 默认配置文件名(如 "config.toml"),当 argv 未提供时使用
|
||||
// skip_flags - 以 NULL 结尾的字符串数组,argv 扫描时跳过这些标志及其下一个参数
|
||||
//
|
||||
// 返回值:
|
||||
// 0 - 成功,svc.initialized == true,至少 ai + session 已就绪
|
||||
// 1 - dstalk_init 失败
|
||||
// 2 - AI 服务未找到
|
||||
// 3 - Session 服务未找到
|
||||
//
|
||||
int dstalk_frontend_init(FrontendServices& svc,
|
||||
int argc = 0, char* argv[] = nullptr,
|
||||
const char* default_cfg = "config.toml",
|
||||
const char* const* skip_flags = nullptr);
|
||||
|
||||
// ---- 便捷辅助 ----
|
||||
|
||||
// 将 dstalk_message_t 数组的内容追加到 session 服务(一次一条)。
|
||||
// 常用于 Ctrl+O 加载会话后重建前端消息列表。
|
||||
// 返回实际追加的条数。
|
||||
int dstalk_frontend_replay_history(FrontendServices& svc);
|
||||
|
||||
#endif // DSTALK_FRONTEND_COMMON_HPP
|
||||
124
dstalk_frontend_common/src/frontend_common.cpp
Normal file
124
dstalk_frontend_common/src/frontend_common.cpp
Normal 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; // 调用方自行遍历并重建前端消息列表
|
||||
}
|
||||
Reference in New Issue
Block a user