From 65bd9294321b532226f9ae6bc2815b96c2d44bc3 Mon Sep 17 00:00:00 2001 From: Cedric Meng <14017092+douyixuan@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:35:43 +0800 Subject: [PATCH 1/7] feat: enhance GitHub Copilot Device Flow with OAuth token management and API token retrieval - Fixed request header for managing OAuth tokens and retrieving API tokens. - Enhanced model definitions and added new models for better compatibility. --- src/main/presenter/githubCopilotDeviceFlow.ts | 189 ++++++- .../presenter/llmProviderPresenter/index.ts | 13 +- .../providers/githubCopilotProvider.ts | 470 +++++++++++------- src/main/presenter/oauthPresenter.ts | 110 ++-- 4 files changed, 539 insertions(+), 243 deletions(-) diff --git a/src/main/presenter/githubCopilotDeviceFlow.ts b/src/main/presenter/githubCopilotDeviceFlow.ts index e2571e494..a9ffeff58 100644 --- a/src/main/presenter/githubCopilotDeviceFlow.ts +++ b/src/main/presenter/githubCopilotDeviceFlow.ts @@ -25,9 +25,26 @@ export interface AccessTokenResponse { error_description?: string } +export interface CopilotTokenResponse { + token: string + expires_at: number + refresh_in?: number +} + +export interface ApiToken { + apiKey: string + expiresAt: Date +} + +export interface CopilotConfig { + oauthToken?: string + apiToken?: ApiToken +} + export class GitHubCopilotDeviceFlow { private config: DeviceFlowConfig private pollingInterval: NodeJS.Timeout | null = null + private oauthToken: string | null = null constructor(config: DeviceFlowConfig) { this.config = config @@ -49,8 +66,84 @@ export class GitHubCopilotDeviceFlow { return accessToken } catch (error) { - console.error('Failed to start device flow', error) - throw new Error('Failed to start device flow') + console.error('[GitHub Copilot] Device flow failed:', error) + throw new Error( + `Device flow authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}` + ) + } + } + + /** + * 获取 Copilot API token + * 使用OAuth token交换Copilot API token + */ + public async getCopilotToken(): Promise { + if (!this.oauthToken) { + throw new Error('No OAuth token available') + } + + // 使用OAuth token从GitHub API获取Copilot token + const tokenUrl = 'https://api.github.com/copilot_internal/v2/token' + + try { + const response = await fetch(tokenUrl, { + method: 'GET', + headers: { + Authorization: `Bearer ${this.oauthToken}`, + Accept: 'application/json', + 'User-Agent': 'DeepChat/1.0.0' + } + }) + + if (!response.ok) { + const errorText = await response.text().catch(() => '') + throw new Error( + `Failed to get Copilot token: ${response.status} ${response.statusText} - ${errorText}` + ) + } + + const data = (await response.json()) as { token: string; expires_at: number } + return data.token + } catch (error) { + console.error('[GitHub Copilot][DeviceFlow] Failed to get Copilot token:', error) + throw error + } + } + + /** + * 检查是否已经有有效的认证状态 + */ + public async checkExistingAuth(externalToken?: string): Promise { + try { + // 如果提供了外部 token,使用它 + if (externalToken) { + this.oauthToken = externalToken + + // 尝试获取 API token 来验证认证状态 + try { + await this.getCopilotToken() + return this.oauthToken + } catch (error) { + this.oauthToken = null + return null + } + } + + // 检查内部存储的 token + if (this.oauthToken) { + // 尝试获取 API token 来验证认证状态 + try { + await this.getCopilotToken() + return this.oauthToken! + } catch (error) { + this.oauthToken = null + } + } + + return null + } catch (error) { + console.warn('[GitHub Copilot][DeviceFlow] Error checking existing auth:', error) + return null } } @@ -64,23 +157,29 @@ export class GitHubCopilotDeviceFlow { scope: this.config.scope } - const response = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - 'User-Agent': 'DeepChat/1.0.0' - }, - body: JSON.stringify(body) - }) - - if (!response.ok) { - throw new Error(`Failed to request device code: ${response.status} ${response.statusText}`) - } + try { + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + 'User-Agent': 'DeepChat/1.0.0' + }, + body: JSON.stringify(body) + }) - const data = (await response.json()) as DeviceCodeResponse + if (!response.ok) { + const errorText = await response.text().catch(() => '') + throw new Error( + `Failed to request device code: ${response.status} ${response.statusText} - ${errorText}` + ) + } - return data + const data = (await response.json()) as DeviceCodeResponse + return data + } catch (error) { + throw error + } } /** @@ -364,11 +463,18 @@ export class GitHubCopilotDeviceFlow { const startTime = Date.now() const expiresAt = startTime + deviceCodeResponse.expires_in * 1000 let pollCount = 0 + let currentInterval = deviceCodeResponse.interval const poll = async () => { pollCount++ - if (pollCount > 50) { - reject(new Error('Poll count exceeded')) + + if (pollCount > 100) { + // 增加最大轮询次数 + if (this.pollingInterval) { + clearInterval(this.pollingInterval) + this.pollingInterval = null + } + reject(new Error('Maximum polling attempts exceeded')) return } @@ -412,7 +518,7 @@ export class GitHubCopilotDeviceFlow { // Increase polling interval if (this.pollingInterval) { clearInterval(this.pollingInterval) - this.pollingInterval = setInterval(poll, (deviceCodeResponse.interval + 5) * 1000) + this.pollingInterval = setInterval(poll, currentInterval * 1000) } return @@ -421,7 +527,7 @@ export class GitHubCopilotDeviceFlow { clearInterval(this.pollingInterval) this.pollingInterval = null } - reject(new Error('Device code expired')) + reject(new Error('Device code expired during polling')) return case 'access_denied': @@ -429,10 +535,14 @@ export class GitHubCopilotDeviceFlow { clearInterval(this.pollingInterval) this.pollingInterval = null } - reject(new Error('User denied access')) + reject(new Error('User denied access to GitHub Copilot')) return default: + if (this.pollingInterval) { + clearInterval(this.pollingInterval) + this.pollingInterval = null + } reject(new Error(`OAuth error: ${data.error_description || data.error}`)) return } @@ -446,8 +556,8 @@ export class GitHubCopilotDeviceFlow { resolve(data.access_token) return } - } catch { - // ignore + } catch (error) { + // 继续轮询,网络错误可能是暂时的 } } @@ -468,6 +578,13 @@ export class GitHubCopilotDeviceFlow { this.pollingInterval = null } } + + /** + * 清理资源 + */ + public dispose(): void { + this.stopPolling() + } } // GitHub Copilot Device Flow configuration @@ -486,5 +603,29 @@ export function createGitHubCopilotDeviceFlow(): GitHubCopilotDeviceFlow { scope: 'read:user read:org' } + console.log('[GitHub Copilot][DeviceFlow] Creating device flow with config:', { + clientId: clientId.substring(0, 10) + '...', + scope: config.scope + }) + return new GitHubCopilotDeviceFlow(config) } + +/** + * 创建一个全局的 GitHub Copilot Device Flow 实例 + */ +let globalDeviceFlowInstance: GitHubCopilotDeviceFlow | null = null + +export function getGlobalGitHubCopilotDeviceFlow(): GitHubCopilotDeviceFlow { + if (!globalDeviceFlowInstance) { + globalDeviceFlowInstance = createGitHubCopilotDeviceFlow() + } + return globalDeviceFlowInstance +} + +export function disposeGlobalGitHubCopilotDeviceFlow(): void { + if (globalDeviceFlowInstance) { + globalDeviceFlowInstance.dispose() + globalDeviceFlowInstance = null + } +} diff --git a/src/main/presenter/llmProviderPresenter/index.ts b/src/main/presenter/llmProviderPresenter/index.ts index 1fa8f7311..4b8ac7829 100644 --- a/src/main/presenter/llmProviderPresenter/index.ts +++ b/src/main/presenter/llmProviderPresenter/index.ts @@ -1531,8 +1531,17 @@ export class LLMProviderPresenter implements ILlmProviderPresenter { return { isOk: false, errorMsg: `Model test failed: ${errorMessage}` } } } else { - // 如果没有提供modelId,使用provider的check方法进行基础验证 - return await provider.check() + // 如果没有提供modelId,使用provider自己的check方法进行基本验证 + console.log( + `[LLMProviderPresenter] No modelId provided, using provider's own check method for ${providerId}` + ) + try { + return await provider.check() + } catch (error) { + console.error(`Provider ${providerId} check failed:`, error) + const errorMessage = error instanceof Error ? error.message : String(error) + return { isOk: false, errorMsg: `Provider check failed: ${errorMessage}` } + } } } catch (error) { console.error(`Provider ${providerId} check failed:`, error) diff --git a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts index 2c3b2d636..e46e808b7 100644 --- a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts +++ b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts @@ -10,6 +10,10 @@ import { } from '@shared/presenter' import { BaseLLMProvider, SUMMARY_TITLES_PROMPT } from '../baseProvider' import { HttpsProxyAgent } from 'https-proxy-agent' +import { + getGlobalGitHubCopilotDeviceFlow, + GitHubCopilotDeviceFlow +} from '../../githubCopilotDeviceFlow' // 扩展RequestInit类型以支持agent属性 interface RequestInitWithAgent extends RequestInit { @@ -28,62 +32,38 @@ export class GithubCopilotProvider extends BaseLLMProvider { private tokenExpiresAt: number = 0 private baseApiUrl = 'https://api.githubcopilot.com' private tokenUrl = 'https://api.github.com/copilot_internal/v2/token' + private deviceFlow: GitHubCopilotDeviceFlow | null = null constructor(provider: LLM_PROVIDER, configPresenter: IConfigPresenter) { super(provider, configPresenter) - - console.log('🎯 [GitHub Copilot] Constructor called') - console.log(` Base API URL: ${this.baseApiUrl}`) - console.log(` Token URL: ${this.tokenUrl}`) - console.log(` Provider config:`, { - id: provider.id, - name: provider.name, - enable: provider.enable, - hasApiKey: !!provider.apiKey, - apiKeyLength: provider.apiKey?.length || 0 - }) - this.init() } protected async init() { - console.log('🚀 [GitHub Copilot] Starting provider initialization...') - console.log(` Provider enabled: ${this.provider.enable}`) - console.log(` Provider name: ${this.provider.name}`) - console.log(` Provider ID: ${this.provider.id}`) - if (this.provider.enable) { try { - console.log('📋 [GitHub Copilot] Setting initialized flag...') this.isInitialized = true + this.deviceFlow = getGlobalGitHubCopilotDeviceFlow() - console.log('📚 [GitHub Copilot] Fetching models list...') - // 始终加载模型列表,不依赖于token状态 - await this.fetchModels() + // 检查现有认证状态 + if (this.provider.apiKey) { + const existingToken = await this.deviceFlow.checkExistingAuth(this.provider.apiKey) + if (!existingToken) { + this.provider.apiKey = '' + } + } - console.log('🔧 [GitHub Copilot] Auto-enabling models if needed...') + await this.fetchModels() await this.autoEnableModelsIfNeeded() - - console.info('✅ [GitHub Copilot] Provider initialized successfully:', this.provider.name) + console.log(`[GitHub Copilot] Initialized successfully`) } catch (error) { - console.warn( - '❌ [GitHub Copilot] Provider initialization failed:', - this.provider.name, - error - ) - console.error(' Initialization error details:', error) - - // 即使初始化失败,也要确保模型列表可用 + console.warn(`[GitHub Copilot] Initialization failed:`, error) try { - console.log('🔄 [GitHub Copilot] Trying to fetch models after init error...') await this.fetchModels() - console.log('✅ [GitHub Copilot] Models fetched successfully after init error') } catch (modelError) { - console.warn('❌ [GitHub Copilot] Failed to fetch models after init error:', modelError) + console.warn(`[GitHub Copilot] Failed to fetch models:`, modelError) } } - } else { - console.log('⏸️ [GitHub Copilot] Provider is disabled, skipping initialization') } } @@ -92,21 +72,23 @@ export class GithubCopilotProvider extends BaseLLMProvider { } private async getCopilotToken(): Promise { - console.log('🔍 [GitHub Copilot] Starting getCopilotToken process...') + // 优先使用设备流获取 token + if (this.deviceFlow) { + try { + return await this.deviceFlow.getCopilotToken() + } catch (error) { + console.warn('[GitHub Copilot] Device flow failed, using provider API key') + } + } // 检查token是否过期 if (this.copilotToken && Date.now() < this.tokenExpiresAt) { - console.log('✅ [GitHub Copilot] Using cached token (not expired)') - console.log(` Token expires at: ${new Date(this.tokenExpiresAt).toISOString()}`) - console.log(` Current time: ${new Date().toISOString()}`) return this.copilotToken } - console.log('🔄 [GitHub Copilot] Need to fetch new Copilot token') - console.log( - ` Provider API Key: ${this.provider.apiKey ? 'EXISTS (length: ' + this.provider.apiKey.length + ')' : 'NOT SET'}` - ) - console.log(` Token URL: ${this.tokenUrl}`) + if (!this.provider.apiKey) { + throw new Error('No GitHub OAuth token available. Please use device flow authentication.') + } // 获取新的token const headers: Record = { @@ -115,15 +97,6 @@ export class GithubCopilotProvider extends BaseLLMProvider { 'User-Agent': 'DeepChat/1.0.0' } - console.log('📋 [GitHub Copilot] Request headers:') - console.log( - ' Authorization:', - headers.Authorization ? `Bearer ${this.provider.apiKey?.substring(0, 10)}...` : 'NOT SET' - ) - console.log(' Accept:', headers.Accept) - console.log(' User-Agent:', headers['User-Agent']) - console.log(' X-GitHub-Api-Version:', headers['X-GitHub-Api-Version']) - const requestOptions: RequestInitWithAgent = { method: 'GET', headers @@ -132,52 +105,23 @@ export class GithubCopilotProvider extends BaseLLMProvider { // 添加代理支持 const proxyUrl = proxyConfig.getProxyUrl() if (proxyUrl) { - console.log('🌐 [GitHub Copilot] Using proxy:', proxyUrl) const agent = new HttpsProxyAgent(proxyUrl) requestOptions.agent = agent - } else { - console.log('🌐 [GitHub Copilot] No proxy configured') } - console.log('📤 [GitHub Copilot] Making request to GitHub Copilot API...') - console.log(` Method: ${requestOptions.method}`) - console.log(` URL: ${this.tokenUrl}`) - try { const response = await fetch(this.tokenUrl, requestOptions) - console.log('📥 [GitHub Copilot] Received response:') - console.log(` Status: ${response.status} ${response.statusText}`) - console.log(` OK: ${response.ok}`) - console.log(' Response headers:') - response.headers.forEach((value, key) => { - console.log(` ${key}: ${value}`) - }) - if (!response.ok) { let errorMessage = `Failed to get Copilot token: ${response.status} ${response.statusText}` - console.log('❌ [GitHub Copilot] Request failed!') - console.log(` Error status: ${response.status}`) - console.log(` Error text: ${response.statusText}`) - - // 尝试读取响应体以获得更多错误信息 - try { - const errorBody = await response.text() - console.log(` Error body: ${errorBody}`) - } catch (bodyError) { - console.log(` Could not read error body: ${bodyError}`) - } - // 提供更具体的错误信息和解决建议 if (response.status === 404) { errorMessage = `GitHub Copilot 访问被拒绝 (404)。请检查: 1. 您的 GitHub 账户是否有有效的 GitHub Copilot 订阅 2. OAuth token 权限不足 - 需要 'read:org' 权限访问 Copilot API 3. 请重新进行 OAuth 登录以获取正确的权限范围 -4. 访问 https://github.com/features/copilot 检查订阅状态 - -注意:DeepChat 现在需要 'read:user' 和 'read:org' 权限才能访问 GitHub Copilot API。` +4. 访问 https://github.com/features/copilot 检查订阅状态` } else if (response.status === 401) { errorMessage = `GitHub OAuth token 无效或已过期 (401)。请重新登录授权并确保获取了正确的权限范围。` } else if (response.status === 403) { @@ -191,52 +135,203 @@ export class GithubCopilotProvider extends BaseLLMProvider { throw new Error(errorMessage) } - console.log('✅ [GitHub Copilot] Successfully received response, parsing JSON...') const data: CopilotTokenResponse = await response.json() - - console.log('📊 [GitHub Copilot] Token response data:') - console.log(` Token: ${data.token ? data.token.substring(0, 20) + '...' : 'NOT PRESENT'}`) - console.log( - ` Expires at: ${data.expires_at} (${new Date(data.expires_at * 1000).toISOString()})` - ) - console.log(` Refresh in: ${data.refresh_in || 'N/A'}`) - this.copilotToken = data.token this.tokenExpiresAt = data.expires_at * 1000 // 转换为毫秒 - console.log('💾 [GitHub Copilot] Token cached successfully') return this.copilotToken } catch (error) { - console.error('💥 [GitHub Copilot] Error getting Copilot token:', error) - console.error( - ' Error type:', - error instanceof Error ? error.constructor.name : typeof error - ) - console.error(' Error message:', error instanceof Error ? error.message : error) - if (error instanceof Error && error.stack) { - console.error(' Stack trace:', error.stack) - } + console.error('[GitHub Copilot] Error getting Copilot token:', error) throw error } } protected async fetchProviderModels(): Promise { - // Use aggregated Provider DB as authoritative source for model list - const dbModels = this.configPresenter.getDbProviderModels(this.provider.id).map((m) => ({ - id: m.id, - name: m.name, - group: m.group || 'GitHub Copilot', - providerId: this.provider.id, - isCustom: false, - contextLength: m.contextLength, - maxTokens: m.maxTokens, - vision: m.vision || false, - functionCall: m.functionCall || false, - reasoning: m.reasoning || false, - ...(m.type ? { type: m.type } : {}) - })) + // GitHub Copilot 支持的模型列表 + const models: MODEL_META[] = [ + { + id: 'gpt-5', + name: 'GPT-5', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 128000, + maxTokens: 8192, + vision: true, + functionCall: true, + reasoning: false + }, + { + id: 'gpt-5-mini', + name: 'GPT-5 mini', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 128000, + maxTokens: 16384, + vision: true, + functionCall: true, + reasoning: false + }, + { + id: 'gpt-4.1', + name: 'GPT-4.1', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 64000, + maxTokens: 4096, + vision: true, + functionCall: true, + reasoning: false + }, + { + id: 'gpt-4o-2024-05-13', + name: 'GPT-4o', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 64000, + maxTokens: 4096, + vision: true, + functionCall: true, + reasoning: false + }, + { + id: 'gpt-4', + name: 'GPT-4', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 32768, + maxTokens: 4096, + vision: false, + functionCall: true, + reasoning: false + }, + { + id: 'gpt-3.5-turbo', + name: 'GPT-3.5 Turbo', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 12288, + maxTokens: 4096, + vision: false, + functionCall: true, + reasoning: false + }, + { + id: 'o1', + name: 'o1', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 20000, + maxTokens: 32768, + vision: false, + functionCall: false, + reasoning: true + }, + { + id: 'o3-mini', + name: 'o3-mini', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 64000, + maxTokens: 65536, + vision: false, + functionCall: false, + reasoning: true + }, + { + id: 'claude-sonnet-4', + name: 'Claude Sonnet 4', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 200000, + maxTokens: 8192, + vision: true, + functionCall: true, + reasoning: false + }, + { + id: 'claude-sonnet-4.5', + name: 'Claude Sonnet 4.5', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 200000, + maxTokens: 8192, + vision: true, + functionCall: true, + reasoning: false + }, + { + id: 'claude-3.5-sonnet', + name: 'Claude Sonnet 3.5', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 200000, + maxTokens: 8192, + vision: true, + functionCall: true, + reasoning: false + }, + { + id: 'claude-3.7-sonnet', + name: 'Claude Sonnet 3.7', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 90000, + maxTokens: 8192, + vision: true, + functionCall: true, + reasoning: false + }, + { + id: 'claude-3.7-sonnet-thought', + name: 'Claude Sonnet 3.7 Thinking', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 90000, + maxTokens: 8192, + vision: true, + functionCall: true, + reasoning: false + }, + { + id: 'gemini-2.5-pro', + name: 'Gemini 2.5 Pro', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 128000, + maxTokens: 8192, + vision: true, + functionCall: true, + reasoning: false + }, + { + id: 'gemini-2.0-flash-001', + name: 'Gemini 2.0 Flash', + group: 'GitHub Copilot', + providerId: this.provider.id, + isCustom: false, + contextLength: 128000, + maxTokens: 8192, + vision: true, + functionCall: true, + reasoning: false + } + ] - return dbModels + return models } private formatMessages(messages: ChatMessage[]): Array<{ role: string; content: string }> { @@ -251,7 +346,7 @@ export class GithubCopilotProvider extends BaseLLMProvider { modelId: string, _modelConfig: ModelConfig, temperature: number, - maxTokens: number, + _maxTokens: number, tools: MCPToolDefinition[] ): AsyncGenerator { if (!modelId) throw new Error('Model ID is required') @@ -259,18 +354,15 @@ export class GithubCopilotProvider extends BaseLLMProvider { const token = await this.getCopilotToken() const formattedMessages = this.formatMessages(messages) - // Build request body with standard parameters - const requestBody: any = { + const requestBody = { + intent: true, + n: 1, model: modelId, messages: formattedMessages, - max_tokens: maxTokens || 4096, stream: true, - temperature: temperature ?? 0.7 - } - - // Add tools when available - if (tools && tools.length > 0) { - requestBody.tools = tools + temperature: temperature ?? 0.7, + max_tokens: _maxTokens || 4096, + ...(tools && tools.length > 0 && { tools }) } const headers: Record = { @@ -278,8 +370,8 @@ export class GithubCopilotProvider extends BaseLLMProvider { 'Content-Type': 'application/json', Accept: 'text/event-stream', 'User-Agent': 'DeepChat/1.0.0', - 'editor-version': 'vscode/1.97.2', - 'editor-plugin-version': 'copilot.vim/1.16.0' + 'Editor-Version': 'DeepChat/1.0.0', + 'Copilot-Integration-Id': 'vscode-chat' } // 添加详细的请求日志 @@ -309,21 +401,32 @@ export class GithubCopilotProvider extends BaseLLMProvider { console.log(` OK: ${response.ok}`) if (!response.ok) { - console.log('❌ [GitHub Copilot] Stream request failed!') - console.log(` Request URL: ${this.baseApiUrl}/chat/completions`) - console.log(` Request Method: POST`) - console.log(` Request Headers:`, headers) - console.log(` Request Body:`, JSON.stringify(requestBody, null, 2)) - - // 尝试读取错误响应 + let errorBody = '' try { - const errorText = await response.text() - console.log(` Error Response Body: ${errorText}`) + errorBody = await response.text() } catch (e) { - console.log(` Could not read error response: ${e}`) + // ignore + } + + // 特殊处理403错误 + if (response.status === 403) { + throw new Error( + `GitHub Copilot 访问被拒绝 (403)。\n\n可能的原因:\n` + + `1. GitHub Copilot 订阅已过期或未激活\n` + + `2. 需要重新认证以获取正确的访问权限\n` + + `3. API访问策略已更新,需要使用最新的认证方式\n` + + `4. 您的账户可能没有访问此API的权限\n\n` + + `建议解决方案:\n` + + `- 访问 https://github.com/settings/copilot 检查订阅状态\n` + + `- 在DeepChat设置中重新进行 GitHub Copilot 登录\n` + + `- 确保您的 GitHub 账户有有效的 Copilot 订阅\n` + + `- 如果是企业账户,请联系管理员确认访问权限` + ) } - throw new Error(`GitHub Copilot API error: ${response.status} ${response.statusText}`) + throw new Error( + `GitHub Copilot API error: ${response.status} ${response.statusText} - ${errorBody}` + ) } if (!response.body) { @@ -408,18 +511,19 @@ export class GithubCopilotProvider extends BaseLLMProvider { messages: ChatMessage[], modelId: string, temperature?: number, - maxTokens?: number + _maxTokens?: number ): Promise { if (!modelId) throw new Error('Model ID is required') try { const token = await this.getCopilotToken() const formattedMessages = this.formatMessages(messages) - // Build request body with standard parameters - const requestBody: any = { + const requestBody = { + intent: true, + n: 1, model: modelId, messages: formattedMessages, - max_tokens: maxTokens || 4096, + max_tokens: _maxTokens || 4096, stream: false, temperature: temperature ?? 0.7 } @@ -429,8 +533,8 @@ export class GithubCopilotProvider extends BaseLLMProvider { 'Content-Type': 'application/json', Accept: 'application/json', 'User-Agent': 'DeepChat/1.0.0', - 'editor-version': 'vscode/1.97.2', - 'editor-plugin-version': 'copilot.vim/1.16.0' + 'Editor-Version': 'DeepChat/1.0.0', + 'Copilot-Integration-Id': 'vscode-chat' } // 添加详细的请求日志 @@ -460,21 +564,32 @@ export class GithubCopilotProvider extends BaseLLMProvider { console.log(` OK: ${response.ok}`) if (!response.ok) { - console.log('❌ [GitHub Copilot] Completion request failed!') - console.log(` Request URL: ${this.baseApiUrl}/chat/completions`) - console.log(` Request Method: POST`) - console.log(` Request Headers:`, headers) - console.log(` Request Body:`, JSON.stringify(requestBody, null, 2)) - - // 尝试读取错误响应 + let errorBody = '' try { - const errorText = await response.text() - console.log(` Error Response Body: ${errorText}`) + errorBody = await response.text() } catch (e) { - console.log(` Could not read error response: ${e}`) + // ignore } - throw new Error(`GitHub Copilot API error: ${response.status} ${response.statusText}`) + // 特殊处理403错误 + if (response.status === 403) { + throw new Error( + `GitHub Copilot 访问被拒绝 (403)。\n\n可能的原因:\n` + + `1. GitHub Copilot 订阅已过期或未激活\n` + + `2. 需要重新认证以获取正确的访问权限\n` + + `3. API访问策略已更新,需要使用最新的认证方式\n` + + `4. 您的账户可能没有访问此API的权限\n\n` + + `建议解决方案:\n` + + `- 访问 https://github.com/settings/copilot 检查订阅状态\n` + + `- 在DeepChat设置中重新进行 GitHub Copilot 登录\n` + + `- 确保您的 GitHub 账户有有效的 Copilot 订阅\n` + + `- 如果是企业账户,请联系管理员确认访问权限` + ) + } + + throw new Error( + `GitHub Copilot API error: ${response.status} ${response.statusText} - ${errorBody}` + ) } const data = await response.json() @@ -540,48 +655,41 @@ export class GithubCopilotProvider extends BaseLLMProvider { } async check(): Promise<{ isOk: boolean; errorMsg: string | null }> { - console.log('🔍 [GitHub Copilot] Starting provider check...') - console.log(` Provider ID: ${this.provider.id}`) - console.log(` Provider Name: ${this.provider.name}`) - console.log(` Provider Enabled: ${this.provider.enable}`) - try { // 检查是否有 API Key - console.log('🔑 [GitHub Copilot] Checking API Key...') if (!this.provider.apiKey || !this.provider.apiKey.trim()) { - console.log('❌ [GitHub Copilot] No API Key found') return { isOk: false, errorMsg: '请先使用 GitHub OAuth 登录以获取访问令牌' } } - console.log(`✅ [GitHub Copilot] API Key exists (length: ${this.provider.apiKey.length})`) - console.log(` API Key preview: ${this.provider.apiKey.substring(0, 20)}...`) - - console.log( - '🎯 [GitHub Copilot] Attempting to get Copilot token (this will test the connection)...' - ) await this.getCopilotToken() - - console.log('✅ [GitHub Copilot] Provider check successful!') return { isOk: true, errorMsg: null } } catch (error) { - console.log('❌ [GitHub Copilot] Provider check failed!') - console.error(' Error details:', error) - let errorMsg = error instanceof Error ? error.message : 'Unknown error' - // 如果是网络错误,提供更友好的提示 - if (errorMsg.includes('fetch failed') || errorMsg.includes('network')) { + // 分析错误类型并提供更具体的建议 + if (errorMsg.includes('404')) { + errorMsg = `GitHub Copilot 访问被拒绝 (404)。请检查: +1. 您的 GitHub 账户是否有有效的 GitHub Copilot 订阅 +2. 访问 https://github.com/features/copilot 检查订阅状态 +3. 如果是组织账户,请确保组织已启用 Copilot 并且您有访问权限` + } else if (errorMsg.includes('401')) { + errorMsg = `GitHub OAuth token 无效或已过期 (401)。请重新登录授权并确保获取了正确的权限范围。` + } else if (errorMsg.includes('403')) { + errorMsg = `GitHub Copilot 访问被禁止 (403)。请检查: +1. 您的 GitHub Copilot 订阅是否有效且处于活跃状态 +2. 是否达到了 API 使用限制 +3. OAuth token 是否包含 'read:org' 权限范围 +4. 如果是组织账户,请确保组织已启用 Copilot 并且您有访问权限` + } else if (errorMsg.includes('fetch failed') || errorMsg.includes('network')) { errorMsg = `网络连接失败。请检查: 1. 网络连接是否正常 2. 代理设置是否正确 3. 防火墙是否阻止了 GitHub API 访问` } - console.log(` Final error message: ${errorMsg}`) - return { isOk: false, errorMsg diff --git a/src/main/presenter/oauthPresenter.ts b/src/main/presenter/oauthPresenter.ts index 56dad2a68..bd33c30db 100644 --- a/src/main/presenter/oauthPresenter.ts +++ b/src/main/presenter/oauthPresenter.ts @@ -3,8 +3,9 @@ import { presenter } from '.' import * as http from 'http' import { URL } from 'url' import { createGitHubCopilotOAuth } from './githubCopilotOAuth' -import { createGitHubCopilotDeviceFlow } from './githubCopilotDeviceFlow' +import { getGlobalGitHubCopilotDeviceFlow } from './githubCopilotDeviceFlow' import { createAnthropicOAuth } from './anthropicOAuth' +import { eventBus } from '@/eventbus' export interface OAuthConfig { authUrl: string @@ -44,40 +45,42 @@ export class OAuthPresenter { */ async startGitHubCopilotDeviceFlowLogin(providerId: string): Promise { try { - console.log('Starting GitHub Copilot Device Flow login for provider:', providerId) + const githubDeviceFlow = getGlobalGitHubCopilotDeviceFlow() + const provider = presenter.configPresenter.getProviderById(providerId) - // Use dedicated GitHub Copilot Device Flow implementation - console.log('Creating GitHub Device Flow instance...') - const githubDeviceFlow = createGitHubCopilotDeviceFlow() + // 首先检查现有认证状态 + if (provider && provider.apiKey) { + const existingToken = await githubDeviceFlow.checkExistingAuth(provider.apiKey) + if (existingToken) { + return true + } + } - // Start Device Flow login - console.log('Starting Device Flow login...') + // 开始Device Flow登录 const accessToken = await githubDeviceFlow.startDeviceFlow() - console.log('Received access token:', accessToken ? 'SUCCESS' : 'FAILED') // Validate token console.log('Validating access token...') const isValid = await this.validateGitHubAccessToken(accessToken) - console.log('Token validation result:', isValid) - if (!isValid) { throw new Error('Obtained access token is invalid') } - // Save access token to provider configuration - console.log('Saving access token to provider configuration...') - const provider = presenter.configPresenter.getProviderById(providerId) + // 保存访问令牌到provider配置 if (provider) { provider.apiKey = accessToken presenter.configPresenter.setProviderById(providerId, provider) - console.log('Access token saved successfully') + console.log('[GitHub Copilot] Device Flow login completed successfully') + + // 触发provider更新事件,通知前端刷新UI + eventBus.emit('providerUpdated', { providerId }) } else { - console.warn('Provider not found:', providerId) + throw new Error(`Provider ${providerId} not found`) } return true } catch (error) { - console.error('GitHub Copilot Device Flow login failed:', error) + console.error('[GitHub Copilot] Device Flow login failed:', error) return false } } @@ -87,49 +90,84 @@ export class OAuthPresenter { */ async startGitHubCopilotLogin(providerId: string): Promise { try { - console.log('Starting GitHub Copilot OAuth login for provider:', providerId) + console.log( + '[GitHub Copilot][OAuth] Starting traditional OAuth login for provider:', + providerId + ) - // Use dedicated GitHub Copilot OAuth implementation - console.log('Creating GitHub OAuth instance...') + // 使用专门的GitHub Copilot OAuth实现 + console.log('[GitHub Copilot][OAuth] Creating GitHub OAuth instance...') const githubOAuth = createGitHubCopilotOAuth() - // Start OAuth login - console.log('Starting OAuth login flow...') + // 开始OAuth登录 + console.log('[GitHub Copilot][OAuth] Starting OAuth login flow...') const authCode = await githubOAuth.startLogin() - console.log('Received auth code:', authCode ? 'SUCCESS' : 'FAILED') + console.log( + '[GitHub Copilot][OAuth] OAuth login completed, auth code received:', + authCode ? 'SUCCESS' : 'FAILED' + ) + + if (authCode) { + console.log(`[GitHub Copilot][OAuth] Auth code preview: ${authCode.substring(0, 10)}...`) + } // 用授权码交换访问令牌 - console.log('Exchanging auth code for access token...') + console.log('[GitHub Copilot][OAuth] Exchanging auth code for access token...') const accessToken = await githubOAuth.exchangeCodeForToken(authCode) - console.log('Received access token:', accessToken ? 'SUCCESS' : 'FAILED') + console.log( + '[GitHub Copilot][OAuth] Token exchange completed, access token received:', + accessToken ? 'SUCCESS' : 'FAILED' + ) + + if (accessToken) { + console.log( + `[GitHub Copilot][OAuth] Access token preview: ${accessToken.substring(0, 10)}...` + ) + } // Validate token - console.log('Validating access token...') + console.log('[GitHub Copilot][OAuth] Validating access token...') const isValid = await githubOAuth.validateToken(accessToken) - console.log('Token validation result:', isValid) + console.log('[GitHub Copilot][OAuth] Token validation result:', isValid) if (!isValid) { - throw new Error('Obtained access token is invalid') + console.error('[GitHub Copilot][OAuth] Token validation failed - token is invalid') + throw new Error('获取的访问令牌无效') } - // Save access token to provider configuration - console.log('Saving access token to provider configuration...') + // 保存访问令牌到provider配置 + console.log('[GitHub Copilot][OAuth] Saving access token to provider configuration...') const provider = presenter.configPresenter.getProviderById(providerId) if (provider) { provider.apiKey = accessToken presenter.configPresenter.setProviderById(providerId, provider) - console.log('Access token saved successfully') + console.log( + '[GitHub Copilot][OAuth] Access token saved successfully to provider:', + providerId + ) + console.log('[GitHub Copilot][OAuth] Traditional OAuth login completed successfully') + + // 触发provider更新事件,通知前端刷新UI + eventBus.emit('providerUpdated', { providerId }) } else { - console.warn('Provider not found:', providerId) + console.error('[GitHub Copilot][OAuth] Provider not found:', providerId) + throw new Error(`Provider ${providerId} not found`) } - console.log('GitHub Copilot OAuth login completed successfully') return true } catch (error) { - console.error('GitHub Copilot OAuth login failed:') - console.error('Error type:', error instanceof Error ? error.constructor.name : typeof error) - console.error('Error message:', error instanceof Error ? error.message : error) - console.error('Error stack:', error instanceof Error ? error.stack : 'No stack trace') + console.error('[GitHub Copilot][OAuth][ERROR] Traditional OAuth login failed:') + console.error( + '[GitHub Copilot][OAuth][ERROR] Error type:', + error instanceof Error ? error.constructor.name : typeof error + ) + console.error( + '[GitHub Copilot][OAuth][ERROR] Error message:', + error instanceof Error ? error.message : error + ) + if (error instanceof Error && error.stack) { + console.error('[GitHub Copilot][OAuth][ERROR] Stack trace:', error.stack) + } return false } } From 5546b5789197e5db1aabb093383fd42ef2001312 Mon Sep 17 00:00:00 2001 From: Cedric Meng <14017092+douyixuan@users.noreply.github.com> Date: Sat, 18 Oct 2025 20:42:34 +0800 Subject: [PATCH 2/7] fix: remove privacy related log --- src/main/presenter/githubCopilotDeviceFlow.ts | 2 +- src/main/presenter/githubCopilotOAuth.ts | 9 +++------ src/main/presenter/llmProviderPresenter/oauthHelper.ts | 2 +- .../providers/githubCopilotProvider.ts | 4 ++-- src/main/presenter/oauthPresenter.ts | 8 +++----- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/main/presenter/githubCopilotDeviceFlow.ts b/src/main/presenter/githubCopilotDeviceFlow.ts index a9ffeff58..119958b8c 100644 --- a/src/main/presenter/githubCopilotDeviceFlow.ts +++ b/src/main/presenter/githubCopilotDeviceFlow.ts @@ -604,7 +604,7 @@ export function createGitHubCopilotDeviceFlow(): GitHubCopilotDeviceFlow { } console.log('[GitHub Copilot][DeviceFlow] Creating device flow with config:', { - clientId: clientId.substring(0, 10) + '...', + clientIdConfigured: !!clientId, scope: config.scope }) diff --git a/src/main/presenter/githubCopilotOAuth.ts b/src/main/presenter/githubCopilotOAuth.ts index 1c17c08ba..d40b86040 100644 --- a/src/main/presenter/githubCopilotOAuth.ts +++ b/src/main/presenter/githubCopilotOAuth.ts @@ -131,7 +131,7 @@ export class GitHubCopilotOAuth { this.closeWindow() reject(new Error(`GitHub authorization failed: ${error}`)) } else if (code) { - console.log('OAuth success, received code:', code) + console.log('OAuth success, received authorization code') this.closeWindow() resolve(code) } else { @@ -266,13 +266,10 @@ export function createGitHubCopilotOAuth(): GitHubCopilotOAuth { } if (is.dev) { console.log('Final OAuth config:', { - clientId: - config.clientId.substring(0, 4) + - '****' + - config.clientId.substring(config.clientId.length - 4), + clientIdConfigured: !!config.clientId, redirectUri: config.redirectUri, scope: config.scope, - clientSecretLength: config.clientSecret.length + clientSecretConfigured: !!config.clientSecret }) } diff --git a/src/main/presenter/llmProviderPresenter/oauthHelper.ts b/src/main/presenter/llmProviderPresenter/oauthHelper.ts index 45248e9bf..48fb4e2b7 100644 --- a/src/main/presenter/llmProviderPresenter/oauthHelper.ts +++ b/src/main/presenter/llmProviderPresenter/oauthHelper.ts @@ -102,7 +102,7 @@ export class OAuthHelper { eventBus.send(CONFIG_EVENTS.OAUTH_LOGIN_ERROR, SendTarget.ALL_WINDOWS, error) reject(new Error(`OAuth授权失败: ${error}`)) } else if (code) { - console.log('OAuth success, received code:', code) + console.log('OAuth success, received authorization code') eventBus.send(CONFIG_EVENTS.OAUTH_LOGIN_SUCCESS, SendTarget.ALL_WINDOWS, code) resolve(code) } else { diff --git a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts index e46e808b7..01c535d3c 100644 --- a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts +++ b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts @@ -378,7 +378,7 @@ export class GithubCopilotProvider extends BaseLLMProvider { console.log('📤 [GitHub Copilot] Sending stream request:') console.log(` URL: ${this.baseApiUrl}/chat/completions`) console.log(` Model: ${modelId}`) - console.log(` Headers:`, headers) + console.log(` Headers: ${Object.keys(headers).join(', ')}`) console.log(` Request Body:`, JSON.stringify(requestBody, null, 2)) const requestOptions: RequestInitWithAgent = { @@ -541,7 +541,7 @@ export class GithubCopilotProvider extends BaseLLMProvider { console.log('📤 [GitHub Copilot] Sending completion request:') console.log(` URL: ${this.baseApiUrl}/chat/completions`) console.log(` Model: ${modelId}`) - console.log(` Headers:`, headers) + console.log(` Headers: ${Object.keys(headers).join(', ')}`) console.log(` Request Body:`, JSON.stringify(requestBody, null, 2)) const requestOptions: RequestInitWithAgent = { diff --git a/src/main/presenter/oauthPresenter.ts b/src/main/presenter/oauthPresenter.ts index bd33c30db..0ab191a68 100644 --- a/src/main/presenter/oauthPresenter.ts +++ b/src/main/presenter/oauthPresenter.ts @@ -108,7 +108,7 @@ export class OAuthPresenter { ) if (authCode) { - console.log(`[GitHub Copilot][OAuth] Auth code preview: ${authCode.substring(0, 10)}...`) + console.log('[GitHub Copilot][OAuth] Auth code received successfully') } // 用授权码交换访问令牌 @@ -120,9 +120,7 @@ export class OAuthPresenter { ) if (accessToken) { - console.log( - `[GitHub Copilot][OAuth] Access token preview: ${accessToken.substring(0, 10)}...` - ) + console.log('[GitHub Copilot][OAuth] Access token received successfully') } // Validate token @@ -412,7 +410,7 @@ export class OAuthPresenter { console.error('OAuth server callback error:', error) this.callbackReject?.(new Error(`OAuth authorization failed: ${error}`)) } else if (code) { - console.log('OAuth server callback success, received code:', code) + console.log('OAuth server callback success, received authorization code') this.callbackResolve?.(code) } From 88feaed77359e843ab7fd5c8826d65624cbe5437 Mon Sep 17 00:00:00 2001 From: Cedric Meng <14017092+douyixuan@users.noreply.github.com> Date: Sat, 18 Oct 2025 20:44:41 +0800 Subject: [PATCH 3/7] fix: OAuth 2.0 for slow_down response --- src/main/presenter/githubCopilotDeviceFlow.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/presenter/githubCopilotDeviceFlow.ts b/src/main/presenter/githubCopilotDeviceFlow.ts index 119958b8c..8bdeede51 100644 --- a/src/main/presenter/githubCopilotDeviceFlow.ts +++ b/src/main/presenter/githubCopilotDeviceFlow.ts @@ -515,7 +515,8 @@ export class GitHubCopilotDeviceFlow { return // Continue polling case 'slow_down': - // Increase polling interval + // Increase polling interval by at least 5 seconds as per OAuth 2.0 spec + currentInterval += 5 if (this.pollingInterval) { clearInterval(this.pollingInterval) this.pollingInterval = setInterval(poll, currentInterval * 1000) From 3372cb68ad07ffa17a01c958a2914a32f0bbf3df Mon Sep 17 00:00:00 2001 From: Cedric Meng <14017092+douyixuan@users.noreply.github.com> Date: Sat, 18 Oct 2025 21:06:47 +0800 Subject: [PATCH 4/7] fix: handle lint errors --- src/main/presenter/githubCopilotDeviceFlow.ts | 9 +++++---- .../providers/githubCopilotProvider.ts | 6 +++--- src/main/presenter/oauthPresenter.ts | 6 ++++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/presenter/githubCopilotDeviceFlow.ts b/src/main/presenter/githubCopilotDeviceFlow.ts index 8bdeede51..9cddf8dd9 100644 --- a/src/main/presenter/githubCopilotDeviceFlow.ts +++ b/src/main/presenter/githubCopilotDeviceFlow.ts @@ -123,7 +123,7 @@ export class GitHubCopilotDeviceFlow { try { await this.getCopilotToken() return this.oauthToken - } catch (error) { + } catch { this.oauthToken = null return null } @@ -135,7 +135,7 @@ export class GitHubCopilotDeviceFlow { try { await this.getCopilotToken() return this.oauthToken! - } catch (error) { + } catch { this.oauthToken = null } } @@ -557,8 +557,9 @@ export class GitHubCopilotDeviceFlow { resolve(data.access_token) return } - } catch (error) { - // 继续轮询,网络错误可能是暂时的 + } catch { + // Continue polling, network errors may be temporary + return } } diff --git a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts index 01c535d3c..0872c4988 100644 --- a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts +++ b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts @@ -76,7 +76,7 @@ export class GithubCopilotProvider extends BaseLLMProvider { if (this.deviceFlow) { try { return await this.deviceFlow.getCopilotToken() - } catch (error) { + } catch { console.warn('[GitHub Copilot] Device flow failed, using provider API key') } } @@ -404,7 +404,7 @@ export class GithubCopilotProvider extends BaseLLMProvider { let errorBody = '' try { errorBody = await response.text() - } catch (e) { + } catch { // ignore } @@ -567,7 +567,7 @@ export class GithubCopilotProvider extends BaseLLMProvider { let errorBody = '' try { errorBody = await response.text() - } catch (e) { + } catch { // ignore } diff --git a/src/main/presenter/oauthPresenter.ts b/src/main/presenter/oauthPresenter.ts index 0ab191a68..23a5d98b5 100644 --- a/src/main/presenter/oauthPresenter.ts +++ b/src/main/presenter/oauthPresenter.ts @@ -107,10 +107,12 @@ export class OAuthPresenter { authCode ? 'SUCCESS' : 'FAILED' ) - if (authCode) { - console.log('[GitHub Copilot][OAuth] Auth code received successfully') + if (!authCode) { + throw new Error('Failed to obtain authorization code') } + console.log('[GitHub Copilot][OAuth] Auth code received successfully') + // 用授权码交换访问令牌 console.log('[GitHub Copilot][OAuth] Exchanging auth code for access token...') const accessToken = await githubOAuth.exchangeCodeForToken(authCode) From cc787fc519f01fa5e553c52f37a3cd9e066fe0b8 Mon Sep 17 00:00:00 2001 From: Cedric Meng <14017092+douyixuan@users.noreply.github.com> Date: Sat, 18 Oct 2025 21:36:24 +0800 Subject: [PATCH 5/7] fix: provider fetched from publicdb --- .../providers/githubCopilotProvider.ts | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts index 0872c4988..6976af240 100644 --- a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts +++ b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts @@ -147,7 +147,30 @@ export class GithubCopilotProvider extends BaseLLMProvider { } protected async fetchProviderModels(): Promise { - // GitHub Copilot 支持的模型列表 + // Try to get models from publicdb first + const dbModels = this.configPresenter.getDbProviderModels(this.provider.id) + if (dbModels.length > 0) { + // Convert RENDERER_MODEL_META to MODEL_META format + return dbModels.map((m) => ({ + id: m.id, + name: m.name, + group: m.group, + providerId: m.providerId, + isCustom: m.isCustom, + contextLength: m.contextLength, + maxTokens: m.maxTokens, + vision: m.vision, + functionCall: m.functionCall, + reasoning: m.reasoning, + enableSearch: m.enableSearch + })) + } + + // Fallback to hardcoded models if publicdb doesn't have copilot models yet + console.warn( + `[GitHub Copilot] No models found in publicdb for provider ${this.provider.id}, using fallback models` + ) + const models: MODEL_META[] = [ { id: 'gpt-5', From 4891353a08c294a8feb8c58eaffd7da9a76524c7 Mon Sep 17 00:00:00 2001 From: Cedric Meng <14017092+douyixuan@users.noreply.github.com> Date: Sat, 18 Oct 2025 22:57:29 +0800 Subject: [PATCH 6/7] fix(githubCopilotProvider): update request body logging format for clarity --- .../providers/githubCopilotProvider.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts index 6976af240..0978b17dd 100644 --- a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts +++ b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts @@ -402,7 +402,9 @@ export class GithubCopilotProvider extends BaseLLMProvider { console.log(` URL: ${this.baseApiUrl}/chat/completions`) console.log(` Model: ${modelId}`) console.log(` Headers: ${Object.keys(headers).join(', ')}`) - console.log(` Request Body:`, JSON.stringify(requestBody, null, 2)) + console.log( + ` Request Body: { messages: ${formattedMessages.length}, model: "${modelId}", temperature: ${temperature}, max_tokens: ${_maxTokens} }` + ) const requestOptions: RequestInitWithAgent = { method: 'POST', @@ -565,7 +567,9 @@ export class GithubCopilotProvider extends BaseLLMProvider { console.log(` URL: ${this.baseApiUrl}/chat/completions`) console.log(` Model: ${modelId}`) console.log(` Headers: ${Object.keys(headers).join(', ')}`) - console.log(` Request Body:`, JSON.stringify(requestBody, null, 2)) + console.log( + ` Request Body: { messages: ${formattedMessages.length}, model: "${modelId}", temperature: ${temperature}, max_tokens: ${_maxTokens} }` + ) const requestOptions: RequestInitWithAgent = { method: 'POST', From b1d4bc5efd1be41b71deb0c992a5a3c48bc6b5b6 Mon Sep 17 00:00:00 2001 From: Cedric Meng <14017092+douyixuan@users.noreply.github.com> Date: Sun, 19 Oct 2025 00:06:22 +0800 Subject: [PATCH 7/7] fix(githubCopilotProvider): improve error handling and logging in device flow --- .../providers/githubCopilotProvider.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts index 0978b17dd..95a0b19c9 100644 --- a/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts +++ b/src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts @@ -76,8 +76,11 @@ export class GithubCopilotProvider extends BaseLLMProvider { if (this.deviceFlow) { try { return await this.deviceFlow.getCopilotToken() - } catch { - console.warn('[GitHub Copilot] Device flow failed, using provider API key') + } catch (error) { + console.warn( + '[GitHub Copilot] Device flow failed, falling back to provider API key:', + error + ) } } @@ -683,14 +686,7 @@ export class GithubCopilotProvider extends BaseLLMProvider { async check(): Promise<{ isOk: boolean; errorMsg: string | null }> { try { - // 检查是否有 API Key - if (!this.provider.apiKey || !this.provider.apiKey.trim()) { - return { - isOk: false, - errorMsg: '请先使用 GitHub OAuth 登录以获取访问令牌' - } - } - + // Device flow may be active without apiKey; proceed to token retrieval await this.getCopilotToken() return { isOk: true, errorMsg: null } } catch (error) {