parseUserAgentFull extracts agent name, version, and OS details from a User-Agent string. Supported formats: "codex_cli_rs/0.112.0 (Windows 10.0.26200; x86_64) WindowsTerminal" "claude-code/1.0.0 (Linux 6.1.0; x86_64)" "codex-cli"
(ua string)
| 112 | // "claude-code/1.0.0 (Linux 6.1.0; x86_64)" |
| 113 | // "codex-cli" |
| 114 | func parseUserAgentFull(ua string) agentInfo { |
| 115 | ua = strings.TrimSpace(ua) |
| 116 | if ua == "" { |
| 117 | return agentInfo{name: "unknown-agent", osName: "unknown"} |
| 118 | } |
| 119 | |
| 120 | info := agentInfo{} |
| 121 | |
| 122 | // Extract parenthesized section: (OS Version; Arch) |
| 123 | if m := uaParenRegex.FindStringSubmatch(ua); len(m) > 1 { |
| 124 | parts := strings.Split(m[1], ";") |
| 125 | if len(parts) >= 1 { |
| 126 | osField := strings.TrimSpace(parts[0]) |
| 127 | // "Windows 10.0.26200" → osName="Windows", osVersion="10.0.26200" |
| 128 | if spIdx := strings.IndexByte(osField, ' '); spIdx > 0 { |
| 129 | info.osName = osField[:spIdx] |
| 130 | info.osVersion = osField[spIdx+1:] |
| 131 | } else { |
| 132 | info.osName = osField |
| 133 | } |
| 134 | } |
| 135 | if len(parts) >= 2 { |
| 136 | info.arch = strings.TrimSpace(parts[1]) |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | // Extract text after closing paren as terminal info. |
| 141 | if closeIdx := strings.LastIndexByte(ua, ')'); closeIdx > 0 && closeIdx < len(ua)-1 { |
| 142 | info.terminal = strings.TrimSpace(ua[closeIdx+1:]) |
| 143 | } |
| 144 | |
| 145 | // Extract name/version from the part before the paren. |
| 146 | prefix := ua |
| 147 | if openIdx := strings.IndexByte(ua, '('); openIdx > 0 { |
| 148 | prefix = strings.TrimSpace(ua[:openIdx]) |
| 149 | } |
| 150 | |
| 151 | // Try "name/version" format. |
| 152 | if slashIdx := strings.IndexByte(prefix, '/'); slashIdx > 0 { |
| 153 | info.name = prefix[:slashIdx] |
| 154 | info.version = prefix[slashIdx+1:] |
| 155 | } else if parts := strings.SplitN(prefix, " ", 2); len(parts) == 2 { |
| 156 | info.name = parts[0] |
| 157 | info.version = parts[1] |
| 158 | } else { |
| 159 | info.name = prefix |
| 160 | } |
| 161 | |
| 162 | // Defaults. |
| 163 | if info.osName == "" { |
| 164 | info.osName = "unknown" |
| 165 | } |
| 166 | if info.version == "" { |
| 167 | info.version = "unknown" |
| 168 | } |
| 169 | |
| 170 | return info |
| 171 | } |
no outgoing calls
no test coverage detected