mobius 0.9.7

A small, modular Rust framework for building coding agents
Documentation
use super::super::*;
use super::support::model_request;
use crate::backend::model::STREAM_RETRY_LIMIT;

#[test]
fn implicit_prompt_cache_omits_options() {
    let provider = OpenAiSocket::with_authorization(
        Arc::new(ApiKeyAuthorization::new("test-key".into())),
        "https://example.com/v1",
        "wss://example.com/v1/responses",
        "test-model",
        reqwest::Client::new(),
    )
    .expect("provider");
    let body = response_body(
        "test-model",
        &model_request(),
        &[],
        None,
        None,
        &[],
        provider.explicit_prompt_cache,
    )
    .expect("response body");

    assert_eq!(
        (
            provider.prompt_cache_capability(),
            body.get("prompt_cache_options")
        ),
        (PromptCacheCapability::Implicit, None)
    );
}

#[test]
fn generic_processing_error_is_a_retryable_stream_failure() {
    let request_id = "922d2b28-14a7-4b76-be1e-ae6be18309b9";
    let event = serde_json::json!({
        "type": "response.failed",
        "response": {
            "error": {
                "message": format!(
                    "An error occurred while processing your request. You can retry your request. Please include the request ID {request_id} in your message."
                )
            }
        }
    });

    let Exchange::Retry { retry_after } =
        failed_exchange(&event, false).expect("retryable failure")
    else {
        panic!("expected retryable exchange");
    };
    assert_eq!(retry_after, None);
    assert!(matches!(
        failed_exchange(&event, true).expect("streamed failure"),
        Exchange::Retry { retry_after: None }
    ));
}

#[test]
fn stream_failures_do_not_enable_http_fallback() {
    let mut state = SocketState {
        connection: None,
        continuation: None,
        use_http: false,
        last_used_at: Instant::now(),
    };

    for _ in 0..STREAM_RETRY_LIMIT {
        let Error::Provider(error) = websocket_failure(&mut state, None) else {
            panic!("expected provider error");
        };
        assert!(error.is_stream_interrupted());
    }

    assert!(!state.use_http);
}

#[test]
fn continuation_sends_only_new_items_and_resets_on_rewrite() {
    let known = vec![
        serde_json::json!({"role": "user", "content": "one"}),
        serde_json::json!({"role": "assistant", "content": "two"}),
    ];
    let mut state = SocketState {
        connection: None,
        continuation: Some(Continuation {
            response_id: "resp-1".into(),
            known_items: known.len(),
            fingerprint: fingerprint(known.iter()).expect("fingerprint"),
            envelope_fingerprint: envelope_fingerprint("test-model", &model_request(), None, &[])
                .expect("envelope fingerprint"),
        }),
        use_http: false,
        last_used_at: Instant::now(),
    };
    let mut continued = known.clone();
    continued.push(serde_json::json!({"type": "function_call_output"}));
    let envelope = envelope_fingerprint("test-model", &model_request(), None, &[])
        .expect("envelope fingerprint");
    let (response, input) = continuation_input(&mut state, &continued, envelope).expect("continue");
    assert_eq!(response.as_deref(), Some("resp-1"));
    assert_eq!(
        input,
        &[serde_json::json!({"type": "function_call_output"})]
    );

    let changed_envelope = envelope_fingerprint(
        "test-model",
        &ModelRequest {
            instructions: "Changed instructions",
            ..model_request()
        },
        None,
        &[],
    )
    .expect("changed envelope fingerprint");
    let (response, input) =
        continuation_input(&mut state, &continued, changed_envelope).expect("envelope reset");
    assert_eq!(response, None);
    assert_eq!(input, continued);
    assert!(state.continuation.is_none());

    let rewritten = vec![serde_json::json!({"type": "compaction"})];
    let (response, input) = continuation_input(&mut state, &rewritten, envelope).expect("reset");
    assert_eq!(response, None);
    assert_eq!(input, rewritten);
    assert!(state.continuation.is_none());

    state.continuation = Some(Continuation {
        response_id: "resp-2".into(),
        known_items: known.len(),
        fingerprint: fingerprint(known.iter()).expect("fingerprint"),
        envelope_fingerprint: envelope,
    });
    let (response, input) =
        response_input(&mut state, &known, false, envelope).expect("stateless request");
    assert_eq!(response, None);
    assert_eq!(input, known);
    assert!(state.continuation.is_none());
}