From c224e5eab7fff0f1044a3a38993edee970b72021 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Mar 2026 16:13:07 +0000
Subject: [PATCH 1/3] Initial plan
From 68a6001e2334e75199ef9a5e58c6c7f61c88dec0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Mar 2026 16:17:13 +0000
Subject: [PATCH 2/3] chore: initial plan for extractAPIBasePath implementation
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/04cc1879-ebb0-4ab8-93a5-0dccdddb4f85
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
---
.../daily-multi-device-docs-tester.lock.yml | 42 ++++++++--------
.github/workflows/unbloat-docs.lock.yml | 50 +++++++++----------
2 files changed, 46 insertions(+), 46 deletions(-)
diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml
index 18a5d196ac..0e6f795d99 100644
--- a/.github/workflows/daily-multi-device-docs-tester.lock.yml
+++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml
@@ -27,7 +27,7 @@
# - shared/docs-server-lifecycle.md
# - shared/reporting.md
#
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f5a4802459021b8361db58fb67d33ae472aaffe53fceda01e56ad10d735f2c27","strict":true,"agent_id":"claude"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6747fad76bb2d4f0fe944fc4d92885fc495ec8c1ce60b1e268fd169ac9268de7","strict":true,"agent_id":"claude"}
name: "Multi-Device Docs Tester"
"on":
@@ -143,15 +143,15 @@ jobs:
run: |
bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh
{
- cat << 'GH_AW_PROMPT_55a476ca092f3fd9_EOF'
+ cat << 'GH_AW_PROMPT_c81a109c621158fb_EOF'
- GH_AW_PROMPT_55a476ca092f3fd9_EOF
+ GH_AW_PROMPT_c81a109c621158fb_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/playwright_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_55a476ca092f3fd9_EOF'
+ cat << 'GH_AW_PROMPT_c81a109c621158fb_EOF'
Tools: create_issue, upload_asset, missing_tool, missing_data, noop
@@ -185,20 +185,20 @@ jobs:
{{/if}}
- GH_AW_PROMPT_55a476ca092f3fd9_EOF
+ GH_AW_PROMPT_c81a109c621158fb_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_55a476ca092f3fd9_EOF'
+ cat << 'GH_AW_PROMPT_c81a109c621158fb_EOF'
- GH_AW_PROMPT_55a476ca092f3fd9_EOF
- cat << 'GH_AW_PROMPT_55a476ca092f3fd9_EOF'
+ GH_AW_PROMPT_c81a109c621158fb_EOF
+ cat << 'GH_AW_PROMPT_c81a109c621158fb_EOF'
{{#runtime-import .github/workflows/shared/docs-server-lifecycle.md}}
- GH_AW_PROMPT_55a476ca092f3fd9_EOF
- cat << 'GH_AW_PROMPT_55a476ca092f3fd9_EOF'
+ GH_AW_PROMPT_c81a109c621158fb_EOF
+ cat << 'GH_AW_PROMPT_c81a109c621158fb_EOF'
{{#runtime-import .github/workflows/shared/reporting.md}}
- GH_AW_PROMPT_55a476ca092f3fd9_EOF
- cat << 'GH_AW_PROMPT_55a476ca092f3fd9_EOF'
+ GH_AW_PROMPT_c81a109c621158fb_EOF
+ cat << 'GH_AW_PROMPT_c81a109c621158fb_EOF'
{{#runtime-import .github/workflows/daily-multi-device-docs-tester.md}}
- GH_AW_PROMPT_55a476ca092f3fd9_EOF
+ GH_AW_PROMPT_c81a109c621158fb_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
@@ -372,12 +372,12 @@ jobs:
mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_8a777334cac793e2_EOF'
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_c93bece35bcc7ae1_EOF'
{"create_issue":{"expires":48,"labels":["cookie"],"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"upload_asset":{"allowed-exts":[".png",".jpg",".jpeg"],"branch":"assets/${{ github.workflow }}","max-size":10240}}
- GH_AW_SAFE_OUTPUTS_CONFIG_8a777334cac793e2_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_c93bece35bcc7ae1_EOF
- name: Write Safe Outputs Tools
run: |
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_a4806cbf9d506369_EOF'
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_d2eba1dc753a725e_EOF'
{
"description_suffixes": {
"create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Labels [\"cookie\"] will be automatically added.",
@@ -386,8 +386,8 @@ jobs:
"repo_params": {},
"dynamic_tools": []
}
- GH_AW_SAFE_OUTPUTS_TOOLS_META_a4806cbf9d506369_EOF
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_310b49412733c666_EOF'
+ GH_AW_SAFE_OUTPUTS_TOOLS_META_d2eba1dc753a725e_EOF
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_03255beea463aa05_EOF'
{
"create_issue": {
"defaultMax": 1,
@@ -489,7 +489,7 @@ jobs:
}
}
}
- GH_AW_SAFE_OUTPUTS_VALIDATION_310b49412733c666_EOF
+ GH_AW_SAFE_OUTPUTS_VALIDATION_03255beea463aa05_EOF
node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs
- name: Generate Safe Outputs MCP Server Config
id: safe-outputs-config
@@ -560,7 +560,7 @@ jobs:
export GH_AW_ENGINE="claude"
export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.6'
- cat << GH_AW_MCP_CONFIG_47aa03a5c9473b5a_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
+ cat << GH_AW_MCP_CONFIG_6f62c6e537c76192_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
{
"mcpServers": {
"github": {
@@ -624,7 +624,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_47aa03a5c9473b5a_EOF
+ GH_AW_MCP_CONFIG_6f62c6e537c76192_EOF
- name: Download activation artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml
index 39009e85ae..4a01f4fa7b 100644
--- a/.github/workflows/unbloat-docs.lock.yml
+++ b/.github/workflows/unbloat-docs.lock.yml
@@ -28,7 +28,7 @@
# - shared/mcp/qmd-docs.md
# - shared/reporting.md
#
-# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"618b829bbc7f47e9574ed3e1557438fae0bfc6ca453edab14981b726236131ba","strict":true,"agent_id":"claude"}
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"1d5cfd4f91bd8fcf04b0386757779cc674a35eb53bac2a45273366b85d389beb","strict":true,"agent_id":"claude"}
name: "Documentation Unbloat"
"on":
@@ -187,9 +187,9 @@ jobs:
run: |
bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh
{
- cat << 'GH_AW_PROMPT_3d06355205885e9a_EOF'
+ cat << 'GH_AW_PROMPT_2d537e0f4ae05d79_EOF'
- GH_AW_PROMPT_3d06355205885e9a_EOF
+ GH_AW_PROMPT_2d537e0f4ae05d79_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
@@ -197,12 +197,12 @@ jobs:
cat "${RUNNER_TEMP}/gh-aw/prompts/qmd_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md"
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
- cat << 'GH_AW_PROMPT_3d06355205885e9a_EOF'
+ cat << 'GH_AW_PROMPT_2d537e0f4ae05d79_EOF'
Tools: add_comment, create_pull_request, upload_asset, missing_tool, missing_data, noop
- GH_AW_PROMPT_3d06355205885e9a_EOF
+ GH_AW_PROMPT_2d537e0f4ae05d79_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md"
- cat << 'GH_AW_PROMPT_3d06355205885e9a_EOF'
+ cat << 'GH_AW_PROMPT_2d537e0f4ae05d79_EOF'
upload_asset: provide a file path; returns a URL; assets are published after the workflow completes (safeoutputs).
@@ -234,23 +234,23 @@ jobs:
{{/if}}
- GH_AW_PROMPT_3d06355205885e9a_EOF
+ GH_AW_PROMPT_2d537e0f4ae05d79_EOF
cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
- cat << 'GH_AW_PROMPT_3d06355205885e9a_EOF'
+ cat << 'GH_AW_PROMPT_2d537e0f4ae05d79_EOF'
- GH_AW_PROMPT_3d06355205885e9a_EOF
- cat << 'GH_AW_PROMPT_3d06355205885e9a_EOF'
+ GH_AW_PROMPT_2d537e0f4ae05d79_EOF
+ cat << 'GH_AW_PROMPT_2d537e0f4ae05d79_EOF'
{{#runtime-import .github/workflows/shared/reporting.md}}
- GH_AW_PROMPT_3d06355205885e9a_EOF
- cat << 'GH_AW_PROMPT_3d06355205885e9a_EOF'
+ GH_AW_PROMPT_2d537e0f4ae05d79_EOF
+ cat << 'GH_AW_PROMPT_2d537e0f4ae05d79_EOF'
{{#runtime-import .github/workflows/shared/docs-server-lifecycle.md}}
- GH_AW_PROMPT_3d06355205885e9a_EOF
- cat << 'GH_AW_PROMPT_3d06355205885e9a_EOF'
+ GH_AW_PROMPT_2d537e0f4ae05d79_EOF
+ cat << 'GH_AW_PROMPT_2d537e0f4ae05d79_EOF'
{{#runtime-import .github/workflows/shared/mcp/qmd-docs.md}}
- GH_AW_PROMPT_3d06355205885e9a_EOF
- cat << 'GH_AW_PROMPT_3d06355205885e9a_EOF'
+ GH_AW_PROMPT_2d537e0f4ae05d79_EOF
+ cat << 'GH_AW_PROMPT_2d537e0f4ae05d79_EOF'
{{#runtime-import .github/workflows/unbloat-docs.md}}
- GH_AW_PROMPT_3d06355205885e9a_EOF
+ GH_AW_PROMPT_2d537e0f4ae05d79_EOF
} > "$GH_AW_PROMPT"
- name: Interpolate variables and render templates
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
@@ -466,12 +466,12 @@ jobs:
mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_8c6de75ca9d63c58_EOF'
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_815bd590907de7b8_EOF'
{"add_comment":{"max":1},"create_pull_request":{"auto_merge":true,"draft":true,"expires":48,"fallback_as_issue":false,"labels":["documentation","automation"],"max":1,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"reviewers":["copilot"],"title_prefix":"[docs] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"upload_asset":{"allowed-exts":[".png",".jpg",".jpeg"],"branch":"assets/${{ github.workflow }}","max-size":10240}}
- GH_AW_SAFE_OUTPUTS_CONFIG_8c6de75ca9d63c58_EOF
+ GH_AW_SAFE_OUTPUTS_CONFIG_815bd590907de7b8_EOF
- name: Write Safe Outputs Tools
run: |
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_2bd18671c86f4c47_EOF'
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_6882e4174d89c62c_EOF'
{
"description_suffixes": {
"add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added.",
@@ -481,8 +481,8 @@ jobs:
"repo_params": {},
"dynamic_tools": []
}
- GH_AW_SAFE_OUTPUTS_TOOLS_META_2bd18671c86f4c47_EOF
- cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_b9c0a140e277dd38_EOF'
+ GH_AW_SAFE_OUTPUTS_TOOLS_META_6882e4174d89c62c_EOF
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_7e6df8d287392c6f_EOF'
{
"add_comment": {
"defaultMax": 1,
@@ -605,7 +605,7 @@ jobs:
}
}
}
- GH_AW_SAFE_OUTPUTS_VALIDATION_b9c0a140e277dd38_EOF
+ GH_AW_SAFE_OUTPUTS_VALIDATION_7e6df8d287392c6f_EOF
node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs
- name: Generate Safe Outputs MCP Server Config
id: safe-outputs-config
@@ -710,7 +710,7 @@ jobs:
export GH_AW_ENGINE="claude"
export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.6'
- cat << GH_AW_MCP_CONFIG_c8fb4c5e383447e3_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
+ cat << GH_AW_MCP_CONFIG_249bb59b60f3bd35_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
{
"mcpServers": {
"github": {
@@ -787,7 +787,7 @@ jobs:
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
}
}
- GH_AW_MCP_CONFIG_c8fb4c5e383447e3_EOF
+ GH_AW_MCP_CONFIG_249bb59b60f3bd35_EOF
- name: Download activation artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
From d7923df3d4682eb77300756ae50e5f202aab6277 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Mar 2026 16:38:38 +0000
Subject: [PATCH 3/3] fix: pass
--openai-api-base-path/--anthropic-api-base-path to AWF when URL contains
path
When OPENAI_BASE_URL or ANTHROPIC_BASE_URL in engine.env contains a path prefix
(e.g., /serving-endpoints), the compiler was stripping the path and only passing
the hostname via --openai-api-target. This caused API requests to go to the wrong
URL, returning 404.
This fix adds extractAPIBasePath() to extract the path component, and passes it
via --openai-api-base-path and --anthropic-api-base-path to AWF.
Fixes: github/gh-aw#21320
Agent-Logs-Url: https://github.com/github/gh-aw/sessions/04cc1879-ebb0-4ab8-93a5-0dccdddb4f85
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
---
pkg/workflow/awf_helpers.go | 56 ++++++++++++
pkg/workflow/awf_helpers_test.go | 146 +++++++++++++++++++++++++++++++
2 files changed, 202 insertions(+)
diff --git a/pkg/workflow/awf_helpers.go b/pkg/workflow/awf_helpers.go
index f97884d9b6..4fc68614be 100644
--- a/pkg/workflow/awf_helpers.go
+++ b/pkg/workflow/awf_helpers.go
@@ -221,6 +221,21 @@ func BuildAWFArgs(config AWFCommandConfig) []string {
awfHelpersLog.Printf("Added --anthropic-api-target=%s", anthropicTarget)
}
+ // Pass base path if URL contains a path component
+ // This is required for endpoints with path prefixes (e.g., Databricks /serving-endpoints,
+ // Azure OpenAI /openai/deployments/, corporate LLM routers with path-based routing)
+ openaiBasePath := extractAPIBasePath(config.WorkflowData, "OPENAI_BASE_URL")
+ if openaiBasePath != "" {
+ awfArgs = append(awfArgs, "--openai-api-base-path", openaiBasePath)
+ awfHelpersLog.Printf("Added --openai-api-base-path=%s", openaiBasePath)
+ }
+
+ anthropicBasePath := extractAPIBasePath(config.WorkflowData, "ANTHROPIC_BASE_URL")
+ if anthropicBasePath != "" {
+ awfArgs = append(awfArgs, "--anthropic-api-base-path", anthropicBasePath)
+ awfHelpersLog.Printf("Added --anthropic-api-base-path=%s", anthropicBasePath)
+ }
+
// Add Copilot API target for custom Copilot endpoints (GHEC, GHES, or custom).
// Resolved from engine.api-target (explicit) or GITHUB_COPILOT_BASE_URL in engine.env (implicit).
if copilotTarget := GetCopilotAPITarget(config.WorkflowData); copilotTarget != "" {
@@ -355,6 +370,47 @@ func extractAPITargetHost(workflowData *WorkflowData, envVar string) string {
return host
}
+// extractAPIBasePath extracts the path component from a custom API base URL in engine.env.
+// Returns the path prefix (e.g., "/serving-endpoints") or empty string if no path is present.
+// Root-only paths ("/") and empty paths return empty string.
+//
+// This is used to pass --openai-api-base-path and --anthropic-api-base-path to AWF when
+// the configured base URL contains a path (e.g., Databricks serving endpoints, Azure OpenAI
+// deployments, or corporate LLM routers with path-based routing).
+func extractAPIBasePath(workflowData *WorkflowData, envVar string) string {
+ if workflowData == nil || workflowData.EngineConfig == nil || workflowData.EngineConfig.Env == nil {
+ return ""
+ }
+
+ baseURL, exists := workflowData.EngineConfig.Env[envVar]
+ if !exists || baseURL == "" {
+ return ""
+ }
+
+ // Remove protocol prefix if present
+ host := baseURL
+ if idx := strings.Index(host, "://"); idx != -1 {
+ host = host[idx+3:]
+ }
+
+ // Extract path (everything after the first /)
+ if idx := strings.Index(host, "/"); idx != -1 {
+ path := host[idx:] // e.g., "/serving-endpoints"
+ // Strip query string or fragment if present
+ if qi := strings.IndexAny(path, "?#"); qi != -1 {
+ path = path[:qi]
+ }
+ // Remove trailing slashes; a root-only path "/" becomes "" and returns empty
+ path = strings.TrimRight(path, "/")
+ if path != "" {
+ awfHelpersLog.Printf("Extracted API base path from %s: %s", envVar, path)
+ return path
+ }
+ }
+
+ return ""
+}
+
// GetCopilotAPITarget returns the effective Copilot API target hostname, checking in order:
// 1. engine.api-target (explicit, takes precedence)
// 2. GITHUB_COPILOT_BASE_URL in engine.env (implicit, derived from the configured Copilot base URL)
diff --git a/pkg/workflow/awf_helpers_test.go b/pkg/workflow/awf_helpers_test.go
index 9439d5b1af..9ba93f1ba2 100644
--- a/pkg/workflow/awf_helpers_test.go
+++ b/pkg/workflow/awf_helpers_test.go
@@ -249,6 +249,152 @@ func TestAWFCustomAPITargetFlags(t *testing.T) {
})
}
+// TestExtractAPIBasePath tests the extractAPIBasePath function that extracts
+// path components from custom API base URLs in engine.env
+func TestExtractAPIBasePath(t *testing.T) {
+ tests := []struct {
+ name string
+ url string
+ expected string
+ }{
+ {"databricks serving endpoint", "https://host.com/serving-endpoints", "/serving-endpoints"},
+ {"azure openai deployment", "https://host.com/openai/deployments/gpt-4", "/openai/deployments/gpt-4"},
+ {"simple path", "https://host.com/v1", "/v1"},
+ {"trailing slash stripped", "https://host.com/api/", "/api"},
+ {"multiple trailing slashes stripped", "https://host.com/api///", "/api"},
+ {"no path", "https://host.com", ""},
+ {"bare hostname", "host.com", ""},
+ {"root path only", "https://host.com/", ""},
+ {"query string stripped", "https://host.com/api?param=value", "/api"},
+ {"fragment stripped", "https://host.com/api#section", "/api"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ workflowData := &WorkflowData{
+ EngineConfig: &EngineConfig{
+ Env: map[string]string{
+ "OPENAI_BASE_URL": tt.url,
+ },
+ },
+ }
+ result := extractAPIBasePath(workflowData, "OPENAI_BASE_URL")
+ assert.Equal(t, tt.expected, result, "Extracted base path should match expected value")
+ })
+ }
+
+ t.Run("returns empty string when workflow data is nil", func(t *testing.T) {
+ result := extractAPIBasePath(nil, "OPENAI_BASE_URL")
+ assert.Empty(t, result, "Should return empty string for nil workflow data")
+ })
+
+ t.Run("returns empty string when engine config is nil", func(t *testing.T) {
+ workflowData := &WorkflowData{EngineConfig: nil}
+ result := extractAPIBasePath(workflowData, "OPENAI_BASE_URL")
+ assert.Empty(t, result, "Should return empty string when engine config is nil")
+ })
+
+ t.Run("returns empty string when env var not set", func(t *testing.T) {
+ workflowData := &WorkflowData{
+ EngineConfig: &EngineConfig{
+ Env: map[string]string{"OTHER_VAR": "value"},
+ },
+ }
+ result := extractAPIBasePath(workflowData, "OPENAI_BASE_URL")
+ assert.Empty(t, result, "Should return empty string when env var not set")
+ })
+}
+
+// TestAWFBasePathFlags tests that BuildAWFArgs includes --openai-api-base-path and
+// --anthropic-api-base-path when the configured URLs contain a path component
+func TestAWFBasePathFlags(t *testing.T) {
+ t.Run("includes openai-api-base-path when OPENAI_BASE_URL has path component", func(t *testing.T) {
+ workflowData := &WorkflowData{
+ Name: "test-workflow",
+ EngineConfig: &EngineConfig{
+ ID: "codex",
+ Env: map[string]string{
+ "OPENAI_BASE_URL": "https://stone-dataplatform.cloud.databricks.com/serving-endpoints",
+ "OPENAI_API_KEY": "${{ secrets.DATABRICKS_KEY }}",
+ },
+ },
+ NetworkPermissions: &NetworkPermissions{
+ Firewall: &FirewallConfig{Enabled: true},
+ },
+ }
+
+ config := AWFCommandConfig{
+ EngineName: "codex",
+ WorkflowData: workflowData,
+ AllowedDomains: "github.com",
+ }
+
+ args := BuildAWFArgs(config)
+ argsStr := strings.Join(args, " ")
+
+ assert.Contains(t, argsStr, "--openai-api-target", "Should include --openai-api-target flag")
+ assert.Contains(t, argsStr, "--openai-api-base-path", "Should include --openai-api-base-path flag")
+ assert.Contains(t, argsStr, "/serving-endpoints", "Should include the path component")
+ })
+
+ t.Run("includes anthropic-api-base-path when ANTHROPIC_BASE_URL has path component", func(t *testing.T) {
+ workflowData := &WorkflowData{
+ Name: "test-workflow",
+ EngineConfig: &EngineConfig{
+ ID: "claude",
+ Env: map[string]string{
+ "ANTHROPIC_BASE_URL": "https://proxy.company.com/anthropic/v1",
+ "ANTHROPIC_API_KEY": "${{ secrets.ANTHROPIC_KEY }}",
+ },
+ },
+ NetworkPermissions: &NetworkPermissions{
+ Firewall: &FirewallConfig{Enabled: true},
+ },
+ }
+
+ config := AWFCommandConfig{
+ EngineName: "claude",
+ WorkflowData: workflowData,
+ AllowedDomains: "github.com",
+ }
+
+ args := BuildAWFArgs(config)
+ argsStr := strings.Join(args, " ")
+
+ assert.Contains(t, argsStr, "--anthropic-api-target", "Should include --anthropic-api-target flag")
+ assert.Contains(t, argsStr, "--anthropic-api-base-path", "Should include --anthropic-api-base-path flag")
+ assert.Contains(t, argsStr, "/anthropic/v1", "Should include the path component")
+ })
+
+ t.Run("does not include base-path flags when URLs have no path", func(t *testing.T) {
+ workflowData := &WorkflowData{
+ Name: "test-workflow",
+ EngineConfig: &EngineConfig{
+ ID: "codex",
+ Env: map[string]string{
+ "OPENAI_BASE_URL": "https://openai-proxy.company.com",
+ "ANTHROPIC_BASE_URL": "https://anthropic-proxy.company.com",
+ },
+ },
+ NetworkPermissions: &NetworkPermissions{
+ Firewall: &FirewallConfig{Enabled: true},
+ },
+ }
+
+ config := AWFCommandConfig{
+ EngineName: "codex",
+ WorkflowData: workflowData,
+ AllowedDomains: "github.com",
+ }
+
+ args := BuildAWFArgs(config)
+ argsStr := strings.Join(args, " ")
+
+ assert.NotContains(t, argsStr, "--openai-api-base-path", "Should not include --openai-api-base-path when no path in URL")
+ assert.NotContains(t, argsStr, "--anthropic-api-base-path", "Should not include --anthropic-api-base-path when no path in URL")
+ })
+}
+
// TestBuildAWFArgsAuditDir tests that BuildAWFArgs always includes --audit-dir
// pointing to the AWF audit directory for policy-manifest.json and other audit files
func TestBuildAWFArgsAuditDir(t *testing.T) {