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