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