@codeguide/core 0.0.24 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/__tests__/services/codespace/codespace-models.test.ts +458 -0
  2. package/__tests__/services/security-keys.test.ts +587 -0
  3. package/codeguide.ts +3 -1
  4. package/dist/codeguide.d.ts +2 -1
  5. package/dist/codeguide.js +1 -0
  6. package/dist/examples/project-filtering-example.js +3 -3
  7. package/dist/services/codespace/codespace-service.d.ts +52 -1
  8. package/dist/services/codespace/codespace-service.js +87 -0
  9. package/dist/services/codespace/codespace-types.d.ts +45 -0
  10. package/dist/services/codespace/index.d.ts +1 -1
  11. package/dist/services/index.d.ts +2 -0
  12. package/dist/services/index.js +4 -1
  13. package/dist/services/projects/project-types.d.ts +4 -6
  14. package/dist/services/security-keys/index.d.ts +3 -0
  15. package/dist/services/security-keys/index.js +21 -0
  16. package/dist/services/security-keys/security-keys-service.d.ts +111 -0
  17. package/dist/services/security-keys/security-keys-service.js +190 -0
  18. package/dist/services/security-keys/security-keys-types.d.ts +105 -0
  19. package/dist/services/security-keys/security-keys-types.js +8 -0
  20. package/examples/project-filtering-example.ts +3 -3
  21. package/package.json +1 -1
  22. package/services/codespace/codespace-service.ts +104 -0
  23. package/services/codespace/codespace-types.ts +65 -1
  24. package/services/codespace/index.ts +12 -1
  25. package/services/index.ts +2 -0
  26. package/services/projects/project-types.ts +4 -6
  27. package/services/security-keys/index.ts +25 -0
  28. package/services/security-keys/security-keys-service.ts +229 -0
  29. package/services/security-keys/security-keys-types.ts +187 -0
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Security Keys Service Types
3
+ *
4
+ * This file contains type definitions for the Security Keys service,
5
+ * which manages provider API keys and GitHub tokens.
6
+ */
7
+ export interface SecurityKeyData {
8
+ id: string;
9
+ created_at: string;
10
+ user_id: string;
11
+ name: string;
12
+ displayed_name: string;
13
+ value_masked: string;
14
+ value?: string;
15
+ object_value: Record<string, any>;
16
+ encryption: string;
17
+ key_version: string;
18
+ }
19
+ export interface ProviderAPIKeyData extends SecurityKeyData {
20
+ provider_id: string;
21
+ provider_name: string;
22
+ provider_key: string;
23
+ provider_logo_src?: string;
24
+ object_value: {
25
+ provider_id: string;
26
+ };
27
+ }
28
+ export interface GitHubTokenData extends SecurityKeyData {
29
+ object_value: {
30
+ token_type: string;
31
+ };
32
+ }
33
+ export interface SuccessResponse<T> {
34
+ status: 'success';
35
+ data: T;
36
+ }
37
+ export interface ErrorResponse {
38
+ detail: string;
39
+ }
40
+ export interface CreateProviderAPIKeyRequest {
41
+ provider_key: string;
42
+ api_key: string;
43
+ }
44
+ export interface CreateProviderAPIKeyResponse extends SuccessResponse<ProviderAPIKeyData> {
45
+ }
46
+ export interface GetProviderAPIKeyResponse extends SuccessResponse<ProviderAPIKeyData> {
47
+ }
48
+ export interface ListProviderAPIKeysResponse extends SuccessResponse<ProviderAPIKeyData[]> {
49
+ }
50
+ export interface CreateGitHubTokenRequest {
51
+ github_token: string;
52
+ }
53
+ export interface CreateGitHubTokenResponse extends SuccessResponse<GitHubTokenData> {
54
+ }
55
+ export interface GetGitHubTokenResponse extends SuccessResponse<GitHubTokenData> {
56
+ }
57
+ export interface ProviderNotFoundError extends ErrorResponse {
58
+ detail: string;
59
+ }
60
+ export interface DuplicateKeyError extends ErrorResponse {
61
+ detail: string;
62
+ }
63
+ export interface KeyNotFoundError extends ErrorResponse {
64
+ detail: string;
65
+ }
66
+ export interface InvalidGitHubTokenFormatError extends ErrorResponse {
67
+ detail: string;
68
+ }
69
+ export interface GitHubTokenValidationError extends ErrorResponse {
70
+ detail: string;
71
+ }
72
+ export interface DuplicateGitHubTokenError extends ErrorResponse {
73
+ detail: string;
74
+ }
75
+ export interface GitHubTokenNotFoundError extends ErrorResponse {
76
+ detail: string;
77
+ }
78
+ export interface AuthenticationError extends ErrorResponse {
79
+ detail: string;
80
+ }
81
+ export interface InternalServerError extends ErrorResponse {
82
+ detail: string;
83
+ }
84
+ export type ProviderAPIKeyResponse = CreateProviderAPIKeyResponse | GetProviderAPIKeyResponse | ListProviderAPIKeysResponse | RevokeProviderAPIKeyResponse | SecurityKeysError;
85
+ export interface RevokeProviderAPIKeyResponse {
86
+ status: string;
87
+ message: string;
88
+ revoked_provider_id: string;
89
+ }
90
+ export interface RevokeGitHubTokenResponse {
91
+ status: string;
92
+ message: string;
93
+ revoked_at: string;
94
+ }
95
+ export interface ProviderAPIKeyNotFoundError extends ErrorResponse {
96
+ detail: string;
97
+ }
98
+ export interface ProviderAPIKeyDeletionError extends ErrorResponse {
99
+ detail: string;
100
+ }
101
+ export interface GitHubTokenDeletionError extends ErrorResponse {
102
+ detail: string;
103
+ }
104
+ export type SecurityKeysError = ProviderNotFoundError | DuplicateKeyError | KeyNotFoundError | InvalidGitHubTokenFormatError | GitHubTokenValidationError | DuplicateGitHubTokenError | GitHubTokenNotFoundError | ProviderAPIKeyNotFoundError | ProviderAPIKeyDeletionError | GitHubTokenDeletionError | AuthenticationError | InternalServerError | ErrorResponse;
105
+ export type GitHubTokenResponse = CreateGitHubTokenResponse | GetGitHubTokenResponse | RevokeGitHubTokenResponse | SecurityKeysError;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * Security Keys Service Types
4
+ *
5
+ * This file contains type definitions for the Security Keys service,
6
+ * which manages provider API keys and GitHub tokens.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -52,8 +52,8 @@ async function demonstrateProjectFiltering() {
52
52
  }
53
53
 
54
54
  const paginatedResponse = await codeGuide.projects.getPaginatedProjects(paginatedRequest)
55
- console.log(`Page ${paginatedResponse.pagination.page} of ${paginatedResponse.pagination.total_pages}`)
56
- console.log(`Total projects with repositories: ${paginatedResponse.pagination.total}`)
55
+ console.log(`Page ${paginatedResponse.page} of ${paginatedResponse.total_pages}`)
56
+ console.log(`Total projects with repositories: ${paginatedResponse.count}`)
57
57
  console.log(`Showing ${paginatedResponse.data.length} projects:`)
58
58
 
59
59
  paginatedResponse.data.forEach((project: Project) => {
@@ -80,7 +80,7 @@ async function demonstrateProjectFiltering() {
80
80
  }
81
81
 
82
82
  const filteredProjects = await codeGuide.projects.getPaginatedProjects(combinedFilters)
83
- console.log(`Found ${filteredProjects.pagination.total} projects matching filters:`)
83
+ console.log(`Found ${filteredProjects.count} projects matching filters:`)
84
84
  filteredProjects.data.forEach((project: Project) => {
85
85
  console.log(`- ${project.title} (Updated: ${project.updated_at})`)
86
86
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeguide/core",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "Core package for code guidance with programmatic API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,6 +15,13 @@ import {
15
15
  CodespaceTaskDetailedResponse,
16
16
  CodespaceQuestionnaireRequest,
17
17
  CodespaceQuestionnaireResponse,
18
+ // Codespace Models Types
19
+ GetCodespaceModelsQuery,
20
+ GetCodespaceModelsResponse,
21
+ GetCodespaceModelResponse,
22
+ GetLLMModelProvidersResponse,
23
+ GetLLMModelProviderResponse,
24
+ GetModelsByProviderResponse,
18
25
  } from './codespace-types'
19
26
 
20
27
  export class CodespaceService extends BaseService {
@@ -91,6 +98,103 @@ export class CodespaceService extends BaseService {
91
98
  return this.get<CodespaceTaskDetailedResponse>(`/codespace/task/${codespaceTaskId}/detailed`)
92
99
  }
93
100
 
101
+ // ============================================================================
102
+ // Codespace Models Methods
103
+ // ============================================================================
104
+
105
+ /**
106
+ * Get all codespace models with optional filtering
107
+ *
108
+ * GET /api/codespace-models/models
109
+ *
110
+ * @param query - Optional query parameters for filtering
111
+ * @returns Promise resolving to array of codespace models with provider info
112
+ */
113
+ async getCodespaceModels(query?: GetCodespaceModelsQuery): Promise<GetCodespaceModelsResponse> {
114
+ const params = this.buildQueryParams(query)
115
+ const url = params
116
+ ? `/api/codespace-models/models?${params}`
117
+ : '/api/codespace-models/models'
118
+
119
+ return this.get<GetCodespaceModelsResponse>(url)
120
+ }
121
+
122
+ /**
123
+ * Get a specific codespace model by ID
124
+ *
125
+ * GET /api/codespace-models/models/{model_id}
126
+ *
127
+ * @param modelId - The UUID of the model
128
+ * @returns Promise resolving to the codespace model with provider info
129
+ */
130
+ async getCodespaceModel(modelId: string): Promise<GetCodespaceModelResponse> {
131
+ if (!modelId) {
132
+ throw new Error('model_id is required')
133
+ }
134
+ return this.get<GetCodespaceModelResponse>(`/api/codespace-models/models/${modelId}`)
135
+ }
136
+
137
+ /**
138
+ * Get all LLM model providers
139
+ *
140
+ * GET /api/codespace-models/providers
141
+ *
142
+ * @returns Promise resolving to array of LLM model providers
143
+ */
144
+ async getLLMModelProviders(): Promise<GetLLMModelProvidersResponse> {
145
+ return this.get<GetLLMModelProvidersResponse>('/api/codespace-models/providers')
146
+ }
147
+
148
+ /**
149
+ * Get a specific LLM model provider by ID
150
+ *
151
+ * GET /api/codespace-models/providers/{provider_id}
152
+ *
153
+ * @param providerId - The UUID of the provider
154
+ * @returns Promise resolving to the LLM model provider
155
+ */
156
+ async getLLMModelProvider(providerId: string): Promise<GetLLMModelProviderResponse> {
157
+ if (!providerId) {
158
+ throw new Error('provider_id is required')
159
+ }
160
+ return this.get<GetLLMModelProviderResponse>(`/api/codespace-models/providers/${providerId}`)
161
+ }
162
+
163
+ /**
164
+ * Get models by provider
165
+ *
166
+ * GET /api/codespace-models/providers/{provider_id}/models
167
+ *
168
+ * @param providerId - The UUID of the provider
169
+ * @returns Promise resolving to array of models from the specified provider
170
+ */
171
+ async getModelsByProvider(providerId: string): Promise<GetModelsByProviderResponse> {
172
+ if (!providerId) {
173
+ throw new Error('provider_id is required')
174
+ }
175
+ return this.get<GetModelsByProviderResponse>(`/api/codespace-models/providers/${providerId}/models`)
176
+ }
177
+
178
+ /**
179
+ * Build URL query parameters from an object
180
+ *
181
+ * @param params - The parameters object
182
+ * @returns URL query string
183
+ */
184
+ private buildQueryParams(params?: Record<string, any>): string {
185
+ if (!params) return ''
186
+
187
+ const searchParams = new URLSearchParams()
188
+
189
+ Object.entries(params).forEach(([key, value]) => {
190
+ if (value !== undefined && value !== null) {
191
+ searchParams.append(key, value.toString())
192
+ }
193
+ })
194
+
195
+ return searchParams.toString()
196
+ }
197
+
94
198
  private validateCodespaceTaskRequest(request: CreateCodespaceTaskRequestV2): void {
95
199
  if (!request.project_id) {
96
200
  throw new Error('project_id is required')
@@ -181,4 +181,68 @@ export interface CodespaceQuestionnaireResponse {
181
181
  success: boolean
182
182
  questions: string[]
183
183
  message: string
184
- }
184
+ }
185
+
186
+ // ============================================================================
187
+ // Codespace Models Types
188
+ // ============================================================================
189
+
190
+ export interface LLMModelProviderInDB {
191
+ id: string
192
+ created_at: string
193
+ name?: string
194
+ key?: string
195
+ logo_src?: string
196
+ }
197
+
198
+ export interface CodespaceModelInDB {
199
+ id: string
200
+ created_at: string
201
+ key?: string
202
+ name?: string
203
+ provider_id?: string
204
+ base_url?: string
205
+ completion_base_url?: string
206
+ execution_mode?: 'opencode' | 'claude-code' | 'docs-only' | 'implementation'
207
+ logo_src?: string
208
+ }
209
+
210
+ export interface CodespaceModelWithProvider extends CodespaceModelInDB {
211
+ provider?: LLMModelProviderInDB
212
+ }
213
+
214
+ export interface GetCodespaceModelsQuery {
215
+ provider_id?: string
216
+ execution_mode?: string
217
+ }
218
+
219
+ export interface GetCodespaceModelsResponse extends Array<CodespaceModelWithProvider> {}
220
+
221
+ export interface GetCodespaceModelResponse extends CodespaceModelWithProvider {}
222
+
223
+ export interface GetLLMModelProvidersResponse extends Array<LLMModelProviderInDB> {}
224
+
225
+ export interface GetLLMModelProviderResponse extends LLMModelProviderInDB {}
226
+
227
+ export interface GetModelsByProviderResponse extends Array<CodespaceModelInDB> {}
228
+
229
+ // ============================================================================
230
+ // Codespace Models Error Types
231
+ // ============================================================================
232
+
233
+ export interface CodespaceModelNotFoundError {
234
+ detail: string // "Codespace model with ID {model_id} not found"
235
+ }
236
+
237
+ export interface AuthenticationRequiredError {
238
+ detail: string // "Authentication required"
239
+ }
240
+
241
+ export interface CodespaceModelsFetchError {
242
+ detail: string // "Failed to fetch codespace models"
243
+ }
244
+
245
+ export type CodespaceModelsError =
246
+ | CodespaceModelNotFoundError
247
+ | AuthenticationRequiredError
248
+ | CodespaceModelsFetchError
@@ -14,5 +14,16 @@ export type {
14
14
  TechnicalDocument,
15
15
  GetProjectTasksByCodespaceResponse,
16
16
  CodespaceQuestionnaireRequest,
17
- CodespaceQuestionnaireResponse
17
+ CodespaceQuestionnaireResponse,
18
+ // Codespace Models Types
19
+ LLMModelProviderInDB,
20
+ CodespaceModelInDB,
21
+ CodespaceModelWithProvider,
22
+ GetCodespaceModelsQuery,
23
+ GetCodespaceModelsResponse,
24
+ GetCodespaceModelResponse,
25
+ GetLLMModelProvidersResponse,
26
+ GetLLMModelProviderResponse,
27
+ GetModelsByProviderResponse,
28
+ CodespaceModelsError
18
29
  } from './codespace-types'
package/services/index.ts CHANGED
@@ -18,6 +18,7 @@ export { SubscriptionService } from './subscriptions'
18
18
  export { CancellationFunnelService } from './cancellation-funnel'
19
19
  export { CodespaceService } from './codespace'
20
20
  export { ExternalTokenService } from './external-tokens'
21
+ export { SecurityKeysService } from './security-keys'
21
22
 
22
23
  // Re-export all types for convenience
23
24
  export * from './generation'
@@ -30,3 +31,4 @@ export * from './subscriptions'
30
31
  export * from './cancellation-funnel'
31
32
  export * from './codespace'
32
33
  export * from './external-tokens'
34
+ export * from './security-keys'
@@ -117,12 +117,10 @@ export interface PaginatedProjectsRequest {
117
117
  export interface PaginatedProjectsResponse {
118
118
  status: string
119
119
  data: Project[]
120
- pagination: {
121
- total: number
122
- page: number
123
- page_size: number
124
- total_pages: number
125
- }
120
+ count: number
121
+ page: number
122
+ page_size: number
123
+ total_pages: number
126
124
  }
127
125
 
128
126
  export interface ConnectRepositoryRequest {
@@ -0,0 +1,25 @@
1
+ export { SecurityKeysService } from './security-keys-service'
2
+
3
+ // Export all types
4
+ export * from './security-keys-types'
5
+
6
+ // Re-export commonly used types for convenience
7
+ export type {
8
+ SecurityKeyData,
9
+ ProviderAPIKeyData,
10
+ GitHubTokenData,
11
+ CreateProviderAPIKeyRequest,
12
+ CreateProviderAPIKeyResponse,
13
+ GetProviderAPIKeyResponse,
14
+ ListProviderAPIKeysResponse,
15
+ RevokeProviderAPIKeyResponse,
16
+ CreateGitHubTokenRequest,
17
+ CreateGitHubTokenResponse,
18
+ GetGitHubTokenResponse,
19
+ RevokeGitHubTokenResponse,
20
+ SuccessResponse,
21
+ ErrorResponse,
22
+ SecurityKeysError,
23
+ ProviderAPIKeyResponse,
24
+ GitHubTokenResponse
25
+ } from './security-keys-types'
@@ -0,0 +1,229 @@
1
+ import { BaseService } from '../base/base-service'
2
+ import { APIServiceConfig } from '../../types'
3
+ import {
4
+ CreateProviderAPIKeyRequest,
5
+ CreateProviderAPIKeyResponse,
6
+ GetProviderAPIKeyResponse,
7
+ ListProviderAPIKeysResponse,
8
+ RevokeProviderAPIKeyResponse,
9
+ CreateGitHubTokenRequest,
10
+ CreateGitHubTokenResponse,
11
+ GetGitHubTokenResponse,
12
+ RevokeGitHubTokenResponse,
13
+ SecurityKeysError
14
+ } from './security-keys-types'
15
+
16
+ /**
17
+ * Security Keys Service
18
+ *
19
+ * This service provides methods for managing security keys including:
20
+ * - Provider API Keys (OpenAI, Anthropic, etc.)
21
+ * - GitHub Tokens
22
+ *
23
+ * API endpoints match the actual backend implementation:
24
+ * - POST /security-keys/provider-api-key
25
+ * - GET /security-keys/provider-api-key/{provider_key}
26
+ * - GET /security-keys/provider-api-keys
27
+ * - DELETE /security-keys/provider-api-key/{provider_key}
28
+ * - POST /security-keys/github-token
29
+ * - GET /security-keys/github-token
30
+ * - DELETE /security-keys/github-token
31
+ */
32
+ export class SecurityKeysService extends BaseService {
33
+ constructor(config: APIServiceConfig) {
34
+ super(config)
35
+ }
36
+
37
+ // ============================================================================
38
+ // Provider API Key Methods
39
+ // ============================================================================
40
+
41
+ /**
42
+ * Save a new provider API key
43
+ *
44
+ * POST /security-keys/provider-api-key
45
+ *
46
+ * @param request - The provider API key creation request
47
+ * @returns Promise resolving to the created API key response
48
+ * @throws {Error} When provider_id or api_key is missing
49
+ */
50
+ async createProviderAPIKey(request: CreateProviderAPIKeyRequest): Promise<CreateProviderAPIKeyResponse> {
51
+ this.validateProviderAPIKeyRequest(request)
52
+
53
+ return this.post<CreateProviderAPIKeyResponse>(
54
+ '/security-keys/provider-api-key',
55
+ request
56
+ )
57
+ }
58
+
59
+ /**
60
+ * Get a provider API key by provider key
61
+ *
62
+ * GET /security-keys/provider-api-key/{provider_key}
63
+ *
64
+ * @param providerKey - The provider key (e.g., 'openai', 'anthropic')
65
+ * @param reveal - Whether to reveal the actual API key (default: false)
66
+ * @returns Promise resolving to the API key response
67
+ * @throws {Error} When provider_key is missing
68
+ */
69
+ async getProviderAPIKey(providerKey: string, reveal: boolean = false): Promise<GetProviderAPIKeyResponse> {
70
+ if (!providerKey) {
71
+ throw new Error('provider_key is required')
72
+ }
73
+
74
+ const url = reveal
75
+ ? `/security-keys/provider-api-key/${providerKey}?reveal=true`
76
+ : `/security-keys/provider-api-key/${providerKey}`
77
+
78
+ return this.get<GetProviderAPIKeyResponse>(url)
79
+ }
80
+
81
+ /**
82
+ * List all provider API keys
83
+ *
84
+ * GET /security-keys/provider-api-keys
85
+ *
86
+ * @param reveal - Whether to reveal the actual API keys (default: false)
87
+ * @returns Promise resolving to the list of all provider API keys
88
+ */
89
+ async listProviderAPIKeys(reveal: boolean = false): Promise<ListProviderAPIKeysResponse> {
90
+ const url = reveal
91
+ ? '/security-keys/provider-api-keys?reveal=true'
92
+ : '/security-keys/provider-api-keys'
93
+
94
+ return this.get<ListProviderAPIKeysResponse>(url)
95
+ }
96
+
97
+ /**
98
+ * Delete a provider API key by provider key
99
+ *
100
+ * DELETE /security-keys/provider-api-key/{provider_key}
101
+ *
102
+ * @param providerKey - The provider key (e.g., 'openai', 'anthropic')
103
+ * @returns Promise resolving to the deletion response
104
+ * @throws {Error} When provider_key is missing
105
+ */
106
+ async revokeProviderAPIKey(providerKey: string): Promise<RevokeProviderAPIKeyResponse> {
107
+ if (!providerKey) {
108
+ throw new Error('provider_key is required')
109
+ }
110
+
111
+ return this.delete<RevokeProviderAPIKeyResponse>(`/security-keys/provider-api-key/${providerKey}`)
112
+ }
113
+
114
+ // ============================================================================
115
+ // GitHub Token Methods
116
+ // ============================================================================
117
+
118
+ /**
119
+ * Save a new GitHub token
120
+ *
121
+ * POST /security-keys/github-token
122
+ *
123
+ * @param request - The GitHub token creation request
124
+ * @returns Promise resolving to the created GitHub token response
125
+ * @throws {Error} When github_token is missing or invalid
126
+ */
127
+ async createGitHubToken(request: CreateGitHubTokenRequest): Promise<CreateGitHubTokenResponse> {
128
+ this.validateGitHubTokenRequest(request)
129
+
130
+ return this.post<CreateGitHubTokenResponse>(
131
+ '/security-keys/github-token',
132
+ request
133
+ )
134
+ }
135
+
136
+ /**
137
+ * Get the GitHub token
138
+ *
139
+ * GET /security-keys/github-token
140
+ *
141
+ * @param reveal - Whether to reveal the actual token (default: false)
142
+ * @returns Promise resolving to the GitHub token response
143
+ */
144
+ async getGitHubToken(reveal: boolean = false): Promise<GetGitHubTokenResponse> {
145
+ const url = reveal
146
+ ? '/security-keys/github-token?reveal=true'
147
+ : '/security-keys/github-token'
148
+
149
+ return this.get<GetGitHubTokenResponse>(url)
150
+ }
151
+
152
+ /**
153
+ * Delete the GitHub token
154
+ *
155
+ * DELETE /security-keys/github-token
156
+ *
157
+ * @returns Promise resolving to the deletion response
158
+ */
159
+ async revokeGitHubToken(): Promise<RevokeGitHubTokenResponse> {
160
+ return this.delete<RevokeGitHubTokenResponse>('/security-keys/github-token')
161
+ }
162
+
163
+ // ============================================================================
164
+ // Utility Methods
165
+ // ============================================================================
166
+
167
+ /**
168
+ * Validate provider API key creation request
169
+ *
170
+ * @param request - The request to validate
171
+ * @throws {Error} When validation fails
172
+ */
173
+ private validateProviderAPIKeyRequest(request: CreateProviderAPIKeyRequest): void {
174
+ if (!request.provider_key) {
175
+ throw new Error('provider_key is required')
176
+ }
177
+ if (!request.api_key) {
178
+ throw new Error('api_key is required')
179
+ }
180
+ if (request.api_key.length < 10) {
181
+ throw new Error('api_key must be at least 10 characters long')
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Validate GitHub token creation request
187
+ * Matches the API validation for GitHub token formats
188
+ *
189
+ * @param request - The request to validate
190
+ * @throws {Error} When validation fails
191
+ */
192
+ private validateGitHubTokenRequest(request: CreateGitHubTokenRequest): void {
193
+ if (!request.github_token) {
194
+ throw new Error('github_token is required')
195
+ }
196
+
197
+ // Valid GitHub token prefixes according to the API
198
+ const validPrefixes = ['ghp_', 'gho_', 'ghu_', 'ghs_', 'ghr_', 'github_pat_']
199
+ const hasValidPrefix = validPrefixes.some(prefix => request.github_token.startsWith(prefix))
200
+
201
+ if (!hasValidPrefix) {
202
+ throw new Error('Invalid GitHub token format. Expected format: ghp_*, gho_*, ghu_*, ghs_*, or ghr_* followed by 36 characters')
203
+ }
204
+
205
+ // GitHub tokens should be at least 40 characters (prefix + 36 chars)
206
+ if (request.github_token.length < 40) {
207
+ throw new Error('GitHub token must be at least 40 characters long')
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Handle API errors consistently
213
+ *
214
+ * @param error - The error response from the API
215
+ * @throws {Error} With the API error message
216
+ */
217
+ private handleAPIError(error: any): never {
218
+ if (error.response?.data?.detail) {
219
+ throw new Error(error.response.data.detail)
220
+ }
221
+ if (error.response?.data?.message) {
222
+ throw new Error(error.response.data.message)
223
+ }
224
+ if (error.message) {
225
+ throw new Error(error.message)
226
+ }
227
+ throw new Error('An unexpected error occurred')
228
+ }
229
+ }