Add metadata validation script and module documentation

- Introduced a new Python script `check_agents_metadata.py` for validating agent metadata, including YAML parsing, rating ranges, and cross-references.
- Added usage instructions and exit codes for the script.
- Created a new markdown file `模块目录和功能说明.md` to outline the directory structure and functionality of the modules.
- Added a text file `说明此文件不可AI修改.txt` to specify that certain files should not be modified by AI, including important information about the `dstalk` framework and its modules.
This commit is contained in:
2026-05-31 00:00:58 +08:00
parent 3cc9ee95e4
commit f2da0f2ed4
43 changed files with 2467 additions and 800 deletions

View File

@@ -1,4 +1,11 @@
// MSVC 14.16 (VS 2017) doesn't provide std::to_address (C++20)
/*
* @file network_plugin.cpp
* @brief Network plugin: HTTP/HTTPS POST and streaming via Boost.Beast + OpenSSL.
* 网络插件:基于 Boost.Beast + OpenSSL 的 HTTP/HTTPS POST 和流式传输。
* Copyright (c) 2026 dstalk contributors. GPLv3.
*/
// MSVC 14.16 (VS 2017) 不提供 std::to_address (C++20) / MSVC 14.16 (VS 2017) doesn't provide std::to_address (C++20)
#define BOOST_ASIO_DISABLE_STD_TO_ADDRESS
#include "dstalk/dstalk_host.h"
@@ -29,21 +36,22 @@ namespace ssl = boost::asio::ssl;
using tcp = asio::ip::tcp;
// ============================================================
// Global state
// 全局状态 / Global state
// ============================================================
static const dstalk_host_api_t* g_host = nullptr;
static dstalk_config_service_t* g_config_svc = nullptr;
// ============================================================
// Minimal JSON header parser
// Parses {"key1":"value1","key2":"value2"} into unordered_map
// 极简 JSON 头解析器 / Minimal JSON header parser
// 将 {"key1":"value1","key2":"value2"} 解析到 unordered_map / Parses {"key1":"value1","key2":"value2"} into unordered_map
// ============================================================
// 将扁平 JSON 对象中的字符串键值对解析到 unordered_map / Parse a flat JSON object of string key-value pairs into an unordered_map.
static std::unordered_map<std::string, std::string> parse_headers_json(const char* json) {
std::unordered_map<std::string, std::string> headers;
if (!json || !*json) return headers;
std::string s(json);
// Very simple state-machine parser for flat string-key/value objects
// 极简状态机解析器,处理扁平的字符串键值对象 / Very simple state-machine parser for flat string-key/value objects
enum { OUTSIDE, IN_KEY, AFTER_KEY, IN_VALUE } state = OUTSIDE;
std::string current_key;
std::string current_value;
@@ -64,7 +72,7 @@ static std::unordered_map<std::string, std::string> parse_headers_json(const cha
break;
case IN_VALUE:
if (c == '"') {
// Read until closing quote
// 读取到闭合引号 / Read until closing quote
++i;
while (i < s.size() && s[i] != '"') {
if (s[i] == '\\' && i + 1 < s.size()) { current_value += s[++i]; }
@@ -81,7 +89,7 @@ static std::unordered_map<std::string, std::string> parse_headers_json(const cha
}
// ============================================================
// HTTP Client implementation (adapted from dstalk-core HttpClient)
// HTTP 客户端实现(改编自 dstalk-core HttpClient / HTTP Client implementation (adapted from dstalk-core HttpClient)
// ============================================================
struct HttpClientCtx {
asio::io_context ioc;
@@ -91,15 +99,22 @@ struct HttpClientCtx {
HttpClientCtx() {
ssl_ctx.set_default_verify_paths();
// Enable peer certificate verification (CVSS 7.4 fix).
// set_default_verify_paths() loads system CA bundle; without verify_peer
// 启用对等证书验证 (CVSS 7.4 修复) / Enable peer certificate verification (CVSS 7.4 fix).
// set_default_verify_paths() 加载系统 CA 包;没有 verify_peer
// CA 存储不会被查询——任何证书(自签名/过期)都将被接受 / set_default_verify_paths() loads system CA bundle; without verify_peer
// the CA store is never consulted — any cert (self-signed/expired) is accepted.
// TODO: Windows: set_default_verify_paths() may not locate system CAs;
// TODO: Windows: set_default_verify_paths() 可能无法定位系统 CA
// 如果验证失败,设置 SSL_CERT_FILE 环境变量或捆绑 cacert.pem / Windows: set_default_verify_paths() may not locate system CAs;
// if verification fails, set SSL_CERT_FILE env or bundle a cacert.pem.
ssl_ctx.set_verify_mode(ssl::verify_peer);
}
};
// 核心 HTTP/HTTPS POST支持可选 SSE 流式传输。执行 DNS 解析、
// TLS 握手(含 SNI 和主机名验证),然后发送请求。
// 如果 cb 非空,响应体将逐行解析用于流式传输 / Core HTTP/HTTPS POST with optional SSE streaming. Performs DNS resolve,
// TLS handshake with SNI and hostname verification, then sends the request.
// If `cb` is non-null, response body is parsed line-by-line for streaming.
static int do_post_stream(
const char* host,
const char* port,
@@ -117,11 +132,11 @@ static int do_post_stream(
return -1;
}
// Initialize output
// 初始化输出 / Initialize output
*response_body = nullptr;
*status_code = -1;
// Build C++ lambda from C callback
// 从 C 回调构建 C++ lambda / Build C++ lambda from C callback
std::function<bool(const std::string&)> on_line;
if (cb) {
on_line = [cb, userdata](const std::string& line) -> bool {
@@ -131,7 +146,7 @@ static int do_post_stream(
HttpClientCtx ctx;
// Read timeouts from config if available
// 从配置读取超时设置 / Read timeouts from config if available
if (g_config_svc) {
const char* ct = g_config_svc->get("http.connect_timeout");
const char* rt = g_config_svc->get("http.request_timeout");
@@ -147,7 +162,9 @@ static int do_post_stream(
try {
tcp::resolver resolver(ctx.ioc);
// DNS resolve with 10-second timeout. Boost.Asio's synchronous
// DNS 解析10 秒超时。Boost.Asio 的同步 resolve()
// 在内部运行 io_context因此定时器的 async_wait 回调在 resolve() 期间执行,
// 并在超时触发时调用 resolver.cancel() / DNS resolve with 10-second timeout. Boost.Asio's synchronous
// resolve() runs the io_context internally, so the timer's async_wait
// callback executes during resolve() and calls resolver.cancel() when
// the deadline fires.
@@ -172,7 +189,7 @@ static int do_post_stream(
beast::ssl_stream<beast::tcp_stream> stream(ctx.ioc, ctx.ssl_ctx);
beast::flat_buffer buffer;
// SNI hostname
// SNI 主机名 / SNI hostname
if (!SSL_set_tlsext_host_name(stream.native_handle(), host)) {
if (g_host) g_host->log(DSTALK_LOG_ERROR,
"do_post_stream: SNI hostname set failed for %s", host);
@@ -180,7 +197,9 @@ static int do_post_stream(
goto done;
}
// Hostname verification: require server certificate CN/SAN to match
// 主机名验证:要求服务器证书 CN/SAN 匹配 'host'。
// 与 ssl::verify_peer 协同工作——没有它的话,
// 使用不同主机名的有效 CA 签名证书进行 MITM 攻击仍可通过 / Hostname verification: require server certificate CN/SAN to match
// 'host'. This works in conjunction with ssl::verify_peer on the
// context — without it MITM with a valid CA-signed cert for a
// different hostname would still pass.
@@ -191,19 +210,19 @@ static int do_post_stream(
goto done;
}
// Connect
// 连接 / Connect
beast::get_lowest_layer(stream).expires_after(
std::chrono::seconds(ctx.connect_timeout));
beast::get_lowest_layer(stream).connect(endpoints);
beast::get_lowest_layer(stream).expires_never();
// SSL handshake
// SSL 握手 / SSL handshake
beast::get_lowest_layer(stream).expires_after(
std::chrono::seconds(ctx.connect_timeout));
stream.handshake(ssl::stream_base::client);
beast::get_lowest_layer(stream).expires_never();
// Build HTTP POST request
// 构建 HTTP POST 请求 / Build HTTP POST request
http::request<http::string_body> req{http::verb::post, target, 11};
req.set(http::field::host, host);
req.set(http::field::user_agent, "dstalk/0.1");
@@ -211,19 +230,19 @@ static int do_post_stream(
req.body() = body;
req.prepare_payload();
// Add extra headers from JSON
// 从 JSON 添加额外的头 / Add extra headers from JSON
auto extra_headers = parse_headers_json(headers_json);
for (const auto& h : extra_headers) {
req.set(h.first, h.second);
}
// Send
// 发送 / Send
beast::get_lowest_layer(stream).expires_after(
std::chrono::seconds(ctx.request_timeout));
http::write(stream, req);
beast::get_lowest_layer(stream).expires_never();
// Read response
// 读取响应 / Read response
http::response_parser<http::string_body> parser;
parser.body_limit(16 * 1024 * 1024);
beast::get_lowest_layer(stream).expires_after(
@@ -310,8 +329,9 @@ done:
}
// ============================================================
// Service implementations
// 服务实现 / Service implementations
// ============================================================
// 同步 HTTP POST返回完整响应体 / Synchronous HTTP POST returning the complete response body.
static int http_post_json(
const char* host, const char* port,
const char* target, const char* body,
@@ -322,6 +342,7 @@ static int http_post_json(
nullptr, nullptr, response_body, status_code);
}
// HTTP POST 带 SSE 流式传输:响应行到达时通过 cb 回调传递 / HTTP POST with SSE streaming: response lines are delivered to `cb` as they arrive.
static int http_post_stream(
const char* host, const char* port,
const char* target, const char* body,
@@ -339,32 +360,35 @@ static dstalk_http_service_t g_service = {
};
// ============================================================
// Plugin lifecycle
// 插件生命周期 / Plugin lifecycle
// ============================================================
// 插件初始化:保存主机指针,查询 config 服务,注册 http 服务 / Plugin init: store host pointer, query config service, register http service.
static int on_init(const dstalk_host_api_t* host) {
g_host = host;
// Query config service (declared dependency)
// 查询 config 服务(声明的依赖) / Query config service (declared dependency)
g_config_svc = (dstalk_config_service_t*)host->query_service("config", 1);
return host->register_service("http", 1, &g_service);
}
// 插件关闭:无需清理 / Plugin shutdown: nothing to clean up.
static void on_shutdown() {
// nothing to clean up
// 无需清理 / nothing to clean up
}
static dstalk_plugin_info_t g_info = {
"http", // name
"1.0.0", // version
"HTTP/HTTPS client service using Boost.Beast + OpenSSL", // description
"http", // name 名称
"1.0.0", // version 版本
"HTTP/HTTPS client service using Boost.Beast + OpenSSL", // description 描述
DSTALK_API_VERSION, // api_version
{"config", nullptr}, // dependencies
{"config", nullptr}, // dependencies 依赖
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;
}