feat: add OpenAI-compatible AI provider plugin with SSE streaming support
- Implemented the OpenAI-compatible AI provider plugin, including configuration, chat, and chat_stream functionalities. - Added support for SSE streaming and tool calls. - Integrated Boost.JSON for JSON handling. - Created CMake configuration for the plugin. - Added error handling and logging throughout the plugin.
This commit is contained in:
9
plugins_base/config/CMakeLists.txt
Normal file
9
plugins_base/config/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
add_library(plugin-config SHARED src/config_plugin.cpp)
|
||||
|
||||
target_link_libraries(plugin-config PRIVATE dstalk)
|
||||
|
||||
set_target_properties(plugin-config PROPERTIES
|
||||
PREFIX ""
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins"
|
||||
)
|
||||
75
plugins_base/config/include/toml_parse.h
Normal file
75
plugins_base/config/include/toml_parse.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* @file toml_parse.h
|
||||
* @brief Lightweight single-header TOML parser (subset: flat key-value pairs).
|
||||
* 轻量级单头文件 TOML 解析器(子集:扁平键值对)。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
// 共享 TOML 解析器 —— 由 ConfigStore(核心)和 config 插件共同使用 / Shared TOML parser — used by both ConfigStore (core) and config plugin.
|
||||
// W12.2: Extracted from config_store.cpp:23-61 and config_plugin.cpp:28-66
|
||||
// to eliminate the 74-line code duplication (W11.2 audit Finding 1).
|
||||
// Does NOT support: inline tables, arrays, multi-line strings, escape sequences.
|
||||
// 不支持:内联表、数组、多行字符串、转义序列。
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace dstalk {
|
||||
namespace toml {
|
||||
|
||||
/// 解析 TOML 字符串,对每个键值对调用 on_kv(full_key, value) / Parse a TOML string, calling on_kv(full_key, value) for each key-value pair.
|
||||
/// 支持 [section] 标题、key = "value" 键值对、# 注释、空行 / Supports [section] headers, key = "value" pairs, # comments, blank lines.
|
||||
template<typename F>
|
||||
inline void parse(const std::string& content, F&& on_kv)
|
||||
{
|
||||
std::string current_section;
|
||||
size_t pos = 0;
|
||||
|
||||
while (pos < content.size()) {
|
||||
// 去除左侧空白 / Trim left whitespace
|
||||
while (pos < content.size() && (content[pos] == ' ' || content[pos] == '\t'))
|
||||
pos++;
|
||||
if (pos >= content.size()) break;
|
||||
|
||||
// 提取下一行 / Extract next line
|
||||
size_t nl = content.find('\n', pos);
|
||||
std::string line = (nl != std::string::npos)
|
||||
? content.substr(pos, nl - pos) : content.substr(pos);
|
||||
pos = (nl != std::string::npos) ? nl + 1 : content.size();
|
||||
|
||||
// 去除右侧空白(包括 \r) / Trim right whitespace (including \r)
|
||||
while (!line.empty() && (line.back() == '\r' || line.back() == ' '))
|
||||
line.pop_back();
|
||||
|
||||
// 跳过空行和注释 / Skip empty lines and comments
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
|
||||
// 节标题: [section_name] / Section header: [section_name]
|
||||
if (line[0] == '[' && line.back() == ']') {
|
||||
current_section = line.substr(1, line.size() - 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 键 = 值 / Key = value
|
||||
size_t eq = line.find('=');
|
||||
if (eq == std::string::npos) continue;
|
||||
|
||||
std::string key = line.substr(0, eq);
|
||||
while (!key.empty() && key.back() == ' ') key.pop_back();
|
||||
if (key.empty()) continue;
|
||||
|
||||
std::string val = line.substr(eq + 1);
|
||||
while (!val.empty() && (val.front() == ' ' || val.front() == '\t'))
|
||||
val.erase(0, 1);
|
||||
if (val.size() >= 2 && val.front() == '"' && val.back() == '"')
|
||||
val = val.substr(1, val.size() - 2);
|
||||
|
||||
std::string full_key = current_section.empty()
|
||||
? key : current_section + "." + key;
|
||||
|
||||
on_kv(full_key, val);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace toml
|
||||
} // namespace dstalk
|
||||
110
plugins_base/config/src/config_plugin.cpp
Normal file
110
plugins_base/config/src/config_plugin.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* @file config_plugin.cpp
|
||||
* @brief Config plugin: TOML file parsing and key-value configuration service.
|
||||
* 配置插件:TOML 文件解析和键值配置服务。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#include "dstalk/dstalk_host.h"
|
||||
#include "dstalk/dstalk_services.h"
|
||||
#include "../include/toml_parse.h"
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
// ============================================================
|
||||
// 全局状态 / Global state
|
||||
// ============================================================
|
||||
static const dstalk_host_api_t* g_host = nullptr;
|
||||
|
||||
// ============================================================
|
||||
// 服务实现 / Service implementations
|
||||
//
|
||||
// W12.2: Eliminated private ConfigStore (was 90 lines duplicating core).
|
||||
// All get/set/load_file now delegate to the host store via g_host->config_get
|
||||
// and g_host->config_set, making the host store the single source of truth.
|
||||
// TOML parsing uses the shared dstalk::toml::parse() from toml_parse.h.
|
||||
// ============================================================
|
||||
|
||||
// 从主机存储中按 key 获取配置值 / Retrieve a configuration value by key from the host store.
|
||||
static const char* config_get(const char* key) {
|
||||
if (!g_host) return nullptr;
|
||||
return g_host->config_get(key);
|
||||
}
|
||||
|
||||
// 将键值对存入主机存储 / Store a configuration key-value pair into the host store.
|
||||
static int config_set(const char* key, const char* value) {
|
||||
if (!g_host) return -1;
|
||||
return g_host->config_set(key, value);
|
||||
}
|
||||
|
||||
// 解析指定路径的 TOML 文件,将所有键值对加载到主机存储中 / Parse a TOML file at `path` and load all key-value pairs into the host store.
|
||||
static int config_load_file(const char* path) {
|
||||
if (!g_host || !path) return -1;
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) return -1;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << file.rdbuf();
|
||||
std::string data = ss.str();
|
||||
|
||||
int count = 0;
|
||||
dstalk::toml::parse(data, [&](const std::string& key, const std::string& value) {
|
||||
g_host->config_set(key.c_str(), value.c_str());
|
||||
++count;
|
||||
});
|
||||
|
||||
g_host->log(DSTALK_LOG_INFO,
|
||||
"config: loaded %d entries from %s into host store", count, path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static dstalk_config_service_t g_service = {
|
||||
config_get,
|
||||
config_set,
|
||||
config_load_file
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 插件生命周期 / Plugin lifecycle
|
||||
// ============================================================
|
||||
// 插件初始化:保存主机指针并注册 config 服务 vtable / Plugin init: store host pointer and register the config service vtable.
|
||||
static int on_init(const dstalk_host_api_t* host) {
|
||||
g_host = host;
|
||||
|
||||
// W12.2: 该服务现为 host->config_get/set 的薄封装,建议直接调用主机 API / This service is now a thin wrapper around host->config_get/set.
|
||||
// Direct host API calls are preferred.
|
||||
host->log(DSTALK_LOG_INFO,
|
||||
"plugin config service is deprecated, prefer host->config_get/set");
|
||||
|
||||
int rc = host->register_service("config", 1, &g_service);
|
||||
if (rc != 0) {
|
||||
host->log(DSTALK_LOG_WARN,
|
||||
"config: register_service failed (rc=%d), service name may conflict", rc);
|
||||
}
|
||||
return (rc >= 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
// 插件关闭:无需清理本地存储(所有数据在主机存储中) / Plugin shutdown: no local store to clean up (all data lives in host store).
|
||||
static void on_shutdown() {
|
||||
// W12.2: No local store to clean up — all data lives in host store.
|
||||
// 无需清理本地存储——所有数据位于主机存储中。
|
||||
}
|
||||
|
||||
static dstalk_plugin_info_t g_info = {
|
||||
"config", // name
|
||||
"1.0.0", // version
|
||||
"Configuration service with TOML file support (deprecated: use host->config_get/set)",
|
||||
DSTALK_API_VERSION, // api_version
|
||||
{nullptr}, // dependencies (none)
|
||||
on_init, // on_init
|
||||
on_shutdown, // on_shutdown
|
||||
nullptr // on_event
|
||||
};
|
||||
|
||||
// 必须入口点:返回插件描述符给主机 / Mandatory entry point: returns the plugin descriptor to the host.
|
||||
extern "C" DSTALK_PLUGIN_EXPORT dstalk_plugin_info_t* dstalk_plugin_init(void) {
|
||||
return &g_info;
|
||||
}
|
||||
Reference in New Issue
Block a user