feat: add AI endpoint manager plugin with configuration and routing capabilities
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 `ai_endpoint_mgr` plugin to manage multiple AI provider endpoints.
- Added configuration reference documentation for `config.toml`.
- Implemented endpoint loading, active endpoint switching, and model mutation.
- Included error handling for missing endpoints and configuration failures.
- Developed unit tests covering various scenarios including error paths and concurrency.
This commit is contained in:
2026-06-03 21:07:25 +08:00
parent 28ae90a6cc
commit 4745ce1f1c
18 changed files with 1570 additions and 34 deletions

View File

@@ -43,6 +43,7 @@ class SseSession;
// 插件服务 vtable 的全局指针,在启动时从主机查询获取。
static const dstalk_ai_service_t* g_ai = nullptr;
static const dstalk_session_service_t* g_session = nullptr;
static const dstalk_ai_endpoint_mgr_t* g_endpoint_mgr = nullptr; // I08: AI endpoint manager可选/ optional
// ---- 运行时状态 / Runtime state ----
// g_quit signals the main loop to exit (set by Ctrl+C).
@@ -208,8 +209,19 @@ static void run_chat_worker(
};
// 调用流式 AI 聊天 / Call streaming AI chat
dstalk_chat_result_t result = g_ai->chat_stream(
history, history_count, nullptr, token_cb, &cb_data);
// I08: 优先通过 endpoint_mgr 路由fallback 到 g_ai / prefer endpoint_mgr, fallback to g_ai
dstalk_chat_result_t result = {};
const bool use_mgr = (g_endpoint_mgr && g_endpoint_mgr->count() > 0);
if (use_mgr) {
result = g_endpoint_mgr->chat_stream(
nullptr, history, history_count, nullptr, token_cb, &cb_data);
} else if (g_ai) {
result = g_ai->chat_stream(
history, history_count, nullptr, token_cb, &cb_data);
} else {
result.ok = 0;
result.error = dstalk_strdup("AI service unavailable");
}
// 将 AI 回复加入会话 / Add AI reply to session
if (result.ok) {
@@ -221,7 +233,11 @@ static void run_chat_worker(
bool ok = result.ok;
std::string content_copy = result.content ? result.content : "";
std::string error_copy = result.error ? result.error : "";
g_ai->free_result(&result);
// I08: 根据路由来源释放 result / free result based on routing source
if (use_mgr) g_endpoint_mgr->free_result(&result);
else if (g_ai) g_ai->free_result(&result);
else if (result.error) dstalk_free((void*)result.error);
asio::post(ioc, [weak_sse, ok, content_copy, error_copy]() {
if (auto sse = weak_sse.lock()) {
@@ -373,6 +389,23 @@ private:
if (model) st["model"] = std::string(model);
st["status"] = "running";
// I08/I09: endpoint manager 状态 / endpoint manager status
if (g_endpoint_mgr) {
st["endpoint_mgr_available"] = true;
st["endpoint_count"] = g_endpoint_mgr->count();
const char* active = g_endpoint_mgr->get_active();
if (active) st["active_endpoint"] = std::string(active);
char* list_json = g_endpoint_mgr->list_json();
if (list_json) {
boost::system::error_code ec;
auto jv = boost::json::parse(list_json, ec);
if (!ec) st["endpoints"] = std::move(jv);
dstalk_free(list_json);
}
} else {
st["endpoint_mgr_available"] = false;
}
auto self = shared_from_this();
http::response<http::string_body> res{http::status::ok, request_.version()};
res.set("Access-Control-Allow-Origin", "*");
@@ -510,6 +543,9 @@ int main(int argc, char* argv[])
if (!ai_provider) ai_provider = "ai_openai";
g_ai = static_cast<const dstalk_ai_service_t*>(dstalk_service_query(ai_provider, 1));
g_session = static_cast<const dstalk_session_service_t*>(dstalk_service_query("session", 1));
// I08: 查询 AI endpoint manager可选服务/ query AI endpoint manager (optional service)
g_endpoint_mgr = static_cast<const dstalk_ai_endpoint_mgr_t*>(
dstalk_service_query("ai_endpoint_mgr", 1));
if (!g_ai) {
std::fprintf(stderr, "[dstalk_web] AI service not found (check plugins directory)\n");