Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/presenter/llmProviderPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,8 @@ export class LLMProviderPresenter implements ILlmProviderPresenter {
return { isOk: false, errorMsg: `Model test failed: ${errorMessage}` }
}
} else {
return { isOk: false, errorMsg: 'Model ID is required' }
// 如果没有提供modelId,使用provider的check方法进行基础验证
return await provider.check()
}
} catch (error) {
console.error(`Provider ${providerId} check failed:`, error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface CopilotTokenResponse {
export class GithubCopilotProvider extends BaseLLMProvider {
private copilotToken: string | null = null
private tokenExpiresAt: number = 0
private baseApiUrl = 'https://copilot-proxy.githubusercontent.com'
private baseApiUrl = 'https://api.githubcopilot.com'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the new GitHub Copilot API endpoint

The base API URL has been changed to https://api.githubcopilot.com. Ensure this is the correct and stable endpoint for GitHub Copilot.


🌐 Web query:

What is the current official API endpoint for GitHub Copilot? Is https://api.githubcopilot.com the correct endpoint?

💡 Result:

GitHub Copilot offers several API endpoints for different functionalities:

  1. Copilot Metrics API: This API provides aggregated metrics for various GitHub Copilot features, such as active users, code completions, and Copilot Chat usage. The base endpoint is:

    https://api.github.com/copilot/usage
    

    For example, to retrieve Copilot metrics for an organization, you can use:

    https://api.github.com/orgs/{org}/copilot/usage
    

    Replace {org} with your organization's name. (docs.github.com)

  2. Copilot User Management API: This API allows you to manage GitHub Copilot Business subscriptions for your organization, including adding or removing users and teams. The base endpoint is:

    https://api.github.com/orgs/{org}/copilot
    

    For instance, to list all Copilot seat assignments for an organization:

    https://api.github.com/orgs/{org}/copilot/billing/seats
    

    Replace {org} with your organization's name. (docs.github.com)

  3. Copilot Chat API: For Copilot Chat functionalities, the endpoint is:

    https://api.individual.githubcopilot.com/github/chat/threads
    

    This endpoint is used for managing chat threads and messages within Copilot Chat. (den.dev)

Regarding the endpoint https://api.githubcopilot.com, it is not recognized as an official GitHub Copilot API endpoint. The correct endpoints are as listed above.

Citations:


Update GitHub Copilot API endpoint

The assigned base URL (https://api.githubcopilot.com) is not an official Copilot endpoint. Please replace it with the correct one for your use case:

• For Copilot Metrics (usage & telemetry):
https://api.github.com/copilot/usage

• For Copilot Business user/seat management:
https://api.github.com/orgs/{org}/copilot

• For Copilot Chat threads & messages:
https://api.individual.githubcopilot.com/github/chat/threads

Location:

  • File: src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts
  • Line: 29

Suggested change (example using Metrics API):

- private baseApiUrl = 'https://api.githubcopilot.com'
+ private baseApiUrl = 'https://api.github.com/copilot/usage'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private baseApiUrl = 'https://api.githubcopilot.com'
private baseApiUrl = 'https://api.github.com/copilot/usage'
🤖 Prompt for AI Agents
In src/main/presenter/llmProviderPresenter/providers/githubCopilotProvider.ts at
line 29, the baseApiUrl is set to an incorrect GitHub Copilot endpoint. Update
the baseApiUrl to the correct official endpoint based on your use case, for
example, use 'https://api.github.com/copilot/usage' for Copilot Metrics, or
choose the appropriate URL for Business user management or Chat threads. Replace
the existing URL string with the correct one accordingly.

private tokenUrl = 'https://api.github.com/copilot_internal/v2/token'

constructor(provider: LLM_PROVIDER, configPresenter: ConfigPresenter) {
Expand Down Expand Up @@ -112,8 +112,7 @@ export class GithubCopilotProvider extends BaseLLMProvider {
const headers: Record<string, string> = {
Authorization: `Bearer ${this.provider.apiKey}`,
Accept: 'application/json',
'User-Agent': 'DeepChat/1.0.0',
'X-GitHub-Api-Version': '2022-11-28'
'User-Agent': 'DeepChat/1.0.0'
}

console.log('📋 [GitHub Copilot] Request headers:')
Expand Down Expand Up @@ -249,32 +248,32 @@ export class GithubCopilotProvider extends BaseLLMProvider {
reasoning: false
},
{
id: 'o1-preview',
name: 'o1 Preview',
id: 'gpt-4.1',
name: 'GPT-4.1',
group: 'GitHub Copilot',
providerId: this.provider.id,
isCustom: false,
contextLength: 128000,
maxTokens: 32768,
vision: false,
functionCall: false,
reasoning: true
contextLength: 200000,
maxTokens: 8192,
vision: true,
functionCall: true,
reasoning: false
},
{
id: 'o1-mini',
name: 'o1 Mini',
id: 'claude-3.7-sonnet',
name: 'Claude 3.7 sonnet',
group: 'GitHub Copilot',
providerId: this.provider.id,
isCustom: false,
contextLength: 128000,
maxTokens: 65536,
vision: false,
functionCall: false,
reasoning: true
contextLength: 200000,
maxTokens: 8192,
vision: true,
functionCall: true,
reasoning: false
},
{
id: 'claude-3-5-sonnet',
name: 'Claude 3.5 Sonnet',
id: 'claude-sonnet-4',
name: 'Claude Sonnet 4',
group: 'GitHub Copilot',
providerId: this.provider.id,
isCustom: false,
Expand All @@ -283,7 +282,45 @@ export class GithubCopilotProvider extends BaseLLMProvider {
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: 200000,
maxTokens: 8192,
vision: true,
functionCall: true,
reasoning: false
},
{
id: 'o3-mini',
name: 'O3 Mini',
group: 'GitHub Copilot',
providerId: this.provider.id,
isCustom: false,
contextLength: 128000,
maxTokens: 4096,
vision: true,
functionCall: true,
reasoning: true
},
{
id: 'o3',
name: 'O3',
group: 'GitHub Copilot',
providerId: this.provider.id,
isCustom: false,
contextLength: 128000,
maxTokens: 4096,
vision: true,
functionCall: true,
reasoning: true
}


]

return models
Expand All @@ -304,27 +341,41 @@ export class GithubCopilotProvider extends BaseLLMProvider {
maxTokens: number,
tools: MCPToolDefinition[]
): AsyncGenerator<LLMCoreStreamEvent, void, unknown> {
if (!modelId) throw new Error('Model ID is required')
try {
const token = await this.getCopilotToken()
const formattedMessages = this.formatMessages(messages)

const requestBody = {
// Build request body with standard parameters
const requestBody: any = {
model: modelId,
messages: formattedMessages,
temperature: temperature || 0.7,
max_tokens: maxTokens || 4096,
stream: true,
...(tools && tools.length > 0 && { tools })
temperature: temperature || 0.7
}

// Add tools when available
if (tools && tools.length > 0) {
requestBody.tools = tools
}

const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Accept: 'text/event-stream',
'User-Agent': 'DeepChat/1.0.0',
'X-GitHub-Api-Version': '2022-11-28'
'editor-version': 'vscode/1.97.2',
'editor-plugin-version': 'copilot.vim/1.16.0'
}

// 添加详细的请求日志
console.log('📤 [GitHub Copilot] Sending stream request:')
console.log(` URL: ${this.baseApiUrl}/chat/completions`)
console.log(` Model: ${modelId}`)
console.log(` Headers:`, headers)
console.log(` Request Body:`, JSON.stringify(requestBody, null, 2))

const requestOptions: RequestInitWithAgent = {
method: 'POST',
headers,
Expand All @@ -340,7 +391,25 @@ export class GithubCopilotProvider extends BaseLLMProvider {

const response = await fetch(`${this.baseApiUrl}/chat/completions`, requestOptions)

console.log('📥 [GitHub Copilot] Stream API Response:')
console.log(` Status: ${response.status} ${response.statusText}`)
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))

// 尝试读取错误响应
try {
const errorText = await response.text()
console.log(` Error Response Body: ${errorText}`)
} catch (e) {
console.log(` Could not read error response: ${e}`)
}

throw new Error(`GitHub Copilot API error: ${response.status} ${response.statusText}`)
}

Expand Down Expand Up @@ -428,26 +497,36 @@ export class GithubCopilotProvider extends BaseLLMProvider {
temperature?: number,
maxTokens?: number
): Promise<LLMResponse> {
if (!modelId) throw new Error('Model ID is required')
try {
const token = await this.getCopilotToken()
const formattedMessages = this.formatMessages(messages)

const requestBody = {
// Build request body with standard parameters
const requestBody: any = {
model: modelId,
messages: formattedMessages,
temperature: temperature || 0.7,
max_tokens: maxTokens || 4096,
stream: false
stream: false,
temperature: temperature || 0.7
}

const headers: Record<string, string> = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Accept: 'application/json',
'User-Agent': 'DeepChat/1.0.0',
'X-GitHub-Api-Version': '2022-11-28'
'editor-version': 'vscode/1.97.2',
'editor-plugin-version': 'copilot.vim/1.16.0'
}

// 添加详细的请求日志
console.log('📤 [GitHub Copilot] Sending completion request:')
console.log(` URL: ${this.baseApiUrl}/chat/completions`)
console.log(` Model: ${modelId}`)
console.log(` Headers:`, headers)
console.log(` Request Body:`, JSON.stringify(requestBody, null, 2))

const requestOptions: RequestInitWithAgent = {
method: 'POST',
headers,
Expand All @@ -463,7 +542,25 @@ export class GithubCopilotProvider extends BaseLLMProvider {

const response = await fetch(`${this.baseApiUrl}/chat/completions`, requestOptions)

console.log('📥 [GitHub Copilot] Completion API Response:')
console.log(` Status: ${response.status} ${response.statusText}`)
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))

// 尝试读取错误响应
try {
const errorText = await response.text()
console.log(` Error Response Body: ${errorText}`)
} catch (e) {
console.log(` Could not read error response: ${e}`)
}

throw new Error(`GitHub Copilot API error: ${response.status} ${response.statusText}`)
}

Expand Down Expand Up @@ -496,6 +593,7 @@ export class GithubCopilotProvider extends BaseLLMProvider {
temperature?: number,
maxTokens?: number
): Promise<LLMResponse> {
if (!modelId) throw new Error('Model ID is required')
return this.completions(
[
{
Expand Down
45 changes: 8 additions & 37 deletions src/renderer/src/components/settings/GitHubCopilotOAuth.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@
variant="outline"
size="xs"
class="text-xs text-normal rounded-lg"
:disabled="isValidating"
@click="validateToken"
@click="openModelCheckDialog"
>
<Icon
:icon="isValidating ? 'lucide:loader-2' : 'lucide:check-check'"
:class="['w-4 h-4 text-muted-foreground', { 'animate-spin': isValidating }]"
/>
<Icon icon="lucide:check-check" class="w-4 h-4 text-muted-foreground" />
{{ t('settings.provider.verifyKey') }}
</Button>
<Button
Expand Down Expand Up @@ -125,6 +121,7 @@ import { Icon } from '@iconify/vue'
import { usePresenter } from '@/composables/usePresenter'
import { useSettingsStore } from '@/stores/settings'
import type { LLM_PROVIDER } from '@shared/presenter'
import { useModelCheckStore } from '@/stores/modelCheck'

const { t } = useI18n()

Expand All @@ -138,13 +135,14 @@ const emit = defineEmits<{
}>()

const oauthPresenter = usePresenter('oauthPresenter')
const llmProviderPresenter = usePresenter('llmproviderPresenter')
const settingsStore = useSettingsStore()
const modelCheckStore = useModelCheckStore()

const isLoggingIn = ref(false)
const isValidating = ref(false)
const validationResult = ref<{ success: boolean; message: string } | null>(null)



const hasToken = computed(() => {
return !!(props.provider.apiKey && props.provider.apiKey.trim())
})
Expand Down Expand Up @@ -221,31 +219,8 @@ const startOAuthLogin = async () => {
}
}

/**
* 验证Token
*/
const validateToken = async () => {
if (!hasToken.value) return

isValidating.value = true
validationResult.value = null

try {
const result = await llmProviderPresenter.check(props.provider.id)
validationResult.value = {
success: result.isOk,
message: result.isOk
? t('settings.provider.tokenValid')
: result.errorMsg || t('settings.provider.tokenInvalid')
}
} catch (error) {
validationResult.value = {
success: false,
message: error instanceof Error ? error.message : t('settings.provider.tokenInvalid')
}
} finally {
isValidating.value = false
}
const openModelCheckDialog = () => {
modelCheckStore.openDialog(props.provider.id)
}

/**
Expand Down Expand Up @@ -281,10 +256,6 @@ const clearValidationAfterDelay = () => {

// 监听验证结果变化,自动清除
onMounted(() => {
// 如果有Token,可以自动验证一次
if (hasToken.value) {
validateToken()
}
})

onUnmounted(() => {
Expand Down