prodex 0.54.0

OpenAI profile pooling and safe auto-rotate for Codex CLI and Claude Code
Documentation
use super::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RuntimeAnthropicNativeClientToolKind {
    Shell,
    Computer,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct RuntimeAnthropicNativeClientToolCall {
    pub(crate) kind: RuntimeAnthropicNativeClientToolKind,
    pub(crate) max_output_length: Option<u64>,
}

pub(crate) fn runtime_proxy_translate_anthropic_shell_tool_call(
    block: &serde_json::Value,
) -> Option<(
    String,
    serde_json::Value,
    RuntimeAnthropicNativeClientToolCall,
)> {
    let name = block
        .get("name")
        .and_then(serde_json::Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())?;
    if runtime_proxy_anthropic_client_tool_name(name) != Some("bash") {
        return None;
    }
    let call_id = block
        .get("id")
        .and_then(serde_json::Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())?
        .to_string();
    let input = block.get("input")?.as_object()?;
    if input
        .get("restart")
        .and_then(serde_json::Value::as_bool)
        .unwrap_or(false)
    {
        return None;
    }
    let command = input
        .get("command")
        .and_then(serde_json::Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())?;
    let mut action = serde_json::Map::new();
    action.insert(
        "commands".to_string(),
        serde_json::json!([command.to_string()]),
    );
    if let Some(timeout) = input.get("timeout_ms").and_then(serde_json::Value::as_u64) {
        action.insert(
            "timeout_ms".to_string(),
            serde_json::Value::Number(timeout.into()),
        );
    }
    if let Some(max_output_length) = input
        .get("max_output_length")
        .and_then(serde_json::Value::as_u64)
    {
        action.insert(
            "max_output_length".to_string(),
            serde_json::Value::Number(max_output_length.into()),
        );
    }
    Some((
        call_id.clone(),
        serde_json::json!({
            "type": "shell_call",
            "call_id": call_id,
            "action": serde_json::Value::Object(action),
            "status": "completed",
        }),
        RuntimeAnthropicNativeClientToolCall {
            kind: RuntimeAnthropicNativeClientToolKind::Shell,
            max_output_length: input
                .get("max_output_length")
                .and_then(serde_json::Value::as_u64),
        },
    ))
}

pub(crate) fn runtime_proxy_anthropic_coordinate_component(
    value: &serde_json::Value,
) -> Option<i64> {
    value.as_i64().or_else(|| {
        value
            .as_u64()
            .and_then(|component| i64::try_from(component).ok())
    })
}

pub(crate) fn runtime_proxy_anthropic_coordinate_pair(
    value: Option<&serde_json::Value>,
) -> Option<(i64, i64)> {
    let coordinates = value?.as_array()?;
    if coordinates.len() < 2 {
        return None;
    }
    let x = runtime_proxy_anthropic_coordinate_component(coordinates.first()?)?;
    let y = runtime_proxy_anthropic_coordinate_component(coordinates.get(1)?)?;
    Some((x, y))
}

pub(crate) fn runtime_proxy_anthropic_computer_keypress_keys(
    key_combo: &str,
) -> Option<Vec<String>> {
    let keys = key_combo
        .split('+')
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(|value| value.to_ascii_uppercase())
        .collect::<Vec<_>>();
    (!keys.is_empty()).then_some(keys)
}

pub(crate) fn runtime_proxy_translate_anthropic_computer_action(
    input: &serde_json::Map<String, serde_json::Value>,
) -> Option<serde_json::Value> {
    let action = input
        .get("action")
        .and_then(serde_json::Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())?;
    match action {
        "screenshot" => Some(serde_json::json!({ "type": "screenshot" })),
        "left_click" | "right_click" | "middle_click" => {
            let (x, y) = runtime_proxy_anthropic_coordinate_pair(input.get("coordinate"))?;
            let button = match action {
                "left_click" => "left",
                "right_click" => "right",
                "middle_click" => "middle",
                _ => unreachable!(),
            };
            Some(serde_json::json!({
                "type": "click",
                "button": button,
                "x": x,
                "y": y,
            }))
        }
        "double_click" => {
            let (x, y) = runtime_proxy_anthropic_coordinate_pair(input.get("coordinate"))?;
            Some(serde_json::json!({
                "type": "double_click",
                "x": x,
                "y": y,
            }))
        }
        "mouse_move" => {
            let (x, y) = runtime_proxy_anthropic_coordinate_pair(input.get("coordinate"))?;
            Some(serde_json::json!({
                "type": "move",
                "x": x,
                "y": y,
            }))
        }
        "type" => input
            .get("text")
            .and_then(serde_json::Value::as_str)
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .map(|text| {
                serde_json::json!({
                    "type": "type",
                    "text": text,
                })
            }),
        "key" => input
            .get("key")
            .and_then(serde_json::Value::as_str)
            .map(str::trim)
            .filter(|value| !value.is_empty())
            .and_then(runtime_proxy_anthropic_computer_keypress_keys)
            .map(|keys| {
                serde_json::json!({
                    "type": "keypress",
                    "keys": keys,
                })
            }),
        "wait" => Some(serde_json::json!({ "type": "wait" })),
        _ => None,
    }
}

pub(crate) fn runtime_proxy_translate_anthropic_computer_tool_call(
    block: &serde_json::Value,
) -> Option<(
    String,
    serde_json::Value,
    RuntimeAnthropicNativeClientToolCall,
)> {
    let name = block
        .get("name")
        .and_then(serde_json::Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())?;
    if runtime_proxy_anthropic_client_tool_name(name) != Some("computer") {
        return None;
    }
    let call_id = block
        .get("id")
        .and_then(serde_json::Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())?
        .to_string();
    let input = block.get("input")?.as_object()?;
    let action = runtime_proxy_translate_anthropic_computer_action(input)?;
    Some((
        call_id.clone(),
        serde_json::json!({
            "type": "computer_call",
            "call_id": call_id,
            "actions": [action],
            "status": "completed",
        }),
        RuntimeAnthropicNativeClientToolCall {
            kind: RuntimeAnthropicNativeClientToolKind::Computer,
            max_output_length: None,
        },
    ))
}