Skip to main content

switchyard_protocol/
envelope.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! The request/response envelope: the normalized [`LlmRequest`]/[`LlmResponse`] paired
5//! with the original provider payload and correlation [`Metadata`].
6
7use crate::{LlmRequest, LlmResponse, Metadata};
8use std::collections::{HashMap, HashSet};
9
10/// Cross-cutting request context passed through algorithms and LLM clients.
11#[derive(Clone, Debug, Default, PartialEq, Eq)]
12pub struct Context {
13    /// Caller-specific values propagated through the request.
14    pub values: HashMap<String, String>,
15    /// Targets this request must not route to. A caller may set these up front to keep a
16    /// request off a given target; routing also adds any target that overflows its context
17    /// window mid-request.
18    excluded_targets: HashSet<String>,
19}
20
21impl Context {
22    /// Excludes a target from this request, returning whether it was newly excluded.
23    /// Re-excluding returns `false`, which is what bounds the overflow retry once every
24    /// target has been tried.
25    pub fn exclude_target(&mut self, target: impl Into<String>) -> bool {
26        self.excluded_targets.insert(target.into())
27    }
28
29    /// Whether this request is barred from routing to `target`.
30    pub fn is_excluded(&self, target: &str) -> bool {
31        self.excluded_targets.contains(target)
32    }
33}
34
35/// Agentic-stack events fed to an algorithm out of band (e.g. tool results, budget
36/// updates) — in libsy, via `Algorithm::process_signals`.
37///
38/// A placeholder today; a stateful algorithm can begin consuming signals as the enum
39/// grows without changing the orchestrator contract.
40#[derive(Clone)]
41pub struct Signals {}
42
43/// A request an algorithm routes: the normalized [`LlmRequest`] plus optional
44/// host-owned raw data and correlation [`Metadata`].
45#[derive(Clone, Default)]
46pub struct Request {
47    /// The normalized request an algorithm routes.
48    pub llm_request: LlmRequest,
49    /// An optional whole request body retained by the host. libsy and the translation
50    /// codecs do not read it. Exact same-format codec replay instead uses
51    /// [`LlmRequest::preservation`].
52    pub raw_request: Option<serde_json::Value>,
53    /// Correlation metadata carried through the request.
54    pub metadata: Option<Metadata>,
55}
56
57impl Request {
58    /// Returns the model name supplied by the inbound request, when present.
59    ///
60    /// This is not necessarily the target selected by a routing decision.
61    pub fn requested_model(&self) -> Option<&str> {
62        self.llm_request.model.as_deref()
63    }
64}
65
66/// A response an algorithm returns: the [`LlmResponse`] (streamed or aggregate) plus
67/// optional correlation [`Metadata`].
68///
69/// Not `Clone` — `llm_response` may own a live stream.
70pub struct Response {
71    /// The neutral model response — a chunk stream or the buffered aggregate.
72    pub llm_response: LlmResponse,
73    /// Correlation metadata carried through the response.
74    pub metadata: Option<Metadata>,
75}
76
77impl Response {
78    /// Returns the model recorded by a buffered response.
79    ///
80    /// Returns `None` for a live stream because inspecting its `MessageStart`
81    /// event would require consuming the stream.
82    pub fn selected_model(&self) -> Option<&str> {
83        self.llm_response.selected_model()
84    }
85}