claude-codex 0.3.1

Run Claude Code on your Claude and ChatGPT subscriptions at once, routed per model name
Documentation
use http::HeaderMap;

pub const CLAUDE_SESSION_HEADER: &str = "x-claude-code-session-id";
pub const CLAUDE_AGENT_HEADER: &str = "x-claude-code-agent-id";
pub const CLAUDE_PARENT_AGENT_HEADER: &str = "x-claude-code-parent-agent-id";

const MAX_IDENTITY_LEN: usize = 512;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ConversationIdentity {
    Main(String),
    Agent(String, String),
}

impl ConversationIdentity {
    pub fn from_headers(headers: &HeaderMap) -> Option<Self> {
        let session = read_identity_header(headers, CLAUDE_SESSION_HEADER);
        let agent = read_identity_header(headers, CLAUDE_AGENT_HEADER);
        let parent = read_identity_header(headers, CLAUDE_PARENT_AGENT_HEADER);

        if session.is_invalid() || agent.is_invalid() || parent.is_invalid() {
            return None;
        }

        match (session.value(), agent.value(), parent.value()) {
            (Some(session_id), Some(agent_id), _) => {
                Some(Self::Agent(session_id.to_string(), agent_id.to_string()))
            }
            (Some(session_id), None, None) => Some(Self::Main(session_id.to_string())),
            _ => None,
        }
    }
}

#[derive(Debug)]
enum ParsedHeader<'a> {
    Missing,
    Valid(&'a str),
    Invalid,
}

impl ParsedHeader<'_> {
    fn value(&self) -> Option<&str> {
        match self {
            Self::Valid(value) => Some(value),
            Self::Missing | Self::Invalid => None,
        }
    }

    fn is_invalid(&self) -> bool {
        matches!(self, Self::Invalid)
    }
}

fn read_identity_header<'a>(headers: &'a HeaderMap, name: &str) -> ParsedHeader<'a> {
    let mut values = headers.get_all(name).iter();
    let Some(value) = values.next() else {
        return ParsedHeader::Missing;
    };
    if values.next().is_some() {
        return ParsedHeader::Invalid;
    }

    let Ok(value) = value.to_str() else {
        return ParsedHeader::Invalid;
    };
    let value = value.trim_matches(|character| matches!(character, ' ' | '\t'));
    if value.is_empty()
        || value.len() > MAX_IDENTITY_LEN
        || value.contains(',')
        || !value.bytes().all(|byte| byte.is_ascii_graphic())
    {
        return ParsedHeader::Invalid;
    }

    ParsedHeader::Valid(value)
}

#[cfg(test)]
mod tests {
    use super::*;
    use http::{HeaderName, HeaderValue};

    fn headers(values: &[(&str, &str)]) -> HeaderMap {
        let mut headers = HeaderMap::new();
        for (name, value) in values {
            headers.append(
                HeaderName::from_bytes(name.as_bytes()).unwrap(),
                HeaderValue::from_str(value).unwrap(),
            );
        }
        headers
    }

    #[test]
    fn parses_main_agent_and_lineage_shapes() {
        let cases = [
            (
                "main",
                vec![(CLAUDE_SESSION_HEADER, "session-a")],
                Some(ConversationIdentity::Main("session-a".to_string())),
            ),
            (
                "direct agent without parent",
                vec![
                    (CLAUDE_SESSION_HEADER, "session-a"),
                    (CLAUDE_AGENT_HEADER, "agent-a"),
                ],
                Some(ConversationIdentity::Agent(
                    "session-a".to_string(),
                    "agent-a".to_string(),
                )),
            ),
            (
                "nested direct child",
                vec![
                    (CLAUDE_SESSION_HEADER, "session-a"),
                    (CLAUDE_AGENT_HEADER, "agent-child"),
                    (CLAUDE_PARENT_AGENT_HEADER, "agent-parent"),
                ],
                Some(ConversationIdentity::Agent(
                    "session-a".to_string(),
                    "agent-child".to_string(),
                )),
            ),
            (
                "sibling one",
                vec![
                    (CLAUDE_SESSION_HEADER, "session-a"),
                    (CLAUDE_AGENT_HEADER, "agent-sibling-one"),
                    (CLAUDE_PARENT_AGENT_HEADER, "agent-parent"),
                ],
                Some(ConversationIdentity::Agent(
                    "session-a".to_string(),
                    "agent-sibling-one".to_string(),
                )),
            ),
            (
                "sibling two",
                vec![
                    (CLAUDE_SESSION_HEADER, "session-a"),
                    (CLAUDE_AGENT_HEADER, "agent-sibling-two"),
                    (CLAUDE_PARENT_AGENT_HEADER, "agent-parent"),
                ],
                Some(ConversationIdentity::Agent(
                    "session-a".to_string(),
                    "agent-sibling-two".to_string(),
                )),
            ),
            (
                "same agent in another session",
                vec![
                    (CLAUDE_SESSION_HEADER, "session-b"),
                    (CLAUDE_AGENT_HEADER, "agent-a"),
                ],
                Some(ConversationIdentity::Agent(
                    "session-b".to_string(),
                    "agent-a".to_string(),
                )),
            ),
            (
                "outer space and tab",
                vec![
                    (CLAUDE_SESSION_HEADER, " \tsession-a\t "),
                    (CLAUDE_AGENT_HEADER, "\tagent-a "),
                    (CLAUDE_PARENT_AGENT_HEADER, " agent-parent\t"),
                ],
                Some(ConversationIdentity::Agent(
                    "session-a".to_string(),
                    "agent-a".to_string(),
                )),
            ),
        ];

        for (name, values, expected) in cases {
            assert_eq!(
                ConversationIdentity::from_headers(&headers(&values)),
                expected,
                "{name}"
            );
        }
    }

    #[test]
    fn parent_is_validation_only_and_never_changes_the_owner() {
        let direct = ConversationIdentity::from_headers(&headers(&[
            (CLAUDE_SESSION_HEADER, "session-a"),
            (CLAUDE_AGENT_HEADER, "agent-child"),
        ]));
        let nested = ConversationIdentity::from_headers(&headers(&[
            (CLAUDE_SESSION_HEADER, "session-a"),
            (CLAUDE_AGENT_HEADER, "agent-child"),
            (CLAUDE_PARENT_AGENT_HEADER, "agent-parent"),
        ]));
        let reparented = ConversationIdentity::from_headers(&headers(&[
            (CLAUDE_SESSION_HEADER, "session-a"),
            (CLAUDE_AGENT_HEADER, "agent-child"),
            (CLAUDE_PARENT_AGENT_HEADER, "another-parent"),
        ]));

        assert_eq!(direct, nested);
        assert_eq!(nested, reparented);
    }

    #[test]
    fn ambiguous_or_absent_tuples_are_stateless() {
        let cases = [
            ("all missing", vec![]),
            (
                "agent without session",
                vec![(CLAUDE_AGENT_HEADER, "agent-a")],
            ),
            (
                "parent without direct agent",
                vec![
                    (CLAUDE_SESSION_HEADER, "session-a"),
                    (CLAUDE_PARENT_AGENT_HEADER, "agent-parent"),
                ],
            ),
            (
                "parent alone",
                vec![(CLAUDE_PARENT_AGENT_HEADER, "agent-parent")],
            ),
            (
                "agent and parent without session",
                vec![
                    (CLAUDE_AGENT_HEADER, "agent-a"),
                    (CLAUDE_PARENT_AGENT_HEADER, "agent-parent"),
                ],
            ),
        ];

        for (name, values) in cases {
            assert_eq!(
                ConversationIdentity::from_headers(&headers(&values)),
                None,
                "{name}"
            );
        }
    }

    #[test]
    fn rejects_malformed_text_in_every_identity_field() {
        let malformed = [
            ("empty", ""),
            ("spaces only", "   "),
            ("tabs only", "\t\t"),
            ("internal space", "two values"),
            ("internal tab", "two\tvalues"),
            ("leading comma", ",value"),
            ("trailing comma", "value,"),
            ("coalesced", "first, second"),
            ("oversize", "oversize-placeholder"),
        ];

        for field in [
            CLAUDE_SESSION_HEADER,
            CLAUDE_AGENT_HEADER,
            CLAUDE_PARENT_AGENT_HEADER,
        ] {
            for (shape, placeholder) in malformed {
                let value = if shape == "oversize" {
                    "x".repeat(MAX_IDENTITY_LEN + 1)
                } else {
                    placeholder.to_string()
                };
                let mut values = vec![
                    (CLAUDE_SESSION_HEADER, "session-a"),
                    (CLAUDE_AGENT_HEADER, "agent-a"),
                    (CLAUDE_PARENT_AGENT_HEADER, "agent-parent"),
                ];
                values
                    .iter_mut()
                    .find(|(name, _)| *name == field)
                    .unwrap()
                    .1 = &value;
                assert_eq!(
                    ConversationIdentity::from_headers(&headers(&values)),
                    None,
                    "field={field} shape={shape}"
                );
            }
        }
    }

    #[test]
    fn rejects_duplicate_headers_in_every_identity_field() {
        for field in [
            CLAUDE_SESSION_HEADER,
            CLAUDE_AGENT_HEADER,
            CLAUDE_PARENT_AGENT_HEADER,
        ] {
            let mut values = vec![
                (CLAUDE_SESSION_HEADER, "session-a"),
                (CLAUDE_AGENT_HEADER, "agent-a"),
                (CLAUDE_PARENT_AGENT_HEADER, "agent-parent"),
            ];
            values.push((field, "duplicate"));
            assert_eq!(
                ConversationIdentity::from_headers(&headers(&values)),
                None,
                "field={field}"
            );
        }
    }

    #[test]
    fn rejects_nontext_headers_in_every_identity_field() {
        for field in [
            CLAUDE_SESSION_HEADER,
            CLAUDE_AGENT_HEADER,
            CLAUDE_PARENT_AGENT_HEADER,
        ] {
            let mut values = headers(&[
                (CLAUDE_SESSION_HEADER, "session-a"),
                (CLAUDE_AGENT_HEADER, "agent-a"),
                (CLAUDE_PARENT_AGENT_HEADER, "agent-parent"),
            ]);
            values.insert(field, HeaderValue::from_bytes(&[0x80]).unwrap());
            assert_eq!(
                ConversationIdentity::from_headers(&values),
                None,
                "field={field}"
            );
        }
    }

    #[test]
    fn malformed_agent_cannot_downgrade_a_valid_session_to_main() {
        for malformed_agent in ["", "agent one", "agent-a,agent-b"] {
            let identity = ConversationIdentity::from_headers(&headers(&[
                (CLAUDE_SESSION_HEADER, "session-a"),
                (CLAUDE_AGENT_HEADER, malformed_agent),
            ]));
            assert_eq!(identity, None, "agent={malformed_agent:?}");
        }
    }
}