Refactor to plugin architecture with B3 CLI UX, C2 smoke tests, C3 CI scripts
Architecture overhaul (Wave 1-4 collaborative work): - Migrated dstalk-core from monolithic api.cpp to plugin-based design with host/service_registry/event_bus/plugin_loader and topological initialization. - Split public headers into dstalk_host.h / dstalk_services.h / dstalk_lsp.h / dstalk_types.h; deleted obsolete dstalk_api.h and inlined TLS/file/net code now provided by plugins. - Added 9 plugins: deepseek, anthropic, network, session, context, tools, config, file-io, lsp; AI plugins register as "ai.<provider>" services. B3 CLI interaction enhancement: - Prompt now shows current model name (A1). - /status command prints model/base_url/api_key (sanitized: shown only as set/unset)/services readiness (A2). - SIGINT/Ctrl+C handled on POSIX (signal) and Windows (SetConsoleCtrlHandler); /quit no longer std::exit(0) but sets a quit flag so dstalk_shutdown runs exactly once via natural control flow (B1+B2). - Cross-DLL free fixed: print_file uses dstalk_free instead of std::free (B4). - --batch mode plus isatty auto-detection for piped stdin (C1). - fgets truncation detection with friendly error and stdin draining (C3). - Distinct exit codes (init/AI/service-unavailable) (C4). - /model rejects empty model name (C5). C2 smoke test extension: - 4 new test blocks: null-safety (file_io/session/tools/config), escape-boundary round-trip, tools->execute call chain, session robustness (add(nullptr), clear -> token_count == 0). C3 CI build scripts: - scripts/ci-build.sh and scripts/ci-build.bat invoke cmake configure + parallel build + ctest, suitable for GitHub Actions. Build verified: dstalk-cli compiles, smoke test passes via ctest. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,52 +0,0 @@
|
||||
#ifndef DSTALK_API_H
|
||||
#define DSTALK_API_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- DLL 导出 / 导入宏 ---- */
|
||||
#if defined(_WIN32)
|
||||
#ifdef DSTALK_BUILD_DLL
|
||||
#define DSTALK_API __declspec(dllexport)
|
||||
#else
|
||||
#define DSTALK_API __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define DSTALK_API __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
/* ---- 初始化和配置 ---- */
|
||||
DSTALK_API int dstalk_init(const char* config_path);
|
||||
DSTALK_API void dstalk_destroy(void);
|
||||
|
||||
/* 在 init 之后可修改 API 参数 (init 也会从配置文件读取) */
|
||||
DSTALK_API void dstalk_set_api_key(const char* api_key);
|
||||
DSTALK_API void dstalk_set_base_url(const char* base_url);
|
||||
DSTALK_API void dstalk_set_model(const char* model);
|
||||
|
||||
/* ---- AI 对话 ---- */
|
||||
/* 同步对话: 发送 input,返回完整 AI 回复 (调用方通过 dstalk_free_string 释放) */
|
||||
DSTALK_API int dstalk_chat(const char* input, char** output);
|
||||
|
||||
/* 流式对话: 每收到一个 token 调用回调,回调返回 0 继续,非 0 取消 */
|
||||
typedef int (*dstalk_stream_cb)(const char* token, void* userdata);
|
||||
DSTALK_API int dstalk_chat_stream(const char* input, dstalk_stream_cb cb, void* userdata);
|
||||
|
||||
/* 释放由 dstalk_chat / dstalk_file_read 分配的字符串 */
|
||||
DSTALK_API void dstalk_free_string(char* str);
|
||||
|
||||
/* ---- 会话管理 ---- */
|
||||
DSTALK_API void dstalk_session_clear(void); /* 清空对话历史 */
|
||||
DSTALK_API int dstalk_session_save(const char* path); /* 保存会话到文件 */
|
||||
DSTALK_API int dstalk_session_load(const char* path); /* 从文件恢复会话 */
|
||||
|
||||
/* ---- 文件操作 ---- */
|
||||
DSTALK_API int dstalk_file_read(const char* path, char** content);
|
||||
DSTALK_API int dstalk_file_write(const char* path, const char* content);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DSTALK_API_H */
|
||||
132
dstalk-core/include/dstalk/dstalk_host.h
Normal file
132
dstalk-core/include/dstalk/dstalk_host.h
Normal file
@@ -0,0 +1,132 @@
|
||||
#ifndef DSTALK_HOST_H
|
||||
#define DSTALK_HOST_H
|
||||
|
||||
#include "dstalk_types.h"
|
||||
#include "dstalk_services.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// === 平台导出宏 ===
|
||||
#ifndef DSTALK_API
|
||||
#if defined(_WIN32)
|
||||
#ifdef DSTALK_BUILD_DLL
|
||||
#define DSTALK_API __declspec(dllexport)
|
||||
#else
|
||||
#define DSTALK_API __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define DSTALK_API __attribute__((visibility("default")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// === 插件导出宏 ===
|
||||
#if defined(_WIN32)
|
||||
#define DSTALK_PLUGIN_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define DSTALK_PLUGIN_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
// === API 版本 ===
|
||||
#define DSTALK_API_VERSION 1
|
||||
#define DSTALK_MAX_DEPS 8
|
||||
|
||||
// === 诊断 ===
|
||||
typedef void (*dstalk_diag_cb)(int severity, const char* file,
|
||||
int line, const char* func, const char* message);
|
||||
|
||||
#define DSTALK_ERROR_RETURN(expr, retval) do { \
|
||||
if (!(expr)) { \
|
||||
dstalk_log(DSTALK_LOG_ERROR, "[%s:%d] %s: assertion '%s' failed", \
|
||||
__FILE__, __LINE__, __func__, #expr); \
|
||||
return (retval); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
DSTALK_API void dstalk_set_diag_callback(dstalk_diag_cb cb);
|
||||
|
||||
// === 事件处理器 ===
|
||||
typedef void (*dstalk_event_handler_fn)(int event_type, const void* data, void* userdata);
|
||||
|
||||
// === Host 提供给插件的 API 表 ===
|
||||
typedef struct {
|
||||
// 服务注册/查询
|
||||
int (*register_service)(const char* name, int version, void* vtable);
|
||||
void*(*query_service)(const char* name, int min_version);
|
||||
|
||||
// 事件
|
||||
int (*event_subscribe)(int event_type, dstalk_event_handler_fn handler, void* userdata);
|
||||
int (*event_emit)(int event_type, const void* data);
|
||||
void (*event_unsubscribe)(int sub_id);
|
||||
|
||||
// 配置
|
||||
const char* (*config_get)(const char* key);
|
||||
int (*config_set)(const char* key, const char* value);
|
||||
|
||||
// 日志
|
||||
void (*log)(int level, const char* fmt, ...);
|
||||
|
||||
// 内存
|
||||
void* (*alloc)(size_t size);
|
||||
void (*free)(void* ptr);
|
||||
char* (*strdup)(const char* s);
|
||||
} dstalk_host_api_t;
|
||||
|
||||
// === 插件信息结构 ===
|
||||
typedef struct {
|
||||
const char* name; // 插件名称(唯一标识)
|
||||
const char* version; // 语义化版本号,如 "1.0.0"
|
||||
const char* description; // 描述
|
||||
int api_version; // 必须 == DSTALK_API_VERSION
|
||||
|
||||
// 依赖声明(以 NULL 结尾)
|
||||
const char* dependencies[DSTALK_MAX_DEPS];
|
||||
|
||||
// 生命周期回调
|
||||
int (*on_init)(const dstalk_host_api_t* host);
|
||||
void (*on_shutdown)(void);
|
||||
|
||||
// 事件处理(可选)
|
||||
void (*on_event)(int event_type, const void* data);
|
||||
} dstalk_plugin_info_t;
|
||||
|
||||
// === 插件入口函数 ===
|
||||
typedef dstalk_plugin_info_t* (*dstalk_plugin_init_fn)(void);
|
||||
|
||||
// === Host 公共 API ===
|
||||
|
||||
// 初始化/销毁
|
||||
DSTALK_API int dstalk_init(const char* config_path);
|
||||
DSTALK_API void dstalk_shutdown(void);
|
||||
|
||||
// 插件管理
|
||||
DSTALK_API int dstalk_plugin_load(const char* path);
|
||||
DSTALK_API int dstalk_plugin_unload(int plugin_id);
|
||||
DSTALK_API int dstalk_plugin_list(char** output_json);
|
||||
|
||||
// 服务查询
|
||||
DSTALK_API void* dstalk_service_query(const char* service_name, int min_version);
|
||||
|
||||
// 事件系统
|
||||
DSTALK_API int dstalk_event_subscribe(int event_type, dstalk_event_handler_fn handler, void* userdata);
|
||||
DSTALK_API int dstalk_event_emit(int event_type, const void* data);
|
||||
DSTALK_API void dstalk_event_unsubscribe(int subscription_id);
|
||||
|
||||
// 配置
|
||||
DSTALK_API const char* dstalk_config_get(const char* key);
|
||||
DSTALK_API int dstalk_config_set(const char* key, const char* value);
|
||||
|
||||
// 日志
|
||||
DSTALK_API void dstalk_log(int level, const char* fmt, ...);
|
||||
|
||||
// 内存
|
||||
DSTALK_API void* dstalk_alloc(size_t size);
|
||||
DSTALK_API void dstalk_free(void* ptr);
|
||||
DSTALK_API char* dstalk_strdup(const char* s);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // DSTALK_HOST_H
|
||||
91
dstalk-core/include/dstalk/dstalk_lsp.h
Normal file
91
dstalk-core/include/dstalk/dstalk_lsp.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#ifndef DSTALK_LSP_H
|
||||
#define DSTALK_LSP_H
|
||||
|
||||
#include "dstalk_host.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- LSP 服务器生命周期 ---- */
|
||||
|
||||
/*
|
||||
* 启动语言服务器进程
|
||||
* server_cmd: 命令字符串,例如 "clangd" 或 "pyright --stdio" 或完整路径
|
||||
* language: 语言标识,例如 "c", "cpp", "python", "javascript", "rust"
|
||||
* returns: 0 成功, -1 失败
|
||||
*/
|
||||
DSTALK_API int dstalk_lsp_start(const char* server_cmd, const char* language);
|
||||
|
||||
/*
|
||||
* 停止语言服务器
|
||||
* 发送 shutdown 请求,然后发送 exit 通知
|
||||
* 关闭管道,终止子进程
|
||||
*/
|
||||
DSTALK_API void dstalk_lsp_stop(void);
|
||||
|
||||
/* ---- 文档管理 ---- */
|
||||
|
||||
/*
|
||||
* 在语言服务器中打开一个文档
|
||||
* uri: 文件 URI,例如 "file:///path/to/file.c"
|
||||
* content: 文件内容文本
|
||||
* language_id: 语言 ID,例如 "c", "cpp", "python", "javascript"
|
||||
* returns: 0 成功, -1 失败
|
||||
*/
|
||||
DSTALK_API int dstalk_lsp_open(const char* uri, const char* content,
|
||||
const char* language_id);
|
||||
|
||||
/*
|
||||
* 关闭语言服务器中的文档
|
||||
* uri: 文件 URI
|
||||
* returns: 0 成功, -1 失败
|
||||
*/
|
||||
DSTALK_API int dstalk_lsp_close(const char* uri);
|
||||
|
||||
/* ---- 查询操作 ---- */
|
||||
|
||||
/*
|
||||
* 获取诊断信息 (编译错误、警告等)
|
||||
* uri: 文件 URI
|
||||
* output: 输出参数,JSON 格式的诊断列表 (调用方通过 dstalk_free 释放)
|
||||
* returns: 0 成功, -1 失败
|
||||
*
|
||||
* JSON 输出格式示例:
|
||||
* [
|
||||
* {
|
||||
* "range": { "start": {"line":0,"character":0}, "end":{"line":0,"character":5} },
|
||||
* "severity": 1,
|
||||
* "message": "error message"
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
DSTALK_API int dstalk_lsp_diagnostics(const char* uri, char** output);
|
||||
|
||||
/*
|
||||
* 获取悬停信息 (类型、文档等)
|
||||
* uri: 文件 URI
|
||||
* line: 行号 (0-based)
|
||||
* character: 列号 (0-based, UTF-16 code units)
|
||||
* output: 输出参数,JSON 格式的悬停信息 (调用方通过 dstalk_free 释放)
|
||||
* returns: 0 成功, -1 失败
|
||||
*/
|
||||
DSTALK_API int dstalk_lsp_hover(const char* uri, int line, int character,
|
||||
char** output);
|
||||
|
||||
/*
|
||||
* 获取代码补全建议
|
||||
* uri: 文件 URI
|
||||
* line: 行号 (0-based)
|
||||
* character: 列号 (0-based, UTF-16 code units)
|
||||
* output: 输出参数,JSON 格式的补全列表 (调用方通过 dstalk_free 释放)
|
||||
* returns: 0 成功, -1 失败
|
||||
*/
|
||||
DSTALK_API int dstalk_lsp_completion(const char* uri, int line, int character,
|
||||
char** output);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DSTALK_LSP_H */
|
||||
97
dstalk-core/include/dstalk/dstalk_services.h
Normal file
97
dstalk-core/include/dstalk/dstalk_services.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#ifndef DSTALK_SERVICES_H
|
||||
#define DSTALK_SERVICES_H
|
||||
|
||||
#include "dstalk_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// === AI 服务 vtable (实际服务名由插件注册: "ai.deepseek" / "ai.anthropic") ===
|
||||
typedef struct {
|
||||
int (*configure)(const char* provider, const char* base_url,
|
||||
const char* api_key, const char* model,
|
||||
int max_tokens, double temperature);
|
||||
dstalk_chat_result_t (*chat)(
|
||||
const dstalk_message_t* history, int history_len,
|
||||
const char* user_input,
|
||||
const char* tools_json);
|
||||
dstalk_chat_result_t (*chat_stream)(
|
||||
const dstalk_message_t* history, int history_len,
|
||||
const char* user_input,
|
||||
dstalk_stream_cb cb, void* userdata);
|
||||
void (*free_result)(dstalk_chat_result_t* result);
|
||||
} dstalk_ai_service_t;
|
||||
|
||||
// === Session 服务 (service name: "session") ===
|
||||
typedef struct {
|
||||
void (*add)(const dstalk_message_t* msg);
|
||||
void (*clear)(void);
|
||||
int (*save)(const char* path);
|
||||
int (*load)(const char* path);
|
||||
const dstalk_message_t* (*history)(int* out_count);
|
||||
int (*token_count)(void);
|
||||
} dstalk_session_service_t;
|
||||
|
||||
// === Context 服务 (service name: "context") ===
|
||||
typedef struct {
|
||||
size_t (*count_tokens)(const dstalk_message_t* msgs, int count);
|
||||
int (*trim)(const dstalk_message_t* in, int in_count,
|
||||
dstalk_message_t** out, int* out_count,
|
||||
size_t max_tokens);
|
||||
void (*set_max_tokens)(size_t max);
|
||||
} dstalk_context_service_t;
|
||||
|
||||
// === HTTP 服务 (service name: "http") ===
|
||||
typedef struct {
|
||||
int (*post_json)(const char* host, const char* port,
|
||||
const char* target, const char* body,
|
||||
const char* headers_json,
|
||||
char** response_body, int* status_code);
|
||||
int (*post_stream)(const char* host, const char* port,
|
||||
const char* target, const char* body,
|
||||
const char* headers_json,
|
||||
dstalk_stream_cb cb, void* userdata,
|
||||
char** response_body, int* status_code);
|
||||
} dstalk_http_service_t;
|
||||
|
||||
// === File IO 服务 (service name: "file_io") ===
|
||||
typedef struct {
|
||||
int (*read)(const char* path, char** content);
|
||||
int (*write)(const char* path, const char* content);
|
||||
} dstalk_file_io_service_t;
|
||||
|
||||
// === Config 服务 (service name: "config") ===
|
||||
typedef struct {
|
||||
const char* (*get)(const char* key);
|
||||
int (*set)(const char* key, const char* value);
|
||||
int (*load_file)(const char* path);
|
||||
} dstalk_config_service_t;
|
||||
|
||||
// === Tools 服务 (service name: "tools") ===
|
||||
typedef char* (*dstalk_tool_handler_fn)(const char* args_json);
|
||||
typedef struct {
|
||||
int (*register_tool)(const char* name, const char* desc,
|
||||
const char* params_schema,
|
||||
dstalk_tool_handler_fn handler);
|
||||
void (*unregister_tool)(const char* name);
|
||||
char* (*get_tools_json)(void);
|
||||
char* (*execute)(const char* name, const char* args_json);
|
||||
} dstalk_tools_service_t;
|
||||
|
||||
// === LSP 服务 (service name: "lsp") ===
|
||||
typedef struct {
|
||||
int (*start)(const char* server_cmd, const char* language);
|
||||
void (*stop)(void);
|
||||
int (*open_document)(const char* uri, const char* content, const char* lang_id);
|
||||
int (*close_document)(const char* uri);
|
||||
int (*get_diagnostics)(const char* uri, char** json_out);
|
||||
int (*get_hover)(const char* uri, int line, int col, char** json_out);
|
||||
int (*get_completion)(const char* uri, int line, int col, char** json_out);
|
||||
} dstalk_lsp_service_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // DSTALK_SERVICES_H
|
||||
52
dstalk-core/include/dstalk/dstalk_types.h
Normal file
52
dstalk-core/include/dstalk/dstalk_types.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef DSTALK_TYPES_H
|
||||
#define DSTALK_TYPES_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// 消息结构(跨插件共享)
|
||||
typedef struct {
|
||||
const char* role; // "user", "assistant", "system", "tool"
|
||||
const char* content; // 消息内容
|
||||
const char* tool_call_id; // tool 响应时必填
|
||||
const char* tool_calls_json;// assistant 返回的工具调用(JSON 数组)
|
||||
} dstalk_message_t;
|
||||
|
||||
// 聊天结果
|
||||
typedef struct {
|
||||
int ok;
|
||||
const char* content; // dstalk_strdup 分配,调用方 dstalk_free
|
||||
const char* error; // dstalk_strdup 分配
|
||||
int http_status;
|
||||
const char* tool_calls_json;// dstalk_strdup 分配
|
||||
} dstalk_chat_result_t;
|
||||
|
||||
// 流式回调
|
||||
typedef int (*dstalk_stream_cb)(const char* token, void* userdata);
|
||||
|
||||
// 事件类型
|
||||
enum {
|
||||
DSTALK_EVENT_MESSAGE = 1, // data = dstalk_message_t*
|
||||
DSTALK_EVENT_SESSION_CLEAR,
|
||||
DSTALK_EVENT_CONFIG_CHANGED,
|
||||
DSTALK_EVENT_PLUGIN_LOADED, // data = plugin info JSON string
|
||||
DSTALK_EVENT_PLUGIN_UNLOADED,
|
||||
DSTALK_EVENT_CUSTOM = 1000, // 插件自定义事件起始值
|
||||
};
|
||||
|
||||
// 日志级别
|
||||
enum {
|
||||
DSTALK_LOG_DEBUG = 0,
|
||||
DSTALK_LOG_INFO = 1,
|
||||
DSTALK_LOG_WARN = 2,
|
||||
DSTALK_LOG_ERROR = 3,
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // DSTALK_TYPES_H
|
||||
Reference in New Issue
Block a user