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

@@ -27,6 +27,7 @@
// 在启动时从主机查询获取的服务 vtable 全局指针。
static const dstalk_ai_service_t* g_ai_svc = nullptr;
static const dstalk_session_service_t* g_session_svc = nullptr;
static const dstalk_ai_endpoint_mgr_t* g_endpoint_mgr = nullptr; // I08: AI endpoint manager可选/ optional
// ---- 常量 / Constants ----
@@ -287,10 +288,19 @@ static void renderStatusBar(AppContext& ctx) {
}
// 状态文本:模型名 | 消息条数 | 流式状态 / Status text: model name | message count | streaming state
// I08: 添加 endpoint manager 信息(如果可用)/ add endpoint manager info (if available)
char buf[256];
snprintf(buf, sizeof(buf), "%s | %d messages | %s",
gs.model_name.c_str(), msgCount,
gs.streaming ? "streaming" : "ready");
const char* active_ep = nullptr;
if (g_endpoint_mgr) active_ep = g_endpoint_mgr->get_active();
if (active_ep && active_ep[0]) {
snprintf(buf, sizeof(buf), "%s | %d messages | %s | ep:%s",
gs.model_name.c_str(), msgCount,
gs.streaming ? "streaming" : "ready", active_ep);
} else {
snprintf(buf, sizeof(buf), "%s | %d messages | %s",
gs.model_name.c_str(), msgCount,
gs.streaming ? "streaming" : "ready");
}
drawText(r, static_cast<float>(PADDING),
barY + (STATUS_H - CHAR_H) / 2.0f, buf, COL_WHITE);
}
@@ -832,6 +842,9 @@ int main(int argc, char* argv[]) {
if (!ai_provider) ai_provider = "ai_openai";
g_ai_svc = static_cast<const dstalk_ai_service_t*>(dstalk_service_query(ai_provider, 1));
g_session_svc = 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_svc) dstalk_log(3, "AI service not found (check plugins directory)");
if (!g_session_svc) dstalk_log(3, "Session service not found");
@@ -899,15 +912,25 @@ int main(int argc, char* argv[]) {
std::string& userMsg =
ctx.state.messages[ctx.state.messages.size() - 2].content;
int rc = -1;
if (g_ai_svc) {
// I08: 优先通过 endpoint_mgr 路由fallback 到 g_ai_svc / prefer endpoint_mgr, fallback to g_ai_svc
const bool use_mgr = (g_endpoint_mgr && g_endpoint_mgr->count() > 0);
if (use_mgr || g_ai_svc) {
int hcount = 0;
const dstalk_message_t* history = g_session_svc
? g_session_svc->history(&hcount) : nullptr;
dstalk_chat_result_t result = g_ai_svc->chat_stream(
history, hcount, userMsg.c_str(),
streamTokenCallback, &ctx);
dstalk_chat_result_t result;
if (use_mgr) {
result = g_endpoint_mgr->chat_stream(
nullptr, history, hcount, userMsg.c_str(),
streamTokenCallback, &ctx);
} else {
result = g_ai_svc->chat_stream(
history, hcount, userMsg.c_str(),
streamTokenCallback, &ctx);
}
rc = result.ok ? 0 : -1;
g_ai_svc->free_result(&result);
if (use_mgr) g_endpoint_mgr->free_result(&result);
else g_ai_svc->free_result(&result);
}
// 流式传输完成(或被取消)