-
Notifications
You must be signed in to change notification settings - Fork 8.4k
[GitHub] github:copilot is a routing alias — no model control or visibility #897
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
1c38084
de4c1b2
db1f9f3
65c00e1
c451548
144cc6e
b3e1a61
d1c1829
ee6f94c
20599b2
3f8d867
6d02223
9cc8902
b717f8f
a42bf48
29130a8
faaa5e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -216,7 +216,11 @@ function getGithubProviderModel( | |
| processEnv: NodeJS.ProcessEnv = process.env, | ||
| ): string { | ||
| if (isEnvTruthy(processEnv.CLAUDE_CODE_USE_GITHUB)) { | ||
| return processEnv.OPENAI_MODEL?.trim() || GITHUB_PROVIDER_DEFAULT_MODEL | ||
| return ( | ||
| processEnv.GITHUB_MODEL?.trim() || | ||
| processEnv.OPENAI_MODEL?.trim() || | ||
| GITHUB_PROVIDER_DEFAULT_MODEL | ||
| ) | ||
| } | ||
| return GITHUB_PROVIDER_DEFAULT_MODEL | ||
| } | ||
|
Comment on lines
259
to
266
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,10 +102,17 @@ export function detectProvider(): { name: string; model: string; baseUrl: string | |
| } | ||
|
|
||
| if (useGithub) { | ||
| const model = process.env.OPENAI_MODEL || 'github:copilot' | ||
| const baseUrl = | ||
| process.env.OPENAI_BASE_URL || 'https://api.githubcopilot.com' | ||
| return { name: 'GitHub Copilot', model, baseUrl, isLocal: false } | ||
| const rawModel = process.env.GITHUB_MODEL?.trim() || process.env.OPENAI_MODEL?.trim() || 'github:copilot' | ||
| const resolvedRequest = resolveProviderRequest({ | ||
| model: rawModel, | ||
| baseUrl: process.env.OPENAI_BASE_URL, | ||
| }) | ||
|
LoackyBit marked this conversation as resolved.
Comment on lines
116
to
+121
|
||
| const baseUrl = resolvedRequest.baseUrl | ||
| let displayModel = resolvedRequest.resolvedModel | ||
| if (resolvedRequest.reasoning?.effort) { | ||
| displayModel = `${displayModel} (${resolvedRequest.reasoning.effort})` | ||
| } | ||
| return { name: 'GitHub Copilot', model: displayModel, baseUrl, isLocal: false } | ||
| } | ||
|
|
||
| if (useOpenAI) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,6 +69,7 @@ import { | |
| } from './toolArgumentNormalization.js' | ||
| import { logApiCallStart, logApiCallEnd } from '../../utils/requestLogging.js' | ||
| import { createStreamState, processStreamChunk, getStreamStats } from '../../utils/streamingOptimizer.js' | ||
| import { updateGithubRateLimit } from '../../utils/githubRateLimit.js' | ||
|
|
||
| type SecretValueSource = Partial<{ | ||
| OPENAI_API_KEY: string | ||
|
|
@@ -1343,6 +1344,11 @@ class OpenAIShimMessages { | |
| const response = await self._doRequest(request, params, options) | ||
| httpResponse = response | ||
|
|
||
| // Capture GitHub rate-limit headers from every response | ||
| if (isGithubModelsMode()) { | ||
| updateGithubRateLimit(response.headers as unknown as Headers) | ||
|
Comment on lines
1351
to
+1359
|
||
| } | ||
|
|
||
| if (params.stream) { | ||
| const isResponsesStream = response.url?.includes('/responses') | ||
| return new OpenAIShimStream( | ||
|
|
@@ -2200,6 +2206,9 @@ export function createOpenAIShimClient(options: { | |
| process.env.OPENAI_BASE_URL ??= GITHUB_COPILOT_BASE | ||
| process.env.OPENAI_API_KEY ??= | ||
| process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN ?? '' | ||
| if (process.env.GITHUB_MODEL && !process.env.OPENAI_MODEL) { | ||
| process.env.OPENAI_MODEL = process.env.GITHUB_MODEL | ||
| } | ||
| } | ||
|
|
||
| // Map Bankr env vars to OpenAI-compatible ones when present | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| import { afterEach, expect, test } from 'bun:test' | ||
|
|
||
| import { | ||
| updateGithubRateLimit, | ||
| getGithubRateLimitState, | ||
| formatGithubRateLimitSummary, | ||
| resetGithubRateLimitState, | ||
| } from './githubRateLimit.js' | ||
|
|
||
| afterEach(() => { | ||
| resetGithubRateLimitState() | ||
| }) | ||
|
|
||
| test('updateGithubRateLimit parses request and token limits', () => { | ||
| const headers = new Headers({ | ||
| 'x-ratelimit-limit-requests': '100', | ||
| 'x-ratelimit-remaining-requests': '42', | ||
| 'x-ratelimit-limit-tokens': '500000', | ||
| 'x-ratelimit-remaining-tokens': '123456', | ||
| }) | ||
|
|
||
| updateGithubRateLimit(headers) | ||
| const state = getGithubRateLimitState() | ||
|
|
||
| expect(state.limitRequests).toBe(100) | ||
| expect(state.remainingRequests).toBe(42) | ||
| expect(state.limitTokens).toBe(500000) | ||
| expect(state.remainingTokens).toBe(123456) | ||
| }) | ||
|
|
||
| test('updateGithubRateLimit parses epoch-seconds reset timestamp', () => { | ||
| const epoch = Math.floor(Date.now() / 1000) + 3600 | ||
| const headers = new Headers({ | ||
| 'x-ratelimit-reset-requests': String(epoch), | ||
| 'x-ratelimit-remaining-requests': '10', | ||
| }) | ||
|
|
||
| updateGithubRateLimit(headers) | ||
| const state = getGithubRateLimitState() | ||
|
|
||
| expect(state.resetRequestsAt).not.toBeNull() | ||
| expect(state.resetRequestsAt!.getTime()).toBe(epoch * 1000) | ||
| }) | ||
|
|
||
| test('updateGithubRateLimit only updates present headers', () => { | ||
| const headers = new Headers({ | ||
| 'x-ratelimit-remaining-requests': '5', | ||
| }) | ||
|
|
||
| updateGithubRateLimit(headers) | ||
| const state = getGithubRateLimitState() | ||
|
|
||
| expect(state.remainingRequests).toBe(5) | ||
| expect(state.limitRequests).toBeNull() | ||
| expect(state.limitTokens).toBeNull() | ||
| expect(state.remainingTokens).toBeNull() | ||
| }) | ||
|
|
||
| test('formatGithubRateLimitSummary returns null when no data', () => { | ||
| expect(formatGithubRateLimitSummary()).toBeNull() | ||
| }) | ||
|
|
||
| test('formatGithubRateLimitSummary formats requests and tokens', () => { | ||
| const headers = new Headers({ | ||
| 'x-ratelimit-limit-requests': '100', | ||
| 'x-ratelimit-remaining-requests': '42', | ||
| 'x-ratelimit-limit-tokens': '500000', | ||
| 'x-ratelimit-remaining-tokens': '123456', | ||
| }) | ||
|
|
||
| updateGithubRateLimit(headers) | ||
| const summary = formatGithubRateLimitSummary() | ||
|
|
||
| expect(summary).toContain('requests: 42/100 remaining') | ||
| expect(summary).toContain('tokens: 123456/500000 remaining') | ||
| }) | ||
|
|
||
| test('formatGithubRateLimitSummary handles partial data', () => { | ||
| const headers = new Headers({ | ||
| 'x-ratelimit-remaining-requests': '7', | ||
| }) | ||
|
|
||
| updateGithubRateLimit(headers) | ||
| const summary = formatGithubRateLimitSummary() | ||
|
|
||
| expect(summary).toContain('requests remaining: 7') | ||
| expect(summary).not.toContain('tokens') | ||
| }) | ||
|
|
||
| test('resetGithubRateLimitState clears all data', () => { | ||
| const headers = new Headers({ | ||
| 'x-ratelimit-limit-requests': '100', | ||
| 'x-ratelimit-remaining-requests': '42', | ||
| }) | ||
|
|
||
| updateGithubRateLimit(headers) | ||
| resetGithubRateLimitState() | ||
|
|
||
| expect(getGithubRateLimitState().limitRequests).toBeNull() | ||
| expect(getGithubRateLimitState().remainingRequests).toBeNull() | ||
| expect(formatGithubRateLimitSummary()).toBeNull() | ||
| }) |
Uh oh!
There was an error while loading. Please reload this page.