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:
170
README.md
170
README.md
@@ -10,7 +10,7 @@
|
||||
|
||||
dstalk 是一款 AI 编程助手命令行工具。通过调用 DeepSeek V4 大模型(兼容 OpenAI 和 Anthropic API),在终端里用自然语言完成代码编写、重构、调试和文件操作。功能对标 Claude Code、OpenCode、KiloCode。
|
||||
|
||||
核心设计为 **CDLL + 多前端解耦**:
|
||||
核心设计为 **插件化 CDLL + 多前端解耦**:
|
||||
|
||||
```text
|
||||
┌───────────────────────────────────────────────────────────┐
|
||||
@@ -26,20 +26,41 @@ dstalk 是一款 AI 编程助手命令行工具。通过调用 DeepSeek V4 大
|
||||
└──────────────────────────┼─────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────────────▼─────────────────────────────────┐
|
||||
│ 核心层 (dstalk-core.dll) │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌──────────────────────┐ │
|
||||
│ │ 网络通讯 │ │ 文件读写 │ │ AI 接口适配 │ │
|
||||
│ │ Boost.Beast│ │ C++ 标准库 │ │ DeepSeek / OpenAI │ │
|
||||
│ │ + OpenSSL │ │ │ │ / Anthropic │ │
|
||||
│ └────────────┘ └────────────┘ └──────────────────────┘ │
|
||||
│ 核心层 (dstalk-core.dll) — 插件宿主 │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ Host: 插件加载 · 服务注册 · 事件总线 · 配置管理 │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ │ 服务查询 │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ deepseek │ │ anthropic│ │ network │ │ lsp │ │
|
||||
│ │ (ai) │ │ (ai) │ │ (http) │ │ 客户端 │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ session │ │ context │ │ file-io │ │ tools │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **`dstalk-core`** —— C11/C++20 高性能核心 DLL,负责网络通信、AI 接口调用、文件 I/O。
|
||||
- **`dstalk-core`** —— C11/C++20 插件化宿主 DLL,负责插件加载、服务注册/查询、事件总线、配置管理。
|
||||
- **`dstalk-cli`** —— 命令行前端,ANSI 转义码实现,调用 `dstalk.dll`。
|
||||
- **`dstalk-gui`** —— 图形化前端,SDL3 跨平台窗口,调用 `dstalk.dll`。
|
||||
- **`plugins/`** —— 9 个功能插件,编译为独立 DLL,通过 C ABI 动态注册服务。
|
||||
|
||||
核心与界面完全解耦,可以轻松编写自己的前端,或把 AI 能力嵌入到现有工具中。
|
||||
核心与界面完全解耦,可以轻松编写自己的前端,或把 AI 能力嵌入到现有工具中。所有功能通过插件实现,插件只需引用 `dstalk.dll` 即可。
|
||||
|
||||
---
|
||||
|
||||
## 核心功能
|
||||
|
||||
| 功能 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| **多后端 AI 支持** | 已完成 | DeepSeek V4 和 Anthropic Claude 通过插件独立加载,`config.toml` 中 `ai.provider` 一键切换 |
|
||||
| **流式输出** | 已完成 | SSE 流式响应,终端逐字打印 AI 思考过程 |
|
||||
| **多轮会话** | 已完成 | 上下文窗口连续对话,支持 `/clear` 清空、`/save` `/load` 持久化 |
|
||||
| **文件读写工具** | 已完成 | 内置 `/file` 命令集,支持列目录、查看、读取、写入文件 |
|
||||
| **LSP 集成** | 已完成 | 完整 LSP 客户端(子进程管理、JSON-RPC 2.0),支持诊断、悬停、补全 |
|
||||
| **插件系统** | 已完成 | 9 个功能插件,拓扑排序依赖管理,DLL 动态加载,服务注册/查询 |
|
||||
| **GUI 前端** | 已完成 | SDL3 跨平台图形界面,流式输出、会话管理、输入历史、剪贴板 |
|
||||
|
||||
---
|
||||
|
||||
@@ -108,7 +129,7 @@ build/dstalk-cli/dstalk-cli.exe # 命令行模式
|
||||
```text
|
||||
$ dstalk-cli
|
||||
|
||||
dstalk v0.1.0 | 模型: deepseek-v4 | /help 查看帮助
|
||||
dstalk v0.1.0 | 模型: deepseek-v4-pro | /help 查看帮助
|
||||
|
||||
> 帮我写一个读取 CSV 并计算平均值的 C 程序
|
||||
|
||||
@@ -170,24 +191,43 @@ $ dstalk-cli
|
||||
```text
|
||||
dstalk/
|
||||
├── deps/
|
||||
│ └── conanfile.txt # Conan2 依赖声明
|
||||
├── dstalk-core/ # 核心 DLL
|
||||
│ └── conanfile.txt # Conan2 依赖声明 (Boost, OpenSSL, SDL3)
|
||||
├── dstalk-core/ # 核心 DLL — 插件宿主
|
||||
│ ├── include/dstalk/
|
||||
│ │ └── dstalk_api.h # 公开 C API 头文件
|
||||
│ │ ├── dstalk_host.h # 公开 API: 宏定义、宿主API、插件生命周期
|
||||
│ │ ├── dstalk_services.h # 服务接口 vtable 定义 (AI/Session/Context/HTTP/FileIO/Config/Tools/LSP)
|
||||
│ │ ├── dstalk_types.h # 共享类型: 消息、结果、事件、日志等级
|
||||
│ │ └── dstalk_lsp.h # LSP 便捷函数 (委托给 lsp 插件)
|
||||
│ ├── src/
|
||||
│ │ ├── api.cpp # API 实现
|
||||
│ │ ├── net/ # 网络通信 (HTTP/HTTPS)
|
||||
│ │ ├── ai/ # AI 接口适配
|
||||
│ │ └── file/ # 文件读写
|
||||
│ │ ├── host.cpp # 宿主: 初始化、服务查询、LSP 便捷函数
|
||||
│ │ ├── config_store.cpp/.hpp # 配置管理 (TOML 解析)
|
||||
│ │ ├── event_bus.cpp/.hpp # 事件总线 (发布/订阅)
|
||||
│ │ ├── service_registry.cpp/.hpp # 服务注册表 (名称→vtable)
|
||||
│ │ ├── plugin_loader.cpp/.hpp # 插件加载器 (DLL 加载、拓扑排序、依赖管理)
|
||||
│ │ └── boost_json.cpp # Boost.JSON 编译单元
|
||||
│ └── CMakeLists.txt
|
||||
├── plugins/ # 功能插件 (每个编译为独立 DLL)
|
||||
│ ├── deepseek/ # DeepSeek AI (服务名: ai.deepseek)
|
||||
│ ├── anthropic/ # Anthropic Claude (服务名: ai.anthropic)
|
||||
│ ├── network/ # HTTP/HTTPS 客户端 (服务名: http)
|
||||
│ ├── session/ # 会话管理 (服务名: session)
|
||||
│ ├── context/ # 上下文/Token 管理 (服务名: context)
|
||||
│ ├── file-io/ # 文件读写 (服务名: file_io)
|
||||
│ ├── tools/ # 工具注册/执行 (服务名: tools)
|
||||
│ ├── lsp/ # LSP 客户端 (服务名: lsp)
|
||||
│ ├── config/ # 配置服务 (服务名: config)
|
||||
│ └── CMakeLists.txt # 插件构建 (按依赖顺序)
|
||||
├── dstalk-cli/ # 命令行前端 (ANSI)
|
||||
│ ├── src/main.cpp
|
||||
│ └── CMakeLists.txt
|
||||
├── dstalk-gui/ # 图形化前端 (SDL3)
|
||||
│ ├── src/main.cpp
|
||||
│ └── CMakeLists.txt
|
||||
├── tests/ # 单元测试
|
||||
│ └── CMakeLists.txt
|
||||
├── examples/ # 示例代码
|
||||
│ └── example_plugin/
|
||||
│ └── example_plugin.cpp # 插件开发示例
|
||||
├── tests/ # 集成测试
|
||||
│ └── smoke_test.cpp
|
||||
├── CMakeLists.txt # 根 CMake
|
||||
└── README.md
|
||||
```
|
||||
@@ -196,47 +236,79 @@ dstalk/
|
||||
|
||||
## 公开 API
|
||||
|
||||
头文件: [dstalk-core/include/dstalk/dstalk_api.h](dstalk-core/include/dstalk/dstalk_api.h)
|
||||
头文件:
|
||||
- [dstalk_host.h](dstalk-core/include/dstalk/dstalk_host.h) — 宿主 API、插件生命周期
|
||||
- [dstalk_services.h](dstalk-core/include/dstalk/dstalk_services.h) — 服务接口 vtable 定义
|
||||
- [dstalk_types.h](dstalk-core/include/dstalk/dstalk_types.h) — 共享类型
|
||||
- [dstalk_lsp.h](dstalk-core/include/dstalk/dstalk_lsp.h) — LSP 便捷函数
|
||||
|
||||
```c
|
||||
/* 初始化与销毁 */
|
||||
/* 宿主生命周期 */
|
||||
int dstalk_init(const char* config_path);
|
||||
void dstalk_destroy(void);
|
||||
void dstalk_shutdown(void);
|
||||
|
||||
/* AI 对话 */
|
||||
int dstalk_chat(const char* input, char** output);
|
||||
void dstalk_free_string(char* str);
|
||||
/* 插件管理 */
|
||||
int dstalk_plugin_load(const char* path);
|
||||
int dstalk_plugin_unload(int plugin_id);
|
||||
int dstalk_plugin_list(char** output_json);
|
||||
|
||||
/* 文件操作 */
|
||||
int dstalk_file_read(const char* path, char** content);
|
||||
int dstalk_file_write(const char* path, const char* content);
|
||||
/* 服务查询 —— 通过名称获取插件注册的 vtable */
|
||||
void* dstalk_service_query(const char* service_name, int min_version);
|
||||
|
||||
/* 事件总线 */
|
||||
int dstalk_event_subscribe(int event_type, dstalk_event_handler_fn handler, void* userdata);
|
||||
int dstalk_event_emit(int event_type, const void* data);
|
||||
void dstalk_event_unsubscribe(int subscription_id);
|
||||
|
||||
/* 配置 */
|
||||
const char* dstalk_config_get(const char* key);
|
||||
int dstalk_config_set(const char* key, const char* value);
|
||||
|
||||
/* 内存管理 */
|
||||
void* dstalk_alloc(size_t size);
|
||||
void dstalk_free(void* ptr);
|
||||
char* dstalk_strdup(const char* s);
|
||||
|
||||
/* LSP 便捷函数 (委托给 lsp 插件) */
|
||||
int dstalk_lsp_start(const char* server_cmd, const char* language);
|
||||
void dstalk_lsp_stop(void);
|
||||
int dstalk_lsp_open(const char* uri, const char* content, const char* language_id);
|
||||
int dstalk_lsp_close(const char* uri);
|
||||
int dstalk_lsp_diagnostics(const char* uri, char** output);
|
||||
int dstalk_lsp_hover(const char* uri, int line, int character, char** output);
|
||||
int dstalk_lsp_completion(const char* uri, int line, int character, char** output);
|
||||
```
|
||||
|
||||
**调用约定:**
|
||||
|
||||
- 所有字符串均为 UTF-8 编码
|
||||
- `dstalk_chat` / `dstalk_file_read` 分配的内存由调用方通过 `dstalk_free_string` 释放
|
||||
- 通过 `dstalk_service_query` 获取服务 vtable,再通过函数指针调用具体功能
|
||||
- `dstalk_free` 释放所有 API 返回的堆内存
|
||||
- 返回 `0` 成功,负数表示错误码
|
||||
|
||||
**跨语言调用示例:**
|
||||
|
||||
```c
|
||||
#include "dstalk/dstalk_api.h"
|
||||
#include "dstalk/dstalk_host.h"
|
||||
#include "dstalk/dstalk_services.h"
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
if (dstalk_init("config.json") != 0) {
|
||||
if (dstalk_init("config.toml") != 0) {
|
||||
fprintf(stderr, "初始化失败\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* reply = NULL;
|
||||
if (dstalk_chat("解释这段代码", &reply) == 0) {
|
||||
printf("AI: %s\n", reply);
|
||||
dstalk_free_string(reply);
|
||||
// 查询 AI 服务
|
||||
const char* provider = dstalk_config_get("ai.provider");
|
||||
if (!provider) provider = "ai.deepseek";
|
||||
const dstalk_ai_service_t* ai = dstalk_service_query(provider, 1);
|
||||
if (ai) {
|
||||
ai->configure(provider, "https://api.deepseek.com/v1", "sk-xxx",
|
||||
"deepseek-v4-pro", 4096, 0.7);
|
||||
}
|
||||
|
||||
dstalk_destroy();
|
||||
dstalk_shutdown();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
@@ -255,16 +327,25 @@ A: 主要支持 DeepSeek V4,同时兼容 OpenAI GPT 系列和 Anthropic Claude
|
||||
A: CLI 适合终端/SSH/CI 环境,GUI 适合需要富文本和鼠标交互的场景。两者共享同一核心 DLL,功能一致。
|
||||
|
||||
**Q: 如何配置 API Key?**
|
||||
A: 首次运行前,手动创建项目目录下的 `config.toml`:
|
||||
A: 首次运行前,手动创建项目目录下的 `config.toml`,按需选择后端:
|
||||
|
||||
```toml
|
||||
[api]
|
||||
provider = "deepseek"
|
||||
base_url = "https://api.deepseek.com/v1"
|
||||
api_key = "sk-xxxxxxxx"
|
||||
model = "deepseek-v4"
|
||||
# 选择 AI 后端插件: ai.deepseek 或 ai.anthropic
|
||||
ai.provider = "ai.deepseek"
|
||||
|
||||
# DeepSeek
|
||||
api.base_url = "https://api.deepseek.com/v1"
|
||||
api.api_key = "sk-xxxxxxxx"
|
||||
api.model = "deepseek-v4-pro"
|
||||
|
||||
# Anthropic Claude (切换 ai.provider 为 "ai.anthropic" 即可)
|
||||
# api.base_url = "https://api.anthropic.com/v1"
|
||||
# api.api_key = "sk-ant-xxxxxxxx"
|
||||
# api.model = "claude-opus-4-20250514"
|
||||
```
|
||||
|
||||
修改 `ai.provider` 字段即可在不同后端间切换,无需改动代码。
|
||||
|
||||
---
|
||||
|
||||
## 路线图
|
||||
@@ -273,8 +354,9 @@ model = "deepseek-v4"
|
||||
|------|------|
|
||||
| **Phase 1** | 项目骨架、CMake 构建、DLL 导出、CLI 前端主循环 |
|
||||
| **Phase 2** | HTTPS 网络层、DeepSeek API 对接、基本对话 |
|
||||
| **Phase 3** (当前) | 流式输出、多轮会话、文件读写工具、CLI 体验对齐 |
|
||||
| **Phase 4** | SDL3 GUI 完善、插件系统、LSP 集成 |
|
||||
| **Phase 3** | ~~流式输出、多轮会话、文件读写工具、CLI 体验对齐~~ |
|
||||
| **Phase 4** (当前) | ~~插件化架构重构、多后端 AI、LSP 客户端、SDL3 GUI~~ |
|
||||
| **Phase 5** | GUI 完善、工具调用(Function Calling)、插件生态、多语言扩展 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user