prodex 0.37.0

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

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct RuntimeAnthropicServerToolUsage {
    pub(crate) web_search_requests: u64,
    pub(crate) web_fetch_requests: u64,
    pub(crate) code_execution_requests: u64,
    pub(crate) tool_search_requests: u64,
}

impl RuntimeAnthropicServerToolUsage {
    pub(crate) fn add_assign(&mut self, other: Self) {
        self.web_search_requests += other.web_search_requests;
        self.web_fetch_requests += other.web_fetch_requests;
        self.code_execution_requests += other.code_execution_requests;
        self.tool_search_requests += other.tool_search_requests;
    }
}

#[derive(Debug, Clone, Default)]
pub(crate) struct RuntimeAnthropicServerTools {
    pub(crate) aliases: BTreeMap<String, RuntimeAnthropicRegisteredServerTool>,
    pub(crate) web_search: bool,
    pub(crate) mcp: bool,
    pub(crate) tool_search: bool,
}

#[derive(Debug, Clone)]
pub(crate) struct RuntimeAnthropicRegisteredServerTool {
    pub(crate) response_name: String,
    pub(crate) block_type: String,
}

impl RuntimeAnthropicServerTools {
    pub(crate) fn needs_buffered_translation(&self) -> bool {
        self.web_search
    }

    pub(crate) fn register(&mut self, tool_name: &str, canonical_name: &str) {
        self.register_with_block_type(tool_name, canonical_name, "server_tool_use");
    }

    pub(crate) fn register_with_block_type(
        &mut self,
        tool_name: &str,
        response_name: &str,
        block_type: &str,
    ) {
        let tool_name = tool_name.trim();
        let response_name = response_name.trim();
        let block_type = block_type.trim();
        if tool_name.is_empty() || response_name.is_empty() || block_type.is_empty() {
            return;
        }
        let registration = RuntimeAnthropicRegisteredServerTool {
            response_name: response_name.to_string(),
            block_type: block_type.to_string(),
        };
        self.aliases
            .insert(tool_name.to_string(), registration.clone());
        if let Some(normalized) = runtime_proxy_anthropic_builtin_server_tool_name(tool_name) {
            self.aliases.insert(normalized.to_string(), registration);
        }
        if response_name == "web_search" {
            self.web_search = true;
        } else if block_type == "mcp_tool_use" {
            self.mcp = true;
        } else if response_name.starts_with("tool_search_tool_") {
            self.tool_search = true;
        }
    }

    pub(crate) fn registration_for_call(
        &self,
        tool_name: &str,
    ) -> Option<&RuntimeAnthropicRegisteredServerTool> {
        let tool_name = tool_name.trim();
        if tool_name.is_empty() {
            return None;
        }
        self.aliases.get(tool_name).or_else(|| {
            runtime_proxy_anthropic_builtin_server_tool_name(tool_name)
                .and_then(|normalized| self.aliases.get(normalized))
        })
    }

    pub(crate) fn canonical_name_for_call(&self, tool_name: &str) -> Option<&str> {
        self.registration_for_call(tool_name)
            .map(|registration| registration.response_name.as_str())
    }
}

#[derive(Debug, Clone, Default)]
pub(crate) struct RuntimeAnthropicMcpServer {
    pub(crate) name: String,
    pub(crate) url: Option<String>,
    pub(crate) authorization_token: Option<String>,
    pub(crate) headers: serde_json::Map<String, serde_json::Value>,
    pub(crate) description: Option<String>,
}

#[derive(Debug, Clone, Default)]
pub(crate) struct RuntimeAnthropicTranslatedTools {
    pub(crate) tools: Vec<serde_json::Value>,
    pub(crate) server_tools: RuntimeAnthropicServerTools,
    pub(crate) tool_name_aliases: BTreeMap<String, String>,
    pub(crate) native_tool_names: BTreeSet<String>,
    pub(crate) memory: bool,
}

impl RuntimeAnthropicTranslatedTools {
    pub(crate) fn implicit_tool_choice(&self) -> Option<serde_json::Value> {
        (self.server_tools.web_search
            && self.tools.len() == 1
            && self.tools.first().and_then(|tool| {
                tool.get("type")
                    .and_then(serde_json::Value::as_str)
                    .map(str::trim)
            }) == Some("web_search"))
        .then(|| serde_json::Value::String("required".to_string()))
    }
}