NeoGraph 0.16.0
A C++20 Graph Agent Engine Library — LangGraph for C++
Loading...
Searching...
No Matches
types.h
1// Portable NeoGraph projections and full typed SDK outcome consumer helpers.
2#pragma once
3
4#include <algorithm>
5#include <limits>
6#include <map>
7#include <unordered_set>
8#include <mutex>
9#include <optional>
10#include <stdexcept>
11#include <string>
12#include <vector>
13#include <neograph/json.h>
14#include <core/value.h>
15#include <neograph/provider_outcome_codec.h>
16#include <json/json.h>
17
18namespace neograph {
19
27struct ToolCall {
28 std::string id;
29 std::string name;
30 std::string arguments;
31};
32
40 std::string role;
41 std::string content;
42 std::vector<ToolCall> tool_calls;
43 std::string tool_call_id;
44 std::string tool_name;
46 std::string tool_status;
47 bool tool_retryable = false;
48 bool tool_effect_uncertain = false;
49 std::vector<std::string> image_urls;
51 std::string reasoning;
53 json reasoning_details = json::array();
54};
55
62struct ChatTool {
63 std::string name;
64 std::string description;
70 std::optional<bool> strict;
71};
72
77 std::string kind;
78 std::string mime_type;
79 std::string base64_data;
80 std::string url;
81 std::string file_id;
82 json metadata = json::object();
83};
84
85
86// Provider reports and conservative budget authority are intentionally separate.
87class NEOGRAPH_API UsageAccumulator {
88public:
89 struct AuthoritySnapshot {
90 std::uint64_t charged = 0, reserved = 0;
91 std::vector<std::string> provider_effects;
92 sp::Usage reports;
93 bool has_report = false;
94 };
95 AuthoritySnapshot authority_snapshot() const {
96 std::lock_guard lock(mutex_);
97 AuthoritySnapshot result;
98 result.charged = charged_;
99 result.reserved = reserved_;
100 result.provider_effects.assign(provider_effects_.begin(), provider_effects_.end());
101 std::sort(result.provider_effects.begin(), result.provider_effects.end());
102 result.reports = reports_;
103 result.has_report = has_report_;
104 return result;
105 }
106 // Recorded playback may observe authenticated custody, never spend/refund it.
107 void seal_observation() {
108 std::lock_guard lock(mutex_);
109 observation_only_ = true;
110 }
111 // Caller must authenticate custody and original scope/ceiling first.
112 // Restoration is one-shot into a pristine bank, never replacement/reset.
113 void restore_authority(AuthoritySnapshot snapshot) {
114 std::lock_guard lock(mutex_);
115 require_live_authority_locked();
116 if (authority_restored_ || charged_ != 0 || reserved_ != 0 || has_report_ || !provider_effects_.empty())
117 throw std::logic_error("Spending authority may only restore into a pristine bank");
118 std::unordered_set<std::string> effects;
119 effects.reserve(snapshot.provider_effects.size());
120 for (auto& key : snapshot.provider_effects)
121 if (!effects.insert(std::move(key)).second)
122 throw std::invalid_argument("Spending authority snapshot has duplicate effect identities");
123 charged_ = snapshot.charged;
124 reserved_ = snapshot.reserved;
125 reports_ = std::move(snapshot.reports);
126 has_report_ = snapshot.has_report;
127 provider_effects_ = std::move(effects);
128 authority_restored_ = true;
129 }
130 bool remember_provider_effect(std::string key) {
131 std::lock_guard lock(mutex_);
132 require_live_authority_locked();
133 return provider_effects_.insert(std::move(key)).second;
134 }
135 void observe(const sp::Usage& usage) {
136 std::lock_guard lock(mutex_);
137 require_live_authority_locked();
138 add_report(usage);
139 }
140 void add(const sp::Usage& usage) {
141 std::lock_guard lock(mutex_);
142 require_live_authority_locked();
143 add_report(usage);
144 if (const auto charge = final_charge(usage)) charged_ = sum(charged_, *charge);
145 }
146 bool try_reserve(std::uint64_t tokens, std::uint64_t ceiling) {
147 std::lock_guard lock(mutex_);
148 require_live_authority_locked();
149 if (tokens == 0 || ceiling == 0) return false;
150 const auto committed = sum(charged_, reserved_);
151 if (committed > ceiling || tokens > ceiling - committed) return false;
152 reserved_ = sum(reserved_, tokens);
153 return true;
154 }
155 // Only for operations proven not dispatched. Unknown delivery retains authority.
156 void release_reservation(std::uint64_t tokens) {
157 std::lock_guard lock(mutex_);
158 require_live_authority_locked();
159 reserved_ -= std::min(reserved_, tokens);
160 }
161 void settle_reservation(std::uint64_t reserved, const sp::Usage& usage) {
162 std::lock_guard lock(mutex_);
163 require_live_authority_locked();
164 add_report(usage);
165 const auto charge = final_charge(usage);
166 if (!charge) return;
167 const auto held = std::min(reserved_, reserved);
168 reserved_ -= held;
169 charged_ = sum(charged_, *charge);
170 }
171 sp::Usage snapshot() const {
172 std::lock_guard lock(mutex_);
173 return reports_;
174 }
175 std::uint64_t total_tokens_wide() const noexcept {
176 std::lock_guard lock(mutex_);
177 return sum(charged_, reserved_);
178 }
179 // Only trusted durable journals restore conservative spending authority.
180 // This neither invents a provider report nor releases a reservation.
181 void restore_charge(std::uint64_t amount) {
182 std::lock_guard lock(mutex_);
183 require_live_authority_locked();
184 charged_ = sum(charged_, amount);
185 }
186 void restore_reservation(std::uint64_t amount) {
187 std::lock_guard lock(mutex_);
188 require_live_authority_locked();
189 reserved_ = sum(reserved_, amount);
190 }
191 static std::optional<std::uint64_t> conservative_final_charge(const sp::Usage& usage) {
192 return final_charge(usage);
193 }
194private:
195 void require_live_authority_locked() const {
196 if (observation_only_)
197 throw std::logic_error("Recorded replay bank is observation-only");
198 }
199 static std::uint64_t sum(std::uint64_t a, std::uint64_t b) noexcept {
200 return b > std::numeric_limits<std::uint64_t>::max() - a
201 ? std::numeric_limits<std::uint64_t>::max() : a + b;
202 }
203 static std::optional<std::uint64_t> final_charge(const sp::Usage& usage) {
204 if (usage.stage != sp::UsageStage::Final || usage.quality != sp::UsageQuality::Consistent ||
205 !usage.input_total || !usage.output_total) return {};
206 const auto input = usage.input_total->value, output = usage.output_total->value;
207 if (output > std::numeric_limits<std::uint64_t>::max() - input) return {};
208 const auto total = input + output;
209 if ((usage.total && usage.total->value < total) ||
210 (usage.provider_reported_total && usage.provider_reported_total->value < total)) return {};
211 return std::max({total, usage.total ? usage.total->value : total,
212 usage.provider_reported_total ? usage.provider_reported_total->value : total});
213 }
214 void add_report(const sp::Usage& usage) {
215 if (!has_report_) { reports_ = usage; has_report_ = true; return; }
216 // This bank aggregates tokens, not monetary reports. On a second report,
217 // the first call's cost is no longer a valid snapshot of the aggregate.
218 // Exact money evidence remains in each retained provider outcome.
219 if (!provider_codec::provider_cost_absent(reports_.provider_cost))
220 reports_.provider_cost = {};
221 auto fold = [&](std::string_view counter, std::optional<sp::Count>& target,
222 const std::optional<sp::Count>& value) {
223 if (!target || !value) { target.reset(); return; }
224 if (value->value > std::numeric_limits<std::uint64_t>::max() - target->value) {
225 target.reset();
226 reports_.quality = sp::UsageQuality::Inconsistent;
227 reports_.conflicts.push_back({std::string(counter), "aggregate counter overflow"});
228 return;
229 }
230 target->value += value->value;
231 target->evidence = sp::Evidence::Derived;
232 };
233 fold("input_total", reports_.input_total, usage.input_total);
234 fold("output_total", reports_.output_total, usage.output_total);
235 fold("total", reports_.total, usage.total);
236 fold("provider_reported_total", reports_.provider_reported_total, usage.provider_reported_total);
237 fold("input_uncached", reports_.input_uncached, usage.input_uncached);
238 fold("cache_read", reports_.cache_read, usage.cache_read);
239 fold("cache_write", reports_.cache_write, usage.cache_write);
240 fold("reasoning", reports_.reasoning, usage.reasoning);
241 for (auto entry = reports_.extra.begin(); entry != reports_.extra.end();) {
242 const auto value = usage.extra.find(entry->first);
243 if (value == usage.extra.end()) { entry = reports_.extra.erase(entry); continue; }
244 if (value->second.value > std::numeric_limits<std::uint64_t>::max() - entry->second.value) {
245 reports_.quality = sp::UsageQuality::Inconsistent;
246 reports_.conflicts.push_back({entry->first, "aggregate counter overflow"});
247 entry = reports_.extra.erase(entry);
248 continue;
249 }
250 entry->second.value += value->second.value;
251 entry->second.evidence = sp::Evidence::Derived;
252 ++entry;
253 }
254 if (reports_.stage == sp::UsageStage::Missing && usage.stage == sp::UsageStage::Missing)
255 reports_.stage = sp::UsageStage::Missing;
256 else if (reports_.stage != sp::UsageStage::Final || usage.stage != sp::UsageStage::Final)
257 reports_.stage = sp::UsageStage::Partial;
258 if (usage.quality == sp::UsageQuality::Inconsistent) reports_.quality = usage.quality;
259 reports_.conflicts.insert(reports_.conflicts.end(), usage.conflicts.begin(), usage.conflicts.end());
260 }
261 mutable std::mutex mutex_;
262 sp::Usage reports_;
263 bool has_report_ = false;
264 bool authority_restored_ = false;
265 bool observation_only_ = false;
266 std::uint64_t charged_ = 0, reserved_ = 0;
267 std::unordered_set<std::string> provider_effects_;
268};
269
270// --- ADL serialization: ChatMessage/ToolCall <-> json ---
271// These live in the same namespace as the types for ADL lookup.
272
276inline void to_json(json& j, const ToolCall& tc) {
277 j = json{{"id", tc.id}, {"name", tc.name}, {"arguments", tc.arguments}};
278}
279
283inline void from_json(const json& j, ToolCall& tc) {
284 tc.id = j.value("id", "");
285 tc.name = j.value("name", "");
286 tc.arguments = j.value("arguments", "");
287}
288
292inline void to_json(json& j, const ChatMessage& msg) {
293 j["role"] = msg.role;
294 j["content"] = msg.content;
295 if (!msg.tool_calls.empty()) {
296 j["tool_calls"] = json::array();
297 for (const auto& tc : msg.tool_calls) {
298 json tc_j;
299 to_json(tc_j, tc);
300 j["tool_calls"].push_back(tc_j);
301 }
302 }
303 if (!msg.tool_call_id.empty()) j["tool_call_id"] = msg.tool_call_id;
304 if (!msg.tool_name.empty()) j["tool_name"] = msg.tool_name;
305 if (!msg.tool_status.empty()) j["tool_status"] = msg.tool_status;
306 if (msg.tool_retryable) j["tool_retryable"] = true;
307 if (msg.tool_effect_uncertain) j["tool_effect_uncertain"] = true;
308 if (!msg.image_urls.empty()) j["image_urls"] = msg.image_urls;
309 if (!msg.reasoning.empty()) j["reasoning"] = msg.reasoning;
310 if (!msg.reasoning_details.empty()) j["reasoning_details"] = msg.reasoning_details;
311}
312
316inline void from_json(const json& j, ChatMessage& msg) {
317 msg.role = j.value("role", "");
318 msg.content = j.value("content", "");
319 if (j.contains("tool_calls") && j["tool_calls"].is_array()) {
320 for (const auto& tc_j : j["tool_calls"]) {
321 ToolCall tc;
322 from_json(tc_j, tc);
323 msg.tool_calls.push_back(tc);
324 }
325 }
326 msg.tool_call_id = j.value("tool_call_id", "");
327 msg.tool_name = j.value("tool_name", "");
328 msg.tool_status = j.value("tool_status", "");
329 msg.tool_retryable = j.value("tool_retryable", false);
330 msg.tool_effect_uncertain = j.value("tool_effect_uncertain", false);
331 if (j.contains("image_urls") && j["image_urls"].is_array()) {
332 msg.image_urls = j["image_urls"].get<std::vector<std::string>>();
333 }
334 msg.reasoning = j.value("reasoning", "");
335 if (j.contains("reasoning_details") && !j["reasoning_details"].is_array()) {
336 throw std::invalid_argument("ChatMessage reasoning_details must be an array");
337 }
338 msg.reasoning_details = j.contains("reasoning_details")
339 ? j["reasoning_details"]
340 : json::array();
341}
342
343
391class NEOGRAPH_API ProviderFailure final : public std::runtime_error {
392public:
393 explicit ProviderFailure(std::shared_ptr<const sp::Outcome> outcome)
394 : std::runtime_error(std::get<sp::Failure>(*outcome).error.safe_message),
395 outcome_(std::move(outcome)) {}
396 const std::shared_ptr<const sp::Outcome>& outcome() const noexcept { return outcome_; }
397private:
398 std::shared_ptr<const sp::Outcome> outcome_;
399};
400
401inline std::shared_ptr<const sp::Outcome> outcome_or_throw(std::shared_ptr<const sp::Outcome> result) {
402 if (!result) throw std::invalid_argument("Provider returned no owned outcome");
403 if (std::holds_alternative<sp::Failure>(*result)) throw ProviderFailure(result);
404 return result;
405}
406inline const std::vector<sp::Message>& outcome_messages(const sp::Outcome& outcome) noexcept {
407 if (const auto* completion = std::get_if<sp::Completion>(&outcome)) return completion->messages;
408 return std::get<sp::Failure>(outcome).partial.messages;
409}
410inline const sp::Usage& outcome_usage(const sp::Outcome& outcome) noexcept {
411 if (const auto* completion = std::get_if<sp::Completion>(&outcome)) return completion->usage;
412 return std::get<sp::Failure>(outcome).partial.usage;
413}
414namespace detail {
415// A usage record that holds nothing at all: no stage, no counter, no cost. The
416// provider reported nothing, which is not the same as reporting zero.
417inline bool usage_holds_nothing(const sp::Usage& usage) noexcept {
418 return usage.stage == sp::UsageStage::Missing &&
419 usage.quality == sp::UsageQuality::Consistent &&
420 !usage.input_total && !usage.output_total && !usage.total && !usage.provider_reported_total &&
421 !usage.input_uncached && !usage.cache_read && !usage.cache_write && !usage.reasoning &&
422 usage.extra.empty() && usage.conflicts.empty() &&
423 provider_codec::provider_cost_absent(usage.provider_cost);
424}
425// The statuses SchemaProvider 0.3.1 labels `RejectedBeforeOutput`: the server
426// completed an answer that declines the request. 408 and 425 ask the client to
427// try again and 429 can follow started work, so they stay possibly accepted.
428inline bool http_status_declines_request(int status) noexcept {
429 return status >= 400 && status < 500 && status != 408 && status != 425 && status != 429;
430}
431} // namespace detail
432
436inline bool provider_failure_proves_not_sent(const sp::Failure& failure) noexcept {
437 const auto& attempt = failure.error.attempt;
438 const auto& partial = failure.partial;
439 return failure.error.retry_safety == sp::RetrySafety::NotSent &&
440 !attempt.request_may_have_left && attempt.request_body_bytes == 0 &&
441 !attempt.response_head_seen && attempt.transport_internal_resends == 0 && !attempt.prior_usage_unknown &&
442 partial.messages.empty() && !partial.stop && !partial.wire_envelope &&
443 partial.raw_events.empty() && detail::usage_holds_nothing(partial.usage);
444}
445
451inline bool provider_failure_proves_rejected_before_output(const sp::Failure& failure) noexcept {
452 const auto& attempt = failure.error.attempt;
453 const auto& partial = failure.partial;
454 return failure.error.retry_safety == sp::RetrySafety::RejectedBeforeOutput &&
455 detail::http_status_declines_request(failure.error.http_status) &&
456 attempt.request_may_have_left && attempt.response_head_seen &&
457 attempt.transport_internal_resends == 0 && !attempt.prior_usage_unknown &&
458 partial.messages.empty() && !partial.stop && !partial.wire_envelope &&
459 std::all_of(partial.raw_events.begin(), partial.raw_events.end(),
460 [](const sp::RawWire& event) { return event.type == "http.error"; }) &&
461 detail::usage_holds_nothing(partial.usage);
462}
463
472inline bool provider_failure_proves_not_accepted(const sp::Failure& failure) noexcept {
473 return provider_failure_proves_not_sent(failure) ||
474 provider_failure_proves_rejected_before_output(failure);
475}
476inline std::string outcome_text(const sp::Outcome& outcome) {
477 std::string text;
478 for (const auto& message : outcome_messages(outcome))
479 for (const auto& part : message.parts)
480 if (const auto* value = std::get_if<sp::Text>(&part)) text += value->value;
481 return text;
482}
483inline std::vector<ToolCall> client_tool_calls(const sp::Message& message) {
484 std::vector<ToolCall> result;
485 for (const auto& part : message.parts) if (const auto* call = std::get_if<sp::ToolCall>(&part);
486 call && call->kind == sp::ToolCallKind::ClientExecuted && call->input && call->input->root().is_object())
487 result.push_back({call->id, call->name, call->input->root().dump()});
488 return result;
489}
490// A completed turn the vendor paused (`sp::StopKind::PauseTurn`). The documented
491// continuation resends the history, including that assistant turn, unchanged.
492inline bool paused_turn(const sp::Outcome& outcome) noexcept {
493 const auto* completion = std::get_if<sp::Completion>(&outcome);
494 return completion && completion->stop.kind == sp::StopKind::PauseTurn;
495}
496inline std::vector<ToolCall> pending_client_tool_calls(const std::vector<sp::Message>& history) {
497 std::map<std::string_view, const sp::ToolCall*> pending;
498 std::vector<const sp::ToolCall*> order;
499 for (const auto& message : history) for (const auto& part : message.parts) {
500 if (const auto* call = std::get_if<sp::ToolCall>(&part);
501 call && call->kind == sp::ToolCallKind::ClientExecuted && call->input &&
502 call->input->root().is_object()) {
503 if (!pending.emplace(call->id, call).second)
504 throw std::invalid_argument("Client tool call history has duplicate unresolved identities");
505 order.push_back(call);
506 } else if (const auto* result = std::get_if<sp::ToolResult>(&part)) {
507 pending.erase(result->tool_use_id);
508 }
509 }
510 std::vector<ToolCall> result;
511 result.reserve(pending.size());
512 for (const auto* call : order) {
513 const auto found = pending.find(call->id);
514 if (found != pending.end() && found->second == call)
515 result.push_back({call->id, call->name, call->input->root().dump()});
516 }
517 return result;
518}
519inline sp::Role portable_role(std::string_view role) {
520 if (role == "system") return sp::Role::System;
521 if (role == "developer") return sp::Role::Developer;
522 if (role == "user") return sp::Role::User;
523 if (role == "assistant") return sp::Role::Assistant;
524 if (role == "tool") return sp::Role::Tool;
525 throw std::invalid_argument("Invalid portable message role");
526}
527inline std::string_view portable_role_name(sp::Role role) {
528 switch (role) {
529 case sp::Role::System: return "system";
530 case sp::Role::Developer: return "developer";
531 case sp::Role::User: return "user";
532 case sp::Role::Assistant: return "assistant";
533 case sp::Role::Tool: return "tool";
534 }
535 throw std::invalid_argument("Invalid typed message role");
536}
537inline sp::Message portable_message(const ChatMessage& source) {
538 if (!source.reasoning.empty() || !source.reasoning_details.empty())
539 throw std::invalid_argument("Portable projection cannot import native reasoning authority");
540 sp::Message message;
541 message.role = portable_role(source.role);
542 if (message.role == sp::Role::Tool) {
543 if (!source.tool_calls.empty() || !source.image_urls.empty())
544 throw std::invalid_argument("Portable tool result cannot contain calls or images");
545 message.parts.emplace_back(sp::ToolResult{source.tool_call_id, source.content,
546 source.tool_status != "succeeded" && !source.tool_status.empty(),
547 sp::ToolResultHostMetadata{source.tool_name, source.tool_status,
548 source.tool_retryable, source.tool_effect_uncertain}});
549 return message;
550 }
551 if (!source.content.empty()) message.parts.emplace_back(sp::Text{source.content});
552 for (const auto& url : source.image_urls) {
553 // A data URL is inline base64; an https URL is a location the provider fetches.
554 sp::Media image;
555 if (url.starts_with("data:")) {
556 const auto separator = url.find(";base64,");
557 if (separator == std::string::npos)
558 throw std::invalid_argument("Portable inline image requires a base64 data URL");
559 image = sp::Media::image(url.substr(5, separator - 5),
560 std::make_shared<const std::string>(url.substr(separator + 8)));
561 } else {
562 image = sp::Media::url(sp::MediaKind::Image, url);
563 }
564 if (sp::check_media(image, sp::config_defaults::codec_resources_image_decoded_bytes) != sp::MediaFault::None)
565 throw std::invalid_argument("Invalid portable image");
566 message.parts.emplace_back(std::move(image));
567 }
568 for (const auto& call : source.tool_calls) {
569 auto parsed = sp::json::parse(call.arguments);
570 auto* document = std::get_if<sp::json::Document>(&parsed);
571 if (!document || !document->root().is_object())
572 throw std::invalid_argument("Portable tool call requires admitted object arguments");
573 sp::ToolCall typed;
574 typed.id = call.id;
575 typed.name = call.name;
576 typed.input = std::make_shared<const sp::json::Document>(std::move(*document));
577 message.parts.emplace_back(std::move(typed));
578 }
579 return message;
580}
581// Deliberately an observational projection, never used to replay provider history.
582inline ChatMessage project_message(const sp::Message& message) {
583 ChatMessage result;
584 result.role = portable_role_name(message.role);
585 result.tool_calls = client_tool_calls(message);
586 for (const auto& part : message.parts) {
587 if (const auto* text = std::get_if<sp::Text>(&part)) result.content += text->value;
588 else if (const auto* thinking = std::get_if<sp::Thinking>(&part)) result.reasoning += thinking->text;
589 else if (const auto* tool = std::get_if<sp::ToolResult>(&part)) {
590 result.tool_call_id = tool->tool_use_id;
591 result.content += tool->content;
592 result.tool_status = tool->is_error ? "failed" : "succeeded";
593 if (tool->host) {
594 result.tool_name = tool->host->name;
595 result.tool_status = tool->host->status;
596 result.tool_retryable = tool->host->retryable;
597 result.tool_effect_uncertain = tool->host->effect_uncertain;
598 }
599 } else if (const auto* media = std::get_if<sp::Media>(&part);
600 media && media->kind == sp::MediaKind::Image) {
601 if (media->source == sp::MediaSource::Inline && media->data)
602 result.image_urls.push_back("data:" + media->mime + ";base64," + *media->data);
603 else if (media->source == sp::MediaSource::Url)
604 result.image_urls.push_back(media->reference);
605 }
606 }
607 return result;
608}
609inline json message_projection_json(const sp::Message& message) {
610 return provider_codec::encode_message(message);
611}
612inline json usage_to_json(const sp::Usage& usage) { return provider_codec::encode_usage(usage); }
613inline json outcome_projection_json(const sp::Outcome& outcome) {
614 return provider_codec::observe_outcome(outcome);
615}
616NEOGRAPH_API std::string message_digest(const sp::Message& message);
617
618} // namespace neograph
How provider stop reasons reach callers.
Definition types.h:391
Thin C++ RAII wrapper around yyjson with nlohmann-compatible API.
A message in the conversation history.
Definition types.h:39
std::string reasoning
Provider-returned reasoning text. Keep separate from user-visible content.
Definition types.h:51
std::string tool_call_id
ID of the tool call being responded to (role == "tool").
Definition types.h:43
json reasoning_details
Opaque provider-native continuation blocks replayed only on a compatible route.
Definition types.h:53
std::string tool_name
Name of the tool being called.
Definition types.h:44
std::string content
Text content of the message.
Definition types.h:41
std::vector< std::string > image_urls
Base64 data URLs or HTTP URLs for vision support.
Definition types.h:49
std::string role
Message role: "user", "assistant", "tool", or "system".
Definition types.h:40
std::string tool_status
Typed terminal status emitted by the shared ToolExecutionController.
Definition types.h:46
std::vector< ToolCall > tool_calls
Tool calls made by the assistant (if any).
Definition types.h:42
Tool definition metadata sent to the LLM.
Definition types.h:62
json parameters
JSON Schema object describing the tool's parameters.
Definition types.h:65
std::optional< bool > strict
OpenAI Responses function-tool strict mode.
Definition types.h:70
std::string description
Human-readable description of what the tool does.
Definition types.h:64
std::string name
Tool name (must be unique within a session).
Definition types.h:63
Generated provider media.
Definition types.h:76
std::string kind
"image", "video", or "file".
Definition types.h:77
std::string base64_data
Encoded bytes, not decoded or copied into a second buffer.
Definition types.h:79
Represents a single tool invocation requested by the LLM.
Definition types.h:27
std::string name
Name of the tool to invoke.
Definition types.h:29
std::string id
Unique identifier for this tool call.
Definition types.h:28
std::string arguments
JSON-encoded string of tool arguments.
Definition types.h:30