diff --git a/README.md b/README.md index 60fabe9..f898e5e 100644 --- a/README.md +++ b/README.md @@ -1286,3 +1286,6 @@ MIT License - 업데이트: 2026-04-06 16:55 (KST) - IBM/CP4D 계열 연결 점검 결과, 일부 환경은 `/icp4d-api/v1/authorize` 호출 시 `username + password`가 아니라 `username + api_key` JSON 본문을 요구하는 것을 확인했다. - [Cp4dTokenService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Cp4dTokenService.cs) 에서 CP4D 토큰 요청을 먼저 `username + password`, 실패 시 `username + api_key`로 한 번 더 시도하도록 보강해, IBM 연결형 vLLM 환경 호환성을 높였다. +- 업데이트: 2026-04-06 17:01 (KST) + - 모델 등록 단계에서 IBM/CP4D 인증 방식을 명확히 고를 수 있게 분기했다. [ModelRegistrationDialog.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ModelRegistrationDialog.cs)에 `CP4D (사용자 이름 + 비밀번호)`와 `CP4D (사용자 이름 + API 키)` 항목을 따로 추가하고, 선택에 따라 마지막 입력 필드 라벨이 `비밀번호` 또는 `API 키`로 바뀌도록 정리했다. + - [LlmService.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/LlmService.cs), [SettingsViewModel.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/ViewModels/SettingsViewModel.cs), [AppSettings.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Models/AppSettings.cs) 도 함께 갱신해 `cp4d_password`, `cp4d_api_key` 저장값을 공식 지원하고, 기존 `cp4d` 값은 비밀번호 방식으로 계속 호환되게 유지했다. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 5cac9a6..985a55c 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -4980,3 +4980,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎. - Document update: 2026-04-06 16:44 (KST) - Moved the separator for the AX Agent internal-settings `운영 모드` block from the bottom edge to the top edge in `ChatWindow.xaml` so the line reads as the start of the operation-mode section rather than trailing under the previous storage block. - Document update: 2026-04-06 16:49 (KST) - Removed the separator between the AX Agent internal-settings `서비스와 모델` block and the adjacent `등록 모델 관리` block in `ChatWindow.xaml` so the two model-management sections read as one continuous flow instead of two unrelated groups. - Document update: 2026-04-06 16:55 (KST) - Hardened `Cp4dTokenService.cs` for IBM/CP4D-style token endpoints that expect `username + api_key` instead of `username + password`. The service now tries `username/password` first and automatically retries with `username/api_key` before failing. +- Document update: 2026-04-06 17:01 (KST) - Split the CP4D auth choice at model-registration time. `ModelRegistrationDialog.cs` now exposes `CP4D (사용자 이름 + 비밀번호)` and `CP4D (사용자 이름 + API 키)` as separate auth types and changes the final credential label accordingly. +- Document update: 2026-04-06 17:01 (KST) - Updated `LlmService.cs`, `SettingsViewModel.cs`, and `AppSettings.cs` to formally support `cp4d_password` and `cp4d_api_key` while preserving legacy `cp4d` values as the password-based path for backward compatibility. diff --git a/src/AxCopilot/Models/AppSettings.cs b/src/AxCopilot/Models/AppSettings.cs index 0ad32b7..e980f5b 100644 --- a/src/AxCopilot/Models/AppSettings.cs +++ b/src/AxCopilot/Models/AppSettings.cs @@ -1383,7 +1383,7 @@ public class RegisteredModel // ── CP4D (IBM Cloud Pak for Data) 인증 ────────────────────────────── - /// 인증 방식. bearer (기본) | ibm_iam | cp4d + /// 인증 방식. bearer (기본) | ibm_iam | cp4d_password | cp4d_api_key [JsonPropertyName("authType")] public string AuthType { get; set; } = "bearer"; @@ -1395,7 +1395,7 @@ public class RegisteredModel [JsonPropertyName("cp4dUsername")] public string Cp4dUsername { get; set; } = ""; - /// CP4D 비밀번호 또는 API 키 (EncryptionEnabled=true 시 암호화 저장) + /// CP4D 비밀번호 또는 API 키 (인증 방식에 따라 사용, EncryptionEnabled=true 시 암호화 저장) [JsonPropertyName("cp4dPassword")] public string Cp4dPassword { get; set; } = ""; } diff --git a/src/AxCopilot/Services/LlmService.cs b/src/AxCopilot/Services/LlmService.cs index d55556d..04bde33 100644 --- a/src/AxCopilot/Services/LlmService.cs +++ b/src/AxCopilot/Services/LlmService.cs @@ -344,7 +344,9 @@ public partial class LlmService : IDisposable // CP4D 인증 방식인 경우 if (registered != null && - registered.AuthType.Equals("cp4d", StringComparison.OrdinalIgnoreCase) && + (registered.AuthType.Equals("cp4d", StringComparison.OrdinalIgnoreCase) || + registered.AuthType.Equals("cp4d_password", StringComparison.OrdinalIgnoreCase) || + registered.AuthType.Equals("cp4d_api_key", StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrWhiteSpace(registered.Cp4dUrl)) { var password = CryptoService.DecryptIfEnabled(registered.Cp4dPassword, llm.EncryptionEnabled); diff --git a/src/AxCopilot/ViewModels/SettingsViewModel.cs b/src/AxCopilot/ViewModels/SettingsViewModel.cs index 5702360..8065281 100644 --- a/src/AxCopilot/ViewModels/SettingsViewModel.cs +++ b/src/AxCopilot/ViewModels/SettingsViewModel.cs @@ -2028,7 +2028,7 @@ public class RegisteredModelRow : INotifyPropertyChanged private string _cp4dUsername = ""; private string _cp4dPassword = ""; - /// 인증 방식. bearer | ibm_iam | cp4d + /// 인증 방식. bearer | ibm_iam | cp4d_password | cp4d_api_key public string AuthType { get => _authType; @@ -2059,7 +2059,9 @@ public class RegisteredModelRow : INotifyPropertyChanged /// 인증 방식 라벨 public string AuthLabel => (_authType ?? "bearer").ToLowerInvariant() switch { - "cp4d" => "CP4D", + "cp4d" => "CP4D 비밀번호", + "cp4d_password" => "CP4D 비밀번호", + "cp4d_api_key" => "CP4D API 키", "ibm_iam" => "IBM IAM", _ => "Bearer", }; diff --git a/src/AxCopilot/Views/ModelRegistrationDialog.cs b/src/AxCopilot/Views/ModelRegistrationDialog.cs index 47f95f2..652be88 100644 --- a/src/AxCopilot/Views/ModelRegistrationDialog.cs +++ b/src/AxCopilot/Views/ModelRegistrationDialog.cs @@ -272,13 +272,17 @@ internal sealed class ModelRegistrationDialog : Window }; var bearerItem = new ComboBoxItem { Content = "Bearer 토큰 (API 키)", Tag = "bearer" }; var ibmIamItem = new ComboBoxItem { Content = "IBM IAM (토큰 교환)", Tag = "ibm_iam" }; - var cp4dItem = new ComboBoxItem { Content = "CP4D (IBM Cloud Pak for Data)", Tag = "cp4d" }; + var cp4dPasswordItem = new ComboBoxItem { Content = "CP4D (사용자 이름 + 비밀번호)", Tag = "cp4d_password" }; + var cp4dApiKeyItem = new ComboBoxItem { Content = "CP4D (사용자 이름 + API 키)", Tag = "cp4d_api_key" }; _authTypeBox.Items.Add(bearerItem); _authTypeBox.Items.Add(ibmIamItem); - _authTypeBox.Items.Add(cp4dItem); + _authTypeBox.Items.Add(cp4dPasswordItem); + _authTypeBox.Items.Add(cp4dApiKeyItem); _authTypeBox.SelectedItem = existingAuthType switch { - "cp4d" => cp4dItem, + "cp4d" => cp4dPasswordItem, + "cp4d_password" => cp4dPasswordItem, + "cp4d_api_key" => cp4dApiKeyItem, "ibm_iam" => ibmIamItem, _ => bearerItem, }; @@ -302,8 +306,9 @@ internal sealed class ModelRegistrationDialog : Window apiKeyPanel.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _apiKeyBox }); stack.Children.Add(apiKeyPanel); - // ── CP4D 인증: URL + 사용자명 + 비밀번호 ──────────────────────────── - _cp4dPanel = new StackPanel { Visibility = existingAuthType == "cp4d" ? Visibility.Visible : Visibility.Collapsed }; + // ── CP4D 인증: URL + 사용자명 + 비밀번호/API 키 ─────────────────────── + var initialCp4dAuth = existingAuthType is "cp4d" or "cp4d_password" or "cp4d_api_key"; + _cp4dPanel = new StackPanel { Visibility = initialCp4dAuth ? Visibility.Visible : Visibility.Collapsed }; _cp4dPanel.Children.Add(new TextBlock { @@ -340,12 +345,13 @@ internal sealed class ModelRegistrationDialog : Window }; _cp4dPanel.Children.Add(new Border { CornerRadius = new CornerRadius(8), ClipToBounds = true, Child = _cp4dUsernameBox }); - _cp4dPanel.Children.Add(new TextBlock + var cp4dSecretLabel = new TextBlock { - Text = "비밀번호 / API 키", + Text = "비밀번호", FontSize = 12, FontWeight = FontWeights.SemiBold, Foreground = primaryText, Margin = new Thickness(0, 10, 0, 6), - }); + }; + _cp4dPanel.Children.Add(cp4dSecretLabel); _cp4dPasswordBox = new PasswordBox { Password = existingCp4dPassword, @@ -358,14 +364,19 @@ internal sealed class ModelRegistrationDialog : Window stack.Children.Add(_cp4dPanel); // 인증 방식 전환 시 패널 표시/숨김 - _authTypeBox.SelectionChanged += (_, _) => + void UpdateAuthPanels() { - var isCp4d = AuthType == "cp4d"; + var isCp4d = AuthType is "cp4d" or "cp4d_password" or "cp4d_api_key"; _cp4dPanel.Visibility = isCp4d ? Visibility.Visible : Visibility.Collapsed; apiKeyPanel.Visibility = isCp4d ? Visibility.Collapsed : Visibility.Visible; + cp4dSecretLabel.Text = AuthType == "cp4d_api_key" ? "API 키" : "비밀번호"; + } + _authTypeBox.SelectionChanged += (_, _) => + { + UpdateAuthPanels(); }; // 초기 상태 설정 - apiKeyPanel.Visibility = existingAuthType == "cp4d" ? Visibility.Collapsed : Visibility.Visible; + UpdateAuthPanels(); // 보안 안내 var securityNote = new StackPanel