Skip to content
Open
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
139 changes: 112 additions & 27 deletions src/services/api-proxy-service-api-targets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,40 +505,125 @@ describe('API proxy sidecar: API targets and auth forwarding', () => {
});

describe('COPILOT_INTEGRATION_ID forwarding', () => {
it('should not forward GITHUB_COPILOT_INTEGRATION_ID as COPILOT_INTEGRATION_ID to api-proxy', () => {
const orig = process.env.GITHUB_COPILOT_INTEGRATION_ID;
process.env.GITHUB_COPILOT_INTEGRATION_ID = 'agentic-workflows';
try {
const configWithProxy = { ...mockConfig, enableApiProxy: true, copilotGithubToken: 'ghu_test' };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'];
const env = proxy.environment as Record<string, string>;
expect(env.COPILOT_INTEGRATION_ID).toBeUndefined();
} finally {
if (orig !== undefined) {
process.env.GITHUB_COPILOT_INTEGRATION_ID = orig;
let savedEnv: Record<string, string | undefined>;

beforeEach(() => {
// Save both env vars to prevent test flakiness if host has COPILOT_INTEGRATION_ID set
savedEnv = {
COPILOT_INTEGRATION_ID: process.env.COPILOT_INTEGRATION_ID,
GITHUB_COPILOT_INTEGRATION_ID: process.env.GITHUB_COPILOT_INTEGRATION_ID,
};
});

afterEach(() => {
// Restore original env state
for (const key of Object.keys(savedEnv)) {
if (savedEnv[key] !== undefined) {
process.env[key] = savedEnv[key];
} else {
delete process.env.GITHUB_COPILOT_INTEGRATION_ID;
delete process.env[key];
}
}
});

it('should not forward GITHUB_COPILOT_INTEGRATION_ID as COPILOT_INTEGRATION_ID to api-proxy', () => {
process.env.GITHUB_COPILOT_INTEGRATION_ID = 'agentic-workflows';
delete process.env.COPILOT_INTEGRATION_ID;
const configWithProxy = { ...mockConfig, enableApiProxy: true, copilotGithubToken: 'ghu_test' };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'];
const env = proxy.environment as Record<string, string>;
expect(env.COPILOT_INTEGRATION_ID).toBeUndefined();
});

it('should not set COPILOT_INTEGRATION_ID when GITHUB_COPILOT_INTEGRATION_ID is not set', () => {
const orig = process.env.GITHUB_COPILOT_INTEGRATION_ID;
delete process.env.GITHUB_COPILOT_INTEGRATION_ID;
try {
const configWithProxy = { ...mockConfig, enableApiProxy: true, copilotGithubToken: 'ghu_test' };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'];
const env = proxy.environment as Record<string, string>;
expect(env.COPILOT_INTEGRATION_ID).toBeUndefined();
} finally {
if (orig !== undefined) {
process.env.GITHUB_COPILOT_INTEGRATION_ID = orig;
} else {
delete process.env.GITHUB_COPILOT_INTEGRATION_ID;
}
}
delete process.env.COPILOT_INTEGRATION_ID;
const configWithProxy = { ...mockConfig, enableApiProxy: true, copilotGithubToken: 'ghu_test' };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'];
const env = proxy.environment as Record<string, string>;
expect(env.COPILOT_INTEGRATION_ID).toBeUndefined();
});

it('should forward COPILOT_INTEGRATION_ID from host env when explicitly set', () => {
process.env.COPILOT_INTEGRATION_ID = 'my-custom-integration';
delete process.env.GITHUB_COPILOT_INTEGRATION_ID;
Comment on lines +549 to +551
const configWithProxy = { ...mockConfig, enableApiProxy: true, copilotGithubToken: 'ghu_test', envAll: true };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'];
const env = proxy.environment as Record<string, string>;
expect(env.COPILOT_INTEGRATION_ID).toBe('my-custom-integration');
});

it('should not set COPILOT_INTEGRATION_ID when host env var is empty', () => {
process.env.COPILOT_INTEGRATION_ID = '';
delete process.env.GITHUB_COPILOT_INTEGRATION_ID;
const configWithProxy = { ...mockConfig, enableApiProxy: true, copilotGithubToken: 'ghu_test', envAll: true };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'];
const env = proxy.environment as Record<string, string>;
expect(env.COPILOT_INTEGRATION_ID).toBeUndefined();
});

it('should not set COPILOT_INTEGRATION_ID when host env var is whitespace only', () => {
process.env.COPILOT_INTEGRATION_ID = ' ';
delete process.env.GITHUB_COPILOT_INTEGRATION_ID;
const configWithProxy = { ...mockConfig, enableApiProxy: true, copilotGithubToken: 'ghu_test', envAll: true };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'];
const env = proxy.environment as Record<string, string>;
expect(env.COPILOT_INTEGRATION_ID).toBeUndefined();
});

it('should trim surrounding whitespace before forwarding', () => {
process.env.COPILOT_INTEGRATION_ID = ' my-custom-integration ';
delete process.env.GITHUB_COPILOT_INTEGRATION_ID;
const configWithProxy = { ...mockConfig, enableApiProxy: true, copilotGithubToken: 'ghu_test', envAll: true };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'];
const env = proxy.environment as Record<string, string>;
expect(env.COPILOT_INTEGRATION_ID).toBe('my-custom-integration');
});

it('should not set COPILOT_INTEGRATION_ID when nothing is set', () => {
delete process.env.COPILOT_INTEGRATION_ID;
delete process.env.GITHUB_COPILOT_INTEGRATION_ID;
const configWithProxy = { ...mockConfig, enableApiProxy: true, copilotGithubToken: 'ghu_test' };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'];
const env = proxy.environment as Record<string, string>;
expect(env.COPILOT_INTEGRATION_ID).toBeUndefined();
});

it('should forward COPILOT_INTEGRATION_ID from config.additionalEnv (--env flag)', () => {
delete process.env.COPILOT_INTEGRATION_ID;
delete process.env.GITHUB_COPILOT_INTEGRATION_ID;
const configWithProxy = {
...mockConfig,
enableApiProxy: true,
copilotGithubToken: 'ghu_test',
additionalEnv: { COPILOT_INTEGRATION_ID: 'from-cli-flag' },
};
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'];
const env = proxy.environment as Record<string, string>;
expect(env.COPILOT_INTEGRATION_ID).toBe('from-cli-flag');
});

it('should prefer config.additionalEnv over process.env', () => {
process.env.COPILOT_INTEGRATION_ID = 'from-host-env';
delete process.env.GITHUB_COPILOT_INTEGRATION_ID;
const configWithProxy = {
...mockConfig,
enableApiProxy: true,
copilotGithubToken: 'ghu_test',
additionalEnv: { COPILOT_INTEGRATION_ID: 'from-cli-flag' },
};
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const proxy = result.services['api-proxy'];
const env = proxy.environment as Record<string, string>;
expect(env.COPILOT_INTEGRATION_ID).toBe('from-cli-flag');
});
});
});
8 changes: 8 additions & 0 deletions src/services/api-proxy-service-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ export function buildApiProxyServiceConfig(params: ApiProxyServiceConfigParams):
// Forward GITHUB_API_URL so api-proxy can route /models to the correct GitHub REST API
// target on GHES/GHEC (e.g. api.mycompany.ghe.com instead of api.github.com)
...(process.env.GITHUB_API_URL && { GITHUB_API_URL: process.env.GITHUB_API_URL }),
// Forward COPILOT_INTEGRATION_ID if explicitly set (via --env, --env-file, or host env with --env-all)
// so callers can identify themselves to the Copilot API with their own integration ID
// instead of being attributed to AWF's default 'agentic-workflows'.
// Whitespace-only values are treated as unset to avoid accidentally
// shipping a meaningless integration ID.
...(getConfigEnvValue(config, 'COPILOT_INTEGRATION_ID')?.trim() && {
COPILOT_INTEGRATION_ID: getConfigEnvValue(config, 'COPILOT_INTEGRATION_ID')!.trim(),
}),
// Do not forward GITHUB_COPILOT_INTEGRATION_ID — api-proxy defaults to
// 'agentic-workflows' which is the correct integration ID for AWF.
// Note: AWF_VERSION is intentionally NOT forwarded here. It is baked into the api-proxy
Expand Down