Skip to main content

tokn_headers/
agent.rs

1//! Agent-specific header builders used to synthesize outbound request headers.
2
3use crate::map::HeaderMap;
4use crate::schema::HeaderSchema;
5use crate::schemas::{ClaudeCodeHeaders, ClineHeaders, CodexCliHeaders, CopilotCliHeaders, OpencodeHeaders};
6use crate::vars::TemplateVars;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum AgentKind {
10  Opencode,
11  CodexCli,
12  ClaudeCode,
13  Cline,
14  CopilotCli,
15}
16
17impl AgentKind {
18  pub fn from_agent_id(agent_id: &str) -> Option<Self> {
19    match agent_id {
20      "opencode" => Some(Self::Opencode),
21      "codex_exec" | "codex-tui" | "codex" | "codex-cli" => Some(Self::CodexCli),
22      "claude-cli" | "claude-code" => Some(Self::ClaudeCode),
23      "cline" => Some(Self::Cline),
24      "copilot" | "copilot-cli" => Some(Self::CopilotCli),
25      _ => None,
26    }
27  }
28
29  pub fn build_outbound(self, vars: &TemplateVars, inbound: &HeaderMap) -> HeaderMap {
30    match self {
31      Self::Opencode => OpencodeHeaders::build(vars, inbound).dump(),
32      Self::CodexCli => CodexCliHeaders::build(vars, inbound).dump(),
33      Self::ClaudeCode => ClaudeCodeHeaders::build(vars, inbound).dump(),
34      Self::Cline => ClineHeaders::build(vars, inbound).dump(),
35      Self::CopilotCli => CopilotCliHeaders::build(vars, inbound).dump(),
36    }
37  }
38}
39
40pub fn build_agent_headers(agent_id: &str, vars: &TemplateVars, inbound: &HeaderMap) -> HeaderMap {
41  AgentKind::from_agent_id(agent_id)
42    .map(|kind| kind.build_outbound(vars, inbound))
43    .unwrap_or_default()
44}
45
46#[cfg(test)]
47mod tests {
48  use super::*;
49
50  #[test]
51  fn known_agents_round_trip() {
52    for (slug, expected) in [
53      ("opencode", AgentKind::Opencode),
54      ("codex-cli", AgentKind::CodexCli),
55      ("claude-code", AgentKind::ClaudeCode),
56      ("cline", AgentKind::Cline),
57      ("copilot-cli", AgentKind::CopilotCli),
58    ] {
59      assert_eq!(AgentKind::from_agent_id(slug), Some(expected));
60    }
61  }
62
63  #[test]
64  fn aliases_normalize() {
65    assert_eq!(AgentKind::from_agent_id("codex"), Some(AgentKind::CodexCli));
66    assert_eq!(AgentKind::from_agent_id("codex_exec"), Some(AgentKind::CodexCli));
67    assert_eq!(AgentKind::from_agent_id("claude-cli"), Some(AgentKind::ClaudeCode));
68    assert_eq!(AgentKind::from_agent_id("copilot"), Some(AgentKind::CopilotCli));
69  }
70
71  #[test]
72  fn unknown_agent_yields_empty_header_map() {
73    let out = build_agent_headers("anything", &TemplateVars::default(), &HeaderMap::new());
74    assert_eq!(out.len(), 0);
75  }
76
77  #[test]
78  fn build_outbound_for_known_agent_returns_nonempty_map() {
79    let vars = TemplateVars::default();
80    let inbound = HeaderMap::new();
81    for kind in [
82      AgentKind::Opencode,
83      AgentKind::CodexCli,
84      AgentKind::ClaudeCode,
85      AgentKind::Cline,
86      AgentKind::CopilotCli,
87    ] {
88      let out = kind.build_outbound(&vars, &inbound);
89      assert!(!out.is_empty(), "{kind:?} should build a non-empty HeaderMap");
90    }
91  }
92}