systemprompt_wire/
upstream.rs1use serde_json::{Map, Value};
22
23use std::collections::BTreeSet;
24
25use super::anthropic::{AnthropicBeta, BetaPolicy};
26use super::{anthropic, gemini};
27use crate::{Hosting, WireProtocol};
28
29pub const VERTEX_ANTHROPIC_VERSION: &str = "vertex-2023-10-16";
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub struct UpstreamDialect {
36 pub wire: WireProtocol,
37 pub hosting: Hosting,
38}
39
40impl UpstreamDialect {
41 #[must_use]
42 pub const fn new(wire: WireProtocol, hosting: Hosting) -> Self {
43 Self { wire, hosting }
44 }
45
46 #[must_use]
47 pub fn of(wire: WireProtocol, endpoint: &str) -> Self {
48 Self::new(wire, Hosting::of(endpoint))
49 }
50
51 #[must_use]
52 pub fn path(self, upstream_model: &str, stream: bool) -> String {
53 match (self.wire, self.hosting) {
54 (WireProtocol::Anthropic, Hosting::FirstParty) => "/messages".to_owned(),
55 (WireProtocol::Anthropic, Hosting::Vertex) => {
56 let verb = if stream {
57 "streamRawPredict"
58 } else {
59 "rawPredict"
60 };
61 format!("/models/{upstream_model}:{verb}")
62 },
63 (WireProtocol::Gemini, _) => gemini::upstream_path(upstream_model, stream),
64 (WireProtocol::OpenAiChat, _) => "/chat/completions".to_owned(),
65 (WireProtocol::OpenAiResponses, _) => "/responses".to_owned(),
66 }
67 }
68
69 #[must_use]
70 pub fn url(self, endpoint: &str, upstream_model: &str, stream: bool) -> String {
71 format!(
72 "{}{}",
73 endpoint.trim_end_matches('/'),
74 self.path(upstream_model, stream)
75 )
76 }
77
78 #[must_use]
79 pub const fn api_key_header(self) -> Option<&'static str> {
80 match self.wire {
81 WireProtocol::Anthropic => Some("x-api-key"),
82 WireProtocol::Gemini => Some(gemini::API_KEY_HEADER),
83 WireProtocol::OpenAiChat | WireProtocol::OpenAiResponses => None,
84 }
85 }
86
87 #[must_use]
88 pub fn required_headers(self) -> Vec<(&'static str, &'static str)> {
89 match (self.wire, self.hosting) {
90 (WireProtocol::Anthropic, Hosting::FirstParty) => {
91 vec![("anthropic-version", anthropic::ANTHROPIC_VERSION)]
92 },
93 _ => Vec::new(),
94 }
95 }
96
97 #[must_use]
103 pub fn beta_policy(self, accepted: Option<&BTreeSet<AnthropicBeta>>) -> BetaPolicy {
104 match (accepted, self.hosting) {
105 (Some(accepted), _) => BetaPolicy::Only(accepted.clone()),
106 (None, Hosting::FirstParty) => BetaPolicy::ForwardAll,
107 (None, Hosting::Vertex) => BetaPolicy::Only(BTreeSet::new()),
108 }
109 }
110
111 #[must_use]
112 pub const fn drops_forwarded_header(self, name: &str) -> bool {
113 matches!(
114 (self.wire, self.hosting),
115 (WireProtocol::Anthropic, Hosting::Vertex)
116 ) && name.eq_ignore_ascii_case("anthropic-version")
117 }
118
119 pub fn finish_body(self, body: &mut Map<String, Value>) {
121 if (self.wire, self.hosting) == (WireProtocol::Anthropic, Hosting::Vertex) {
122 body.remove("model");
123 body.insert(
124 "anthropic_version".to_owned(),
125 Value::String(VERTEX_ANTHROPIC_VERSION.to_owned()),
126 );
127 }
128 }
129
130 pub fn finish_value(self, body: &mut Value) {
132 if let Some(obj) = body.as_object_mut() {
133 self.finish_body(obj);
134 }
135 }
136}