gproxy-protocol 3.0.0-alpha.0

GPROXY v3 protocol model: operation taxonomy, wire kinds, and the OperationSpec registry
Documentation
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::{Value, json};

use super::*;

#[test]
fn generate_and_stream_shapes_roundtrip_unknown_parts_and_fields() {
    roundtrip::<GenerateContentRequest>(json!({
        "model":"models/gemini-future",
        "contents":[{
            "role":"user",
            "parts":[
                {"text":"hello","futurePartMetadata":{"x":1}},
                {"futurePayload":{"nested":true}}
            ],
            "futureContent":1
        }],
        "generationConfig":{"temperature":0.2,"futureSampling":"adaptive"},
        "futureRequest":true
    }));

    roundtrip::<StreamGenerateContentChunk>(json!({
        "candidates":[{
            "content":{"role":"model","parts":[{"text":"answer"}]},
            "finishReason":"FUTURE_REASON","futureCandidate":{"x":1}
        }],
        "usageMetadata":{"promptTokenCount":2,"candidatesTokenCount":1,"futureUsage":9},
        "responseId":"response_1","futureResponse":"kept"
    }));
    roundtrip::<Content>(json!({
        "role":"user",
        "parts":[{
            "inlineData":{"mimeType":"image/png","data":"AA=="},
            "videoMetadata":{"startOffset":"0s","futureVideo":1},
            "futureInline":{"quality":"original"}
        }]
    }));

    let metadata = GroundingMetadata {
        grounding_chunks: vec![GroundingChunk {
            source: Some(GroundingChunkSource::Web {
                web: WebChunk::default(),
                rest: Default::default(),
            }),
            rest: Default::default(),
        }],
        grounding_supports: vec![GroundingSupport {
            segment: Some(Segment::default()),
            ..Default::default()
        }],
        retrieval_metadata: Some(RetrievalMetadata::default()),
        ..Default::default()
    };
    let wire = serde_json::to_value(metadata).expect("encode grounding metadata");
    assert_eq!(wire["groundingChunks"][0]["web"], json!({}));
    assert_eq!(wire["groundingSupports"][0]["segment"], json!({}));
    assert_eq!(wire["retrievalMetadata"], json!({}));
    assert_eq!(
        serde_json::to_value(UrlMetadata::default()).expect("encode URL metadata"),
        json!({})
    );
}

#[test]
fn ordinary_part_does_not_match_the_flattened_metadata_union() {
    let part: Part = serde_json::from_value(json!({
        "text":"hello","futurePartMetadata":{"x":1}
    }))
    .unwrap();
    assert!(part.metadata.is_none());
    assert_eq!(part.rest["futurePartMetadata"]["x"], 1);

    let video: Part = serde_json::from_value(json!({
        "fileData":{"fileUri":"gs://bucket/video.mp4"},
        "videoMetadata":{"startOffset":"0s"}
    }))
    .unwrap();
    assert!(matches!(
        video.metadata,
        Some(PartMetadata::VideoMetadata { .. })
    ));
}

#[test]
fn models_and_count_tokens_keep_unknown_resource_data() {
    roundtrip::<Model>(json!({
        "name":"models/gemini-future","baseModelId":"gemini-future",
        "version":"1","displayName":"Gemini Future","description":"next",
        "inputTokenLimit":1000000,"outputTokenLimit":65536,
        "supportedGenerationMethods":["generateContent","futureMethod"],
        "futureModel":{"tier":"preview"}
    }));
    roundtrip::<CountTokensRequest>(json!({
        "model":"models/gemini-future",
        "contents":[{"role":"user","parts":[{"text":"count me"}]}],
        "futureCountOption":true
    }));
    roundtrip::<CountTokensResponse>(json!({
        "totalTokens":3,
        "promptTokensDetails":[{"modality":"TEXT","tokenCount":3,"futureDetail":1}],
        "futureCount":"kept"
    }));
}

#[test]
fn remaining_family_models_preserve_documented_and_future_fields() {
    roundtrip::<CachedContent>(json!({
        "name":"cachedContents/cache-1",
        "model":"models/gemini-test",
        "contents":[{"parts":[{"text":"stable context"}]}],
        "ttl":"3600s",
        "futureCacheField":true
    }));
    roundtrip::<BatchGenerateContentRequest>(json!({
        "batch":{
            "model":"models/gemini-test",
            "displayName":"nightly",
            "inputConfig":{"requests":{"requests":[{
                "request":{"contents":[{"parts":[{"text":"hello"}]}]},
                "metadata":{"request":"one"}
            }]}}
        },
        "futureBatchField":1
    }));
    roundtrip::<BatchEmbedContentsRequest>(json!({
        "requests":[{
            "model":"models/gemini-embedding-001",
            "content":{"parts":[{"text":"embed me"}]},
            "embedContentConfig":{
                "taskType":"RETRIEVAL_DOCUMENT",
                "outputDimensionality":256,
                "futureEmbeddingOption":true
            }
        }],
        "futureBatchOption":1
    }));
    roundtrip::<ImagenPredictResponse>(json!({
        "predictions":[{
            "bytesBase64Encoded":"AA==",
            "mimeType":"image/png",
            "futureImageField":"kept"
        }]
    }));
    roundtrip::<VeoOperation>(json!({
        "name":"models/veo/operations/op-1",
        "response":{"generateVideoResponse":{"generatedSamples":[{
            "video":{"uri":"https://example.invalid/v1beta/files/file-1:download"}
        }]}},
        "futureOperation":{"progress":100}
    }));
    roundtrip::<ListFilesResponse>(json!({
        "files":[{
            "name":"files/file-1",
            "mimeType":"video/mp4",
            "state":"ACTIVE",
            "futureFileField":{"x":1}
        }],
        "nextPageToken":"next"
    }));
    roundtrip::<GenerateContentRequest>(json!({
        "contents":[{"role":"user","parts":[{"text":"say hello"}]}],
        "generationConfig":{
            "responseModalities":["AUDIO"],
            "speechConfig":{
                "multiSpeakerVoiceConfig":{"speakerVoiceConfigs":[{
                    "speaker":"Alex",
                    "voiceConfig":{"prebuiltVoiceConfig":{"voiceName":"Kore"}}
                }]},
                "languageCode":"en-US"
            },
            "responseFormat":{"audio":{
                "mimeType":"AUDIO_WAV",
                "delivery":"INLINE",
                "sampleRate":24000
            }}
        }
    }));
}

fn roundtrip<T>(wire: Value)
where
    T: DeserializeOwned + Serialize,
{
    let decoded: T = serde_json::from_value(wire.clone()).expect("decode wire");
    assert_eq!(serde_json::to_value(decoded).expect("encode wire"), wire);
}