载入中...
搜索中...
未找到
ConversationToolchain.cpp
浏览该文件的文档.
3
4#include <algorithm>
5#include <unordered_set>
6
7namespace eve::dialogue {
8namespace {
9
10void rewriteNodeReference(std::string& reference, const std::string& oldId, const std::string& newId) {
11 if (reference == oldId) reference = newId;
12}
13
14} // namespace
15
16eve::Result<void> lintConversationWorkspace(const std::vector<eve::dnut::SequenceAsset>& assets,
17 const std::string& label,
18 std::vector<ConversationDiagnostic>& diagnostics) {
19 const size_t diagnosticsBegin = diagnostics.size();
20 auto linted = lintConversations(assets, label, diagnostics);
21 bool valid = linted.ok();
22 std::unordered_set<std::string> assetIds;
23 for (const auto& asset : assets) assetIds.insert(asset.id);
24 for (const auto& asset : assets) {
25 for (const auto& node : asset.nodes) {
26 if (node.type != "call") continue;
27 const std::string targetId = sequencePayloadString(node, "target");
28 if (targetId.empty()) continue;
29 if (assetIds.find(targetId) == assetIds.end()) {
30 diagnostics.push_back({ConversationDiagnostic::Severity::Error, label, node.sourceLine,
31 "conversation '" + asset.id + "': call node '" + node.id +
32 "' references missing conversation '" + targetId + "'",
33 "MissingConversation", node.sourceColumn, asset.id + "/" + node.id});
34 valid = false;
35 continue;
36 }
37 const auto target = std::find_if(assets.begin(), assets.end(),
38 [&](const auto& candidate) { return candidate.id == targetId; });
39 const eve::Value* arguments = node.payload.find("arguments");
40 for (const auto& parameter : target->parameters) {
41 if (parameter.required && (!arguments || !arguments->find(parameter.name))) {
42 diagnostics.push_back({ConversationDiagnostic::Severity::Error, label, node.sourceLine,
43 "conversation '" + asset.id + "': call node '" + node.id +
44 "' omits required argument '" + parameter.name + "'",
45 "MissingCallArgument", node.sourceColumn, asset.id + "/" + node.id});
46 valid = false;
47 }
48 }
49 if (!arguments || !arguments->isObject()) continue;
50 for (const auto& argument : arguments->keys()) {
51 if (std::none_of(target->parameters.begin(), target->parameters.end(), [&](const auto& parameter) {
52 return parameter.name == argument;
53 })) {
54 diagnostics.push_back({ConversationDiagnostic::Severity::Error, label, node.sourceLine,
55 "conversation '" + asset.id + "': call node '" + node.id +
56 "' passes undeclared argument '" + argument + "'",
57 "UnexpectedCallArgument", node.sourceColumn, asset.id + "/" + node.id});
58 valid = false;
59 }
60 }
61 }
62 }
63 valid = valid &&
64 std::none_of(diagnostics.begin() + static_cast<std::ptrdiff_t>(diagnosticsBegin), diagnostics.end(),
65 [](const auto& diagnostic) {
66 return diagnostic.severity == ConversationDiagnostic::Severity::Error;
67 });
68 if (!valid) {
69 const std::string message = diagnostics.size() > diagnosticsBegin ? diagnostics[diagnosticsBegin].message
70 : "dialogue workspace lint failed";
72 eve::DiagnosticCode::InvalidArgument, message, label, {}, "dialogue.workspace.lint"));
73 }
75}
76
77eve::Result<void> renameConversationAsset(std::vector<eve::dnut::SequenceAsset>& assets, const std::string& oldId,
78 const std::string& newId) {
79 if (oldId.empty() || newId.empty()) return eve::Result<void>::failure(eve::Diagnostic::error(
80 eve::DiagnosticCode::InvalidArgument, "conversation IDs must not be empty", oldId, {}, "dialogue.rename"));
81 auto source = std::find_if(assets.begin(), assets.end(), [&](const auto& asset) { return asset.id == oldId; });
83 eve::DiagnosticCode::InvalidArgument, "conversation not found: " + oldId, oldId, {}, "dialogue.rename"));
84 if (std::any_of(assets.begin(), assets.end(), [&](const auto& asset) { return asset.id == newId; }))
86 eve::DiagnosticCode::InvalidArgument, "conversation already exists: " + newId, newId, {}, "dialogue.rename"));
87 source->id = newId;
88 ++source->version;
89 for (auto& asset : assets)
90 for (auto& node : asset.nodes)
91 if (node.type == "call" && sequencePayloadString(node, "target") == oldId)
92 node.payload.set("target", eve::Value::string(newId));
94}
95
96eve::Result<void> renameConversationNode(std::vector<eve::dnut::SequenceAsset>& assets, const std::string& assetId,
97 const std::string& oldId, const std::string& newId) {
98 if (oldId.empty() || newId.empty()) return eve::Result<void>::failure(eve::Diagnostic::error(
99 eve::DiagnosticCode::InvalidArgument, "node IDs must not be empty", assetId, {}, "dialogue.rename"));
100 auto asset = std::find_if(assets.begin(), assets.end(), [&](const auto& item) { return item.id == assetId; });
101 if (asset == assets.end()) return eve::Result<void>::failure(eve::Diagnostic::error(
102 eve::DiagnosticCode::InvalidArgument, "conversation not found: " + assetId, assetId, {}, "dialogue.rename"));
103 auto node =
104 std::find_if(asset->nodes.begin(), asset->nodes.end(), [&](const auto& item) { return item.id == oldId; });
105 if (node == asset->nodes.end()) return eve::Result<void>::failure(eve::Diagnostic::error(
106 eve::DiagnosticCode::InvalidArgument, "node not found: " + oldId, assetId + "/" + oldId, {}, "dialogue.rename"));
107 if (asset->findNode(newId)) return eve::Result<void>::failure(eve::Diagnostic::error(
108 eve::DiagnosticCode::InvalidArgument, "node already exists: " + newId, assetId + "/" + newId, {}, "dialogue.rename"));
109 node->id = newId;
110 rewriteNodeReference(asset->entry, oldId, newId);
111 for (auto& item : asset->nodes) {
112 rewriteNodeReference(item.next, oldId, newId);
113 if (item.type == "call") {
114 std::string returnNode = sequencePayloadString(item, "return");
115 rewriteNodeReference(returnNode, oldId, newId);
116 if (!returnNode.empty()) item.payload.set("return", eve::Value::string(returnNode));
117 }
118 for (auto& route : item.routes) rewriteNodeReference(route.target, oldId, newId);
119 }
120 ++asset->version;
122}
123
124} // namespace eve::dialogue
LogicalId target
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
Dialogue payload codecs for L1 sequence assets.
std::string label
std::string message
bool valid
std::uint32_t targetId
const RoadNode * node
const UnitySourceAsset & source
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::vector< std::string > keys() const
Return object member names in deterministic order.
Definition Value.cpp:138
static Value string(std::string value)
Compatibility factory for a string value.
Definition Value.h:110
bool isObject() const noexcept
Return true when this value is an object.
Definition Value.h:99
Value * find(const std::string &key) noexcept
Return an object member, or nullptr when absent.
Definition Value.cpp:124
eve::Result< void > renameConversationNode(std::vector< eve::dnut::SequenceAsset > &assets, const std::string &assetId, const std::string &oldId, const std::string &newId)
Rename one stable node and rewrite all references inside its asset.
eve::Result< void > lintConversations(const std::vector< eve::dnut::SequenceAsset > &assets, const std::string &path, std::vector< ConversationDiagnostic > &diagnostics)
Validate references and report unreachable nodes.
eve::Result< void > lintConversationWorkspace(const std::vector< eve::dnut::SequenceAsset > &assets, const std::string &label, std::vector< ConversationDiagnostic > &diagnostics)
Lint a complete multi-file registry, including cross-asset call targets.
std::string sequencePayloadString(const eve::dnut::SequenceNode &node, std::string_view key)
Read an optional string from a node payload; missing or ill-typed values yield empty.
eve::Result< void > renameConversationAsset(std::vector< eve::dnut::SequenceAsset > &assets, const std::string &oldId, const std::string &newId)
Rename an asset and rewrite every cross-asset call target.