|
29 | 29 | #include <unordered_map> |
30 | 30 |
|
31 | 31 | #include <nlohmann/json.hpp> |
| 32 | +#include <zlib.h> |
| 33 | + |
| 34 | +#include "croupier/agent/v1/register.pb.h" |
32 | 35 |
|
33 | 36 | // Logging macros with configuration support |
34 | 37 | // These check the global logger configuration before outputting |
@@ -558,6 +561,10 @@ class CroupierClient::Impl { |
558 | 561 | last_error_.clear(); |
559 | 562 | startHeartbeatLoop(); |
560 | 563 | SDK_LOG_INFO("Connected to agent at " + NormalizeTCPAddress(config_.agent_addr)); |
| 564 | + |
| 565 | + // 控制面 manifest 上传(best-effort,不阻断注册结果) |
| 566 | + maybeRegisterCapabilities(); |
| 567 | + |
561 | 568 | return true; |
562 | 569 | } catch (const std::exception& e) { |
563 | 570 | last_error_ = e.what(); |
@@ -626,6 +633,96 @@ class CroupierClient::Impl { |
626 | 633 | } |
627 | 634 | } |
628 | 635 |
|
| 636 | + // 构建控制面能力清单(provider 元数据 + 函数摘要),与 JS/C#/Go 同构。 |
| 637 | + std::string buildManifestJson() const { |
| 638 | + std::string json = "{\"provider\":{"; |
| 639 | + json += "\"id\":\"" + EscapeJsonString( |
| 640 | + config_.service_id.empty() ? std::string("cpp-service") : config_.service_id) + "\","; |
| 641 | + json += "\"version\":\"" + EscapeJsonString( |
| 642 | + config_.service_version.empty() ? std::string("1.0.0") : config_.service_version) + "\","; |
| 643 | + json += "\"lang\":\"" + EscapeJsonString( |
| 644 | + config_.provider_lang.empty() ? std::string("cpp") : config_.provider_lang) + "\","; |
| 645 | + json += "\"sdk\":\"" + EscapeJsonString( |
| 646 | + config_.provider_sdk.empty() ? std::string("croupier-cpp-sdk") : config_.provider_sdk) + "\"}"; |
| 647 | + json += ",\"functions\":["; |
| 648 | + bool first = true; |
| 649 | + for (const auto& [function_id, desc] : descriptors_) { |
| 650 | + if (function_id.empty()) continue; |
| 651 | + if (!first) json += ","; |
| 652 | + first = false; |
| 653 | + json += "{\"id\":\"" + EscapeJsonString(function_id) + "\""; |
| 654 | + json += ",\"version\":\"" + EscapeJsonString(desc.version.empty() ? std::string("1.0.0") : desc.version) + "\""; |
| 655 | + if (!desc.resource.empty()) json += ",\"resource\":\"" + EscapeJsonString(desc.resource) + "\""; |
| 656 | + if (!desc.operation.empty()) json += ",\"operation\":\"" + EscapeJsonString(desc.operation) + "\""; |
| 657 | + if (!desc.risk.empty()) json += ",\"risk\":\"" + EscapeJsonString(desc.risk) + "\""; |
| 658 | + if (!desc.permission.empty()) json += ",\"permission\":\"" + EscapeJsonString(desc.permission) + "\""; |
| 659 | + if (!desc.description.empty()) json += ",\"description\":\"" + EscapeJsonString(desc.description) + "\""; |
| 660 | + if (!desc.input_schema.empty()) json += ",\"inputSchema\":" + desc.input_schema; |
| 661 | + if (!desc.output_schema.empty()) json += ",\"outputSchema\":" + desc.output_schema; |
| 662 | + json += "}"; |
| 663 | + } |
| 664 | + json += "]}"; |
| 665 | + return json; |
| 666 | + } |
| 667 | + |
| 668 | + // gzip 压缩(RFC 1952 envelope):zlib deflate + windowBits 15+16。 |
| 669 | + static std::vector<uint8_t> GzipCompress(const std::string& data) { |
| 670 | + std::vector<uint8_t> out; |
| 671 | + z_stream stream{}; |
| 672 | + if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) { |
| 673 | + throw std::runtime_error("gzip init failed"); |
| 674 | + } |
| 675 | + stream.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(data.data())); |
| 676 | + stream.avail_in = static_cast<uInt>(data.size()); |
| 677 | + char buffer[4096]; |
| 678 | + int status = Z_OK; |
| 679 | + do { |
| 680 | + stream.next_out = reinterpret_cast<Bytef*>(buffer); |
| 681 | + stream.avail_out = sizeof(buffer); |
| 682 | + status = deflate(&stream, Z_FINISH); |
| 683 | + if (status != Z_OK && status != Z_STREAM_END && status != Z_BUF_ERROR) { |
| 684 | + deflateEnd(&stream); |
| 685 | + throw std::runtime_error("gzip deflate failed"); |
| 686 | + } |
| 687 | + out.insert(out.end(), buffer, buffer + (sizeof(buffer) - stream.avail_out)); |
| 688 | + } while (status != Z_STREAM_END); |
| 689 | + deflateEnd(&stream); |
| 690 | + return out; |
| 691 | + } |
| 692 | + |
| 693 | + // 向控制面(config_.control_addr)上传能力清单。 |
| 694 | + // 独立短连接 + 超时;任何失败仅告警,不影响已完成的函数注册。 |
| 695 | + void maybeRegisterCapabilities() { |
| 696 | + if (config_.control_addr.empty()) { |
| 697 | + return; |
| 698 | + } |
| 699 | + try { |
| 700 | + const auto address = ParseTCPAddress(config_.control_addr); |
| 701 | + TCPTransport control(address.host, address.port, 5000); |
| 702 | + control.Connect(); |
| 703 | + |
| 704 | + ::croupier::agent::v1::ProviderMeta meta; |
| 705 | + meta.set_id(config_.service_id.empty() ? std::string("cpp-service") : config_.service_id); |
| 706 | + meta.set_version(config_.service_version.empty() ? std::string("1.0.0") : config_.service_version); |
| 707 | + meta.set_lang(config_.provider_lang.empty() ? std::string("cpp") : config_.provider_lang); |
| 708 | + meta.set_sdk(config_.provider_sdk.empty() ? std::string("croupier-cpp-sdk") : config_.provider_sdk); |
| 709 | + ::croupier::agent::v1::RegisterCapabilitiesRequest request; |
| 710 | + *request.mutable_provider() = meta; |
| 711 | + const std::string manifest = buildManifestJson(); |
| 712 | + const auto manifest_gz = GzipCompress(manifest); |
| 713 | + request.set_manifest_json_gz(manifest_gz.data(), manifest_gz.size()); |
| 714 | + |
| 715 | + const auto body = SerializeMessage(request); |
| 716 | + const auto [resp_msg, resp_data] = control.Call(protocol::MSG_REGISTER_CAPABILITIES_REQ, body); |
| 717 | + (void)resp_msg; |
| 718 | + (void)resp_data; |
| 719 | + control.Close(); |
| 720 | + SDK_LOG_INFO("Capabilities registered to control plane: " + config_.control_addr); |
| 721 | + } catch (const std::exception& e) { |
| 722 | + SDK_LOG_WARN(std::string("Failed to register capabilities: ") + e.what()); |
| 723 | + } |
| 724 | + } |
| 725 | + |
629 | 726 | std::string registerWithAgent(TCPTransport& transport) { |
630 | 727 | ::croupier::sdk::v1::ProviderConnectRequest request; |
631 | 728 | request.set_service_id(config_.service_id); |
|
0 commit comments