Add unit tests for OpenAI plugin and establish coding standards

- Introduced comprehensive unit tests for the OpenAI plugin, covering SSE parsing, sentinel matching, delta extraction, request building, and more.
- Created a new markdown file detailing coding and naming conventions for the dstalk project, including guidelines for comments, naming rules, code organization, and memory management practices.
This commit is contained in:
2026-05-31 00:51:59 +08:00
parent f2da0f2ed4
commit f6cb51b40a
21 changed files with 343 additions and 131 deletions

View File

@@ -1,18 +1,18 @@
# ============================================================
# 插件目录 — 所有功能插件
# 插件目录 — 所有功能插件 / Plugin directory — all functional plugins
# ============================================================
# 基础插件(无外部服务依赖)
# 基础插件(无依赖) / Base plugins (no dependencies)
add_subdirectory(config)
add_subdirectory(file-io)
add_subdirectory(network)
# 中间插件(依赖基础插件)
add_subdirectory(session)
add_subdirectory(context)
# 上层插件(依赖中间插件)
add_subdirectory(deepseek)
add_subdirectory(anthropic)
add_subdirectory(tools)
add_subdirectory(lsp)
# 依赖基础插件的插件 / Plugins depending on base plugins only
add_subdirectory(network) # 依赖 config / depends on config
add_subdirectory(session) # 依赖 file_io / depends on file_io
add_subdirectory(tools) # 依赖 file_io / depends on file_io
# 依赖其他插件的插件 / Plugins depending on non-base plugins
add_subdirectory(context) # 依赖 session / depends on session
add_subdirectory(openai) # 依赖 http, config / depends on http, config
add_subdirectory(anthropic) # 依赖 http, config / depends on http, config

View File

@@ -1,22 +0,0 @@
cmake_minimum_required(VERSION 3.21)
# ============================================================
# plugin-deepseek — DeepSeek AI 服务 (OpenAI 兼容)
# 依赖: http 服务 (查询), config 服务 (查询)
# ============================================================
add_library(plugin-deepseek SHARED
src/deepseek_plugin.cpp
)
target_link_libraries(plugin-deepseek PRIVATE dstalk)
# Boost.JSON 用于构建/解析请求和响应
find_package(Boost REQUIRED CONFIG)
target_link_libraries(plugin-deepseek PRIVATE boost::boost dstalk_boost_config)
set_target_properties(plugin-deepseek PROPERTIES
PREFIX ""
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins"
)

View File

@@ -0,0 +1,20 @@
# ============================================================
# plugin-openai — OpenAI 兼容 AI 服务 / OpenAI-compatible AI service
# ============================================================
find_package(Boost REQUIRED CONFIG)
add_library(plugin-openai SHARED
src/openai_plugin.cpp
)
target_link_libraries(plugin-openai PRIVATE dstalk)
# Boost.JSON (header-only)
find_package(Boost REQUIRED CONFIG)
target_link_libraries(plugin-openai PRIVATE boost::boost dstalk_boost_config)
set_target_properties(plugin-openai PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins
)

View File

@@ -1,6 +1,6 @@
/*
* @file deepseek_plugin.cpp
* @brief DeepSeek/OpenAI-compatible AI provider plugin with SSE streaming and tool calls.
* @file openai_plugin.cpp
* @brief OpenAI-compatible AI provider plugin with SSE streaming and tool calls.
* DeepSeek/OpenAI AI SSE
* Copyright (c) 2026 dstalk contributors. GPLv3.
*/
@@ -351,18 +351,18 @@ static int my_configure(const char* provider, const char* base_url,
}
host->log(DSTALK_LOG_INFO,
"[deepseek] configured: model=%s base_url=%s max_tokens=%d temperature=%.2f",
"[openai] configured: model=%s base_url=%s max_tokens=%d temperature=%.2f",
g_cfg.model.c_str(), g_cfg.base_url.c_str(),
g_cfg.max_tokens, g_cfg.temperature);
}
return 0;
} catch (const std::exception& e) {
const dstalk_host_api_t* host = g_host.load(std::memory_order_acquire);
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[deepseek] my_configure exception: %s", e.what());
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[openai] my_configure exception: %s", e.what());
return -1;
} catch (...) {
const dstalk_host_api_t* host = g_host.load(std::memory_order_acquire);
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[deepseek] my_configure unknown exception");
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[openai] my_configure unknown exception");
return -1;
}
}
@@ -417,14 +417,14 @@ static dstalk_chat_result_t my_chat(
return r;
} catch (const std::exception& e) {
const dstalk_host_api_t* host = g_host.load(std::memory_order_acquire);
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[deepseek] my_chat exception: %s", e.what());
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[openai] my_chat exception: %s", e.what());
dstalk_chat_result_t r = {};
r.ok = 0;
r.error = host ? host->strdup(e.what()) : nullptr;
return r;
} catch (...) {
const dstalk_host_api_t* host = g_host.load(std::memory_order_acquire);
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[deepseek] my_chat unknown exception");
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[openai] my_chat unknown exception");
dstalk_chat_result_t r = {};
r.ok = 0;
r.error = host ? host->strdup("unknown exception") : nullptr;
@@ -458,11 +458,11 @@ static int sse_line_callback(const char* line, void* userdata)
return 1; // 继续 / continue
} catch (const std::exception& e) {
const dstalk_host_api_t* host = g_host.load(std::memory_order_acquire);
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[deepseek] sse_line_callback exception: %s", e.what());
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[openai] sse_line_callback exception: %s", e.what());
return 0;
} catch (...) {
const dstalk_host_api_t* host = g_host.load(std::memory_order_acquire);
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[deepseek] sse_line_callback unknown exception");
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[openai] sse_line_callback unknown exception");
return 0;
}
}
@@ -580,14 +580,14 @@ static dstalk_chat_result_t my_chat_stream(
return r;
} catch (const std::exception& e) {
const dstalk_host_api_t* host = g_host.load(std::memory_order_acquire);
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[deepseek] my_chat_stream exception: %s", e.what());
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[openai] my_chat_stream exception: %s", e.what());
dstalk_chat_result_t r = {};
r.ok = 0;
r.error = host ? host->strdup(e.what()) : nullptr;
return r;
} catch (...) {
const dstalk_host_api_t* host = g_host.load(std::memory_order_acquire);
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[deepseek] my_chat_stream unknown exception");
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[openai] my_chat_stream unknown exception");
dstalk_chat_result_t r = {};
r.ok = 0;
r.error = host ? host->strdup("unknown exception") : nullptr;
@@ -621,7 +621,7 @@ static dstalk_ai_service_t g_service = {
// ============================================================================
// 生命周期 / Lifecycle
// ============================================================================
// 插件初始化:查询 http 和 config 服务,注册 ai.deepseek 服务 / Plugin init: query http and config services, register ai.deepseek service.
// 插件初始化:查询 http 和 config 服务,注册 ai.openai 服务 / Plugin init: query http and config services, register ai.openai service.
static int on_init(const dstalk_host_api_t* host)
{
try {
@@ -632,20 +632,20 @@ static int on_init(const dstalk_host_api_t* host)
g_config.store(cfg, std::memory_order_release);
if (!http) {
if (host) host->log(DSTALK_LOG_ERROR, "[deepseek] http service not found");
if (host) host->log(DSTALK_LOG_ERROR, "[openai] http service not found");
return -1;
}
if (host) host->log(DSTALK_LOG_INFO, "[deepseek] initializing DeepSeek AI plugin");
if (host) host->log(DSTALK_LOG_INFO, "[openai] initializing OpenAI-compatible AI plugin");
return host->register_service("ai.deepseek", 1, &g_service);
return host->register_service("ai.openai", 1, &g_service);
} catch (const std::exception& e) {
const dstalk_host_api_t* h = g_host.load(std::memory_order_acquire);
if (h && h->log) h->log(DSTALK_LOG_ERROR, "[deepseek] on_init exception: %s", e.what());
if (h && h->log) h->log(DSTALK_LOG_ERROR, "[openai] on_init exception: %s", e.what());
return -1;
} catch (...) {
const dstalk_host_api_t* h = g_host.load(std::memory_order_acquire);
if (h && h->log) h->log(DSTALK_LOG_ERROR, "[deepseek] on_init unknown exception");
if (h && h->log) h->log(DSTALK_LOG_ERROR, "[openai] on_init unknown exception");
return -1;
}
}
@@ -655,7 +655,7 @@ static void on_shutdown()
{
try {
const dstalk_host_api_t* host = g_host.load(std::memory_order_acquire);
if (host) host->log(DSTALK_LOG_INFO, "[deepseek] shutdown");
if (host) host->log(DSTALK_LOG_INFO, "[openai] shutdown");
secure_zero(g_cfg.api_key.data(), g_cfg.api_key.size());
g_cfg.api_key.clear();
g_http.store(nullptr, std::memory_order_release);
@@ -663,10 +663,10 @@ static void on_shutdown()
g_host.store(nullptr, std::memory_order_release);
} catch (const std::exception& e) {
const dstalk_host_api_t* host = g_host.load(std::memory_order_acquire);
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[deepseek] on_shutdown exception: %s", e.what());
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[openai] on_shutdown exception: %s", e.what());
} catch (...) {
const dstalk_host_api_t* host = g_host.load(std::memory_order_acquire);
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[deepseek] on_shutdown unknown exception");
if (host && host->log) host->log(DSTALK_LOG_ERROR, "[openai] on_shutdown unknown exception");
}
}
@@ -674,9 +674,9 @@ static void on_shutdown()
// 插件描述符 / Plugin descriptor
// ============================================================================
static dstalk_plugin_info_t g_info = {
/* .name = */ "deepseek-ai",
/* .name = */ "openai-compat",
/* .version = */ "1.0.0",
/* .description = */ "DeepSeek AI provider (OpenAI-compatible API) / DeepSeek AI 提供者 (OpenAI 兼容 API)",
/* .description = */ "OpenAI-compatible AI provider (OpenAI-compatible API) / OpenAI-compatible AI 提供者 (OpenAI 兼容 API)",
/* .api_version = */ DSTALK_API_VERSION,
/* .dependencies = */ { "http", "config", NULL },
/* .on_init = */ on_init,