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
32 changes: 32 additions & 0 deletions containers/api-proxy/copilot-byok.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,38 @@ describe('createCopilotAdapter — BYOK getAuthHeaders', () => {
expect(config.apiVersion).toBe('2026-07-01');
});

it.each([
['/auto', '2026-08-01'],
['/models/session', '2025-07-16'],
['/models/session/intent', '2025-07-16'],
])('injects the required API version for POST %s', (url, expectedVersion) => {
const adapter = createCopilotAdapter({ COPILOT_GITHUB_TOKEN: githubToken });
const headers = adapter.getAuthHeaders({ url, method: 'POST', headers: {} });

expect(headers['X-GitHub-Api-Version']).toBe(expectedVersion);
});

it('preserves a caller-supplied API version for /auto', () => {
const adapter = createCopilotAdapter({ COPILOT_GITHUB_TOKEN: githubToken });
const headers = adapter.getAuthHeaders({
url: '/auto',
method: 'POST',
headers: { 'x-github-api-version': '2027-01-01' },
});

expect(headers['X-GitHub-Api-Version']).toBeUndefined();
});

it('does not inject GitHub API versions into custom BYOK targets', () => {
const adapter = createCopilotAdapter({
COPILOT_PROVIDER_API_KEY: byokKey,
COPILOT_API_TARGET: 'router.example.com',
});
const headers = adapter.getAuthHeaders({ url: '/auto', method: 'POST', headers: {} });

expect(headers['X-GitHub-Api-Version']).toBeUndefined();
});

it('uses COPILOT_PROVIDER_API_KEY (not COPILOT_GITHUB_TOKEN) for inference in BYOK+token mode', () => {
const adapter = createCopilotAdapter({
COPILOT_GITHUB_TOKEN: githubToken,
Expand Down
30 changes: 29 additions & 1 deletion containers/api-proxy/providers/copilot.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,35 @@ const {
resolveCopilotAuthToken,
deriveCopilotApiTarget,
copilotTargetRequiresGitHubTokenPrefix,
isGithubCopilotCatalogTarget,
} = require('./copilot-auth');
const { bearerAuthHeaders, withCopilotIntegration } = require('./auth-headers');
const { URL } = require('url');
const { COPILOT_ENV } = require('../provider-env-constants');

const COPILOT_MODELS_API_VERSION = '2026-07-01';
const COPILOT_LEGACY_AUTO_API_VERSION = '2025-07-16';
const COPILOT_AUTO_API_VERSION = '2026-08-01';

function hasRequestHeader(req, name) {
return Object.keys(req.headers || {}).some(header => header.toLowerCase() === name.toLowerCase());
}

function getDefaultAutoApiVersion(req, pathname, rawTarget) {
if (
req.method !== 'POST'
|| hasRequestHeader(req, 'x-github-api-version')
|| !isGithubCopilotCatalogTarget(rawTarget)
) {
return undefined;
}

if (pathname === '/auto') return COPILOT_AUTO_API_VERSION;
if (pathname === '/models/session' || pathname === '/models/session/intent') {
return COPILOT_LEGACY_AUTO_API_VERSION;
}
return undefined;
}

/**
* Create the GitHub Copilot provider adapter.
Expand Down Expand Up @@ -261,7 +284,12 @@ function createCopilotAdapter(env, deps = {}) {
return withCopilotIntegration({ 'Authorization': prefix + ' ' + githubToken }, integrationId);
}

return resolveHeaders();
const headers = resolveHeaders();
const autoApiVersion = getDefaultAutoApiVersion(req, reqPathname, rawTarget);
if (autoApiVersion) {
headers['X-GitHub-Api-Version'] = autoApiVersion;
}
return headers;
},
});
}
Expand Down
4 changes: 4 additions & 0 deletions containers/api-proxy/request-headers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ describe('request-headers', () => {
const req = {
headers: {
'x-custom': 'keep-me',
'copilot-session-token': 'session-jwt',
'x-github-tenant': 'tenant-id',
'transfer-encoding': 'chunked',
},
};
Expand All @@ -47,6 +49,8 @@ describe('request-headers', () => {
});

expect(headers['x-initiator']).toBe('agent');
expect(headers['copilot-session-token']).toBe('session-jwt');
expect(headers['x-github-tenant']).toBe('tenant-id');
expect(headers['content-length']).toBe(String(body.length));
expect(headers['transfer-encoding']).toBeUndefined();
});
Expand Down
Loading