Skip to content
Merged
4 changes: 0 additions & 4 deletions .github/workflows/ci-doctor.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions .github/workflows/issue-duplication-detector.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions .github/workflows/pelis-agent-factory-advisor.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions .github/workflows/secret-digger-claude.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions .github/workflows/secret-digger-codex.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions .github/workflows/secret-digger-copilot.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/security-guard.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions .github/workflows/security-review.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/smoke-chroot.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions .github/workflows/smoke-claude.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions .github/workflows/smoke-codex.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions .github/workflows/smoke-copilot.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .github/workflows/test-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ jobs:

- name: Comment PR with coverage comparison
if: github.event_name == 'pull_request'
continue-on-error: true
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
22 changes: 15 additions & 7 deletions containers/api-proxy/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ 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 API target hosts (supports custom endpoints / internal LLM routers)
const OPENAI_API_TARGET = process.env.OPENAI_API_TARGET || 'api.openai.com';
const ANTHROPIC_API_TARGET = process.env.ANTHROPIC_API_TARGET || 'api.anthropic.com';

// 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() {
Expand Down Expand Up @@ -76,7 +80,11 @@ 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,
api_targets: {
openai: OPENAI_API_TARGET,
anthropic: ANTHROPIC_API_TARGET,
copilot: COPILOT_API_TARGET,
},
providers: {
openai: !!OPENAI_API_KEY,
anthropic: !!ANTHROPIC_API_KEY,
Expand Down Expand Up @@ -397,13 +405,13 @@ if (OPENAI_API_KEY) {
const contentLength = parseInt(req.headers['content-length'], 10) || 0;
if (checkRateLimit(req, res, 'openai', contentLength)) return;

proxyRequest(req, res, 'api.openai.com', {
proxyRequest(req, res, OPENAI_API_TARGET, {
'Authorization': `Bearer ${OPENAI_API_KEY}`,
}, 'openai');
});

server.listen(HEALTH_PORT, '0.0.0.0', () => {
logRequest('info', 'server_start', { message: `OpenAI proxy listening on port ${HEALTH_PORT}` });
logRequest('info', 'server_start', { message: `OpenAI proxy listening on port ${HEALTH_PORT}`, target: OPENAI_API_TARGET });
});
} else {
// No OpenAI key — still need a health endpoint on port 10000 for Docker healthcheck
Expand Down Expand Up @@ -436,11 +444,11 @@ if (ANTHROPIC_API_KEY) {
if (!req.headers['anthropic-version']) {
anthropicHeaders['anthropic-version'] = '2023-06-01';
}
proxyRequest(req, res, 'api.anthropic.com', anthropicHeaders, 'anthropic');
proxyRequest(req, res, ANTHROPIC_API_TARGET, anthropicHeaders, 'anthropic');
});

server.listen(10001, '0.0.0.0', () => {
logRequest('info', 'server_start', { message: 'Anthropic proxy listening on port 10001' });
logRequest('info', 'server_start', { message: 'Anthropic proxy listening on port 10001', target: ANTHROPIC_API_TARGET });
});
}

Expand Down Expand Up @@ -488,11 +496,11 @@ if (ANTHROPIC_API_KEY) {
if (!req.headers['anthropic-version']) {
anthropicHeaders['anthropic-version'] = '2023-06-01';
}
proxyRequest(req, res, 'api.anthropic.com', anthropicHeaders);
proxyRequest(req, res, ANTHROPIC_API_TARGET, anthropicHeaders);
});

opencodeServer.listen(10004, '0.0.0.0', () => {
console.log('[API Proxy] OpenCode proxy listening on port 10004 (-> Anthropic)');
console.log(`[API Proxy] OpenCode proxy listening on port 10004 (-> Anthropic at ${ANTHROPIC_API_TARGET})`);
});
}

Expand Down
10 changes: 10 additions & 0 deletions docs/api-proxy-sidecar.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,16 @@ sudo awf --enable-api-proxy [OPTIONS] -- COMMAND
- `api.openai.com` — for OpenAI/Codex
- `api.anthropic.com` — for Anthropic/Claude

**Optional flags for custom upstream endpoints**:

| Flag | Default | Description |
|------|---------|-------------|
| `--openai-api-target <host>` | `api.openai.com` | Custom upstream for OpenAI API requests (e.g. Azure OpenAI or an internal LLM router). Can also be set via `OPENAI_API_TARGET` env var. |
| `--anthropic-api-target <host>` | `api.anthropic.com` | Custom upstream for Anthropic API requests (e.g. an internal Claude router). Can also be set via `ANTHROPIC_API_TARGET` env var. |
| `--copilot-api-target <host>` | auto-derived | Custom upstream for GitHub Copilot API requests (useful for GHES). Can also be set via `COPILOT_API_TARGET` env var. |

> **Important**: When using a custom `--openai-api-target` or `--anthropic-api-target`, you must add the target domain to `--allow-domains` so the firewall permits outbound traffic. AWF will emit a warning if a custom target is set but not in the allowlist.

### Container configuration

The sidecar container:
Expand Down
20 changes: 20 additions & 0 deletions scripts/ci/postprocess-smoke-workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ const shallowDepthRegex = /^(\s+)depth: 1\n/gm;
// instead of pre-built GHCR images that may be stale.
const imageTagRegex = /--image-tag\s+[0-9.]+\s+--skip-pull/g;

// Remove the "Setup Scripts" step from update_cache_memory jobs.
// This step downloads the private github/gh-aw action but is never used in
// update_cache_memory (no subsequent steps reference /opt/gh-aw/actions/).
// With permissions: {} on these jobs, downloading the private action fails
// with 401 Unauthorized.
const updateCacheSetupScriptRegex =
/^(\s+)- name: Setup Scripts\n\1 uses: github\/gh-aw\/actions\/setup@v[\d.]+\n\1 with:\n\1 destination: \/opt\/gh-aw\/actions\n(\1- name: Download cache-memory artifact)/gm;

for (const workflowPath of workflowPaths) {
let content = fs.readFileSync(workflowPath, 'utf-8');
let modified = false;
Expand Down Expand Up @@ -132,6 +140,18 @@ for (const workflowPath of workflowPaths) {
console.log(` Replaced ${imageTagMatches.length} --image-tag/--skip-pull with --build-local`);
}

// Remove unused "Setup Scripts" step from update_cache_memory jobs.
// The step downloads a private action but is never used in these jobs,
// causing 401 Unauthorized failures when permissions: {} is set.
const updateCacheSetupMatches = content.match(updateCacheSetupScriptRegex);
if (updateCacheSetupMatches) {
content = content.replace(updateCacheSetupScriptRegex, '$2');
modified = true;
console.log(
` Removed ${updateCacheSetupMatches.length} unused Setup Scripts step(s) from update_cache_memory`
);
}

if (modified) {
fs.writeFileSync(workflowPath, content);
console.log(`Updated ${workflowPath}`);
Expand Down
Loading
Loading