4#include <neograph/types.h>
5#include <runtime/client.h>
6#include <core/request_controls.h>
7#include <asio/any_io_executor.hpp>
8#include <asio/awaitable.hpp>
20namespace neograph::graph {
23class OwnedManagedBudgetLease;
24class ManagedBudgetEffectReceipt;
29struct ProviderDispatchBudget {
30 std::shared_ptr<UsageAccumulator> usage;
31 std::uint64_t model_token_budget = 0;
32 std::shared_ptr<std::atomic<bool>> budget_exhausted;
33 std::shared_ptr<graph::CancelToken> budget_cancel_token;
34 std::shared_ptr<graph::OwnedManagedBudgetLease> managed_budget_lease;
35 std::shared_ptr<graph::CheckpointStore> managed_budget_store;
36 std::string managed_effect_id;
39enum class ProviderMode : std::uint8_t { Collect, Stream };
46 std::optional<sp::chat::ReasoningOptions> reasoning;
47 std::optional<bool> include_reasoning, usage_include;
48 std::vector<std::string> models;
49 std::vector<sp::chat::OutputModality> modalities;
50 std::optional<sp::chat::AudioOutput> audio;
51 std::optional<sp::OpenRouterImageConfig> image_config;
52 std::vector<sp::OpenRouterPlugin> plugins;
53 std::optional<json> extensions;
55struct ResponsesControls {
57 std::optional<std::string> previous_response_id;
59 std::vector<sp::Message> previous_response_history;
60 std::optional<bool> parallel_tool_calls;
61 std::optional<sp::responses::Verbosity> verbosity;
62 std::optional<sp::responses::Truncation> truncation;
63 std::optional<std::vector<sp::responses::Include>> include;
64 std::vector<std::string> include_names;
66 std::vector<sp::responses::HostedTool> hosted_tools;
67 std::vector<sp::responses::OutputModality> modalities;
68 std::optional<sp::OpenRouterImageConfig> image_config;
69 std::vector<sp::OpenRouterPlugin> plugins;
70 std::vector<sp::responses::McpApproval> mcp_approvals;
71 std::optional<json> extensions;
73struct MessagesControls {
74 std::optional<sp::messages::ThinkingMode> thinking_mode;
75 std::optional<sp::messages::OutputEffort> output_effort;
76 std::optional<sp::messages::CacheControl> cache_control;
77 std::optional<sp::messages::ToolChoice> tool_choice;
80 std::vector<sp::messages::ToolDefinition> tools;
81 std::optional<std::string> container;
82 std::vector<sp::messages::ContainerSkill> skills;
83 std::vector<std::string> betas;
84 std::vector<sp::messages::McpServer> mcp_servers;
85 std::optional<json> extensions;
87struct GenerateControls {
89 std::optional<sp::gemini::HistoryMode> history_mode;
90 std::optional<sp::gemini::ThinkingLevel> thinking_level;
91 std::vector<sp::gemini::SafetySetting> safety_settings;
92 std::optional<sp::gemini::ToolChoice> tool_choice;
93 std::vector<sp::gemini::ResponseModality> response_modalities;
94 std::optional<sp::gemini::SpeechConfig> speech_config;
95 std::optional<sp::gemini::ImageConfig> image_config;
96 std::optional<sp::gemini::AudioTranscriptionConfig> audio_transcription_config;
97 std::optional<sp::gemini::AudioFormat> audio_format;
98 std::vector<sp::gemini::BuiltinTool> builtin_tools;
99 std::vector<sp::gemini::McpServer> mcp_servers;
100 std::optional<bool> server_tool_invocations;
101 std::optional<sp::gemini::RetrievalConfig> retrieval_config;
102 std::optional<json> extensions;
104struct InteractionsControls {
105 std::vector<sp::interactions::ResponseFormat> response_format;
106 std::optional<sp::interactions::SpeechConfiguration> speech_config;
107 std::optional<sp::interactions::TranscriptionConfig> transcription_config;
108 std::vector<sp::interactions::BuiltinTool> builtin_tools;
110 std::optional<std::string> previous_interaction_id, video_task, agent;
111 std::optional<bool> background;
112 std::optional<json> extensions;
119 std::optional<std::uint64_t> max_output_tokens;
120 std::optional<std::uint64_t> max_tool_calls;
121 std::optional<double> temperature, top_p;
122 std::optional<std::string> reasoning_effort, reasoning_summary;
123 std::optional<std::uint64_t> thinking_budget;
124 std::optional<bool> include_thoughts;
125 std::optional<std::string> thinking_level;
126 std::optional<bool> thinking_summaries;
127 std::optional<std::string> service_tier, required_tool;
128 std::optional<sp::OpenRouterRouting> provider;
129 std::optional<sp::ResponseFormat> response_format;
130 std::optional<bool> store;
131 std::string account_scope, system;
134 ResponsesControls responses;
135 MessagesControls messages;
136 GenerateControls generate;
137 InteractionsControls interactions;
143 std::optional<bool> include_reasoning, usage_include;
144 std::vector<std::string> models;
145 std::optional<std::string> previous_response_id;
146 std::vector<sp::Message> previous_response_history;
147 std::optional<bool> parallel_tool_calls;
148 std::optional<sp::responses::Verbosity> verbosity;
149 std::optional<sp::responses::Truncation> truncation;
150 std::optional<std::vector<sp::responses::Include>> responses_include;
151 std::optional<sp::messages::ThinkingMode> thinking_mode;
152 std::optional<sp::messages::OutputEffort> output_effort;
153 std::optional<sp::messages::CacheControl> cache_control;
154 std::optional<sp::messages::ToolChoice> messages_tool_choice;
155 std::optional<sp::gemini::HistoryMode> gemini_history_mode;
156 std::optional<sp::gemini::ThinkingLevel> gemini_thinking_level;
157 std::vector<sp::gemini::SafetySetting> safety_settings;
158 std::optional<sp::gemini::ToolChoice> gemini_tool_choice;
163 std::optional<std::size_t> max_events;
164 std::optional<std::size_t> max_bytes;
172 sp::runtime::PreparedRequest prepared;
173 std::optional<sp::Error> error;
174 std::optional<json> upload;
181 virtual EndpointPreparation prepare(sp::runtime::Client& client, sp::runtime::RunOptions options)
const = 0;
191 sp::runtime::Request payload;
194 std::shared_ptr<const detail::EndpointPayload>
endpoint;
195 ProviderMode mode = ProviderMode::Collect;
196 sp::runtime::RunOptions options;
197 std::shared_ptr<graph::CancelToken> cancel_token;
198 std::function<void(
const sp::Event&)> on_event;
207 std::exception_ptr cause)
208 : std::runtime_error(message), outcome_(std::move(outcome)), cause_(std::move(cause)) {}
209 const sp::runtime::Result& outcome()
const noexcept {
return outcome_; }
210 const std::exception_ptr& cause()
const noexcept {
return cause_; }
212 sp::runtime::Result outcome_;
213 std::exception_ptr cause_;
218 ProviderBudgetSettlementError(sp::runtime::Result outcome, std::exception_ptr cause,
219 std::exception_ptr dispatch_error = {})
220 : ProviderOutcomeError(
"Provider budget settlement could not be persisted",
221 std::move(outcome), std::move(cause)),
222 dispatch_error_(std::move(dispatch_error)) {}
223 const std::exception_ptr& dispatch_error() const noexcept {
return dispatch_error_; }
225 std::exception_ptr dispatch_error_;
228class NEOGRAPH_API ProviderObserverError final :
public ProviderOutcomeError {
230 ProviderObserverError(sp::runtime::Result result, std::exception_ptr cause,
231 sp::ErrorKind kind = sp::ErrorKind::Misuse)
232 : ProviderOutcomeError(kind == sp::ErrorKind::ResourceLimit
233 ?
"Provider event retention resource limit exhausted" :
"Provider event observer failed",
234 std::move(result), std::move(cause)), kind_(kind) {}
235 sp::ErrorKind error_kind() const noexcept {
return kind_; }
241class NEOGRAPH_API PreparedProviderRequest final {
243 PreparedProviderRequest();
244 ~PreparedProviderRequest();
245 PreparedProviderRequest(PreparedProviderRequest&&) noexcept;
246 PreparedProviderRequest& operator=(PreparedProviderRequest&&) noexcept;
247 PreparedProviderRequest(const PreparedProviderRequest&) = delete;
248 PreparedProviderRequest& operator=(const PreparedProviderRequest&) = delete;
249 bool valid() const noexcept;
250 const sp::Error* error() const noexcept;
251 std::string_view family() const noexcept;
252 std::string_view model() const noexcept;
253 std::string_view encoded_body() const noexcept;
254 sp::runtime::SteadyTime deadline() const noexcept;
255 ProviderMode mode() const noexcept;
256 bool is_cancelled() const noexcept;
257 std::optional<std::uint64_t> max_output_tokens() const noexcept;
258 bool requires_native_custody() const noexcept;
259 const sp::descriptor::ValidatedDescriptor* admitted_descriptor() const noexcept;
263 bool is_endpoint_read() const noexcept;
267 bool opens_upload_session() const noexcept;
270 explicit PreparedProviderRequest(std::unique_ptr<Impl>);
271 std::unique_ptr<Impl> impl_;
272 friend class Provider;
275class NEOGRAPH_API ProviderBudgetClaim;
276NEOGRAPH_API ProviderBudgetClaim reserve_provider_dispatch(
277 const PreparedProviderRequest& request, ProviderDispatchBudget budget);
279class NEOGRAPH_API ProviderBudgetClaim final {
281 ProviderBudgetClaim();
282 ~ProviderBudgetClaim();
283 ProviderBudgetClaim(ProviderBudgetClaim&&) noexcept;
284 ProviderBudgetClaim& operator=(ProviderBudgetClaim&&) noexcept;
285 ProviderBudgetClaim(const ProviderBudgetClaim&) = delete;
286 ProviderBudgetClaim& operator=(const ProviderBudgetClaim&) = delete;
287 bool active() const noexcept;
288 std::uint64_t amount() const noexcept;
289 asio::awaitable<
void> begin_managed_effect(
290 const PreparedProviderRequest& request, std::
string effect_id = {});
291 asio::awaitable<void> settle_managed(
292 sp::runtime::Result result, std::exception_ptr dispatch_error = {});
293 void mark_dispatched();
294 std::uint64_t settle(sp::runtime::Result result);
296 ProviderBudgetClaim(ProviderDispatchBudget budget, std::uint64_t amount);
297 void release_undispatched() noexcept;
298 std::uint64_t settle_accounting(sp::runtime::Result result);
299 ProviderDispatchBudget budget_;
300 std::uint64_t amount_ = 0;
301 std::unique_ptr<graph::ManagedBudgetEffectReceipt> managed_effect_;
302 bool dispatched_ = false, settled_ = false, writeahead_committed_ = false;
303 friend ProviderBudgetClaim reserve_provider_dispatch(
304 const PreparedProviderRequest&, ProviderDispatchBudget);
313template <>
struct EndpointOutcome<sp::images::Request> {
using type = sp::images::Outcome; };
314template <>
struct EndpointOutcome<sp::decisions::Request> {
using type = sp::decisions::Outcome; };
315template <>
struct EndpointOutcome<sp::embeddings::Request> {
using type = sp::embeddings::Outcome; };
316template <>
struct EndpointOutcome<sp::rerank::Request> {
using type = sp::rerank::Outcome; };
317template <>
struct EndpointOutcome<sp::moderation::Request> {
using type = sp::moderation::Outcome; };
318template <>
struct EndpointOutcome<sp::speech::Request> {
using type = sp::speech::Outcome; };
319template <>
struct EndpointOutcome<sp::transcription::Request> {
using type = sp::transcription::Outcome; };
320template <>
struct EndpointOutcome<sp::videos::Request> {
using type = sp::jobs::Outcome; };
321template <>
struct EndpointOutcome<sp::jobs::Poll> {
using type = sp::jobs::Outcome; };
322template <>
struct EndpointOutcome<sp::jobs::Download> {
using type = sp::jobs::DownloadOutcome; };
323template <>
struct EndpointOutcome<sp::files::Upload> {
using type = sp::files::Outcome; };
324template <>
struct EndpointOutcome<sp::files::Get> {
using type = sp::files::Outcome; };
325template <>
struct EndpointOutcome<sp::files::List> {
using type = sp::files::PageOutcome; };
326template <>
struct EndpointOutcome<sp::files::Delete> {
using type = sp::files::DeleteOutcome; };
327template <>
struct EndpointOutcome<sp::files::Download> {
using type = sp::jobs::DownloadOutcome; };
328template <>
struct EndpointOutcome<sp::files::Start> {
using type = sp::files::ProgressOutcome; };
329template <>
struct EndpointOutcome<sp::files::Part> {
using type = sp::files::ProgressOutcome; };
330template <>
struct EndpointOutcome<sp::files::Complete> {
using type = sp::files::ProgressOutcome; };
331template <>
struct EndpointOutcome<sp::files::Query> {
using type = sp::files::ProgressOutcome; };
332template <>
struct EndpointOutcome<sp::files::Cancel> {
using type = sp::files::ProgressOutcome; };
333template <>
struct EndpointOutcome<sp::batches::Submit> {
using type = sp::batches::Outcome; };
334template <>
struct EndpointOutcome<sp::batches::Get> {
using type = sp::batches::Outcome; };
335template <>
struct EndpointOutcome<sp::batches::Cancel> {
using type = sp::batches::Outcome; };
336template <>
struct EndpointOutcome<sp::batches::Delete> {
using type = sp::batches::DeleteOutcome; };
337template <>
struct EndpointOutcome<sp::batches::Results> {
using type = sp::jobs::DownloadOutcome; };
338template <>
struct EndpointOutcome<sp::tokens::Count> {
using type = sp::tokens::Outcome; };
339template <>
struct EndpointOutcome<sp::generations::Get> {
using type = sp::generations::Outcome; };
340template <
class Request>
341concept EndpointRequest =
requires {
typename EndpointOutcome<Request>::type; };
342template <Endpo
intRequest Request>
343using EndpointOutcomeOf =
typename EndpointOutcome<Request>::type;
346template <Endpo
intRequest Request>
347EndpointPreparation prepare_endpoint_call(sp::runtime::Client& client, sp::runtime::RunOptions options,
348 const Request& request) {
349 return {client.prepare(request, std::move(options)).release(), std::nullopt, std::nullopt};
355NEOGRAPH_API EndpointPreparation prepare_endpoint_call(sp::runtime::Client& client, sp::runtime::RunOptions options,
356 const sp::files::Upload& request);
357NEOGRAPH_API EndpointPreparation prepare_endpoint_call(sp::runtime::Client& client, sp::runtime::RunOptions options,
358 const sp::files::Part& request);
359template <Endpo
intRequest Request>
360class TypedEndpointPayload final :
public EndpointPayload {
362 explicit TypedEndpointPayload(Request request) : request_(std::move(request)) {}
363 EndpointPreparation prepare(sp::runtime::Client& client, sp::runtime::RunOptions options)
const override {
364 return prepare_endpoint_call(client, std::move(options), request_);
366 bool opens_upload_session() const noexcept
override {
return std::is_same_v<Request, sp::files::Start>; }
372using EndpointStart = std::function<std::function<void()>(std::function<
void()> notify)>;
375NEOGRAPH_API asio::awaitable<void> await_endpoint(EndpointStart start);
379NEOGRAPH_API
bool on_blocking_bridge(
const asio::any_io_executor& executor)
noexcept;
381NEOGRAPH_API sp::runtime::Result blocking_bridge_refusal();
390 virtual std::string get_name()
const = 0;
391 virtual std::string_view family()
const noexcept = 0;
393 sp::runtime::Result dispatch(PreparedProviderRequest request);
394 asio::awaitable<sp::runtime::Result> dispatch_async(PreparedProviderRequest request);
396 asio::awaitable<sp::runtime::Result> invoke_async(
ProviderRequest request);
413 static std::optional<std::uint64_t> conservative_token_upper_bound(
const PreparedProviderRequest& request);
438 template <Endpo
intRequest Request>
440 Request request, sp::runtime::RunOptions options = {}) {
441 return require_endpoint_runtime()->prepare(std::move(request), std::move(options));
444 template <
class Outcome>
446 return require_endpoint_runtime()->complete(std::move(prepared));
448 template <Endpo
intRequest Request>
449 EndpointOutcomeOf<Request> complete_endpoint(Request request, sp::runtime::RunOptions options = {}) {
450 return require_endpoint_runtime()->complete(std::move(request), std::move(options));
455 template <
class Outcome>
457 return run_endpoint(require_endpoint_runtime(), std::move(prepared), host_pumped());
459 template <Endpo
intRequest Request>
460 asio::awaitable<EndpointOutcomeOf<Request>> complete_endpoint_async(
461 Request request, sp::runtime::RunOptions options = {}) {
462 auto client = require_endpoint_runtime();
463 try {
return run_endpoint(client, client->prepare(std::move(request), std::move(options)), host_pumped()); }
464 catch (
const sp::runtime::AdmissionError& error) {
return refused_endpoint<EndpointOutcomeOf<Request>>(error); }
475 using LocalDispatch = std::function<asio::awaitable<sp::runtime::Result>(
476 const PreparedProviderRequest&,
const std::function<
void(
const sp::Event&)>&)>;
477 static PreparedProviderRequest prepare_local(
478 std::shared_ptr<sp::runtime::Client> client,
ProviderRequest request, LocalDispatch dispatch);
482 std::shared_ptr<sp::runtime::Client> client,
ProviderRequest request,
bool host_pumped =
false);
483 static PreparedProviderRequest reject_preparation(sp::Error error);
484 static PreparedProviderRequest observe_prepared(
485 PreparedProviderRequest request,
486 std::function<
void(
const PreparedProviderRequest&)> before,
487 std::function<
void(sp::runtime::Result)> after,
488 std::function<
void(
const sp::Event&)> event = {},
489 std::function<void(std::exception_ptr)> on_error = {})
noexcept;
491 static asio::awaitable<sp::runtime::Result> dispatch_impl(PreparedProviderRequest request);
492 static asio::awaitable<sp::runtime::Result> dispatch_operation(PreparedProviderRequest request);
493 std::shared_ptr<sp::runtime::Client> require_endpoint_runtime()
const;
494 template <
class Outcome>
495 static asio::awaitable<Outcome> run_endpoint(
496 std::shared_ptr<sp::runtime::Client> client, sp::runtime::PreparedEndpoint<Outcome> prepared,
498 if (host_pumped && detail::on_blocking_bridge(
co_await asio::this_coro::executor))
499 co_return sp::runtime::detail::typed_outcome(*detail::blocking_bridge_refusal(),
500 static_cast<const Outcome*
>(
nullptr));
501 sp::runtime::EndpointCall<Outcome> call;
502 auto delivered = std::make_shared<std::optional<Outcome>>();
504 co_await detail::await_endpoint([&](std::function<
void()> notify) {
506 call = client->start(std::move(prepared),
507 [client, delivered, notify = std::move(notify)](Outcome outcome) {
508 *delivered = std::move(outcome);
511 return std::function<void()>([&call] { call.cancel(); });
513 }
catch (
const sp::runtime::AdmissionError& error) {
514 co_return sp::runtime::detail::typed_outcome(*error.outcome(),
static_cast<const Outcome*
>(
nullptr));
518 if (host_pumped && *delivered)
co_return std::move(**delivered);
519 co_return call.join();
521 template <
class Outcome>
522 static asio::awaitable<Outcome> refused_endpoint(sp::runtime::AdmissionError error) {
523 co_return sp::runtime::detail::typed_outcome(*error.outcome(),
static_cast<const Outcome*
>(
nullptr));
533template <Endpo
intRequest Request>
534ProviderRequest make_endpoint_request(Request request) {
535 ProviderRequest result;
536 result.
endpoint = std::make_shared<const detail::TypedEndpointPayload<Request>>(std::move(request));
540NEOGRAPH_API ProviderRequest make_provider_request(
541 const Provider& provider, std::string model, std::vector<sp::Message> messages,
542 std::vector<ChatTool> tools = {}, ProviderControls controls = {},
543 ProviderMode mode = ProviderMode::Collect);
544NEOGRAPH_API
void set_provider_request_messages(ProviderRequest& request, std::vector<sp::Message> messages);
545NEOGRAPH_API
void clear_provider_request_messages(ProviderRequest& request);
546NEOGRAPH_API
const std::vector<sp::Message>& provider_request_messages(
const ProviderRequest& request);
NEOGRAPH_API export/import macro for shared-library builds.
Post-dispatch host failure retaining the exact drained provider outcome.
Capability identity plus a single prepared, owned operation contract.
static PreparedProviderRequest prepare_runtime(std::shared_ptr< sp::runtime::Client > client, ProviderRequest request, bool host_pumped=false)
host_pumped is the client's executor (see host_pumped()): the prepared request then refuses to be dis...
virtual std::shared_ptr< sp::runtime::Client > endpoint_runtime() const
The SchemaProvider client whose endpoints this provider forwards, the same one for the provider's lif...
sp::runtime::PreparedEndpoint< EndpointOutcomeOf< Request > > prepare_endpoint(Request request, sp::runtime::RunOptions options={})
SchemaProvider's non-chat endpoints at this provider's origin (#531).
static std::string request_digest(const PreparedProviderRequest &request)
The dispatch digest of a prepared request: its content and everything the descriptor policy decided a...
static std::optional< std::uint64_t > invocation_upper_bound(const PreparedProviderRequest &request)
Most model invocations one dispatch of this request can bill: the request's model invocations times t...
static std::string request_content_digest(const PreparedProviderRequest &request)
What the request asks for, apart from the policy that admitted it: its family, model,...
static std::string request_policy_identity(const PreparedProviderRequest &request)
The identity of the SchemaProvider descriptor policy that admitted the request, "sha256:" and 64 hex ...
bool has_endpoints() const
Whether this provider forwards SchemaProvider endpoint calls.
virtual bool host_pumped() const noexcept
Whether the SDK client behind this provider runs on a host-pumped executor (a WebAssembly host's even...
static std::shared_ptr< sp::runtime::Client > endpoint_runtime_of(const Provider &inner)
For a provider that wraps another: forward the inner provider's endpoints by returning this from endp...
asio::awaitable< Outcome > complete_endpoint_async(sp::runtime::PreparedEndpoint< Outcome > prepared)
The same call without blocking a thread.
Outcome complete_endpoint(sp::runtime::PreparedEndpoint< Outcome > prepared)
Runs a prepared call on this provider's client and blocks for its outcome.
An endpoint request a ProviderRequest carries instead of a chat payload (#531): it prepares itself on...
virtual bool opens_upload_session() const noexcept
Whether the call opens a chunked upload's session (files::Start, #541).
Controls of one provider family, in that family's own SDK types.
The typed outcome of each SchemaProvider endpoint request that Provider::prepare_endpoint takes (#531...
Typed per-call controls translated into the selected SDK provider family.
std::optional< sp::chat::ReasoningOptions > chat_reasoning
Deprecated in 0.16.0 and removed in 0.17.0: use the family nests (chat.reasoning, responses....
Host-only delivery caps; explicit values may tighten admitted SDK resources.
Portable request controls remain the SDK's declared, family-specific fields.
std::shared_ptr< const detail::EndpointPayload > endpoint
An endpoint call instead of a chat call (#531, make_endpoint_request).
What an endpoint payload prepared.