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: 3 additions & 0 deletions containers/api-proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ Required (at least one):
- `OPENAI_API_KEY` - OpenAI API key for authentication
- `ANTHROPIC_API_KEY` - Anthropic API key for authentication

Optional:
- `COPILOT_API_TARGET` - Target hostname for GitHub Copilot API requests (default: `api.githubcopilot.com`). Useful for GHES deployments.
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

README says the default target is always api.githubcopilot.com, but the proxy now auto-derives api.enterprise.githubcopilot.com when GITHUB_SERVER_URL is set to a non-github.com host. Please update this description to match the implemented precedence (explicit COPILOT_API_TARGET > derived from GITHUB_SERVER_URL > public default).

Suggested change
- `COPILOT_API_TARGET` - Target hostname for GitHub Copilot API requests (default: `api.githubcopilot.com`). Useful for GHES deployments.
- `COPILOT_API_TARGET` - Target hostname for GitHub Copilot API requests. If set, this value takes precedence. If unset and `GITHUB_SERVER_URL` points to a non-github.com host (e.g., GHES), the proxy will auto-derive `api.enterprise.githubcopilot.com`. Otherwise, the default is `api.githubcopilot.com`. Useful for GHES deployments.

Copilot uses AI. Check for mistakes.

Set by AWF:
- `HTTP_PROXY` - Squid proxy URL (http://172.30.0.10:3128)
- `HTTPS_PROXY` - Squid proxy URL (http://172.30.0.10:3128)
Expand Down
27 changes: 26 additions & 1 deletion containers/api-proxy/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,37 @@ const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
const COPILOT_GITHUB_TOKEN = process.env.COPILOT_GITHUB_TOKEN;

// Configurable Copilot API target host (supports GHES/GHEC / custom endpoints)
// Priority: COPILOT_API_TARGET env var > auto-derive from GITHUB_SERVER_URL > default
function deriveCopilotApiTarget() {
if (process.env.COPILOT_API_TARGET) {
return process.env.COPILOT_API_TARGET;
Comment on lines +52 to +53
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

COPILOT_API_TARGET is used as a hostname in both new URL(req.url, \https://${targetHost}`)andhttps.request({ hostname: targetHost }), but deriveCopilotApiTarget()returns the env var verbatim. If a user supplies a full URL (e.g.,https://api.githubcopilot.com`) or includes a path/port/whitespace, requests can fail at runtime. Consider validating/normalizing this value (trim, reject / and scheme, or accept full URLs by parsing and extracting hostname).

Suggested change
if (process.env.COPILOT_API_TARGET) {
return process.env.COPILOT_API_TARGET;
const envTarget = process.env.COPILOT_API_TARGET;
if (envTarget) {
const trimmed = envTarget.trim();
if (trimmed) {
// If a full URL is provided (e.g., https://api.githubcopilot.com),
// parse it and extract the hostname so it can be safely used in
// https.request({ hostname }) and as a URL base.
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
try {
const url = new URL(trimmed);
if (url.hostname) {
return url.hostname;
}
} catch {
// Invalid URL — fall through to serverUrl/default handling below.
}
} else {
// Treat as a bare hostname. Reject obviously invalid forms that would
// break https.request/new URL usage (paths or whitespace).
if (!/[\/\s]/.test(trimmed)) {
return trimmed;
}
// If invalid, fall through to serverUrl/default handling.
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +53
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

Please add test coverage for the new Copilot target selection logic: (1) COPILOT_API_TARGET override wins, and (2) non-github.com GITHUB_SERVER_URL causes the enterprise target to be used. Without a test, regressions here are easy to miss.

Copilot uses AI. Check for mistakes.
}
// For GitHub Enterprise Cloud (*.ghe.com) or GitHub Enterprise Server
// (any GITHUB_SERVER_URL that isn't https://github.com), route to the
// enterprise Copilot API endpoint instead of the individual one.
const serverUrl = process.env.GITHUB_SERVER_URL;
if (serverUrl) {
try {
const hostname = new URL(serverUrl).hostname;
if (hostname !== 'github.com') {
return 'api.enterprise.githubcopilot.com';
}
} catch {
// Invalid URL — fall through to default
}
}
return 'api.githubcopilot.com';
}
const COPILOT_API_TARGET = deriveCopilotApiTarget();

// Squid proxy configuration (set via HTTP_PROXY/HTTPS_PROXY in docker-compose)
const HTTPS_PROXY = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;

logRequest('info', 'startup', {
message: 'Starting AWF API proxy sidecar',
squid_proxy: HTTPS_PROXY || 'not configured',
copilot_api_target: COPILOT_API_TARGET,
providers: {
openai: !!OPENAI_API_KEY,
anthropic: !!ANTHROPIC_API_KEY,
Expand Down Expand Up @@ -433,7 +458,7 @@ if (COPILOT_GITHUB_TOKEN) {
const contentLength = parseInt(req.headers['content-length'], 10) || 0;
if (checkRateLimit(req, res, 'copilot', contentLength)) return;

proxyRequest(req, res, 'api.githubcopilot.com', {
proxyRequest(req, res, COPILOT_API_TARGET, {
'Authorization': `Bearer ${COPILOT_GITHUB_TOKEN}`,
}, 'copilot');
});
Expand Down
7 changes: 7 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,12 @@ program
' Supports OpenAI (Codex) and Anthropic (Claude) APIs.',
false
)
.option(
'--copilot-api-target <host>',
'Target hostname for GitHub Copilot API requests in the api-proxy sidecar.\n' +
' Defaults to api.githubcopilot.com. Useful for GHES deployments.\n' +
' Can also be set via COPILOT_API_TARGET env var.',
Comment on lines +790 to +791
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

CLI help text says the target "Defaults to api.githubcopilot.com", but the sidecar now auto-derives api.enterprise.githubcopilot.com when GITHUB_SERVER_URL is set to a non-github.com host. Please adjust the help text to avoid advertising an unconditional default (e.g., mention the enterprise auto-derive behavior and that the flag/env var overrides it).

Suggested change
' Defaults to api.githubcopilot.com. Useful for GHES deployments.\n' +
' Can also be set via COPILOT_API_TARGET env var.',
' Defaults to api.githubcopilot.com for GitHub.com.\n' +
' When GITHUB_SERVER_URL is set to a non-github.com host, defaults to api.enterprise.githubcopilot.com.\n' +
' This flag or the COPILOT_API_TARGET env var override the default.',

Copilot uses AI. Check for mistakes.
)
.option(
'--rate-limit-rpm <n>',
'Enable rate limiting: max requests per minute per provider (requires --enable-api-proxy)',
Expand Down Expand Up @@ -1064,6 +1070,7 @@ program
openaiApiKey: process.env.OPENAI_API_KEY,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
copilotGithubToken: process.env.COPILOT_GITHUB_TOKEN,
copilotApiTarget: options.copilotApiTarget || process.env.COPILOT_API_TARGET,
};

// Build rate limit config when API proxy is enabled
Expand Down
10 changes: 10 additions & 0 deletions src/docker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,9 @@ export function generateDockerCompose(
if (process.env.USER) environment.USER = process.env.USER;
if (process.env.TERM) environment.TERM = process.env.TERM;
if (process.env.XDG_CONFIG_HOME) environment.XDG_CONFIG_HOME = process.env.XDG_CONFIG_HOME;
// Enterprise environment variables — needed for GHEC/GHES Copilot authentication
if (process.env.GITHUB_SERVER_URL) environment.GITHUB_SERVER_URL = process.env.GITHUB_SERVER_URL;
if (process.env.GITHUB_API_URL) environment.GITHUB_API_URL = process.env.GITHUB_API_URL;
}

// Additional environment variables from --env flags (these override everything)
Expand Down Expand Up @@ -977,6 +980,10 @@ export function generateDockerCompose(
...(config.openaiApiKey && { OPENAI_API_KEY: config.openaiApiKey }),
...(config.anthropicApiKey && { ANTHROPIC_API_KEY: config.anthropicApiKey }),
...(config.copilotGithubToken && { COPILOT_GITHUB_TOKEN: config.copilotGithubToken }),
// Configurable Copilot API target (for GHES/GHEC support)
...(config.copilotApiTarget && { COPILOT_API_TARGET: config.copilotApiTarget }),
// Forward GITHUB_SERVER_URL so api-proxy can auto-derive enterprise endpoints
...(process.env.GITHUB_SERVER_URL && { GITHUB_SERVER_URL: process.env.GITHUB_SERVER_URL }),
// Route through Squid to respect domain whitelisting
Comment on lines +983 to 987
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The api-proxy container is now deriving the Copilot target from GITHUB_SERVER_URL, but docker-compose only forwards GITHUB_SERVER_URL (not GITHUB_API_URL) into the api-proxy service. This differs from the PR description (“Forward GITHUB_SERVER_URL and GITHUB_API_URL to both agent and api-proxy containers by default”). Either forward GITHUB_API_URL as well, or update the PR description/docs to match what the proxy actually needs.

Copilot uses AI. Check for mistakes.
HTTP_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`,
HTTPS_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`,
Expand Down Expand Up @@ -1050,6 +1057,9 @@ export function generateDockerCompose(
if (config.copilotGithubToken) {
environment.COPILOT_API_URL = `http://${networkConfig.proxyIp}:${API_PROXY_PORTS.COPILOT}`;
logger.debug(`GitHub Copilot API will be proxied through sidecar at http://${networkConfig.proxyIp}:${API_PROXY_PORTS.COPILOT}`);
if (config.copilotApiTarget) {
logger.debug(`Copilot API target overridden to: ${config.copilotApiTarget}`);
}

// Set placeholder token for GitHub Copilot CLI compatibility
// Real authentication happens via COPILOT_API_URL pointing to api-proxy
Expand Down
22 changes: 22 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,28 @@ export interface WrapperConfig {
* @default undefined
*/
copilotGithubToken?: string;

/**
* Target hostname for GitHub Copilot API requests (used by API proxy sidecar)
*
* When enableApiProxy is true, this hostname is passed to the Node.js sidecar
* as `COPILOT_API_TARGET`. The proxy will forward Copilot API requests to this host
* instead of the default `api.githubcopilot.com`.
*
* Useful for GitHub Enterprise Server (GHES) deployments where the Copilot API
* endpoint differs from the public default.
*
* Can be set via:
* - CLI flag: `--copilot-api-target <host>`
* - Environment variable: `COPILOT_API_TARGET`
*
* @default 'api.githubcopilot.com'
* @example
Comment on lines +487 to +491
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The @default 'api.githubcopilot.com' claim isn’t always true anymore: the sidecar now auto-derives api.enterprise.githubcopilot.com when GITHUB_SERVER_URL is non-github.com. Please update the @default / description to reflect the actual precedence (explicit target > derived enterprise target > public default).

Copilot uses AI. Check for mistakes.
* ```bash
* awf --enable-api-proxy --copilot-api-target api.github.mycompany.com -- command
* ```
*/
copilotApiTarget?: string;
}

/**
Expand Down
Loading