vtcode_auth/
auth_service.rs1use anyhow::{Result, anyhow};
4use std::sync::Arc;
5
6use crate::AuthCredentialsStoreMode;
7use crate::config::{OpenAIAuthConfig, OpenAIPreferredMethod};
8use crate::openai_chatgpt_oauth::{
9 OpenAIChatGptAuthHandle, OpenAIChatGptSession, OpenAIChatGptSessionRefresher, OpenAICredentialOverview,
10 OpenAIResolvedAuth, OpenAIResolvedAuthSource, load_openai_chatgpt_session_with_mode,
11};
12
13#[derive(Debug, Clone)]
15pub struct OpenAIAccountAuthService {
16 auth_config: OpenAIAuthConfig,
17 storage_mode: AuthCredentialsStoreMode,
18}
19
20impl OpenAIAccountAuthService {
21 #[must_use]
22 pub fn new(auth_config: OpenAIAuthConfig, storage_mode: AuthCredentialsStoreMode) -> Self {
23 Self { auth_config, storage_mode }
24 }
25
26 pub fn resolve_runtime_auth(&self, api_key: Option<String>) -> Result<OpenAIResolvedAuth> {
28 let session = load_openai_chatgpt_session_with_mode(self.storage_mode)?;
29 match self.auth_config.preferred_method {
30 OpenAIPreferredMethod::Chatgpt => {
31 let session = session.ok_or_else(|| anyhow!("Run vtcode login openai"))?;
32 let handle = OpenAIChatGptAuthHandle::new(session, self.auth_config.clone(), self.storage_mode);
33 let api_key = handle.current_api_key()?;
34 Ok(OpenAIResolvedAuth::ChatGpt { api_key, handle })
35 }
36 OpenAIPreferredMethod::ApiKey => {
37 let api_key = require_api_key(api_key)?;
38 Ok(OpenAIResolvedAuth::ApiKey { api_key })
39 }
40 OpenAIPreferredMethod::Auto => {
41 if let Some(session) = session {
42 let handle = OpenAIChatGptAuthHandle::new(session, self.auth_config.clone(), self.storage_mode);
43 let api_key = handle.current_api_key()?;
44 Ok(OpenAIResolvedAuth::ChatGpt { api_key, handle })
45 } else {
46 let api_key = require_api_key(api_key)?;
47 Ok(OpenAIResolvedAuth::ApiKey { api_key })
48 }
49 }
50 }
51 }
52
53 pub fn resolve_external_session_auth(
55 &self,
56 session: OpenAIChatGptSession,
57 refresher: Arc<dyn OpenAIChatGptSessionRefresher>,
58 ) -> Result<OpenAIResolvedAuth> {
59 let handle = OpenAIChatGptAuthHandle::new_external(session, self.auth_config.auto_refresh, refresher);
60 let api_key = handle.current_api_key()?;
61 Ok(OpenAIResolvedAuth::ChatGpt { api_key, handle })
62 }
63
64 pub fn summarize_credentials(&self, api_key: Option<String>) -> Result<OpenAICredentialOverview> {
66 let chatgpt_session = load_openai_chatgpt_session_with_mode(self.storage_mode)?;
67 let api_key_available = api_key.as_ref().is_some_and(|value| !value.trim().is_empty());
68 let active_source = match self.auth_config.preferred_method {
69 OpenAIPreferredMethod::Chatgpt => chatgpt_session.as_ref().map(|_| OpenAIResolvedAuthSource::ChatGpt),
70 OpenAIPreferredMethod::ApiKey => api_key_available.then_some(OpenAIResolvedAuthSource::ApiKey),
71 OpenAIPreferredMethod::Auto => {
72 if chatgpt_session.is_some() {
73 Some(OpenAIResolvedAuthSource::ChatGpt)
74 } else if api_key_available {
75 Some(OpenAIResolvedAuthSource::ApiKey)
76 } else {
77 None
78 }
79 }
80 };
81
82 let (notice, recommendation) = if api_key_available && chatgpt_session.is_some() {
83 let active_label = match active_source {
84 Some(OpenAIResolvedAuthSource::ChatGpt) => "ChatGPT subscription",
85 Some(OpenAIResolvedAuthSource::ApiKey) => "OPENAI_API_KEY",
86 None => "neither credential",
87 };
88 let recommendation = match active_source {
89 Some(OpenAIResolvedAuthSource::ChatGpt) => {
90 "Next step: keep the current priority, run /logout openai to rely on API-key auth only, or set [auth.openai].preferred_method = \"api_key\"."
91 }
92 Some(OpenAIResolvedAuthSource::ApiKey) => {
93 "Next step: keep the current priority, remove OPENAI_API_KEY if ChatGPT should win, or set [auth.openai].preferred_method = \"chatgpt\"."
94 }
95 None => "Next step: choose a single preferred source or set [auth.openai].preferred_method explicitly.",
96 };
97 (
98 Some(format!(
99 "Both ChatGPT subscription auth and OPENAI_API_KEY are available. VT Code is using {active_label} because auth.openai.preferred_method = {}.",
100 self.auth_config.preferred_method.as_str()
101 )),
102 Some(recommendation.to_string()),
103 )
104 } else {
105 (None, None)
106 };
107
108 Ok(OpenAICredentialOverview {
109 api_key_available,
110 chatgpt_session,
111 active_source,
112 preferred_method: self.auth_config.preferred_method,
113 notice,
114 recommendation,
115 })
116 }
117}
118
119fn require_api_key(api_key: Option<String>) -> Result<String> {
120 api_key
121 .map(|value| value.trim().to_string())
122 .filter(|value| !value.is_empty())
123 .ok_or_else(|| anyhow!("OpenAI API key not found"))
124}