1- import { codaroApi } from "@/lib/api" ;
2- import type { AiProfile , AppNotice } from "@/types" ;
1+ import { CodaroApiError , codaroApi } from "@/lib/api" ;
2+ import type { AiProfile , AppNotice , ProviderDiagnostic , ProviderValidationPayload } from "@/types" ;
33
44export type ProviderActionResult = {
55 closeSettings ?: boolean ;
@@ -45,13 +45,18 @@ export async function loginOauthProvider(providerId = "oauth-chatgpt"): Promise<
4545 await sleep ( 1000 ) ;
4646 const status = await codaroApi . oauthStatus ( ) ;
4747 if ( ! status . done ) continue ;
48- if ( status . error ) throw new Error ( status . error ) ;
48+ if ( status . error ) {
49+ throw new CodaroApiError (
50+ Number ( status . diagnostic ?. statusCode ?? 503 ) ,
51+ status . message ?? status . error ,
52+ status . diagnostic ?? undefined ,
53+ ) ;
54+ }
4955 const profile = await codaroApi . aiProfile ( ) ;
50- return {
56+ return withProviderValidation ( profile , {
5157 closeSettings : true ,
52- notice : { tone : "success" , title : "Provider 연결됨" , detail : providerName ( profile ) } ,
5358 profile,
54- } ;
59+ } ) ;
5560 }
5661
5762 return {
@@ -75,10 +80,9 @@ export async function logoutOauthProvider(providerId = "oauth-chatgpt"): Promise
7580export async function selectProvider ( providerId : string ) : Promise < ProviderActionResult > {
7681 const profile = await codaroApi . updateAiProfile ( { provider : providerId } ) ;
7782 const latestProfile = await codaroApi . aiProfile ( ) . catch ( ( ) => profile ) ;
78- return {
79- notice : { tone : "success" , title : "Provider 선택됨" , detail : providerName ( latestProfile ) } ,
83+ return withProviderValidation ( latestProfile , {
8084 profile : latestProfile ,
81- } ;
85+ } ) ;
8286}
8387
8488export async function saveApiProvider ( providerId : string , apiKey : string , baseUrl ?: string ) : Promise < ProviderActionResult > {
@@ -87,38 +91,43 @@ export async function saveApiProvider(providerId: string, apiKey: string, baseUr
8791 }
8892 const profile = await codaroApi . saveAiSecret ( providerId , apiKey ) ;
8993 const latestProfile = await codaroApi . aiProfile ( ) . catch ( ( ) => profile ) ;
90- return {
91- notice : { tone : "success" , title : "Provider 연결됨" , detail : providerName ( latestProfile ) } ,
94+ return withProviderValidation ( latestProfile , {
9295 profile : latestProfile ,
93- } ;
96+ } ) ;
9497}
9598
96- export function providerAuthFailureNotice ( detail : string ) : AppNotice {
99+ export function providerAuthFailureNotice ( error : unknown ) : AppNotice {
100+ const diagnostic = providerDiagnosticFromError ( error ) ;
97101 return {
98102 tone : "error" ,
99103 title : "Provider 로그인 실패" ,
100- detail : isProviderAuthError ( detail ) ? "provider 로그인을 다시 시작하세요." : detail ,
104+ detail : diagnostic ?. message ?? errorMessage ( error ) ,
101105 } ;
102106}
103107
104- export function providerAssistantFailure ( detail : string ) : ProviderAssistantFailure {
105- const authIssue = isProviderAuthError ( detail ) ;
108+ export function providerAssistantFailure ( error : unknown ) : ProviderAssistantFailure {
109+ const diagnostic = providerDiagnosticFromError ( error ) ;
110+ const authIssue = isProviderAuthError ( error ) ;
106111 const content = authIssue
107- ? "provider 로그인이 필요합니다. Provider 설정에서 브라우저 로그인을 완료한 뒤 다시 요청하세요."
108- : detail ;
112+ ? diagnostic ?. message ?? "Provider 로그인이 필요합니다. Provider 설정에서 브라우저 로그인을 완료한 뒤 다시 요청하세요."
113+ : diagnostic ?. message ?? errorMessage ( error ) ;
109114 return {
110115 action : authIssue ? "connect-provider" : undefined ,
111116 content,
112117 notice : {
113118 tone : "error" ,
114- title : authIssue ? "Provider 연결 필요" : "AI 사용 불가" ,
119+ title : authIssue ? "Provider 연결 필요" : "Provider 사용 불가" ,
115120 detail : content ,
116121 } ,
117122 } ;
118123}
119124
120- export function isProviderAuthError ( detail : string ) {
121- const normalized = detail . toLowerCase ( ) ;
125+ export function isProviderAuthError ( error : unknown ) {
126+ const diagnostic = providerDiagnosticFromError ( error ) ;
127+ if ( diagnostic ?. action && [ "connect-provider" , "relogin-provider" , "restart-login" ] . includes ( diagnostic . action ) ) {
128+ return true ;
129+ }
130+ const normalized = errorMessage ( error ) . toLowerCase ( ) ;
122131 return (
123132 normalized . includes ( "oauth authentication required" ) ||
124133 normalized . includes ( "authentication expired" ) ||
@@ -132,10 +141,69 @@ export function isProviderAuthError(detail: string) {
132141 ) ;
133142}
134143
144+ function providerDiagnosticFromError ( error : unknown ) : ProviderDiagnostic | undefined {
145+ if ( error instanceof CodaroApiError ) return error . diagnostic ;
146+ if ( isRecord ( error ) && isRecord ( error . diagnostic ) ) return error . diagnostic as ProviderDiagnostic ;
147+ return undefined ;
148+ }
149+
150+ async function withProviderValidation (
151+ profile : AiProfile ,
152+ base : Omit < ProviderActionResult , "notice" > ,
153+ ) : Promise < ProviderActionResult > {
154+ const provider = String ( profile . activeProvider ?? profile . defaultProvider ?? "" ) ;
155+ if ( ! provider ) {
156+ return {
157+ ...base ,
158+ notice : { tone : "warning" , title : "Provider 확인 필요" , detail : "선택된 provider가 없습니다." } ,
159+ } ;
160+ }
161+ const validation = await validateProvider ( provider , profile . activeModel ) ;
162+ if ( validation . valid ) {
163+ return {
164+ ...base ,
165+ notice : {
166+ tone : "success" ,
167+ title : "Provider 연결됨" ,
168+ detail : validation . model ? `${ provider } · ${ validation . model } ` : providerName ( profile ) ,
169+ } ,
170+ } ;
171+ }
172+ return {
173+ ...base ,
174+ notice : {
175+ tone : "warning" ,
176+ title : "Provider 확인 필요" ,
177+ detail : validation . diagnostic ?. message ?? validation . error ?? "Provider 연결 상태를 확인하지 못했습니다." ,
178+ } ,
179+ } ;
180+ }
181+
182+ async function validateProvider ( provider : string , model ?: unknown ) : Promise < ProviderValidationPayload > {
183+ try {
184+ return await codaroApi . validateAiProvider ( provider , typeof model === "string" ? model : undefined ) ;
185+ } catch ( error ) {
186+ const diagnostic = providerDiagnosticFromError ( error ) ;
187+ return {
188+ valid : false ,
189+ error : diagnostic ?. message ?? errorMessage ( error ) ,
190+ diagnostic,
191+ } ;
192+ }
193+ }
194+
195+ function errorMessage ( error : unknown ) {
196+ return error instanceof Error ? error . message : String ( error ) ;
197+ }
198+
135199function providerName ( profile : AiProfile | null ) {
136200 return String ( profile ?. activeProvider ?? profile ?. provider ?? profile ?. defaultProvider ?? "provider 없음" ) ;
137201}
138202
203+ function isRecord ( value : unknown ) : value is Record < string , unknown > {
204+ return Boolean ( value ) && typeof value === "object" && ! Array . isArray ( value ) ;
205+ }
206+
139207function sleep ( milliseconds : number ) {
140208 return new Promise ( ( resolve ) => window . setTimeout ( resolve , milliseconds ) ) ;
141209}
0 commit comments