gproxy-protocol 3.0.0-alpha.0

GPROXY v3 protocol model: operation taxonomy, wire kinds, and the OperationSpec registry
Documentation
use http::Method;

use crate::operation::{
    ContentGenerationKind, Operation, OperationGroup, OperationKey, OperationKind, WireFamily,
};
use crate::spec::{Affinity, Seg, SettleMode, streaming_sibling};
use crate::specs::REGISTRY;
use crate::{match_ingress, match_ingress_for, request_target};

#[test]
fn ingress_registry_matches_canonical_paths() {
    let mut ids = std::collections::BTreeSet::new();
    for (operation, spec) in REGISTRY.iter() {
        assert!(std::ptr::eq(operation.spec(), spec));
        assert!(ids.insert(operation.id()), "duplicate operation id");

        for ingress in spec.ingress {
            let (path, expected_params) = example_path(ingress.pattern.0);
            let preferred = match ingress.kind {
                OperationKind::Family(family) => Some(family),
                OperationKind::ContentGeneration(_) => None,
            };
            let matched = match_ingress_for(ingress.method, &path, preferred)
                .unwrap_or_else(|| panic!("registry path did not match: {path}"));

            assert_eq!(matched.operation, *operation, "path: {path}");
            assert_eq!(matched.kind, ingress.kind, "path: {path}");
            assert_eq!(matched.stream, ingress.stream, "path: {path}");
            assert_eq!(matched.framing, ingress.framing, "path: {path}");
            assert_eq!(matched.upgrade, ingress.upgrade, "path: {path}");
            assert_eq!(matched.params, expected_params, "path: {path}");
        }
    }

    for (method, path) in [
        (&Method::PATCH, "/v1/models"),
        (&Method::GET, "v1/models"),
        (&Method::GET, "/v1/models/"),
        (&Method::GET, "/v1//models"),
        (&Method::GET, "/v1/models/id/extra"),
        (&Method::GET, "/v1/videos/id/extra"),
        (&Method::GET, "/v1/videos/id/remix"),
        (&Method::POST, "/v1/videos/id/content"),
        (&Method::DELETE, "/v1/videos/characters/id"),
        (&Method::POST, "/v1beta/models/:generateContent"),
        (&Method::POST, "/v1beta/models/model:wrongAction"),
    ] {
        assert!(
            match_ingress(method, path).is_none(),
            "invalid path matched: {path}"
        );
    }

    let promotions: Vec<_> = REGISTRY
        .iter()
        .filter_map(|(operation, _)| streaming_sibling(*operation).map(|next| (*operation, next)))
        .collect();
    assert_eq!(
        promotions,
        vec![(Operation::GenerateContent, Operation::StreamGenerateContent)]
    );
    assert_eq!(streaming_sibling(Operation::CreateImage), None);
    assert_eq!(streaming_sibling(Operation::EditImage), None);
    assert_eq!(
        Operation::CreateRealtimeCall.spec().settle,
        SettleMode::OnSessionEnd
    );
    assert_eq!(
        request_target(
            OperationKey::family(Operation::CreateEmbedding, WireFamily::Gemini),
            "gemini-embedding-001",
        ),
        Some((
            Method::POST,
            "/v1beta/models/gemini-embedding-001:embedContent".into(),
        ))
    );
    assert_eq!(
        request_target(
            OperationKey::family(Operation::BatchCreateEmbedding, WireFamily::Gemini),
            "gemini-embedding-001",
        ),
        Some((
            Method::POST,
            "/v1beta/models/gemini-embedding-001:batchEmbedContents".into(),
        ))
    );
}

#[test]
fn video_specs_keep_job_settlement_and_resource_affinity() {
    use Operation::*;

    for operation in [
        CreateVideo,
        RetrieveVideo,
        ListVideos,
        DeleteVideo,
        DownloadVideoContent,
        RemixVideo,
        CreateVideoCharacter,
        GetVideoCharacter,
        EditVideo,
        ExtendVideo,
    ] {
        assert_eq!(operation.group(), OperationGroup::Video);
    }

    assert_eq!(RetrieveVideo.spec().settle, SettleMode::OnCompletedStatus);
    assert_eq!(RetrieveVideo.spec().affinity, Affinity::Resource("video"));
    assert_eq!(ListVideos.spec().settle, SettleMode::Free);
    assert_eq!(ListVideos.spec().affinity, Affinity::Resource("video"));
    for operation in [
        CreateVideo,
        ListVideos,
        DeleteVideo,
        DownloadVideoContent,
        RemixVideo,
        EditVideo,
        ExtendVideo,
    ] {
        assert_eq!(operation.spec().settle, SettleMode::Free);
        assert_eq!(operation.spec().affinity, Affinity::Resource("video"));
    }
    for operation in [CreateVideoCharacter, GetVideoCharacter] {
        assert_eq!(operation.spec().settle, SettleMode::Free);
        assert_eq!(
            operation.spec().affinity,
            Affinity::Resource("video_character")
        );
    }
}

#[test]
fn memory_summary_is_a_billed_openai_family_operation() {
    let matched = match_ingress(&Method::POST, "/v1/memories/trace_summarize")
        .expect("memory summary ingress");
    assert_eq!(matched.operation, Operation::SummarizeMemory);
    assert_eq!(matched.kind, OperationKind::Family(WireFamily::OpenAi));
    assert_eq!(matched.stream, crate::StreamDetect::Never);
    assert_eq!(matched.operation.group(), OperationGroup::Memories);
    assert_eq!(matched.operation.spec().settle, SettleMode::OnResponse);
    assert_eq!(matched.operation.spec().affinity, Affinity::None);
}

#[test]
fn compact_keeps_session_affinity() {
    assert_eq!(Operation::CompactContent.spec().affinity, Affinity::Session);
}

#[test]
fn conversation_metadata_is_free_and_classified() {
    let matched = match_ingress(&Method::POST, "/v1/conversations").expect("conversation path");
    assert_eq!(matched.operation, Operation::CreateConversation);
    assert_eq!(
        Operation::CreateConversation.group(),
        OperationGroup::Conversation
    );
    assert_eq!(
        Operation::CreateConversation.spec().settle,
        SettleMode::Free
    );
    assert_eq!(
        Operation::CreateConversation.spec().affinity,
        Affinity::None
    );
}

#[test]
fn shared_model_paths_honor_the_wire_family_preference() {
    let default = match_ingress(&Method::GET, "/v1/models").expect("default model list");
    assert_eq!(default.kind, OperationKind::Family(WireFamily::OpenAi));
    let claude = match_ingress_for(&Method::GET, "/v1/models/model-1", Some(WireFamily::Claude))
        .expect("Claude model get");
    assert_eq!(claude.operation, Operation::GetModel);
    assert_eq!(claude.kind, OperationKind::Family(WireFamily::Claude));

    assert_eq!(
        request_target(
            OperationKey::content(
                Operation::StreamGenerateContent,
                ContentGenerationKind::ClaudeMessages,
            ),
            "claude-upstream",
        ),
        Some((Method::POST, "/v1/messages".into()))
    );
    assert_eq!(
        request_target(
            OperationKey::family(Operation::GetModel, WireFamily::Claude),
            "model one",
        ),
        Some((Method::GET, "/v1/models/model%20one".into()))
    );
}

fn example_path(pattern: &[Seg]) -> (String, Vec<(&'static str, String)>) {
    let mut segments = Vec::with_capacity(pattern.len());
    let mut params = Vec::new();

    for segment in pattern {
        match segment {
            Seg::Lit(value) => segments.push((*value).to_owned()),
            Seg::Param(name) => {
                let value = format!("{name}-value");
                segments.push(value.clone());
                params.push((*name, value));
            }
            Seg::ParamAction(name, action) => {
                let value = format!("{name}-value");
                segments.push(format!("{value}:{action}"));
                params.push((*name, value));
            }
            Seg::Rest(name) => {
                let value = format!("{name}-first/{name}-second");
                segments.push(value.clone());
                params.push((*name, value));
            }
        }
    }

    (format!("/{}", segments.join("/")), params)
}