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,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)

View 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

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; // 调用方自行遍历并重建前端消息列表
}