gproxy-protocol 4.1.14

GPROXY v4 connection modelling, operation declarations, vendor wire types and pairwise conversion
Documentation
use gproxy_protocol::{
    Dialect,
    transform::{
        count_tokens::request::*,
        generate::{
            claude_gemini::ClaudeGeminiRequestContext, claude_responses::ClaudeRequestContext,
            gemini_responses::GeminiReplayContext,
        },
        identity::*,
    },
    wire::{claude::count_tokens as c, gemini as g, openai::count_tokens as o},
};
use serde_json::json;
fn flow() -> IdentityFlow {
    IdentityFlow::new(IdNamespace::with_bytes([91; 16]))
}
fn policy(d: Dialect) -> TargetIdPolicy {
    TargetIdPolicy::new(d)
}
#[test]
fn all_six_count_requests_map_without_generation_token_budget() {
    let claude: c::CountTokensRequestBody = serde_json::from_value(
        json!({"model":"source","messages":[{"role":"user","content":"hello"}]}),
    )
    .unwrap();
    let cg = claude_to_gemini(
        claude.clone(),
        "gemini",
        ClaudeGeminiRequestContext::default(),
        &mut flow(),
        &policy(Dialect::Gemini),
    )
    .unwrap()
    .value;
    let embedded = cg.generate_content_request.unwrap();
    assert_eq!(embedded.model, "models/gemini");
    assert!(
        embedded
            .generation_config
            .as_ref()
            .and_then(|v| v.max_output_tokens)
            .is_none()
    );
    assert!(
        claude_to_openai(claude, "openai", &mut flow(), &policy(Dialect::OpenAi))
            .unwrap()
            .value
            .input
            .is_some()
    );
    let gemini: g::CountTokensRequestBody =
        serde_json::from_value(json!({"contents":[{"role":"model","parts":[{"text":"hello"}]}]}))
            .unwrap();
    let gc = gemini_to_claude(
        gemini.clone(),
        "claude",
        &mut flow(),
        &policy(Dialect::Claude),
    )
    .unwrap()
    .value;
    assert_eq!(
        gc.messages[0].role,
        gproxy_protocol::claude::content::Role::Assistant
    );
    assert!(
        serde_json::to_value(gc)
            .unwrap()
            .get("max_tokens")
            .is_none()
    );
    assert!(gemini_to_openai(gemini, "openai", &mut flow(), &policy(Dialect::OpenAi)).is_ok());
    let openai: o::CountTokensRequestBody =
        serde_json::from_value(json!({"input":"hello","instructions":"rules"})).unwrap();
    let oc = openai_to_claude(openai.clone(), "claude", ClaudeRequestContext::default())
        .unwrap()
        .value;
    assert!(oc.system.is_some());
    assert_eq!(oc.model, "claude");
    let og = openai_to_gemini(openai, "gemini", GeminiReplayContext::default())
        .unwrap()
        .value;
    assert!(
        og.generate_content_request
            .unwrap()
            .system_instruction
            .is_some()
    );
}
#[test]
fn claude_tools_images_schema_thinking_and_results_are_counted_as_native_fields() {
    let input:c::CountTokensRequestBody=serde_json::from_value(json!({"model":"source","system":"rules","thinking":{"type":"disabled"},"output_format":{"type":"json_schema","schema":{"type":"object","additionalProperties":false}},"tools":[{"name":"f","input_schema":{"type":"object","additionalProperties":false}}],"messages":[{"role":"user","content":[{"type":"image","source":{"type":"base64","media_type":"image/png","data":"AQI="}}]},{"role":"assistant","content":[{"type":"tool_use","id":"call","name":"f","input":{}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"call","content":"done"}]}],"unknown":{"not_counted":true}})).unwrap();
    let output = claude_to_openai(
        input.clone(),
        "openai",
        &mut flow(),
        &policy(Dialect::OpenAi),
    )
    .unwrap()
    .value;
    let wire = serde_json::to_value(&output).unwrap();
    assert_eq!(
        wire["tools"][0]["parameters"]["additionalProperties"],
        false
    );
    assert_eq!(wire["reasoning"]["effort"], "none");
    assert_eq!(wire["input"][0]["content"][0]["type"], "input_image");
    assert!(wire.get("unknown").is_none());
    assert_eq!(wire["input"][1]["call_id"], wire["input"][2]["call_id"]);
    let back = openai_to_claude(output, "claude", ClaudeRequestContext::default())
        .unwrap()
        .value;
    assert!(back.tools.is_some());
    assert!(back.output_format.is_some());
    assert!(back.thinking.is_some());
    let output = claude_to_gemini(
        input,
        "gemini",
        ClaudeGeminiRequestContext::default(),
        &mut flow(),
        &policy(Dialect::Gemini),
    )
    .unwrap()
    .value;
    let embedded = output.generate_content_request.unwrap();
    assert!(embedded.tools.is_some());
    assert_eq!(
        embedded
            .generation_config
            .unwrap()
            .thinking_config
            .unwrap()
            .thinking_budget,
        Some(0)
    );
}
#[test]
fn embedded_gemini_tools_system_schema_and_missing_call_ids_convert() {
    let input:g::CountTokensRequestBody=serde_json::from_value(json!({"generateContentRequest":{"model":"models/source","systemInstruction":{"parts":[{"text":"rules"}]},"contents":[{"role":"model","parts":[{"functionCall":{"name":"f","args":{}}}]},{"role":"user","parts":[{"functionResponse":{"name":"f","response":{"result":1}}}]}],"tools":[{"functionDeclarations":[{"name":"f","description":"function","parametersJsonSchema":{"type":"object","additionalProperties":false}}]}],"generationConfig":{"responseJsonSchema":{"type":"object"},"thinkingConfig":{"thinkingLevel":"LOW"}}}})).unwrap();
    let output = gemini_to_openai(
        input.clone(),
        "openai",
        &mut flow(),
        &policy(Dialect::OpenAi),
    )
    .unwrap()
    .value;
    let wire = serde_json::to_value(&output).unwrap();
    assert_eq!(wire["instructions"], "rules");
    assert_eq!(wire["reasoning"]["effort"], "low");
    assert_eq!(wire["input"][0]["call_id"], wire["input"][1]["call_id"]);
    assert!(wire["tools"].is_array());
    let output = gemini_to_claude(input, "claude", &mut flow(), &policy(Dialect::Claude))
        .unwrap()
        .value;
    assert!(output.system.is_some());
    assert!(output.tools.is_some());
    assert!(output.output_format.is_some());
}
#[test]
fn a_gemini_system_part_without_text_is_refused_for_claude() {
    for part in [
        json!({}),
        json!({"inlineData":{"mimeType":"image/png","data":"AQI="}}),
    ] {
        let input: g::CountTokensRequestBody = serde_json::from_value(json!({
            "generateContentRequest": {
                "model": "models/a",
                "systemInstruction": {"parts": [part]},
                "contents": [{"role": "user", "parts": [{"text": "hi"}]}]
            }
        }))
        .unwrap();
        assert!(gemini_to_claude(input, "c", &mut flow(), &policy(Dialect::Claude)).is_err());
    }
}

#[test]
fn count_conflicts_context_requirements_and_late_failures_are_explicit() {
    let input: g::CountTokensRequestBody = serde_json::from_value(
        json!({"contents":[],"generateContentRequest":{"model":"models/a","contents":[]}}),
    )
    .unwrap();
    assert!(gemini_to_openai(input, "openai", &mut flow(), &policy(Dialect::OpenAi)).is_err());
    let input: o::CountTokensRequestBody =
        serde_json::from_value(json!({"input":"x","personality":"p"})).unwrap();
    assert!(openai_to_gemini(input, "g", GeminiReplayContext::default()).is_err());
    let input:c::CountTokensRequestBody=serde_json::from_value(json!({"model":"c","messages":[{"role":"assistant","content":[{"type":"tool_use","id":"id","name":"f","input":{}},{"type":"redacted_thinking","data":"native"}]}]})).unwrap();
    let mut ids = flow();
    assert!(claude_to_openai(input, "o", &mut ids, &policy(Dialect::OpenAi)).is_err());
    assert!(
        ids.lookup_source(
            &SourceIdentity::new(Dialect::Claude, Some("id".into()), 0),
            IdentityRole::ToolCall
        )
        .is_none()
    );
}

#[test]
fn image_count_controls_match_generation_without_fake_output_budget() {
    use gproxy_protocol::transform::generate::gemini_responses as pair;
    let config = json!({"responseModalities":["IMAGE"],"responseFormat":{"image":{"mimeType":"IMAGE_JPEG","delivery":"INLINE"},"rest_sentinel":"DROP"}});
    let contents = json!([{"role":"user","parts":[{"text":"draw","rest_sentinel":"DROP"}]}]);
    let count:g::CountTokensRequestBody=serde_json::from_value(json!({"generateContentRequest":{"model":"models/source","contents":contents,"generationConfig":config},"rest_sentinel":"DROP"})).unwrap();
    let generation: g::GenerateContentRequestBody =
        serde_json::from_value(json!({"contents":contents,"generationConfig":config})).unwrap();
    let expected = pair::gemini_to_responses_request(
        generation,
        "selected",
        &mut flow(),
        &policy(Dialect::OpenAi),
    )
    .unwrap()
    .value;
    let actual = gemini_to_openai(count, "selected", &mut flow(), &policy(Dialect::OpenAi))
        .unwrap()
        .value;
    assert_eq!(actual.tools.clone().flatten(), expected.tools);
    assert_eq!(actual.tool_choice.clone().flatten(), expected.tool_choice);
    let wire = serde_json::to_value(&actual).unwrap();
    assert!(!wire.to_string().contains("sentinel"));
    assert!(wire.get("max_tokens").is_none());
    assert!(wire.get("max_output_tokens").is_none());
    let back = openai_to_gemini(actual, "selected", Default::default())
        .unwrap()
        .value
        .generate_content_request
        .unwrap();
    let generation_back =
        pair::responses_to_gemini_request(expected, "selected", Default::default())
            .unwrap()
            .value;
    assert_eq!(back.generation_config, generation_back.generation_config);
    assert_eq!(back.tools, generation_back.tools);
    assert_eq!(back.tool_config, generation_back.tool_config);
    assert!(back.generation_config.unwrap().max_output_tokens.is_none());
}
#[test]
fn count_image_constraints_and_tool_exclusion_use_the_same_native_rules() {
    let gcount = |config| {
        serde_json::from_value::<g::CountTokensRequestBody>(json!({"generateContentRequest":{"model":"models/source","contents":[{"parts":[{"text":"draw"}]}],"generationConfig":config}})).unwrap()
    };
    for config in [
        json!({"responseModalities":["TEXT","IMAGE"],"imageConfig":{"imageSize":"1K"}}),
        json!({"responseModalities":["AUDIO"]}),
    ] {
        assert!(
            gemini_to_openai(
                gcount(config),
                "selected",
                &mut flow(),
                &policy(Dialect::OpenAi)
            )
            .is_ok()
        );
    }
    let disabled:o::CountTokensRequestBody=serde_json::from_value(json!({"input":"text","tools":[{"type":"image_generation","size":"999x999"}],"tool_choice":"none"})).unwrap();
    let actual = openai_to_gemini(disabled, "selected", Default::default())
        .unwrap()
        .value
        .generate_content_request
        .unwrap();
    assert_eq!(
        actual.generation_config.unwrap().response_modalities,
        Some(vec![g::Modality::Text])
    );
    let required:o::CountTokensRequestBody=serde_json::from_value(json!({"input":"task","tools":[{"type":"image_generation"},{"type":"function","name":"f","parameters":{},"strict":false}],"tool_choice":"required"})).unwrap();
    assert!(openai_to_gemini(required, "selected", Default::default()).is_ok());
}

#[test]
fn system_control_only_updates_are_applied_before_counting() {
    let input: c::CountTokensRequestBody = serde_json::from_value(json!({
        "model":"source", "messages":[{"role":"user", "content":"hello"},
            {"role":"system", "output_config":{"effort":"high"}}]
    }))
    .unwrap();
    let openai =
        claude_to_openai(input.clone(), "gpt", &mut flow(), &policy(Dialect::OpenAi)).unwrap();
    let gemini = claude_to_gemini(
        input,
        "gemini",
        Default::default(),
        &mut flow(),
        &policy(Dialect::Gemini),
    )
    .unwrap();
    let openai = serde_json::to_value(openai.value).unwrap();
    let gemini = serde_json::to_value(gemini.value).unwrap();
    assert_eq!(openai["input"].as_array().unwrap().len(), 1);
    assert_eq!(openai["reasoning"]["effort"], "high");
    assert_eq!(
        gemini["generateContentRequest"]["contents"]
            .as_array()
            .unwrap()
            .len(),
        1
    );
    assert_eq!(
        gemini["generateContentRequest"]["generationConfig"]["thinkingConfig"]["thinkingLevel"],
        "HIGH"
    );
}