gproxy_protocol/transform/count_tokens/request/
mod.rs1mod claude_gemini;
5mod claude_openai;
6mod controls;
7mod gemini_openai;
8use crate::{
9 transform::{TransformError, identity::TargetIdPolicy},
10 wire::{gemini as g, openai::count_tokens as o},
11};
12pub use claude_gemini::{claude_to_gemini, gemini_to_claude};
13pub use claude_openai::{claude_to_openai, openai_to_claude};
14pub use gemini_openai::{gemini_to_openai, openai_to_gemini};
15
16fn model(value: impl Into<String>) -> Result<String, TransformError> {
17 let value = value.into();
18 if value.trim().is_empty() {
19 Err(TransformError::missing_metadata("target_model"))
20 } else {
21 Ok(value)
22 }
23}
24
25fn policy(policy: &TargetIdPolicy, dialect: crate::Dialect) -> Result<(), TransformError> {
26 if policy.dialect != dialect {
27 Err(TransformError::shape(
28 "identity.policy",
29 "policy dialect differs from count target",
30 ))
31 } else {
32 Ok(())
33 }
34}
35
36fn openai_state(input: &o::CountTokensRequestBody) -> Result<(), TransformError> {
37 if input
38 .conversation
39 .as_ref()
40 .and_then(Option::as_ref)
41 .is_some()
42 || input
43 .previous_response_id
44 .as_ref()
45 .and_then(Option::as_ref)
46 .is_some()
47 {
48 return Err(TransformError::missing_metadata(
49 "count input requires resolved conversation/previous response history",
50 ));
51 }
52 if input.personality.is_some() || input.truncation == Some(o::Truncation::Auto) {
53 return Err(TransformError::unsupported(
54 "count controls",
55 "personality/automatic truncation requires target-specific host preprocessing",
56 ));
57 }
58 Ok(())
59}
60
61fn gemini_input(
62 input: g::CountTokensRequestBody,
63) -> Result<g::EmbeddedGenerateContentRequest, TransformError> {
64 if input.contents.is_some() && input.generate_content_request.is_some() {
65 return Err(TransformError::shape(
66 "count input",
67 "contents and generateContentRequest are mutually exclusive",
68 ));
69 }
70 if let Some(embedded) = input.generate_content_request {
71 if embedded.model.trim().is_empty() {
72 return Err(TransformError::shape(
73 "generate_content_request.model",
74 "embedded model required",
75 ));
76 }
77 Ok(embedded)
78 } else {
79 let contents = input.contents.ok_or_else(|| {
80 TransformError::missing_metadata("contents or generate_content_request")
81 })?;
82 Ok(g::EmbeddedGenerateContentRequest::builder(String::new(), contents).build())
83 }
84}
85
86fn gemini_target(
87 model: String,
88 contents: Vec<g::Content>,
89 system: Option<g::Content>,
90 tools: Option<Vec<g::Tool>>,
91 tool_config: Option<g::ToolConfig>,
92 config: Option<g::GenerationConfig>,
93) -> g::CountTokensRequestBody {
94 let resource = if model.starts_with("models/") {
95 model
96 } else {
97 format!("models/{model}")
98 };
99 let mut body = g::EmbeddedGenerateContentRequest::builder(resource, contents).build();
100 body.system_instruction = system;
101 body.tools = tools;
102 body.tool_config = tool_config;
103 body.generation_config = config;
104 g::CountTokensRequestBody::builder()
105 .generate_content_request(body)
106 .build()
107}