From fa064c757ba1a79e65d9ae359c5006d2468428eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 00:18:42 +0000 Subject: [PATCH 01/12] Initial plan From beff9884d38d3eed70b09680a1c930c0edf6a2bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 00:37:19 +0000 Subject: [PATCH 02/12] fix(pydantic): install pydantic-ai and use the real pai CLI interface Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/shared/pydantic.md | 99 +++++++++++++--- .github/workflows/smoke-pydantic.lock.yml | 111 +++++++++++++----- .github/workflows/smoke-pydantic.md | 5 +- .../docs/reference/frontmatter-full.md | 5 + pkg/parser/schemas/main_workflow_schema.json | 4 + pkg/workflow/behavior_defined_engine.go | 6 +- .../behavior_defined_engine_model_test.go | 68 +++++++++++ pkg/workflow/engine_definition.go | 15 ++- 8 files changed, 258 insertions(+), 55 deletions(-) create mode 100644 pkg/workflow/behavior_defined_engine_model_test.go diff --git a/.github/workflows/shared/pydantic.md b/.github/workflows/shared/pydantic.md index 12fe0f1073d..ba5100a5556 100644 --- a/.github/workflows/shared/pydantic.md +++ b/.github/workflows/shared/pydantic.md @@ -1,15 +1,17 @@ --- runtimes: - uv: - version: latest + python: + version: "3.12" pre-agent-steps: - - name: Predownload Pydantic AI CLI - run: uv run pai --version + - name: Preinstall Pydantic AI CLI + run: | + python3 -m pip install --quiet --user --disable-pip-version-check "pydantic-ai==$GH_AW_ENGINE_VERSION" + "$HOME/.local/bin/pai" --version engine: id: pydantic-ai - version: "0.1.0" + version: "2.26.0" display-name: Pydantic AI - description: Pydantic AI headless coding agent CLI with MCP support + description: Pydantic AI CLI (pai) running one-shot prompts with MCP tool support experimental: true mcp: true provider: @@ -28,21 +30,79 @@ engine: - raw.githubusercontent.com - api.github.com - objects.githubusercontent.com + - pypi.org + - files.pythonhosted.org provider-domains: copilot: api.githubcopilot.com anthropic: api.anthropic.com openai: api.openai.com execution: - command-name: uv + command-name: pai args: - - run - - pai - - run + - --no-stream step-name: Execute Pydantic AI CLI model-env-var: PAI_MODEL - mcp-config-env-var: GH_AW_MCP_CONFIG + model-env-provider-prefix: openai-chat + model-env-provider-separator: ":" + model-flag: -m + mcp-config-flag: -a write-timestamp: true provider-env-mode: universal-llm-consumer + mcp: + config-path: .pydantic-ai/agent.json + config-adapter: | + // Converts the MCP gateway's standard HTTP-based configuration into a + // Pydantic AI agent spec (https://ai.pydantic.dev), which is the only way + // the `pai` CLI can be given MCP servers: each gateway server becomes an + // `MCP` capability entry and the spec file is passed via `pai -a `. + // The `model` field is mandatory in an agent spec but is always overridden + // by the `-m` flag the compiler adds when the workflow declares a model. + const fs = require("fs"); + const path = require("path"); + + const requireEnvVar = name => { + const value = process.env[name]; + if (!value) throw new Error(`${name} environment variable is required`); + return value; + }; + + const gatewayOutputPath = requireEnvVar("MCP_GATEWAY_OUTPUT"); + const workspace = requireEnvVar("GITHUB_WORKSPACE"); + const gatewayDomain = process.env.MCP_GATEWAY_DOMAIN || "host.docker.internal"; + const gatewayPort = requireEnvVar("MCP_GATEWAY_PORT"); + const gatewayURL = `http://${gatewayDomain}:${gatewayPort}`; + + let cliServers; + try { + cliServers = new Set(JSON.parse(process.env.GH_AW_MCP_CLI_SERVERS || "[]")); + } catch (error) { + throw new Error(`Failed to parse GH_AW_MCP_CLI_SERVERS: ${error instanceof Error ? error.message : String(error)}`); + } + + const gatewayOutput = JSON.parse(fs.readFileSync(gatewayOutputPath, "utf8")); + const rawServers = gatewayOutput.mcpServers; + const servers = rawServers && typeof rawServers === "object" && !Array.isArray(rawServers) ? rawServers : {}; + + const capabilities = []; + for (const [name, entry] of Object.entries(servers)) { + if (cliServers.has(name) || !entry || typeof entry !== "object") continue; + if (typeof entry.url !== "string") { + console.log(`Skipping MCP server ${name}: the Pydantic AI CLI only supports HTTP MCP servers`); + continue; + } + const mcp = { + id: name, + url: entry.url.replace(/^http:\/\/[^/]+\/mcp\//, `${gatewayURL}/mcp/`), + }; + if (entry.headers && typeof entry.headers === "object") mcp.headers = entry.headers; + capabilities.push({ MCP: mcp }); + } + + const configPath = path.join(workspace, ".pydantic-ai", "agent.json"); + fs.mkdirSync(path.dirname(configPath), { recursive: true }); + fs.writeFileSync(configPath, JSON.stringify({ model: "openai-chat:gpt-5", capabilities }, null, 2), { mode: 0o600 }); + fs.chmodSync(configPath, 0o600); + console.log(`Wrote ${capabilities.length} MCP server(s) to ${configPath}`); log-parser: | function parseLog(logContent) { const lines = logContent.split("\n"); @@ -118,9 +178,8 @@ engine: diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index 06170cd9a15..fcfdb8c4256 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"7b47965c89490ac7386727c480547f477af936ba798e089fb6485a5abf85dc77","body_hash":"5d4605d11ff843d9619387c94b8f6fcec25c492df7ff7fe7c8f157f5e30c767f","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"0.1.0"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"astral-sh/setup-uv","sha":"c771a70e6277c0a99b617c7a806ffedaca235ff9","version":"v9.0.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"4f3635f9e909bf8eb9d858b00abb7064dfbf74c579f2024ee81679212f441192","body_hash":"773660b90ed50629358a6e3ff45d618c8d3da1acc040d575cb86e3c9270383ee","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ @@ -44,7 +44,6 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4 @@ -134,7 +133,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Pydantic AI" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-pydantic.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "0.1.0" + GH_AW_INFO_VERSION: "2.26.0" GH_AW_INFO_ENGINE_ID: "pydantic-ai" - name: Generate agentic run info id: generate_aw_info @@ -142,8 +141,8 @@ jobs: GH_AW_INFO_ENGINE_ID: "pydantic-ai" GH_AW_INFO_ENGINE_NAME: "Pydantic AI" GH_AW_INFO_MODEL: "copilot/claude-sonnet-4-5" - GH_AW_INFO_VERSION: "0.1.0" - GH_AW_INFO_AGENT_VERSION: "0.1.0" + GH_AW_INFO_VERSION: "2.26.0" + GH_AW_INFO_AGENT_VERSION: "2.26.0" GH_AW_INFO_WORKFLOW_NAME: "Smoke Pydantic AI" GH_AW_INFO_EXPERIMENTAL: "true" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "false" @@ -460,7 +459,7 @@ jobs: GH_AW_ASSETS_ALLOWED_EXTS: "" GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 - GH_AW_ENGINE_VERSION: "0.1.0" + GH_AW_ENGINE_VERSION: "2.26.0" GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} @@ -499,7 +498,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Pydantic AI" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-pydantic.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "0.1.0" + GH_AW_INFO_VERSION: "2.26.0" GH_AW_INFO_ENGINE_ID: "pydantic-ai" - name: Set runtime paths id: set-runtime-paths @@ -517,11 +516,6 @@ jobs: uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.12' - - name: Setup uv - # zizmor: ignore[github_action_from_unverified_creator_used] - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 - with: - version: 'latest' - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise @@ -578,8 +572,10 @@ jobs: env: GH_AW_SKILL_DIR: ".pydantic-ai/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - - name: Predownload Pydantic AI CLI - run: uv run pai --version + - name: Preinstall Pydantic AI CLI + run: | + python3 -m pip install --quiet --user --disable-pip-version-check "pydantic-ai==$GH_AW_ENGINE_VERSION" + "$HOME/.local/bin/pai" --version - name: Download container images run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7 ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627 ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8 ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196 ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520 @@ -761,6 +757,65 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); + - name: Write Pydantic AI MCP config adapter script + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/actions" + cat <<'GHAW_MCP_CONFIG_ADAPTER_SCRIPT_7e1a4d2c_EOF' > "${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_mcp_config_adapter.cjs" + // Converts the MCP gateway's standard HTTP-based configuration into a + // Pydantic AI agent spec (https://ai.pydantic.dev), which is the only way + // the `pai` CLI can be given MCP servers: each gateway server becomes an + // `MCP` capability entry and the spec file is passed via `pai -a `. + // The `model` field is mandatory in an agent spec but is always overridden + // by the `-m` flag the compiler adds when the workflow declares a model. + const fs = require("fs"); + const path = require("path"); + + const requireEnvVar = name => { + const value = process.env[name]; + if (!value) throw new Error(`${name} environment variable is required`); + return value; + }; + + const gatewayOutputPath = requireEnvVar("MCP_GATEWAY_OUTPUT"); + const workspace = requireEnvVar("GITHUB_WORKSPACE"); + const gatewayDomain = process.env.MCP_GATEWAY_DOMAIN || "host.docker.internal"; + const gatewayPort = requireEnvVar("MCP_GATEWAY_PORT"); + const gatewayURL = `http://${gatewayDomain}:${gatewayPort}`; + + let cliServers; + try { + cliServers = new Set(JSON.parse(process.env.GH_AW_MCP_CLI_SERVERS || "[]")); + } catch (error) { + throw new Error(`Failed to parse GH_AW_MCP_CLI_SERVERS: ${error instanceof Error ? error.message : String(error)}`); + } + + const gatewayOutput = JSON.parse(fs.readFileSync(gatewayOutputPath, "utf8")); + const rawServers = gatewayOutput.mcpServers; + const servers = rawServers && typeof rawServers === "object" && !Array.isArray(rawServers) ? rawServers : {}; + + const capabilities = []; + for (const [name, entry] of Object.entries(servers)) { + if (cliServers.has(name) || !entry || typeof entry !== "object") continue; + if (typeof entry.url !== "string") { + console.log(`Skipping MCP server ${name}: the Pydantic AI CLI only supports HTTP MCP servers`); + continue; + } + const mcp = { + id: name, + url: entry.url.replace(/^http:\/\/[^/]+\/mcp\//, `${gatewayURL}/mcp/`), + }; + if (entry.headers && typeof entry.headers === "object") mcp.headers = entry.headers; + capabilities.push({ MCP: mcp }); + } + + const configPath = path.join(workspace, ".pydantic-ai", "agent.json"); + fs.mkdirSync(path.dirname(configPath), { recursive: true }); + fs.writeFileSync(configPath, JSON.stringify({ model: "openai-chat:gpt-5", capabilities }, null, 2), { mode: 0o600 }); + fs.chmodSync(configPath, 0o600); + console.log(`Wrote ${capabilities.length} MCP server(s) to ${configPath}`); + + GHAW_MCP_CONFIG_ADAPTER_SCRIPT_7e1a4d2c_EOF + chmod 755 "${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_mcp_config_adapter.cjs" - name: Start MCP Gateway id: start-mcp-gateway env: @@ -790,6 +845,7 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="pydantic-ai" + export GH_AW_MCP_CONFIG_ADAPTER="pydantic-ai_mcp_config_adapter.cjs" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh" @@ -998,13 +1054,12 @@ jobs: fi # shellcheck disable=SC1003,SC2016,SC2086 awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && cd "${GITHUB_WORKSPACE}" && uv run pai run "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && cd "${GITHUB_WORKSPACE}" && pai --no-stream -m "$PAI_MODEL" -a .pydantic-ai/agent.json "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - GH_AW_ENGINE_VERSION: 0.1.0 + GH_AW_ENGINE_VERSION: 2.26.0 GH_AW_LLM_PROVIDER: github GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} - GH_AW_MCP_CONFIG: ${{ runner.temp }}/gh-aw/mcp-config/mcp-servers.json GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GITHUB_COPILOT_BASE_URL: http://host.docker.internal:10002 @@ -1012,7 +1067,7 @@ jobs: NO_PROXY: localhost,127.0.0.1,host.docker.internal,host.docker.internal:10000,host.docker.internal:10001,host.docker.internal:10002,host.docker.internal:10003,172.30.0.30,172.30.0.30:10000,172.30.0.30:10001,172.30.0.30:10002,172.30.0.30:10003 OPENAI_API_KEY: ${{ secrets.COPILOT_GITHUB_TOKEN }} OPENAI_BASE_URL: http://host.docker.internal:10002 - PAI_MODEL: copilot/claude-sonnet-4-5 + PAI_MODEL: openai-chat:claude-sonnet-4-5 RUNNER_TEMP: ${{ runner.temp }} TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Configure Git credentials @@ -1195,7 +1250,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Pydantic AI" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-pydantic.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "0.1.0" + GH_AW_INFO_VERSION: "2.26.0" GH_AW_INFO_ENGINE_ID: "pydantic-ai" - name: Download agent output artifact id: download-agent-output @@ -1462,7 +1517,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Pydantic AI" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-pydantic.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "0.1.0" + GH_AW_INFO_VERSION: "2.26.0" GH_AW_INFO_ENGINE_ID: "pydantic-ai" - name: Download agent output artifact id: download-agent-output @@ -1639,7 +1694,7 @@ jobs: printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"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/github/gh-aw-firewall/releases/download/v0.27.44/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.github.com\",\"api.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"objects.githubusercontent.com\",\"raw.githubusercontent.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.44,squid=sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627,agent=sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4,api-proxy=sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7,cli-proxy=sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"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/github/gh-aw-firewall/releases/download/v0.27.44/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.github.com\",\"api.githubcopilot.com\",\"files.pythonhosted.org\",\"github.com\",\"host.docker.internal\",\"objects.githubusercontent.com\",\"pypi.org\",\"raw.githubusercontent.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.44,squid=sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627,agent=sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4,api-proxy=sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7,cli-proxy=sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" @@ -1660,11 +1715,11 @@ jobs: fi # shellcheck disable=SC1003,SC2016,SC2086 awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && cd "${GITHUB_WORKSPACE}" && uv run pai run "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + -- /bin/bash -c 'set +o histexpand; export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && cd "${GITHUB_WORKSPACE}" && pai --no-stream -m "$PAI_MODEL" "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: GITHUB_STEP_SUMMARY: /tmp/gh-aw/step-summary.md COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - GH_AW_ENGINE_VERSION: 0.1.0 + GH_AW_ENGINE_VERSION: 2.26.0 GH_AW_LLM_PROVIDER: github GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -1673,7 +1728,7 @@ jobs: NO_PROXY: localhost,127.0.0.1,host.docker.internal,host.docker.internal:10000,host.docker.internal:10001,host.docker.internal:10002,host.docker.internal:10003,172.30.0.30,172.30.0.30:10000,172.30.0.30:10001,172.30.0.30:10002,172.30.0.30:10003 OPENAI_API_KEY: ${{ secrets.COPILOT_GITHUB_TOKEN }} OPENAI_BASE_URL: http://host.docker.internal:10002 - PAI_MODEL: copilot/claude-sonnet-4-5 + PAI_MODEL: openai-chat:claude-sonnet-4-5 RUNNER_TEMP: ${{ runner.temp }} TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Echo detection step summary @@ -1771,7 +1826,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Pydantic AI" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-pydantic.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "0.1.0" + GH_AW_INFO_VERSION: "2.26.0" GH_AW_INFO_ENGINE_ID: "pydantic-ai" - name: Check command position id: check_command_position @@ -1820,7 +1875,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "pydantic-ai" GH_AW_ENGINE_MODEL: "copilot/claude-sonnet-4-5" - GH_AW_ENGINE_VERSION: "0.1.0" + GH_AW_ENGINE_VERSION: "2.26.0" GH_AW_HEAD_SHA: ${{ github.event.pull_request.head.sha }} GH_AW_PROJECT_UTC: "-08:00" GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} @@ -1864,7 +1919,7 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Smoke Pydantic AI" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/smoke-pydantic.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "0.1.0" + GH_AW_INFO_VERSION: "2.26.0" GH_AW_INFO_ENGINE_ID: "pydantic-ai" - name: Download agent output artifact id: download-agent-output diff --git a/.github/workflows/smoke-pydantic.md b/.github/workflows/smoke-pydantic.md index 3f18d68fd48..97cd9b291e8 100644 --- a/.github/workflows/smoke-pydantic.md +++ b/.github/workflows/smoke-pydantic.md @@ -57,9 +57,8 @@ features: ## Test Requirements -1. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-pydantic-${{ github.run_id }}.txt` with content "Smoke test passed for Pydantic AI" (create the directory if it doesn't exist) -2. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -3. **Repository Access Testing**: Run `git log --oneline -1` in the repository checkout and confirm a commit is reported +1. **Model Connectivity Testing**: Answer the question "What is 2 + 2?" in a single short line. +2. **MCP Tool Testing**: Confirm that the `safeoutputs` MCP tools are available to you. ## Output diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index e163e9170a5..a543e5fbb3a 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -2998,6 +2998,11 @@ engine: # (optional) model-env-provider-prefix: "example-value" + # Separator inserted between the provider prefix and the model name when rewriting + # provider/model values. Defaults to "/". + # (optional) + model-env-provider-separator: "example-value" + # (optional) model-flag: "example-value" diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 2ff6dc39b92..6432ef33272 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -13412,6 +13412,10 @@ "model-env-provider-prefix": { "type": "string" }, + "model-env-provider-separator": { + "type": "string", + "description": "Separator inserted between the provider prefix and the model name when rewriting provider/model values. Defaults to \"/\"." + }, "model-flag": { "type": "string" }, diff --git a/pkg/workflow/behavior_defined_engine.go b/pkg/workflow/behavior_defined_engine.go index 5e20b414616..f09c2e61202 100644 --- a/pkg/workflow/behavior_defined_engine.go +++ b/pkg/workflow/behavior_defined_engine.go @@ -574,7 +574,11 @@ func (e *BehaviorDefinedEngine) applyBehaviorDefinedModelEnv(exec *EngineExecuti modelVal := workflowData.Model if exec.ModelEnvProviderPrefix != "" { if parts := strings.SplitN(modelVal, "/", 2); len(parts) == 2 { - modelVal = exec.ModelEnvProviderPrefix + "/" + parts[1] + separator := exec.ModelEnvProviderSeparator + if separator == "" { + separator = "/" + } + modelVal = exec.ModelEnvProviderPrefix + separator + parts[1] } } env[exec.ModelEnvVarName] = modelVal diff --git a/pkg/workflow/behavior_defined_engine_model_test.go b/pkg/workflow/behavior_defined_engine_model_test.go new file mode 100644 index 00000000000..68205e55372 --- /dev/null +++ b/pkg/workflow/behavior_defined_engine_model_test.go @@ -0,0 +1,68 @@ +//go:build !integration + +package workflow + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBehaviorDefinedEngineModelEnvProviderSeparator(t *testing.T) { + tests := []struct { + name string + prefix string + separator string + model string + expected string + }{ + { + name: "no prefix keeps the model unchanged", + model: "copilot/claude-sonnet-4-5", + expected: "copilot/claude-sonnet-4-5", + }, + { + name: "prefix defaults to a slash separator", + prefix: "openai", + model: "copilot/claude-sonnet-4-5", + expected: "openai/claude-sonnet-4-5", + }, + { + name: "explicit separator is used instead of a slash", + prefix: "openai-chat", + separator: ":", + model: "copilot/claude-sonnet-4-5", + expected: "openai-chat:claude-sonnet-4-5", + }, + { + name: "model without a provider is left unchanged", + prefix: "openai-chat", + separator: ":", + model: "gpt-5", + expected: "gpt-5", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + exec := &EngineExecutionDefinition{ + CommandName: "test-cli", + StepName: "Execute Test CLI", + ModelEnvVarName: "TEST_MODEL", + ModelEnvProviderPrefix: tt.prefix, + ModelEnvProviderSeparator: tt.separator, + } + engine, err := NewBehaviorDefinedEngine(&EngineDefinition{ + ID: "testmodel", + DisplayName: "TestModel", + Behaviors: &EngineBehaviorDefinition{Execution: exec}, + }) + require.NoError(t, err) + + env := map[string]string{} + engine.applyBehaviorDefinedModelEnv(exec, &WorkflowData{Model: tt.model}, env) + assert.Equal(t, tt.expected, env["TEST_MODEL"]) + }) + } +} diff --git a/pkg/workflow/engine_definition.go b/pkg/workflow/engine_definition.go index 1e78160b3e4..9492ac20d94 100644 --- a/pkg/workflow/engine_definition.go +++ b/pkg/workflow/engine_definition.go @@ -205,11 +205,16 @@ type EngineExecutionDefinition struct { StepName string `yaml:"step-name,omitempty"` ModelEnvVarName string `yaml:"model-env-var,omitempty"` ModelEnvProviderPrefix string `yaml:"model-env-provider-prefix,omitempty"` - ModelFlag string `yaml:"model-flag,omitempty"` - MCPConfigEnvVar string `yaml:"mcp-config-env-var,omitempty"` - MCPConfigFlag string `yaml:"mcp-config-flag,omitempty"` - WriteTimestamp bool `yaml:"write-timestamp,omitempty"` - ProviderEnvMode string `yaml:"provider-env-mode,omitempty"` + // ModelEnvProviderSeparator is the separator placed between the provider + // prefix and the model name when rewriting `provider/model` values. + // Defaults to "/" when empty (e.g. "openai/gpt-5"); engines such as + // Pydantic AI expect ":" instead (e.g. "openai:gpt-5"). + ModelEnvProviderSeparator string `yaml:"model-env-provider-separator,omitempty"` + ModelFlag string `yaml:"model-flag,omitempty"` + MCPConfigEnvVar string `yaml:"mcp-config-env-var,omitempty"` + MCPConfigFlag string `yaml:"mcp-config-flag,omitempty"` + WriteTimestamp bool `yaml:"write-timestamp,omitempty"` + ProviderEnvMode string `yaml:"provider-env-mode,omitempty"` // Env holds additional static environment variables to inject into the // execution step. Values are rendered verbatim and are not filtered // through the secrets allowlist, so they must not contain secret values. From 832781ecc0940a9be0601737179ab6f373d4c675 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 00:40:53 +0000 Subject: [PATCH 03/12] docs(pydantic): clarify agent spec model fallback in MCP adapter Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/shared/pydantic.md | 6 ++++-- .github/workflows/smoke-pydantic.lock.yml | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/shared/pydantic.md b/.github/workflows/shared/pydantic.md index ba5100a5556..d4af34f6672 100644 --- a/.github/workflows/shared/pydantic.md +++ b/.github/workflows/shared/pydantic.md @@ -55,8 +55,10 @@ engine: // Pydantic AI agent spec (https://ai.pydantic.dev), which is the only way // the `pai` CLI can be given MCP servers: each gateway server becomes an // `MCP` capability entry and the spec file is passed via `pai -a `. - // The `model` field is mandatory in an agent spec but is always overridden - // by the `-m` flag the compiler adds when the workflow declares a model. + // An agent spec must declare a `model`, but the compiler always appends + // `-m "$PAI_MODEL"` when the workflow declares a model, which takes + // precedence. The value below is only a valid-by-construction fallback for + // workflows that do not declare a model. const fs = require("fs"); const path = require("path"); diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index fcfdb8c4256..51c4804827a 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"4f3635f9e909bf8eb9d858b00abb7064dfbf74c579f2024ee81679212f441192","body_hash":"773660b90ed50629358a6e3ff45d618c8d3da1acc040d575cb86e3c9270383ee","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"828c03728bee464b9b68a90c3cd5d5aced03fb46b1dfedde222fc88c6153b362","body_hash":"773660b90ed50629358a6e3ff45d618c8d3da1acc040d575cb86e3c9270383ee","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -765,8 +765,10 @@ jobs: // Pydantic AI agent spec (https://ai.pydantic.dev), which is the only way // the `pai` CLI can be given MCP servers: each gateway server becomes an // `MCP` capability entry and the spec file is passed via `pai -a `. - // The `model` field is mandatory in an agent spec but is always overridden - // by the `-m` flag the compiler adds when the workflow declares a model. + // An agent spec must declare a `model`, but the compiler always appends + // `-m "$PAI_MODEL"` when the workflow declares a model, which takes + // precedence. The value below is only a valid-by-construction fallback for + // workflows that do not declare a model. const fs = require("fs"); const path = require("path"); From 89fb3cec823085f0a6d024fd177aef83fa32305e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 01:15:03 +0000 Subject: [PATCH 04/12] fix(pydantic): install the AWF binary for behavior-defined engines without npm installation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-pydantic.lock.yml | 4 ++++ pkg/workflow/behavior_defined_engine.go | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index 51c4804827a..8c3e49fe761 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -547,6 +547,8 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.44 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -1592,6 +1594,8 @@ jobs: touch /tmp/gh-aw/threat-detection/detection.log rm -f /tmp/gh-aw/step-summary.md touch /tmp/gh-aw/step-summary.md + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.44 - name: Write Pydantic AI log parser script if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true diff --git a/pkg/workflow/behavior_defined_engine.go b/pkg/workflow/behavior_defined_engine.go index f09c2e61202..c1b476e69dc 100644 --- a/pkg/workflow/behavior_defined_engine.go +++ b/pkg/workflow/behavior_defined_engine.go @@ -178,14 +178,19 @@ func (e *BehaviorDefinedEngine) GetInstallationSteps(workflowData *WorkflowData) // is declared for the engine's CLI itself. if behavior.Installation == nil { if behavior.HarnessScript == "" { - return nil + // Engines that install their CLI through `pre-agent-steps` (e.g. Pydantic AI) + // declare no installation block at all, but the agent still runs inside the + // firewall sandbox, so the AWF binary must be installed. + return BuildNpmEngineInstallStepsWithAWF(nil, workflowData) } return BuildNpmEngineInstallStepsWithAWF([]GitHubActionStep{GenerateNodeJsSetupStep()}, workflowData) } install := behavior.Installation if install.PackageManager != "npm" { - return nil + // Non-npm installations are performed by the engine's own steps, but the AWF + // binary is still required to run the agent inside the firewall sandbox. + return BuildNpmEngineInstallStepsWithAWF(nil, workflowData) } version := install.Version if workflowData != nil && workflowData.EngineConfig != nil && workflowData.EngineConfig.Version != "" { From dda9cff35ae6844e57cee49b73bdcd9d40a4b52b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 01:47:36 +0000 Subject: [PATCH 05/12] fix(pydantic): use the AWF proxy placeholder API key so model requests are token-steered Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/shared/pydantic.md | 6 ++++++ .github/workflows/smoke-pydantic.lock.yml | 6 +++--- pkg/workflow/behavior_defined_engine.go | 6 +++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/shared/pydantic.md b/.github/workflows/shared/pydantic.md index d4af34f6672..d6bd1c2f774 100644 --- a/.github/workflows/shared/pydantic.md +++ b/.github/workflows/shared/pydantic.md @@ -48,6 +48,12 @@ engine: mcp-config-flag: -a write-timestamp: true provider-env-mode: universal-llm-consumer + env: + # `pai` reads its credentials from the environment, so the real provider token + # must be replaced with the AWF api-proxy placeholder key. The proxy performs + # token steering and injects the real credential upstream; forwarding the raw + # token instead makes the proxy treat the request as BYOK and it is rejected. + OPENAI_API_KEY: awf-copilot-proxy mcp: config-path: .pydantic-ai/agent.json config-adapter: | diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index 8c3e49fe761..3837c849af2 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"828c03728bee464b9b68a90c3cd5d5aced03fb46b1dfedde222fc88c6153b362","body_hash":"773660b90ed50629358a6e3ff45d618c8d3da1acc040d575cb86e3c9270383ee","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"32386214518849c8dc565a31fc93e2db62ed0270c22b61d4d9d60af5ddec42e2","body_hash":"773660b90ed50629358a6e3ff45d618c8d3da1acc040d575cb86e3c9270383ee","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -1069,7 +1069,7 @@ jobs: GITHUB_COPILOT_BASE_URL: http://host.docker.internal:10002 GITHUB_WORKSPACE: ${{ github.workspace }} NO_PROXY: localhost,127.0.0.1,host.docker.internal,host.docker.internal:10000,host.docker.internal:10001,host.docker.internal:10002,host.docker.internal:10003,172.30.0.30,172.30.0.30:10000,172.30.0.30:10001,172.30.0.30:10002,172.30.0.30:10003 - OPENAI_API_KEY: ${{ secrets.COPILOT_GITHUB_TOKEN }} + OPENAI_API_KEY: awf-copilot-proxy OPENAI_BASE_URL: http://host.docker.internal:10002 PAI_MODEL: openai-chat:claude-sonnet-4-5 RUNNER_TEMP: ${{ runner.temp }} @@ -1732,7 +1732,7 @@ jobs: GITHUB_COPILOT_BASE_URL: http://host.docker.internal:10002 GITHUB_WORKSPACE: ${{ github.workspace }} NO_PROXY: localhost,127.0.0.1,host.docker.internal,host.docker.internal:10000,host.docker.internal:10001,host.docker.internal:10002,host.docker.internal:10003,172.30.0.30,172.30.0.30:10000,172.30.0.30:10001,172.30.0.30:10002,172.30.0.30:10003 - OPENAI_API_KEY: ${{ secrets.COPILOT_GITHUB_TOKEN }} + OPENAI_API_KEY: awf-copilot-proxy OPENAI_BASE_URL: http://host.docker.internal:10002 PAI_MODEL: openai-chat:claude-sonnet-4-5 RUNNER_TEMP: ${{ runner.temp }} diff --git a/pkg/workflow/behavior_defined_engine.go b/pkg/workflow/behavior_defined_engine.go index c1b476e69dc..a2dabd66143 100644 --- a/pkg/workflow/behavior_defined_engine.go +++ b/pkg/workflow/behavior_defined_engine.go @@ -531,10 +531,14 @@ func (e *BehaviorDefinedEngine) buildBehaviorDefinedExecutionEnv(exec *EngineExe "RUNNER_TEMP": "${{ runner.temp }}", } injectWorkflowCallNetworkAllowedEnv(env, workflowData) - maps.Copy(env, exec.Env) if exec.ProviderEnvMode == behaviorProviderEnvModeUniversalLLMConsumer { e.ApplyUniversalProviderEnv(env, workflowData, firewallEnabled) } + // The engine's own execution env is applied last so it can override the generic + // provider env. Engines whose CLI reads credentials from the environment (rather + // than from flags or a config file) need this to substitute the AWF proxy + // placeholder key for the real provider token. + maps.Copy(env, exec.Env) e.applyBehaviorDefinedMCPEnv(exec, workflowData, env) for _, binding := range e.definition.Auth { if binding.Secret != "" { From 3cc5781dbc7eb1db04afbf952ea4645e14850e58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 02:48:33 +0000 Subject: [PATCH 06/12] fix(pydantic): substitute the AWF proxy key inside the sandbox via a harness script Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/shared/pydantic.md | 64 +++++++++-- .github/workflows/smoke-pydantic.lock.yml | 129 ++++++++++++++++++++-- pkg/workflow/behavior_defined_engine.go | 6 +- 3 files changed, 177 insertions(+), 22 deletions(-) diff --git a/.github/workflows/shared/pydantic.md b/.github/workflows/shared/pydantic.md index d6bd1c2f774..619764f479a 100644 --- a/.github/workflows/shared/pydantic.md +++ b/.github/workflows/shared/pydantic.md @@ -44,16 +44,52 @@ engine: model-env-var: PAI_MODEL model-env-provider-prefix: openai-chat model-env-provider-separator: ":" - model-flag: -m - mcp-config-flag: -a write-timestamp: true provider-env-mode: universal-llm-consumer - env: - # `pai` reads its credentials from the environment, so the real provider token - # must be replaced with the AWF api-proxy placeholder key. The proxy performs - # token steering and injects the real credential upstream; forwarding the raw - # token instead makes the proxy treat the request as BYOK and it is rejected. - OPENAI_API_KEY: awf-copilot-proxy + harness-script: | + const { spawnSync } = require("child_process"); + const { existsSync, readFileSync } = require("fs"); + const { join } = require("path"); + const { homedir } = require("os"); + + const [command, ...commandArgs] = process.argv.slice(2); + + const promptFile = process.env.GH_AW_PROMPT; + if (!promptFile) { + throw new Error("GH_AW_PROMPT is not set"); + } + const workspace = process.env.GITHUB_WORKSPACE; + if (!workspace) { + throw new Error("GITHUB_WORKSPACE is not set"); + } + + const localBin = join(homedir(), ".local", "bin"); + const env = { ...process.env, PATH: `${localBin}:${process.env.PATH || ""}` }; + delete env.COPILOT_GITHUB_TOKEN; + // `pai` reads its credentials from the environment, so the placeholder key the + // AWF api-proxy expects has to be substituted here, inside the sandbox: the + // proxy treats a request whose key matches its own OPENAI_API_KEY as BYOK and + // forwards it upstream instead of steering it to the configured provider. + env.OPENAI_API_KEY = "awf-copilot-proxy"; + + const args = [...commandArgs]; + // The proxy exposes Copilot Claude models under their dotted IDs. + const model = env.PAI_MODEL?.replace(/^([^:]+:claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + if (model) { + args.push("-m", model); + } + const agentSpec = join(workspace, ".pydantic-ai", "agent.json"); + if (existsSync(agentSpec)) { + args.push("-a", agentSpec); + } + args.push(readFileSync(promptFile, "utf8")); + + const result = spawnSync(command, args, { cwd: workspace, encoding: "utf8", env }); + process.stdout.write(result.stdout || ""); + process.stderr.write(result.stderr || ""); + if (result.error || result.status !== 0) { + throw new Error("Pydantic AI execution failed"); + } mcp: config-path: .pydantic-ai/agent.json config-adapter: | @@ -61,7 +97,7 @@ engine: // Pydantic AI agent spec (https://ai.pydantic.dev), which is the only way // the `pai` CLI can be given MCP servers: each gateway server becomes an // `MCP` capability entry and the spec file is passed via `pai -a `. - // An agent spec must declare a `model`, but the compiler always appends + // An agent spec must declare a `model`, but the harness always appends // `-m "$PAI_MODEL"` when the workflow declares a model, which takes // precedence. The value below is only a valid-by-construction fallback for // workflows that do not declare a model. @@ -200,12 +236,20 @@ imports: `model` must use `provider/model` format. Supported providers are `copilot`, `anthropic`, and `openai`. Requests are routed through the AWF proxy, so the model is rewritten to the `openai-chat:` form the Pydantic AI CLI -expects and passed with `-m`. +expects and passed with `-m`. Copilot Claude aliases such as +`claude-sonnet-4-5` are normalized to the dotted model IDs exposed by the +proxy, such as `claude-sonnet-4.5`. MCP servers are rendered into a Pydantic AI agent spec at `.pydantic-ai/agent.json` and passed with `-a`, so safe outputs flow through the standard `safeoutputs` server automatically. +`pai` reads its credentials from the environment, so a harness script assembles +the command line and substitutes the AWF proxy placeholder API key inside the +sandbox. Setting the placeholder on the execution step instead would make the +proxy itself adopt it as a BYOK OpenAI key and forward requests upstream rather +than steering them to the configured provider. + The CLI is installed with `pip install --user pydantic-ai==` before the agent runs. --> diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index 3837c849af2..24826c27540 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"32386214518849c8dc565a31fc93e2db62ed0270c22b61d4d9d60af5ddec42e2","body_hash":"773660b90ed50629358a6e3ff45d618c8d3da1acc040d575cb86e3c9270383ee","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e5612189fab07d8edca0b033a4600409f5ddb8ba261cf336915a807651cf2217","body_hash":"dcede9a0b60ee7fdc6a1765b2c0a04714bc6935d5be216a44803695e2f0dcc05","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ @@ -42,6 +42,7 @@ # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) +# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 # - actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # @@ -547,6 +548,11 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '24' + package-manager-cache: false - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.44 --rootless - name: Determine automatic lockdown mode for GitHub MCP Server @@ -767,7 +773,7 @@ jobs: // Pydantic AI agent spec (https://ai.pydantic.dev), which is the only way // the `pai` CLI can be given MCP servers: each gateway server becomes an // `MCP` capability entry and the spec file is passed via `pai -a `. - // An agent spec must declare a `model`, but the compiler always appends + // An agent spec must declare a `model`, but the harness always appends // `-m "$PAI_MODEL"` when the workflow declares a model, which takes // precedence. The value below is only a valid-by-construction fallback for // workflows that do not declare a model. @@ -938,6 +944,56 @@ jobs: id: pre_agent_audit continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" + - name: Write Pydantic AI harness script + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/actions" + cat <<'GHAW_HARNESS_SCRIPT_3c7b9f1a_EOF' > "${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_harness.cjs" + const { spawnSync } = require("child_process"); + const { existsSync, readFileSync } = require("fs"); + const { join } = require("path"); + const { homedir } = require("os"); + + const [command, ...commandArgs] = process.argv.slice(2); + + const promptFile = process.env.GH_AW_PROMPT; + if (!promptFile) { + throw new Error("GH_AW_PROMPT is not set"); + } + const workspace = process.env.GITHUB_WORKSPACE; + if (!workspace) { + throw new Error("GITHUB_WORKSPACE is not set"); + } + + const localBin = join(homedir(), ".local", "bin"); + const env = { ...process.env, PATH: `${localBin}:${process.env.PATH || ""}` }; + delete env.COPILOT_GITHUB_TOKEN; + // `pai` reads its credentials from the environment, so the placeholder key the + // AWF api-proxy expects has to be substituted here, inside the sandbox: the + // proxy treats a request whose key matches its own OPENAI_API_KEY as BYOK and + // forwards it upstream instead of steering it to the configured provider. + env.OPENAI_API_KEY = "awf-copilot-proxy"; + + const args = [...commandArgs]; + // The proxy exposes Copilot Claude models under their dotted IDs. + const model = env.PAI_MODEL?.replace(/^([^:]+:claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + if (model) { + args.push("-m", model); + } + const agentSpec = join(workspace, ".pydantic-ai", "agent.json"); + if (existsSync(agentSpec)) { + args.push("-a", agentSpec); + } + args.push(readFileSync(promptFile, "utf8")); + + const result = spawnSync(command, args, { cwd: workspace, encoding: "utf8", env }); + process.stdout.write(result.stdout || ""); + process.stderr.write(result.stderr || ""); + if (result.error || result.status !== 0) { + throw new Error("Pydantic AI execution failed"); + } + + GHAW_HARNESS_SCRIPT_3c7b9f1a_EOF + chmod 755 "${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_harness.cjs" - name: Write Pydantic AI log parser script if: always() run: | @@ -1058,8 +1114,9 @@ jobs: fi # shellcheck disable=SC1003,SC2016,SC2086 awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && cd "${GITHUB_WORKSPACE}" && pai --no-stream -m "$PAI_MODEL" -a .pydantic-ai/agent.json "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_harness.cjs pai --no-stream' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: + AWF_REFLECT_ENABLED: 1 COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} GH_AW_ENGINE_VERSION: 2.26.0 GH_AW_LLM_PROVIDER: github @@ -1069,7 +1126,7 @@ jobs: GITHUB_COPILOT_BASE_URL: http://host.docker.internal:10002 GITHUB_WORKSPACE: ${{ github.workspace }} NO_PROXY: localhost,127.0.0.1,host.docker.internal,host.docker.internal:10000,host.docker.internal:10001,host.docker.internal:10002,host.docker.internal:10003,172.30.0.30,172.30.0.30:10000,172.30.0.30:10001,172.30.0.30:10002,172.30.0.30:10003 - OPENAI_API_KEY: awf-copilot-proxy + OPENAI_API_KEY: ${{ secrets.COPILOT_GITHUB_TOKEN }} OPENAI_BASE_URL: http://host.docker.internal:10002 PAI_MODEL: openai-chat:claude-sonnet-4-5 RUNNER_TEMP: ${{ runner.temp }} @@ -1594,8 +1651,65 @@ jobs: touch /tmp/gh-aw/threat-detection/detection.log rm -f /tmp/gh-aw/step-summary.md touch /tmp/gh-aw/step-summary.md + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '24' + package-manager-cache: false - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.44 + - name: Write Pydantic AI harness script + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/actions" + cat <<'GHAW_HARNESS_SCRIPT_3c7b9f1a_EOF' > "${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_harness.cjs" + const { spawnSync } = require("child_process"); + const { existsSync, readFileSync } = require("fs"); + const { join } = require("path"); + const { homedir } = require("os"); + + const [command, ...commandArgs] = process.argv.slice(2); + + const promptFile = process.env.GH_AW_PROMPT; + if (!promptFile) { + throw new Error("GH_AW_PROMPT is not set"); + } + const workspace = process.env.GITHUB_WORKSPACE; + if (!workspace) { + throw new Error("GITHUB_WORKSPACE is not set"); + } + + const localBin = join(homedir(), ".local", "bin"); + const env = { ...process.env, PATH: `${localBin}:${process.env.PATH || ""}` }; + delete env.COPILOT_GITHUB_TOKEN; + // `pai` reads its credentials from the environment, so the placeholder key the + // AWF api-proxy expects has to be substituted here, inside the sandbox: the + // proxy treats a request whose key matches its own OPENAI_API_KEY as BYOK and + // forwards it upstream instead of steering it to the configured provider. + env.OPENAI_API_KEY = "awf-copilot-proxy"; + + const args = [...commandArgs]; + // The proxy exposes Copilot Claude models under their dotted IDs. + const model = env.PAI_MODEL?.replace(/^([^:]+:claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + if (model) { + args.push("-m", model); + } + const agentSpec = join(workspace, ".pydantic-ai", "agent.json"); + if (existsSync(agentSpec)) { + args.push("-a", agentSpec); + } + args.push(readFileSync(promptFile, "utf8")); + + const result = spawnSync(command, args, { cwd: workspace, encoding: "utf8", env }); + process.stdout.write(result.stdout || ""); + process.stderr.write(result.stderr || ""); + if (result.error || result.status !== 0) { + throw new Error("Pydantic AI execution failed"); + } + + GHAW_HARNESS_SCRIPT_3c7b9f1a_EOF + chmod 755 "${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_harness.cjs" - name: Write Pydantic AI log parser script if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1721,9 +1835,10 @@ jobs: fi # shellcheck disable=SC1003,SC2016,SC2086 awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && cd "${GITHUB_WORKSPACE}" && pai --no-stream -m "$PAI_MODEL" "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + -- /bin/bash -c 'set +o histexpand; export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_harness.cjs pai --no-stream' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: GITHUB_STEP_SUMMARY: /tmp/gh-aw/step-summary.md + AWF_REFLECT_ENABLED: 1 COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} GH_AW_ENGINE_VERSION: 2.26.0 GH_AW_LLM_PROVIDER: github @@ -1732,7 +1847,7 @@ jobs: GITHUB_COPILOT_BASE_URL: http://host.docker.internal:10002 GITHUB_WORKSPACE: ${{ github.workspace }} NO_PROXY: localhost,127.0.0.1,host.docker.internal,host.docker.internal:10000,host.docker.internal:10001,host.docker.internal:10002,host.docker.internal:10003,172.30.0.30,172.30.0.30:10000,172.30.0.30:10001,172.30.0.30:10002,172.30.0.30:10003 - OPENAI_API_KEY: awf-copilot-proxy + OPENAI_API_KEY: ${{ secrets.COPILOT_GITHUB_TOKEN }} OPENAI_BASE_URL: http://host.docker.internal:10002 PAI_MODEL: openai-chat:claude-sonnet-4-5 RUNNER_TEMP: ${{ runner.temp }} diff --git a/pkg/workflow/behavior_defined_engine.go b/pkg/workflow/behavior_defined_engine.go index a2dabd66143..c1b476e69dc 100644 --- a/pkg/workflow/behavior_defined_engine.go +++ b/pkg/workflow/behavior_defined_engine.go @@ -531,14 +531,10 @@ func (e *BehaviorDefinedEngine) buildBehaviorDefinedExecutionEnv(exec *EngineExe "RUNNER_TEMP": "${{ runner.temp }}", } injectWorkflowCallNetworkAllowedEnv(env, workflowData) + maps.Copy(env, exec.Env) if exec.ProviderEnvMode == behaviorProviderEnvModeUniversalLLMConsumer { e.ApplyUniversalProviderEnv(env, workflowData, firewallEnabled) } - // The engine's own execution env is applied last so it can override the generic - // provider env. Engines whose CLI reads credentials from the environment (rather - // than from flags or a config file) need this to substitute the AWF proxy - // placeholder key for the real provider token. - maps.Copy(env, exec.Env) e.applyBehaviorDefinedMCPEnv(exec, workflowData, env) for _, binding := range e.definition.Auth { if binding.Secret != "" { From 5410a609514aaac2601fdcb5f6ea196125d7dc8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 02:54:12 +0000 Subject: [PATCH 07/12] fix(pydantic): include the underlying failure detail in the harness error Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/shared/pydantic.md | 2 +- .github/workflows/smoke-pydantic.lock.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/shared/pydantic.md b/.github/workflows/shared/pydantic.md index 619764f479a..1e7509a61d0 100644 --- a/.github/workflows/shared/pydantic.md +++ b/.github/workflows/shared/pydantic.md @@ -88,7 +88,7 @@ engine: process.stdout.write(result.stdout || ""); process.stderr.write(result.stderr || ""); if (result.error || result.status !== 0) { - throw new Error("Pydantic AI execution failed"); + throw new Error(`Pydantic AI execution failed: ${result.error?.message || `exit code ${result.status}`}`); } mcp: config-path: .pydantic-ai/agent.json diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index 24826c27540..0395e08bc6a 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e5612189fab07d8edca0b033a4600409f5ddb8ba261cf336915a807651cf2217","body_hash":"dcede9a0b60ee7fdc6a1765b2c0a04714bc6935d5be216a44803695e2f0dcc05","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"284707f7474c5b6b876371f7b3d8e5334acf3e63be2fc0471fec1c738bce62de","body_hash":"dcede9a0b60ee7fdc6a1765b2c0a04714bc6935d5be216a44803695e2f0dcc05","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -989,7 +989,7 @@ jobs: process.stdout.write(result.stdout || ""); process.stderr.write(result.stderr || ""); if (result.error || result.status !== 0) { - throw new Error("Pydantic AI execution failed"); + throw new Error(`Pydantic AI execution failed: ${result.error?.message || `exit code ${result.status}`}`); } GHAW_HARNESS_SCRIPT_3c7b9f1a_EOF @@ -1705,7 +1705,7 @@ jobs: process.stdout.write(result.stdout || ""); process.stderr.write(result.stderr || ""); if (result.error || result.status !== 0) { - throw new Error("Pydantic AI execution failed"); + throw new Error(`Pydantic AI execution failed: ${result.error?.message || `exit code ${result.status}`}`); } GHAW_HARNESS_SCRIPT_3c7b9f1a_EOF From 469ee164f1a3ce3f9ff765fd823f695f0d5a88d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 03:22:02 +0000 Subject: [PATCH 08/12] fix(pydantic): point pai at the api-proxy copilot port inside the sandbox Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/shared/pydantic.md | 22 ++++++++++++--------- .github/workflows/smoke-pydantic.lock.yml | 24 ++++++++++++++--------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/.github/workflows/shared/pydantic.md b/.github/workflows/shared/pydantic.md index 1e7509a61d0..1cbe64fccef 100644 --- a/.github/workflows/shared/pydantic.md +++ b/.github/workflows/shared/pydantic.md @@ -66,11 +66,14 @@ engine: const localBin = join(homedir(), ".local", "bin"); const env = { ...process.env, PATH: `${localBin}:${process.env.PATH || ""}` }; delete env.COPILOT_GITHUB_TOKEN; - // `pai` reads its credentials from the environment, so the placeholder key the - // AWF api-proxy expects has to be substituted here, inside the sandbox: the - // proxy treats a request whose key matches its own OPENAI_API_KEY as BYOK and - // forwards it upstream instead of steering it to the configured provider. + // The AWF api-proxy selects the upstream provider by the port the client connects + // to and injects the real credentials itself, ignoring the inbound key. AWF rewrites + // OPENAI_BASE_URL inside the sandbox to the proxy's OpenAI port, which forwards to + // api.openai.com, so `pai` — which is configured only through the environment — is + // pointed at the port that steers to the configured provider instead, the same + // endpoint Aider and OpenCode use, with the usual placeholder key. env.OPENAI_API_KEY = "awf-copilot-proxy"; + env.OPENAI_BASE_URL = "http://172.30.0.30:10002"; const args = [...commandArgs]; // The proxy exposes Copilot Claude models under their dotted IDs. @@ -244,11 +247,12 @@ MCP servers are rendered into a Pydantic AI agent spec at `.pydantic-ai/agent.json` and passed with `-a`, so safe outputs flow through the standard `safeoutputs` server automatically. -`pai` reads its credentials from the environment, so a harness script assembles -the command line and substitutes the AWF proxy placeholder API key inside the -sandbox. Setting the placeholder on the execution step instead would make the -proxy itself adopt it as a BYOK OpenAI key and forward requests upstream rather -than steering them to the configured provider. +`pai` is configured only through the environment, so a harness script assembles +the command line and points its OpenAI-compatible client at the AWF api-proxy +port that steers requests to the configured provider. The proxy picks the +upstream provider from the port it is reached on, and AWF rewrites +`OPENAI_BASE_URL` inside the sandbox to the port that forwards to OpenAI, so the +harness overrides it along with the placeholder API key. The CLI is installed with `pip install --user pydantic-ai==` before the agent runs. diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index 0395e08bc6a..b919b3a3387 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"284707f7474c5b6b876371f7b3d8e5334acf3e63be2fc0471fec1c738bce62de","body_hash":"dcede9a0b60ee7fdc6a1765b2c0a04714bc6935d5be216a44803695e2f0dcc05","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a79cb54bb9ba613e443a53f8bc4087145a8bdea175e2650eb29eaf28fc8adaeb","body_hash":"7d1c63bce20dc5786ca6b8c5288d55aef40119319adffca4bb627419497b2e9b","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -967,11 +967,14 @@ jobs: const localBin = join(homedir(), ".local", "bin"); const env = { ...process.env, PATH: `${localBin}:${process.env.PATH || ""}` }; delete env.COPILOT_GITHUB_TOKEN; - // `pai` reads its credentials from the environment, so the placeholder key the - // AWF api-proxy expects has to be substituted here, inside the sandbox: the - // proxy treats a request whose key matches its own OPENAI_API_KEY as BYOK and - // forwards it upstream instead of steering it to the configured provider. + // The AWF api-proxy selects the upstream provider by the port the client connects + // to and injects the real credentials itself, ignoring the inbound key. AWF rewrites + // OPENAI_BASE_URL inside the sandbox to the proxy's OpenAI port, which forwards to + // api.openai.com, so `pai` — which is configured only through the environment — is + // pointed at the port that steers to the configured provider instead, the same + // endpoint Aider and OpenCode use, with the usual placeholder key. env.OPENAI_API_KEY = "awf-copilot-proxy"; + env.OPENAI_BASE_URL = "http://172.30.0.30:10002"; const args = [...commandArgs]; // The proxy exposes Copilot Claude models under their dotted IDs. @@ -1683,11 +1686,14 @@ jobs: const localBin = join(homedir(), ".local", "bin"); const env = { ...process.env, PATH: `${localBin}:${process.env.PATH || ""}` }; delete env.COPILOT_GITHUB_TOKEN; - // `pai` reads its credentials from the environment, so the placeholder key the - // AWF api-proxy expects has to be substituted here, inside the sandbox: the - // proxy treats a request whose key matches its own OPENAI_API_KEY as BYOK and - // forwards it upstream instead of steering it to the configured provider. + // The AWF api-proxy selects the upstream provider by the port the client connects + // to and injects the real credentials itself, ignoring the inbound key. AWF rewrites + // OPENAI_BASE_URL inside the sandbox to the proxy's OpenAI port, which forwards to + // api.openai.com, so `pai` — which is configured only through the environment — is + // pointed at the port that steers to the configured provider instead, the same + // endpoint Aider and OpenCode use, with the usual placeholder key. env.OPENAI_API_KEY = "awf-copilot-proxy"; + env.OPENAI_BASE_URL = "http://172.30.0.30:10002"; const args = [...commandArgs]; // The proxy exposes Copilot Claude models under their dotted IDs. From 92215b2e0038d5e83ed1e19c32ab5725207753d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 03:45:42 +0000 Subject: [PATCH 09/12] fix(pydantic): keep the provider routing prefix in the pai model name Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/shared/pydantic.md | 29 ++++---- .github/workflows/smoke-pydantic.lock.yml | 35 ++++++---- .../docs/reference/frontmatter-full.md | 5 -- pkg/parser/schemas/main_workflow_schema.json | 4 -- pkg/workflow/behavior_defined_engine.go | 6 +- .../behavior_defined_engine_model_test.go | 68 ------------------- pkg/workflow/engine_definition.go | 15 ++-- 7 files changed, 45 insertions(+), 117 deletions(-) delete mode 100644 pkg/workflow/behavior_defined_engine_model_test.go diff --git a/.github/workflows/shared/pydantic.md b/.github/workflows/shared/pydantic.md index 1cbe64fccef..b0845e7f5e1 100644 --- a/.github/workflows/shared/pydantic.md +++ b/.github/workflows/shared/pydantic.md @@ -42,8 +42,6 @@ engine: - --no-stream step-name: Execute Pydantic AI CLI model-env-var: PAI_MODEL - model-env-provider-prefix: openai-chat - model-env-provider-separator: ":" write-timestamp: true provider-env-mode: universal-llm-consumer harness-script: | @@ -76,10 +74,14 @@ engine: env.OPENAI_BASE_URL = "http://172.30.0.30:10002"; const args = [...commandArgs]; - // The proxy exposes Copilot Claude models under their dotted IDs. - const model = env.PAI_MODEL?.replace(/^([^:]+:claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + // The api-proxy steers requests by matching the full `provider/model` name, so the + // routing prefix must survive into the request body: `pai` sends the model name + // verbatim, minus the `openai-chat:` provider marker that selects its + // OpenAI-compatible client. The proxy exposes Copilot Claude models under their + // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `copilot/claude-sonnet-4.5`. + const model = env.PAI_MODEL?.replace(/^(.*claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); if (model) { - args.push("-m", model); + args.push("-m", `openai-chat:${model}`); } const agentSpec = join(workspace, ".pydantic-ai", "agent.json"); if (existsSync(agentSpec)) { @@ -101,9 +103,10 @@ engine: // the `pai` CLI can be given MCP servers: each gateway server becomes an // `MCP` capability entry and the spec file is passed via `pai -a `. // An agent spec must declare a `model`, but the harness always appends - // `-m "$PAI_MODEL"` when the workflow declares a model, which takes - // precedence. The value below is only a valid-by-construction fallback for - // workflows that do not declare a model. + // `-m openai-chat:/` when the workflow declares a model, which + // takes precedence. The value below is only a valid-by-construction fallback for + // workflows that do not declare a model; it carries the `copilot/` routing prefix + // the api-proxy needs to steer the request. const fs = require("fs"); const path = require("path"); @@ -147,7 +150,7 @@ engine: const configPath = path.join(workspace, ".pydantic-ai", "agent.json"); fs.mkdirSync(path.dirname(configPath), { recursive: true }); - fs.writeFileSync(configPath, JSON.stringify({ model: "openai-chat:gpt-5", capabilities }, null, 2), { mode: 0o600 }); + fs.writeFileSync(configPath, JSON.stringify({ model: "openai-chat:copilot/gpt-5", capabilities }, null, 2), { mode: 0o600 }); fs.chmodSync(configPath, 0o600); console.log(`Wrote ${capabilities.length} MCP server(s) to ${configPath}`); log-parser: | @@ -237,9 +240,11 @@ imports: ``` `model` must use `provider/model` format. Supported providers are `copilot`, -`anthropic`, and `openai`. Requests are routed through the AWF proxy, so the -model is rewritten to the `openai-chat:` form the Pydantic AI CLI -expects and passed with `-m`. Copilot Claude aliases such as +`anthropic`, and `openai`. Requests are routed through the AWF proxy, which +steers them by matching the full `provider/model` name, so the model is passed +with `-m openai-chat:/`: `openai-chat:` selects the Pydantic AI +OpenAI-compatible client and is not part of the model name sent upstream, while +`/` reaches the proxy intact. Copilot Claude aliases such as `claude-sonnet-4-5` are normalized to the dotted model IDs exposed by the proxy, such as `claude-sonnet-4.5`. diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index b919b3a3387..b2cf0a6574b 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a79cb54bb9ba613e443a53f8bc4087145a8bdea175e2650eb29eaf28fc8adaeb","body_hash":"7d1c63bce20dc5786ca6b8c5288d55aef40119319adffca4bb627419497b2e9b","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"bfe197c1892a8e5174f8ae8019ea42e5ab416d5a8bdaa1a0ac041306fe14d33b","body_hash":"98716493a5b42a866509f75caf9d187a89391186081ef2328c49b528cbfc2822","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -774,9 +774,10 @@ jobs: // the `pai` CLI can be given MCP servers: each gateway server becomes an // `MCP` capability entry and the spec file is passed via `pai -a `. // An agent spec must declare a `model`, but the harness always appends - // `-m "$PAI_MODEL"` when the workflow declares a model, which takes - // precedence. The value below is only a valid-by-construction fallback for - // workflows that do not declare a model. + // `-m openai-chat:/` when the workflow declares a model, which + // takes precedence. The value below is only a valid-by-construction fallback for + // workflows that do not declare a model; it carries the `copilot/` routing prefix + // the api-proxy needs to steer the request. const fs = require("fs"); const path = require("path"); @@ -820,7 +821,7 @@ jobs: const configPath = path.join(workspace, ".pydantic-ai", "agent.json"); fs.mkdirSync(path.dirname(configPath), { recursive: true }); - fs.writeFileSync(configPath, JSON.stringify({ model: "openai-chat:gpt-5", capabilities }, null, 2), { mode: 0o600 }); + fs.writeFileSync(configPath, JSON.stringify({ model: "openai-chat:copilot/gpt-5", capabilities }, null, 2), { mode: 0o600 }); fs.chmodSync(configPath, 0o600); console.log(`Wrote ${capabilities.length} MCP server(s) to ${configPath}`); @@ -977,10 +978,14 @@ jobs: env.OPENAI_BASE_URL = "http://172.30.0.30:10002"; const args = [...commandArgs]; - // The proxy exposes Copilot Claude models under their dotted IDs. - const model = env.PAI_MODEL?.replace(/^([^:]+:claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + // The api-proxy steers requests by matching the full `provider/model` name, so the + // routing prefix must survive into the request body: `pai` sends the model name + // verbatim, minus the `openai-chat:` provider marker that selects its + // OpenAI-compatible client. The proxy exposes Copilot Claude models under their + // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `copilot/claude-sonnet-4.5`. + const model = env.PAI_MODEL?.replace(/^(.*claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); if (model) { - args.push("-m", model); + args.push("-m", `openai-chat:${model}`); } const agentSpec = join(workspace, ".pydantic-ai", "agent.json"); if (existsSync(agentSpec)) { @@ -1131,7 +1136,7 @@ jobs: NO_PROXY: localhost,127.0.0.1,host.docker.internal,host.docker.internal:10000,host.docker.internal:10001,host.docker.internal:10002,host.docker.internal:10003,172.30.0.30,172.30.0.30:10000,172.30.0.30:10001,172.30.0.30:10002,172.30.0.30:10003 OPENAI_API_KEY: ${{ secrets.COPILOT_GITHUB_TOKEN }} OPENAI_BASE_URL: http://host.docker.internal:10002 - PAI_MODEL: openai-chat:claude-sonnet-4-5 + PAI_MODEL: copilot/claude-sonnet-4-5 RUNNER_TEMP: ${{ runner.temp }} TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Configure Git credentials @@ -1696,10 +1701,14 @@ jobs: env.OPENAI_BASE_URL = "http://172.30.0.30:10002"; const args = [...commandArgs]; - // The proxy exposes Copilot Claude models under their dotted IDs. - const model = env.PAI_MODEL?.replace(/^([^:]+:claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + // The api-proxy steers requests by matching the full `provider/model` name, so the + // routing prefix must survive into the request body: `pai` sends the model name + // verbatim, minus the `openai-chat:` provider marker that selects its + // OpenAI-compatible client. The proxy exposes Copilot Claude models under their + // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `copilot/claude-sonnet-4.5`. + const model = env.PAI_MODEL?.replace(/^(.*claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); if (model) { - args.push("-m", model); + args.push("-m", `openai-chat:${model}`); } const agentSpec = join(workspace, ".pydantic-ai", "agent.json"); if (existsSync(agentSpec)) { @@ -1855,7 +1864,7 @@ jobs: NO_PROXY: localhost,127.0.0.1,host.docker.internal,host.docker.internal:10000,host.docker.internal:10001,host.docker.internal:10002,host.docker.internal:10003,172.30.0.30,172.30.0.30:10000,172.30.0.30:10001,172.30.0.30:10002,172.30.0.30:10003 OPENAI_API_KEY: ${{ secrets.COPILOT_GITHUB_TOKEN }} OPENAI_BASE_URL: http://host.docker.internal:10002 - PAI_MODEL: openai-chat:claude-sonnet-4-5 + PAI_MODEL: copilot/claude-sonnet-4-5 RUNNER_TEMP: ${{ runner.temp }} TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Echo detection step summary diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index a543e5fbb3a..e163e9170a5 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -2998,11 +2998,6 @@ engine: # (optional) model-env-provider-prefix: "example-value" - # Separator inserted between the provider prefix and the model name when rewriting - # provider/model values. Defaults to "/". - # (optional) - model-env-provider-separator: "example-value" - # (optional) model-flag: "example-value" diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 6432ef33272..2ff6dc39b92 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -13412,10 +13412,6 @@ "model-env-provider-prefix": { "type": "string" }, - "model-env-provider-separator": { - "type": "string", - "description": "Separator inserted between the provider prefix and the model name when rewriting provider/model values. Defaults to \"/\"." - }, "model-flag": { "type": "string" }, diff --git a/pkg/workflow/behavior_defined_engine.go b/pkg/workflow/behavior_defined_engine.go index c1b476e69dc..1780a545207 100644 --- a/pkg/workflow/behavior_defined_engine.go +++ b/pkg/workflow/behavior_defined_engine.go @@ -579,11 +579,7 @@ func (e *BehaviorDefinedEngine) applyBehaviorDefinedModelEnv(exec *EngineExecuti modelVal := workflowData.Model if exec.ModelEnvProviderPrefix != "" { if parts := strings.SplitN(modelVal, "/", 2); len(parts) == 2 { - separator := exec.ModelEnvProviderSeparator - if separator == "" { - separator = "/" - } - modelVal = exec.ModelEnvProviderPrefix + separator + parts[1] + modelVal = exec.ModelEnvProviderPrefix + "/" + parts[1] } } env[exec.ModelEnvVarName] = modelVal diff --git a/pkg/workflow/behavior_defined_engine_model_test.go b/pkg/workflow/behavior_defined_engine_model_test.go deleted file mode 100644 index 68205e55372..00000000000 --- a/pkg/workflow/behavior_defined_engine_model_test.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:build !integration - -package workflow - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestBehaviorDefinedEngineModelEnvProviderSeparator(t *testing.T) { - tests := []struct { - name string - prefix string - separator string - model string - expected string - }{ - { - name: "no prefix keeps the model unchanged", - model: "copilot/claude-sonnet-4-5", - expected: "copilot/claude-sonnet-4-5", - }, - { - name: "prefix defaults to a slash separator", - prefix: "openai", - model: "copilot/claude-sonnet-4-5", - expected: "openai/claude-sonnet-4-5", - }, - { - name: "explicit separator is used instead of a slash", - prefix: "openai-chat", - separator: ":", - model: "copilot/claude-sonnet-4-5", - expected: "openai-chat:claude-sonnet-4-5", - }, - { - name: "model without a provider is left unchanged", - prefix: "openai-chat", - separator: ":", - model: "gpt-5", - expected: "gpt-5", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - exec := &EngineExecutionDefinition{ - CommandName: "test-cli", - StepName: "Execute Test CLI", - ModelEnvVarName: "TEST_MODEL", - ModelEnvProviderPrefix: tt.prefix, - ModelEnvProviderSeparator: tt.separator, - } - engine, err := NewBehaviorDefinedEngine(&EngineDefinition{ - ID: "testmodel", - DisplayName: "TestModel", - Behaviors: &EngineBehaviorDefinition{Execution: exec}, - }) - require.NoError(t, err) - - env := map[string]string{} - engine.applyBehaviorDefinedModelEnv(exec, &WorkflowData{Model: tt.model}, env) - assert.Equal(t, tt.expected, env["TEST_MODEL"]) - }) - } -} diff --git a/pkg/workflow/engine_definition.go b/pkg/workflow/engine_definition.go index 9492ac20d94..1e78160b3e4 100644 --- a/pkg/workflow/engine_definition.go +++ b/pkg/workflow/engine_definition.go @@ -205,16 +205,11 @@ type EngineExecutionDefinition struct { StepName string `yaml:"step-name,omitempty"` ModelEnvVarName string `yaml:"model-env-var,omitempty"` ModelEnvProviderPrefix string `yaml:"model-env-provider-prefix,omitempty"` - // ModelEnvProviderSeparator is the separator placed between the provider - // prefix and the model name when rewriting `provider/model` values. - // Defaults to "/" when empty (e.g. "openai/gpt-5"); engines such as - // Pydantic AI expect ":" instead (e.g. "openai:gpt-5"). - ModelEnvProviderSeparator string `yaml:"model-env-provider-separator,omitempty"` - ModelFlag string `yaml:"model-flag,omitempty"` - MCPConfigEnvVar string `yaml:"mcp-config-env-var,omitempty"` - MCPConfigFlag string `yaml:"mcp-config-flag,omitempty"` - WriteTimestamp bool `yaml:"write-timestamp,omitempty"` - ProviderEnvMode string `yaml:"provider-env-mode,omitempty"` + ModelFlag string `yaml:"model-flag,omitempty"` + MCPConfigEnvVar string `yaml:"mcp-config-env-var,omitempty"` + MCPConfigFlag string `yaml:"mcp-config-flag,omitempty"` + WriteTimestamp bool `yaml:"write-timestamp,omitempty"` + ProviderEnvMode string `yaml:"provider-env-mode,omitempty"` // Env holds additional static environment variables to inject into the // execution step. Values are rendered verbatim and are not filtered // through the secrets allowlist, so they must not contain secret values. From f3583c4a403365ff17dc2130a12c0dcdb4903566 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 03:50:49 +0000 Subject: [PATCH 10/12] fix(pydantic): anchor the Claude model normalization regex Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/shared/pydantic.md | 2 +- .github/workflows/smoke-pydantic.lock.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/shared/pydantic.md b/.github/workflows/shared/pydantic.md index b0845e7f5e1..182e15d7694 100644 --- a/.github/workflows/shared/pydantic.md +++ b/.github/workflows/shared/pydantic.md @@ -79,7 +79,7 @@ engine: // verbatim, minus the `openai-chat:` provider marker that selects its // OpenAI-compatible client. The proxy exposes Copilot Claude models under their // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `copilot/claude-sonnet-4.5`. - const model = env.PAI_MODEL?.replace(/^(.*claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + const model = env.PAI_MODEL?.replace(/^((?:[^/]+\/)?claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); if (model) { args.push("-m", `openai-chat:${model}`); } diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index b2cf0a6574b..f04985b1aff 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"bfe197c1892a8e5174f8ae8019ea42e5ab416d5a8bdaa1a0ac041306fe14d33b","body_hash":"98716493a5b42a866509f75caf9d187a89391186081ef2328c49b528cbfc2822","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"568224fbc154eac1eb6aa545ec4123788ac8891393d75b825044c73ac1ddaa68","body_hash":"98716493a5b42a866509f75caf9d187a89391186081ef2328c49b528cbfc2822","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -983,7 +983,7 @@ jobs: // verbatim, minus the `openai-chat:` provider marker that selects its // OpenAI-compatible client. The proxy exposes Copilot Claude models under their // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `copilot/claude-sonnet-4.5`. - const model = env.PAI_MODEL?.replace(/^(.*claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + const model = env.PAI_MODEL?.replace(/^((?:[^/]+\/)?claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); if (model) { args.push("-m", `openai-chat:${model}`); } @@ -1706,7 +1706,7 @@ jobs: // verbatim, minus the `openai-chat:` provider marker that selects its // OpenAI-compatible client. The proxy exposes Copilot Claude models under their // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `copilot/claude-sonnet-4.5`. - const model = env.PAI_MODEL?.replace(/^(.*claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + const model = env.PAI_MODEL?.replace(/^((?:[^/]+\/)?claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); if (model) { args.push("-m", `openai-chat:${model}`); } From 4c15754e7835d33e77780f80a208e58e19b25448 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 04:23:02 +0000 Subject: [PATCH 11/12] fix(pydantic): send the bare model ID and stream responses Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/shared/pydantic.md | 38 +++++++++++---------- .github/workflows/smoke-pydantic.lock.yml | 41 ++++++++++++----------- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/.github/workflows/shared/pydantic.md b/.github/workflows/shared/pydantic.md index 182e15d7694..15e9e577d20 100644 --- a/.github/workflows/shared/pydantic.md +++ b/.github/workflows/shared/pydantic.md @@ -38,8 +38,6 @@ engine: openai: api.openai.com execution: command-name: pai - args: - - --no-stream step-name: Execute Pydantic AI CLI model-env-var: PAI_MODEL write-timestamp: true @@ -74,12 +72,13 @@ engine: env.OPENAI_BASE_URL = "http://172.30.0.30:10002"; const args = [...commandArgs]; - // The api-proxy steers requests by matching the full `provider/model` name, so the - // routing prefix must survive into the request body: `pai` sends the model name - // verbatim, minus the `openai-chat:` provider marker that selects its - // OpenAI-compatible client. The proxy exposes Copilot Claude models under their - // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `copilot/claude-sonnet-4.5`. - const model = env.PAI_MODEL?.replace(/^((?:[^/]+\/)?claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + // `pai` sends the model name verbatim, minus the `openai-chat:` provider marker + // that selects its OpenAI-compatible client, so the bare model ID reaches the + // api-proxy — which steers to the configured provider by the port it is reached + // on, not by a prefix in the model name: Copilot rejects `copilot/` with + // `model_not_supported`. The proxy exposes Copilot Claude models under their + // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `claude-sonnet-4.5`. + const model = env.PAI_MODEL?.replace(/^.*\//, "").replace(/^(claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); if (model) { args.push("-m", `openai-chat:${model}`); } @@ -103,10 +102,9 @@ engine: // the `pai` CLI can be given MCP servers: each gateway server becomes an // `MCP` capability entry and the spec file is passed via `pai -a `. // An agent spec must declare a `model`, but the harness always appends - // `-m openai-chat:/` when the workflow declares a model, which - // takes precedence. The value below is only a valid-by-construction fallback for - // workflows that do not declare a model; it carries the `copilot/` routing prefix - // the api-proxy needs to steer the request. + // `-m openai-chat:` when the workflow declares a model, which takes + // precedence. The value below is only a valid-by-construction fallback for + // workflows that do not declare a model. const fs = require("fs"); const path = require("path"); @@ -150,7 +148,7 @@ engine: const configPath = path.join(workspace, ".pydantic-ai", "agent.json"); fs.mkdirSync(path.dirname(configPath), { recursive: true }); - fs.writeFileSync(configPath, JSON.stringify({ model: "openai-chat:copilot/gpt-5", capabilities }, null, 2), { mode: 0o600 }); + fs.writeFileSync(configPath, JSON.stringify({ model: "openai-chat:gpt-5", capabilities }, null, 2), { mode: 0o600 }); fs.chmodSync(configPath, 0o600); console.log(`Wrote ${capabilities.length} MCP server(s) to ${configPath}`); log-parser: | @@ -240,14 +238,18 @@ imports: ``` `model` must use `provider/model` format. Supported providers are `copilot`, -`anthropic`, and `openai`. Requests are routed through the AWF proxy, which -steers them by matching the full `provider/model` name, so the model is passed -with `-m openai-chat:/`: `openai-chat:` selects the Pydantic AI -OpenAI-compatible client and is not part of the model name sent upstream, while -`/` reaches the proxy intact. Copilot Claude aliases such as +`anthropic`, and `openai`. Requests are routed through the AWF api-proxy, which +steers to the configured provider by the port it is reached on, so the provider +segment is dropped and the bare model ID is passed with `-m openai-chat:` +(`openai-chat:` selects the Pydantic AI OpenAI-compatible client and is not part +of the model name sent upstream). Copilot Claude aliases such as `claude-sonnet-4-5` are normalized to the dotted model IDs exposed by the proxy, such as `claude-sonnet-4.5`. +Responses are streamed. The proxy's aggregated non-streaming body omits +`object` and `choices[].index`, which Pydantic AI rejects during response +validation, so `--no-stream` is deliberately not passed. + MCP servers are rendered into a Pydantic AI agent spec at `.pydantic-ai/agent.json` and passed with `-a`, so safe outputs flow through the standard `safeoutputs` server automatically. diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index f04985b1aff..b14c8fb6622 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"568224fbc154eac1eb6aa545ec4123788ac8891393d75b825044c73ac1ddaa68","body_hash":"98716493a5b42a866509f75caf9d187a89391186081ef2328c49b528cbfc2822","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9f704928ff2aecfdd3d8ba19e23c4243cddbc6f5e42e3422a32dbad9bf3ff4fb","body_hash":"8980e76a3f3c0797925d9bf85481c0d32a0045424e79d4ec205f164bb8b3af33","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -774,10 +774,9 @@ jobs: // the `pai` CLI can be given MCP servers: each gateway server becomes an // `MCP` capability entry and the spec file is passed via `pai -a `. // An agent spec must declare a `model`, but the harness always appends - // `-m openai-chat:/` when the workflow declares a model, which - // takes precedence. The value below is only a valid-by-construction fallback for - // workflows that do not declare a model; it carries the `copilot/` routing prefix - // the api-proxy needs to steer the request. + // `-m openai-chat:` when the workflow declares a model, which takes + // precedence. The value below is only a valid-by-construction fallback for + // workflows that do not declare a model. const fs = require("fs"); const path = require("path"); @@ -821,7 +820,7 @@ jobs: const configPath = path.join(workspace, ".pydantic-ai", "agent.json"); fs.mkdirSync(path.dirname(configPath), { recursive: true }); - fs.writeFileSync(configPath, JSON.stringify({ model: "openai-chat:copilot/gpt-5", capabilities }, null, 2), { mode: 0o600 }); + fs.writeFileSync(configPath, JSON.stringify({ model: "openai-chat:gpt-5", capabilities }, null, 2), { mode: 0o600 }); fs.chmodSync(configPath, 0o600); console.log(`Wrote ${capabilities.length} MCP server(s) to ${configPath}`); @@ -978,12 +977,13 @@ jobs: env.OPENAI_BASE_URL = "http://172.30.0.30:10002"; const args = [...commandArgs]; - // The api-proxy steers requests by matching the full `provider/model` name, so the - // routing prefix must survive into the request body: `pai` sends the model name - // verbatim, minus the `openai-chat:` provider marker that selects its - // OpenAI-compatible client. The proxy exposes Copilot Claude models under their - // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `copilot/claude-sonnet-4.5`. - const model = env.PAI_MODEL?.replace(/^((?:[^/]+\/)?claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + // `pai` sends the model name verbatim, minus the `openai-chat:` provider marker + // that selects its OpenAI-compatible client, so the bare model ID reaches the + // api-proxy — which steers to the configured provider by the port it is reached + // on, not by a prefix in the model name: Copilot rejects `copilot/` with + // `model_not_supported`. The proxy exposes Copilot Claude models under their + // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `claude-sonnet-4.5`. + const model = env.PAI_MODEL?.replace(/^.*\//, "").replace(/^(claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); if (model) { args.push("-m", `openai-chat:${model}`); } @@ -1122,7 +1122,7 @@ jobs: fi # shellcheck disable=SC1003,SC2016,SC2086 awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_harness.cjs pai --no-stream' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_harness.cjs pai' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} @@ -1701,12 +1701,13 @@ jobs: env.OPENAI_BASE_URL = "http://172.30.0.30:10002"; const args = [...commandArgs]; - // The api-proxy steers requests by matching the full `provider/model` name, so the - // routing prefix must survive into the request body: `pai` sends the model name - // verbatim, minus the `openai-chat:` provider marker that selects its - // OpenAI-compatible client. The proxy exposes Copilot Claude models under their - // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `copilot/claude-sonnet-4.5`. - const model = env.PAI_MODEL?.replace(/^((?:[^/]+\/)?claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); + // `pai` sends the model name verbatim, minus the `openai-chat:` provider marker + // that selects its OpenAI-compatible client, so the bare model ID reaches the + // api-proxy — which steers to the configured provider by the port it is reached + // on, not by a prefix in the model name: Copilot rejects `copilot/` with + // `model_not_supported`. The proxy exposes Copilot Claude models under their + // dotted IDs, so `copilot/claude-sonnet-4-5` becomes `claude-sonnet-4.5`. + const model = env.PAI_MODEL?.replace(/^.*\//, "").replace(/^(claude-(?:haiku|sonnet|opus)-\d+)-(\d+)$/, "$1.$2"); if (model) { args.push("-m", `openai-chat:${model}`); } @@ -1850,7 +1851,7 @@ jobs: fi # shellcheck disable=SC1003,SC2016,SC2086 awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --log-level info --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_harness.cjs pai --no-stream' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + -- /bin/bash -c 'set +o histexpand; export no_proxy="${NO_PROXY:-}" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/pydantic-ai_harness.cjs pai' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: GITHUB_STEP_SUMMARY: /tmp/gh-aw/step-summary.md AWF_REFLECT_ENABLED: 1 From f24c9348a3b13457ba8da8225da6493eb1ef37fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Aug 2026 06:08:23 +0000 Subject: [PATCH 12/12] Merge main into copilot/fix-pydantic-integration, resolve lock conflict Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-pydantic.lock.yml | 31 ++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke-pydantic.lock.yml b/.github/workflows/smoke-pydantic.lock.yml index b14c8fb6622..1ba1b5924e4 100644 --- a/.github/workflows/smoke-pydantic.lock.yml +++ b/.github/workflows/smoke-pydantic.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9f704928ff2aecfdd3d8ba19e23c4243cddbc6f5e42e3422a32dbad9bf3ff4fb","body_hash":"8980e76a3f3c0797925d9bf85481c0d32a0045424e79d4ec205f164bb8b3af33","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f0aa608f8ed57b7151bf3f9d6267eca83d4682b45e388d3cf1d4aed083111cae","body_hash":"8980e76a3f3c0797925d9bf85481c0d32a0045424e79d4ec205f164bb8b3af33","strict":true,"agent_id":"pydantic-ai","agent_model":"copilot/claude-sonnet-4-5","engine_versions":{"pydantic-ai":"2.26.0"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request":true} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -618,9 +618,18 @@ jobs: "sanitize": true, "maxLength": 65000 }, + "comment_id": { + "optionalPositiveInteger": true + }, "item_number": { "issueOrPRNumber": true }, + "pr": { + "issueOrPRNumber": true + }, + "pr_number": { + "issueOrPRNumber": true + }, "reply_to_id": { "type": "string", "maxLength": 256 @@ -628,6 +637,16 @@ jobs: "repo": { "type": "string", "maxLength": 256 + }, + "target": { + "type": "string", + "enum": [ + "status" + ] + }, + "temporary_id": { + "type": "string", + "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$" } } }, @@ -1875,6 +1894,16 @@ jobs: if [ -s /tmp/gh-aw/step-summary.md ]; then cat /tmp/gh-aw/step-summary.md fi + - name: Render detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/render_detection_log.cjs'); + await main(); - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always()