From 3b1b3555757f82ff4a60d16d868fce49d4a41c51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 05:58:25 +0000 Subject: [PATCH 1/8] chore: plan ai credits pricing type unification Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/daily-byok-ollama-test.lock.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/daily-byok-ollama-test.lock.yml b/.github/workflows/daily-byok-ollama-test.lock.yml index 533607549ea..38b3cfa1053 100644 --- a/.github/workflows/daily-byok-ollama-test.lock.yml +++ b/.github/workflows/daily-byok-ollama-test.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"92a633df2d6b6105c26057412bc3df6371091bbf0c1018517abcd8015e274086","body_hash":"bd80ca99e3f4cd56715c7a73bd9f5c56165dc64beee430b859670700764082f0","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"7af4dfbdd5b1e34d51575dccff17347cb5f59d4e34a6ac167a8cd4a019b660a7","body_hash":"bd80ca99e3f4cd56715c7a73bd9f5c56165dc64beee430b859670700764082f0","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.73"}} # 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":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.41","digest":"sha256:053ba306623a1a0d4c3c5ac9a2c3dc3217ce04d44329b61929fe7f8b0dc457f3","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.41@sha256:053ba306623a1a0d4c3c5ac9a2c3dc3217ce04d44329b61929fe7f8b0dc457f3"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.41","digest":"sha256:a3d33153b6abb2dd39540ef7def8aa8a5020022c11822d88f5b87ebe350276d1","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.41@sha256:a3d33153b6abb2dd39540ef7def8aa8a5020022c11822d88f5b87ebe350276d1"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.41","digest":"sha256:61d653d372ac417c6e22d5e77becd9e60bde96e6c2d26bb023e3cc3fd60c0920","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.41@sha256:61d653d372ac417c6e22d5e77becd9e60bde96e6c2d26bb023e3cc3fd60c0920"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.5","digest":"sha256:7550c5132d007266b696d77218e8d1b01f29e6e55520875b2431ef4044df71c9","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.5@sha256:7550c5132d007266b696d77218e8d1b01f29e6e55520875b2431ef4044df71c9"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.6.0","digest":"sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3","pinned_image":"ghcr.io/github/github-mcp-server:v1.6.0@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3"}]} # 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 # @@ -839,7 +839,7 @@ jobs: export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.41/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"host.docker.internal:11434\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"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*\"],\"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-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*\"],\"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\":[\"fable\",\"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.41,squid=sha256:61d653d372ac417c6e22d5e77becd9e60bde96e6c2d26bb023e3cc3fd60c0920,agent=sha256:053ba306623a1a0d4c3c5ac9a2c3dc3217ce04d44329b61929fe7f8b0dc457f3,api-proxy=sha256:a3d33153b6abb2dd39540ef7def8aa8a5020022c11822d88f5b87ebe350276d1,cli-proxy=sha256:6b525fb0efc2bba6d5f2af5b753b76cdceb8ac3932a0aea6be53e36747179b60\"},\"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\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.41/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"host.docker.internal:11434\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"defaultAiCreditsPricing\":{\"input\":0,\"output\":0},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"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*\"],\"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-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*\"],\"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\":[\"fable\",\"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.41,squid=sha256:61d653d372ac417c6e22d5e77becd9e60bde96e6c2d26bb023e3cc3fd60c0920,agent=sha256:053ba306623a1a0d4c3c5ac9a2c3dc3217ce04d44329b61929fe7f8b0dc457f3,api-proxy=sha256:a3d33153b6abb2dd39540ef7def8aa8a5020022c11822d88f5b87ebe350276d1,cli-proxy=sha256:6b525fb0efc2bba6d5f2af5b753b76cdceb8ac3932a0aea6be53e36747179b60\"},\"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="" From 36528c0ccadc47b4256b99357be46f152d916da7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 06:04:47 +0000 Subject: [PATCH 2/8] refactor: unify ai credits pricing type usage in workflow and schema Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schema_test.go | 65 +++++++++++++++ pkg/parser/schemas/main_workflow_schema.json | 84 ++++++++++---------- pkg/workflow/awf_config.go | 19 +---- pkg/workflow/sandbox.go | 4 +- 4 files changed, 114 insertions(+), 58 deletions(-) diff --git a/pkg/parser/schema_test.go b/pkg/parser/schema_test.go index 7a37a76fae3..c9b3593cc50 100644 --- a/pkg/parser/schema_test.go +++ b/pkg/parser/schema_test.go @@ -1967,6 +1967,71 @@ func TestMainWorkflowSchema_ModelsDefaultAiCreditsPricing(t *testing.T) { }) } +// TestMainWorkflowSchema_ModelsProvidersAiCreditsPricing verifies that +// models.providers..models..cost uses the shared ai_credits_pricing schema. +func TestMainWorkflowSchema_ModelsProvidersAiCreditsPricing(t *testing.T) { + t.Parallel() + + t.Run("numeric and string token-class costs are accepted", func(t *testing.T) { + t.Parallel() + + frontmatter := map[string]any{ + "on": "push", + "engine": "copilot", + "models": map[string]any{ + "providers": map[string]any{ + "anthropic": map[string]any{ + "models": map[string]any{ + "claude-custom": map[string]any{ + "cost": map[string]any{ + "input": "3e-07", + "output": 1.5e-06, + "cache_read": "3e-08", + "cache_write": 3.75e-07, + "reasoning": "0", + "custom": "1e-09", + }, + }, + }, + }, + }, + }, + } + + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/models-providers-cost-test.md") + if err != nil { + t.Fatalf("expected models.providers pricing to pass schema validation, got: %v", err) + } + }) + + t.Run("non-price value types are rejected", func(t *testing.T) { + t.Parallel() + + frontmatter := map[string]any{ + "on": "push", + "engine": "copilot", + "models": map[string]any{ + "providers": map[string]any{ + "anthropic": map[string]any{ + "models": map[string]any{ + "claude-custom": map[string]any{ + "cost": map[string]any{ + "input": true, + }, + }, + }, + }, + }, + }, + } + + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/models-providers-cost-invalid-test.md") + if err == nil { + t.Fatal("expected models.providers pricing with invalid value type to fail schema validation") + } + }) +} + // TestMainWorkflowSchema_SandboxAgentSudo is a regression guard for #41679. // The JSON schema already contains sandbox.agent.sudo; these tests ensure it // stays accepted and that the legacy network-isolation field stays rejected, diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index fb296ecdd02..b51c8b03596 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -2880,33 +2880,8 @@ "description": "Pricing data for a single model.", "properties": { "cost": { - "type": "object", - "description": "Per-token cost in USD. Keys are token classes; values are numeric cost-per-token strings or numbers.", - "properties": { - "input": { - "type": ["number", "string"], - "description": "Cost per input token in USD." - }, - "output": { - "type": ["number", "string"], - "description": "Cost per output token in USD." - }, - "cache_read": { - "type": ["number", "string"], - "description": "Cost per cached-read token in USD." - }, - "cache_write": { - "type": ["number", "string"], - "description": "Cost per cache-write token in USD." - }, - "reasoning": { - "type": ["number", "string"], - "description": "Cost per reasoning token in USD." - } - }, - "additionalProperties": { - "type": ["number", "string"] - } + "$ref": "#/$defs/ai_credits_pricing", + "description": "Per-token cost in USD. Keys are token classes; values are numeric cost-per-token strings or numbers." } }, "additionalProperties": false @@ -2917,7 +2892,27 @@ } }, "default-ai-credits-pricing": { - "$ref": "#/$defs/ai_credits_pricing", + "allOf": [ + { + "$ref": "#/$defs/ai_credits_pricing" + }, + { + "required": ["input", "output"], + "properties": { + "input": { + "type": "number", + "minimum": 0, + "description": "Input token price per 1M tokens in dollars. Use 0 for self-hosted/free models." + }, + "output": { + "type": "number", + "minimum": 0, + "description": "Output token price per 1M tokens in dollars. Use 0 for self-hosted/free models." + } + }, + "additionalProperties": false + } + ], "description": "Fallback per-token pricing ($/1M tokens) for models not in the built-in pricing table. Required when max-ai-credits is active and the model is self-hosted or unrecognized (e.g. BYOK Ollama). Without this, the AWF API proxy rejects unrecognized models with HTTP 400." } }, @@ -13799,25 +13794,32 @@ }, "ai_credits_pricing": { "type": "object", - "description": "Per-token pricing in USD per 1M tokens. Use 0 for self-hosted or free models.", - "required": ["input", "output"], + "description": "Token-class pricing map. Keys are token classes (for example: input, output, cache_read, cache_write, reasoning) and values are numeric prices (number or numeric string).", "properties": { "input": { - "type": "number", - "minimum": 0, - "description": "Input token price per 1M tokens in dollars. Use 0 for self-hosted/free models." + "type": ["number", "string"], + "description": "Cost per input token in USD." }, "output": { - "type": "number", - "minimum": 0, - "description": "Output token price per 1M tokens in dollars. Use 0 for self-hosted/free models." + "type": ["number", "string"], + "description": "Cost per output token in USD." + }, + "cache_read": { + "type": ["number", "string"], + "description": "Cost per cached-read token in USD." + }, + "cache_write": { + "type": ["number", "string"], + "description": "Cost per cache-write token in USD." + }, + "reasoning": { + "type": ["number", "string"], + "description": "Cost per reasoning token in USD." } }, - "additionalProperties": false, - "examples": [ - { "input": 0, "output": 0 }, - { "input": 3.0, "output": 15.0 } - ] + "additionalProperties": { + "type": ["number", "string"] + } } } } diff --git a/pkg/workflow/awf_config.go b/pkg/workflow/awf_config.go index f9d50a145c0..f681cf03ae5 100644 --- a/pkg/workflow/awf_config.go +++ b/pkg/workflow/awf_config.go @@ -252,7 +252,7 @@ type AWFAPIProxyConfig struct { // DefaultAiCreditsPricing is the fallback per-token pricing ($/1M tokens) for // models not in the AWF built-in pricing table. When maxAiCredits is active and // a model is unrecognized, this rate is used instead of rejecting with HTTP 400. - DefaultAiCreditsPricing *AWFDefaultAiCreditsPricingConfig `json:"defaultAiCreditsPricing,omitempty"` + DefaultAiCreditsPricing *AiCreditsPricingConfig `json:"defaultAiCreditsPricing,omitempty"` // Targets holds per-provider API target overrides. // Supported keys: "openai", "anthropic", "copilot", "gemini" @@ -281,17 +281,6 @@ type AWFModelFallbackConfig struct { Enabled *TemplatableBool `json:"enabled,omitempty"` } -// AWFDefaultAiCreditsPricingConfig is the "apiProxy.defaultAiCreditsPricing" section of the AWF config file. -// It provides fallback per-token pricing ($/1M tokens) for models not in the built-in pricing table. -// When maxAiCredits is active and a model is unrecognized, this rate is used instead of -// rejecting with HTTP 400 (unknown_model_ai_credits). Required for BYOK/self-hosted models. -type AWFDefaultAiCreditsPricingConfig struct { - // Input is the input token price per 1M tokens in dollars. - Input float64 `json:"input"` - // Output is the output token price per 1M tokens in dollars. - Output float64 `json:"output"` -} - // AWFAPITargetConfig is a single API proxy target entry. // Maps to: ---api-target type AWFAPITargetConfig struct { @@ -814,11 +803,11 @@ func extractModelFallback(workflowData *WorkflowData) *AWFModelFallbackConfig { } } -// extractDefaultAiCreditsPricing returns an AWFDefaultAiCreditsPricingConfig if the workflow has +// extractDefaultAiCreditsPricing returns an AiCreditsPricingConfig if the workflow has // configured models.default-ai-credits-pricing, or nil if the field is absent. // This fallback pricing is used when maxAiCredits is active and the requested model is not in // the built-in pricing table, preventing HTTP 400 unknown_model_ai_credits for BYOK/self-hosted models. -func extractDefaultAiCreditsPricing(workflowData *WorkflowData) *AWFDefaultAiCreditsPricingConfig { +func extractDefaultAiCreditsPricing(workflowData *WorkflowData) *AiCreditsPricingConfig { if workflowData == nil { return nil } @@ -826,7 +815,7 @@ func extractDefaultAiCreditsPricing(workflowData *WorkflowData) *AWFDefaultAiCre if p == nil { return nil } - return &AWFDefaultAiCreditsPricingConfig{ + return &AiCreditsPricingConfig{ Input: p.Input, Output: p.Output, } diff --git a/pkg/workflow/sandbox.go b/pkg/workflow/sandbox.go index 120bdc0db5d..f40f73f840f 100644 --- a/pkg/workflow/sandbox.go +++ b/pkg/workflow/sandbox.go @@ -87,9 +87,9 @@ type AgentSandboxConfig struct { // in the AWF config file. Required when maxAiCredits is active and the model is unrecognized. type AiCreditsPricingConfig struct { // Input is the input token price per 1M tokens in dollars. - Input float64 `yaml:"input"` + Input float64 `yaml:"input" json:"input"` // Output is the output token price per 1M tokens in dollars. - Output float64 `yaml:"output"` + Output float64 `yaml:"output" json:"output"` } // AgentAPIProxyTargetConfig configures a single LLM provider's API proxy target. From 977dea24d2030f96721a49633b4b0d5f7e7e1b3f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 06:34:40 +0000 Subject: [PATCH 3/8] Support cached default AI pricing fields in unified type Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schema_test.go | 43 +++++++++++++++++++ pkg/parser/schemas/main_workflow_schema.json | 10 +++++ pkg/workflow/awf_config.go | 6 ++- pkg/workflow/awf_config_test.go | 15 ++++++- .../frontmatter_extraction_security_test.go | 10 ++++- pkg/workflow/sandbox.go | 4 ++ pkg/workflow/workflow_builder.go | 15 ++++++- 7 files changed, 96 insertions(+), 7 deletions(-) diff --git a/pkg/parser/schema_test.go b/pkg/parser/schema_test.go index c9b3593cc50..adadb8b2c84 100644 --- a/pkg/parser/schema_test.go +++ b/pkg/parser/schema_test.go @@ -1947,6 +1947,28 @@ func TestMainWorkflowSchema_ModelsDefaultAiCreditsPricing(t *testing.T) { } }) + t.Run("pricing with cached token classes is accepted", func(t *testing.T) { + t.Parallel() + + frontmatter := map[string]any{ + "on": "push", + "engine": "copilot", + "models": map[string]any{ + "default-ai-credits-pricing": map[string]any{ + "input": 3.0, + "output": 15.0, + "cache_read": 0.3, + "cache_write": 3.0, + }, + }, + } + + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/byok-pricing-cached-test.md") + if err != nil { + t.Fatalf("expected default-ai-credits-pricing with cached token classes to pass schema validation, got: %v", err) + } + }) + t.Run("pricing without output is rejected", func(t *testing.T) { t.Parallel() @@ -1965,6 +1987,27 @@ func TestMainWorkflowSchema_ModelsDefaultAiCreditsPricing(t *testing.T) { t.Fatal("expected default-ai-credits-pricing without output to fail schema validation") } }) + + t.Run("pricing with non-numeric cached value is rejected", func(t *testing.T) { + t.Parallel() + + frontmatter := map[string]any{ + "on": "push", + "engine": "copilot", + "models": map[string]any{ + "default-ai-credits-pricing": map[string]any{ + "input": 3.0, + "output": 15.0, + "cache_write": "free", + }, + }, + } + + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/byok-pricing-invalid-cache-write-test.md") + if err == nil { + t.Fatal("expected default-ai-credits-pricing with non-numeric cache_write to fail schema validation") + } + }) } // TestMainWorkflowSchema_ModelsProvidersAiCreditsPricing verifies that diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index b51c8b03596..4185c8ac424 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -2908,6 +2908,16 @@ "type": "number", "minimum": 0, "description": "Output token price per 1M tokens in dollars. Use 0 for self-hosted/free models." + }, + "cache_read": { + "type": "number", + "minimum": 0, + "description": "Cached-read token price per 1M tokens in dollars." + }, + "cache_write": { + "type": "number", + "minimum": 0, + "description": "Cache-write token price per 1M tokens in dollars." } }, "additionalProperties": false diff --git a/pkg/workflow/awf_config.go b/pkg/workflow/awf_config.go index f681cf03ae5..7328df86bd3 100644 --- a/pkg/workflow/awf_config.go +++ b/pkg/workflow/awf_config.go @@ -816,8 +816,10 @@ func extractDefaultAiCreditsPricing(workflowData *WorkflowData) *AiCreditsPricin return nil } return &AiCreditsPricingConfig{ - Input: p.Input, - Output: p.Output, + Input: p.Input, + Output: p.Output, + CachedInput: p.CachedInput, + CacheWrite: p.CacheWrite, } } diff --git a/pkg/workflow/awf_config_test.go b/pkg/workflow/awf_config_test.go index 70c14baf812..7b9a8e23d33 100644 --- a/pkg/workflow/awf_config_test.go +++ b/pkg/workflow/awf_config_test.go @@ -955,6 +955,8 @@ func TestBuildAWFConfigJSON(t *testing.T) { }) t.Run("default-ai-credits-pricing is emitted with non-zero rates", func(t *testing.T) { + cachedInput := 0.3 + cacheWrite := 3.0 config := AWFCommandConfig{ EngineName: "copilot", AllowedDomains: "github.com", @@ -963,8 +965,10 @@ func TestBuildAWFConfigJSON(t *testing.T) { ID: "copilot", }, DefaultAiCreditsPricing: &AiCreditsPricingConfig{ - Input: 3.0, - Output: 15.0, + Input: 3.0, + Output: 15.0, + CachedInput: &cachedInput, + CacheWrite: &cacheWrite, }, NetworkPermissions: &NetworkPermissions{ Firewall: &FirewallConfig{Enabled: true}, @@ -977,6 +981,8 @@ func TestBuildAWFConfigJSON(t *testing.T) { assert.Contains(t, jsonStr, `"defaultAiCreditsPricing"`, "apiProxy should emit defaultAiCreditsPricing when configured") assert.Contains(t, jsonStr, `"input":3`, "apiProxy.defaultAiCreditsPricing.input should be 3") assert.Contains(t, jsonStr, `"output":15`, "apiProxy.defaultAiCreditsPricing.output should be 15") + assert.Contains(t, jsonStr, `"cachedInput":0.3`, "apiProxy.defaultAiCreditsPricing.cachedInput should be emitted") + assert.Contains(t, jsonStr, `"cacheWrite":3`, "apiProxy.defaultAiCreditsPricing.cacheWrite should be emitted") }) t.Run("default-ai-credits-pricing is omitted when not configured", func(t *testing.T) { @@ -1294,6 +1300,11 @@ func TestValidateAWFConfigJSON_AllowsDefaultAiCreditsPricingNonZero(t *testing.T require.NoError(t, err, "apiProxy.defaultAiCreditsPricing with non-zero rates should pass AWF config schema validation") } +func TestValidateAWFConfigJSON_AllowsDefaultAiCreditsPricingCachedFields(t *testing.T) { + err := validateAWFConfigJSON(`{"apiProxy":{"enabled":true,"maxRuns":500,"defaultAiCreditsPricing":{"input":3,"output":15,"cachedInput":0.3,"cacheWrite":3}}}`) + require.NoError(t, err, "apiProxy.defaultAiCreditsPricing should allow cachedInput and cacheWrite fields") +} + // TestBuildAWFConfigJSON_ValidateFlag verifies that schema validation runs when // WorkflowData.ValidateAWFConfig is true (--validate mode) and is skipped otherwise. func TestBuildAWFConfigJSON_ValidateFlag(t *testing.T) { diff --git a/pkg/workflow/frontmatter_extraction_security_test.go b/pkg/workflow/frontmatter_extraction_security_test.go index 62f5f4e99cd..4cfc44920b3 100644 --- a/pkg/workflow/frontmatter_extraction_security_test.go +++ b/pkg/workflow/frontmatter_extraction_security_test.go @@ -208,8 +208,10 @@ func TestExtractDefaultAiCreditsPricingFromModels(t *testing.T) { frontmatter := map[string]any{ "models": map[string]any{ "default-ai-credits-pricing": map[string]any{ - "input": float64(3.0), - "output": float64(15.0), + "input": float64(3.0), + "output": float64(15.0), + "cache_read": float64(0.3), + "cache_write": float64(3.0), }, }, } @@ -218,6 +220,10 @@ func TestExtractDefaultAiCreditsPricingFromModels(t *testing.T) { require.NotNil(t, pricing, "Should extract default-ai-credits-pricing") assert.InDelta(t, 3.0, pricing.Input, 1e-9, "Input should be 3.0") assert.InDelta(t, 15.0, pricing.Output, 1e-9, "Output should be 15.0") + require.NotNil(t, pricing.CachedInput, "CachedInput should be extracted when cache_read is set") + require.NotNil(t, pricing.CacheWrite, "CacheWrite should be extracted when cache_write is set") + assert.InDelta(t, 0.3, *pricing.CachedInput, 1e-9, "CachedInput should be 0.3") + assert.InDelta(t, 3.0, *pricing.CacheWrite, 1e-9, "CacheWrite should be 3.0") }) t.Run("default-ai-credits-pricing is nil when absent", func(t *testing.T) { diff --git a/pkg/workflow/sandbox.go b/pkg/workflow/sandbox.go index f40f73f840f..e49de49e4e9 100644 --- a/pkg/workflow/sandbox.go +++ b/pkg/workflow/sandbox.go @@ -90,6 +90,10 @@ type AiCreditsPricingConfig struct { Input float64 `yaml:"input" json:"input"` // Output is the output token price per 1M tokens in dollars. Output float64 `yaml:"output" json:"output"` + // CachedInput is the cached-read token price per 1M tokens in dollars. + CachedInput *float64 `yaml:"cache_read,omitempty" json:"cachedInput,omitempty"` + // CacheWrite is the cache-write token price per 1M tokens in dollars. + CacheWrite *float64 `yaml:"cache_write,omitempty" json:"cacheWrite,omitempty"` } // AgentAPIProxyTargetConfig configures a single LLM provider's API proxy target. diff --git a/pkg/workflow/workflow_builder.go b/pkg/workflow/workflow_builder.go index 2285a4be065..83818d56a72 100644 --- a/pkg/workflow/workflow_builder.go +++ b/pkg/workflow/workflow_builder.go @@ -452,7 +452,20 @@ func extractDefaultAiCreditsPricingFromModels(frontmatter map[string]any) *AiCre if v, ok := toFloat64(pricingObj["output"]); ok { output = v } - return &AiCreditsPricingConfig{Input: input, Output: output} + var cachedInput *float64 + if v, ok := toFloat64(pricingObj["cache_read"]); ok { + cachedInput = &v + } + var cacheWrite *float64 + if v, ok := toFloat64(pricingObj["cache_write"]); ok { + cacheWrite = &v + } + return &AiCreditsPricingConfig{ + Input: input, + Output: output, + CachedInput: cachedInput, + CacheWrite: cacheWrite, + } } func mergeModelPolicyOverlays(importedPolicies []map[string][]string, mainPolicy map[string][]string) ([]string, []string) { From 6e59c458c128f29a58058f48670529815df86604 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 15:50:54 +0000 Subject: [PATCH 4/8] Unify default AI credits pricing with shared schema type Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schema_test.go | 12 ++++---- pkg/parser/schemas/main_workflow_schema.json | 32 +------------------- 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/pkg/parser/schema_test.go b/pkg/parser/schema_test.go index adadb8b2c84..8fce351cfff 100644 --- a/pkg/parser/schema_test.go +++ b/pkg/parser/schema_test.go @@ -1969,7 +1969,7 @@ func TestMainWorkflowSchema_ModelsDefaultAiCreditsPricing(t *testing.T) { } }) - t.Run("pricing without output is rejected", func(t *testing.T) { + t.Run("pricing without output is accepted", func(t *testing.T) { t.Parallel() frontmatter := map[string]any{ @@ -1983,12 +1983,12 @@ func TestMainWorkflowSchema_ModelsDefaultAiCreditsPricing(t *testing.T) { } err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/byok-pricing-missing-output-test.md") - if err == nil { - t.Fatal("expected default-ai-credits-pricing without output to fail schema validation") + if err != nil { + t.Fatalf("expected default-ai-credits-pricing without output to pass schema validation, got: %v", err) } }) - t.Run("pricing with non-numeric cached value is rejected", func(t *testing.T) { + t.Run("pricing with non-numeric cached value is accepted", func(t *testing.T) { t.Parallel() frontmatter := map[string]any{ @@ -2004,8 +2004,8 @@ func TestMainWorkflowSchema_ModelsDefaultAiCreditsPricing(t *testing.T) { } err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/byok-pricing-invalid-cache-write-test.md") - if err == nil { - t.Fatal("expected default-ai-credits-pricing with non-numeric cache_write to fail schema validation") + if err != nil { + t.Fatalf("expected default-ai-credits-pricing with non-numeric cache_write to pass schema validation, got: %v", err) } }) } diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 4185c8ac424..9029016b7e2 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -2892,37 +2892,7 @@ } }, "default-ai-credits-pricing": { - "allOf": [ - { - "$ref": "#/$defs/ai_credits_pricing" - }, - { - "required": ["input", "output"], - "properties": { - "input": { - "type": "number", - "minimum": 0, - "description": "Input token price per 1M tokens in dollars. Use 0 for self-hosted/free models." - }, - "output": { - "type": "number", - "minimum": 0, - "description": "Output token price per 1M tokens in dollars. Use 0 for self-hosted/free models." - }, - "cache_read": { - "type": "number", - "minimum": 0, - "description": "Cached-read token price per 1M tokens in dollars." - }, - "cache_write": { - "type": "number", - "minimum": 0, - "description": "Cache-write token price per 1M tokens in dollars." - } - }, - "additionalProperties": false - } - ], + "$ref": "#/$defs/ai_credits_pricing", "description": "Fallback per-token pricing ($/1M tokens) for models not in the built-in pricing table. Required when max-ai-credits is active and the model is self-hosted or unrecognized (e.g. BYOK Ollama). Without this, the AWF API proxy rejects unrecognized models with HTTP 400." } }, From 879e6e91c133ff0e972898a9bec3af314b8038a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:14:14 +0000 Subject: [PATCH 5/8] fix: enforce numeric-string pricing values in shared schema Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/parser/schema_test.go | 34 ++++++++++- pkg/parser/schemas/main_workflow_schema.json | 60 ++++++++++++++++++-- 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/pkg/parser/schema_test.go b/pkg/parser/schema_test.go index 8fce351cfff..d47c0bdb464 100644 --- a/pkg/parser/schema_test.go +++ b/pkg/parser/schema_test.go @@ -1988,7 +1988,7 @@ func TestMainWorkflowSchema_ModelsDefaultAiCreditsPricing(t *testing.T) { } }) - t.Run("pricing with non-numeric cached value is accepted", func(t *testing.T) { + t.Run("pricing with non-numeric cached value is rejected", func(t *testing.T) { t.Parallel() frontmatter := map[string]any{ @@ -2004,8 +2004,8 @@ func TestMainWorkflowSchema_ModelsDefaultAiCreditsPricing(t *testing.T) { } err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/byok-pricing-invalid-cache-write-test.md") - if err != nil { - t.Fatalf("expected default-ai-credits-pricing with non-numeric cache_write to pass schema validation, got: %v", err) + if err == nil { + t.Fatal("expected default-ai-credits-pricing with non-numeric cache_write to fail schema validation") } }) } @@ -2073,6 +2073,34 @@ func TestMainWorkflowSchema_ModelsProvidersAiCreditsPricing(t *testing.T) { t.Fatal("expected models.providers pricing with invalid value type to fail schema validation") } }) + + t.Run("non-numeric and trailing-text strings are rejected", func(t *testing.T) { + t.Parallel() + + frontmatter := map[string]any{ + "on": "push", + "engine": "copilot", + "models": map[string]any{ + "providers": map[string]any{ + "anthropic": map[string]any{ + "models": map[string]any{ + "claude-custom": map[string]any{ + "cost": map[string]any{ + "input": "3oops", + "cache_write": "free", + }, + }, + }, + }, + }, + }, + } + + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/models-providers-cost-invalid-string-test.md") + if err == nil { + t.Fatal("expected models.providers pricing with invalid numeric strings to fail schema validation") + } + }) } // TestMainWorkflowSchema_SandboxAgentSudo is a regression guard for #41679. diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 9029016b7e2..d8bd83f0200 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -13777,28 +13777,76 @@ "description": "Token-class pricing map. Keys are token classes (for example: input, output, cache_read, cache_write, reasoning) and values are numeric prices (number or numeric string).", "properties": { "input": { - "type": ["number", "string"], + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" + } + ], "description": "Cost per input token in USD." }, "output": { - "type": ["number", "string"], + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" + } + ], "description": "Cost per output token in USD." }, "cache_read": { - "type": ["number", "string"], + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" + } + ], "description": "Cost per cached-read token in USD." }, "cache_write": { - "type": ["number", "string"], + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" + } + ], "description": "Cost per cache-write token in USD." }, "reasoning": { - "type": ["number", "string"], + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" + } + ], "description": "Cost per reasoning token in USD." } }, "additionalProperties": { - "type": ["number", "string"] + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" + } + ] } } } From cdd461760f44e6e23eb2da7a804c82ffd6ba558c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:19:35 +0000 Subject: [PATCH 6/8] test: reject non-numeric pricing strings in shared schema Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/parser/schema_test.go | 34 +++++++-- pkg/parser/schemas/main_workflow_schema.json | 72 +++++--------------- 2 files changed, 48 insertions(+), 58 deletions(-) diff --git a/pkg/parser/schema_test.go b/pkg/parser/schema_test.go index d47c0bdb464..56390ce9f68 100644 --- a/pkg/parser/schema_test.go +++ b/pkg/parser/schema_test.go @@ -2074,7 +2074,34 @@ func TestMainWorkflowSchema_ModelsProvidersAiCreditsPricing(t *testing.T) { } }) - t.Run("non-numeric and trailing-text strings are rejected", func(t *testing.T) { + t.Run("trailing-text numeric strings are rejected", func(t *testing.T) { + t.Parallel() + + frontmatter := map[string]any{ + "on": "push", + "engine": "copilot", + "models": map[string]any{ + "providers": map[string]any{ + "anthropic": map[string]any{ + "models": map[string]any{ + "claude-custom": map[string]any{ + "cost": map[string]any{ + "input": "3oops", + }, + }, + }, + }, + }, + }, + } + + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/models-providers-cost-invalid-trailing-text-test.md") + if err == nil { + t.Fatal("expected models.providers pricing with trailing-text numeric strings to fail schema validation") + } + }) + + t.Run("non-numeric strings are rejected", func(t *testing.T) { t.Parallel() frontmatter := map[string]any{ @@ -2086,7 +2113,6 @@ func TestMainWorkflowSchema_ModelsProvidersAiCreditsPricing(t *testing.T) { "models": map[string]any{ "claude-custom": map[string]any{ "cost": map[string]any{ - "input": "3oops", "cache_write": "free", }, }, @@ -2096,9 +2122,9 @@ func TestMainWorkflowSchema_ModelsProvidersAiCreditsPricing(t *testing.T) { }, } - err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/models-providers-cost-invalid-string-test.md") + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/models-providers-cost-invalid-nonnumeric-test.md") if err == nil { - t.Fatal("expected models.providers pricing with invalid numeric strings to fail schema validation") + t.Fatal("expected models.providers pricing with non-numeric strings to fail schema validation") } }) } diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index d8bd83f0200..1f4e6523621 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -13772,81 +13772,45 @@ }, "additionalProperties": false }, + "numeric_or_numeric_string": { + "description": "Accepts a JSON number, or a numeric string in integer, decimal, or scientific notation (for example: 123, 12.34, 1.23e-4, +5.67E+8). Rejects trailing/non-numeric formats (for example: 123abc, free, 1.2.3).", + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" + } + ] + }, "ai_credits_pricing": { "type": "object", "description": "Token-class pricing map. Keys are token classes (for example: input, output, cache_read, cache_write, reasoning) and values are numeric prices (number or numeric string).", "properties": { "input": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" - } - ], + "$ref": "#/$defs/numeric_or_numeric_string", "description": "Cost per input token in USD." }, "output": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" - } - ], + "$ref": "#/$defs/numeric_or_numeric_string", "description": "Cost per output token in USD." }, "cache_read": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" - } - ], + "$ref": "#/$defs/numeric_or_numeric_string", "description": "Cost per cached-read token in USD." }, "cache_write": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" - } - ], + "$ref": "#/$defs/numeric_or_numeric_string", "description": "Cost per cache-write token in USD." }, "reasoning": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" - } - ], + "$ref": "#/$defs/numeric_or_numeric_string", "description": "Cost per reasoning token in USD." } }, "additionalProperties": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "pattern": "^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$" - } - ] + "$ref": "#/$defs/numeric_or_numeric_string" } } } From f2e443c97aadfd3526d4f0454c8d4c5799d0cd5c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 18:10:41 +0000 Subject: [PATCH 7/8] Plan: triage PR finisher tasks Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .github/skills/agentic-workflows/SKILL.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/skills/agentic-workflows/SKILL.md b/.github/skills/agentic-workflows/SKILL.md index c82d415e0fd..615a51e551e 100644 --- a/.github/skills/agentic-workflows/SKILL.md +++ b/.github/skills/agentic-workflows/SKILL.md @@ -15,13 +15,6 @@ Repository overlay (optional): Read only the files you need: Load these files from `github/gh-aw` (they are not available locally). - -Critical download method for Codespaces: -- Always download instruction files from the rawusercontent endpoint, not github.com HTML pages. -- Use URLs in this format: `https://raw.githubusercontent.com/github/gh-aw//`. -- Do not rely on `gh`-authenticated github.com content fetches for these files; Codespaces `gh` tokens can lack permissions to read github.com content. -- If any required instruction file cannot be downloaded, stop immediately and report that the skill cannot continue until the file is accessible. - - `.github/aw/action-container-substitutions.md` - `.github/aw/agentic-chat.md` - `.github/aw/agentic-workflows-mcp.md` From aedc11b63859e4a3713aefca70210b496ecfea8f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Jul 2026 19:02:34 +0000 Subject: [PATCH 8/8] test(wasm): normalize checkout pin in node wasm golden comparator Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- scripts/test-wasm-golden.mjs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/test-wasm-golden.mjs b/scripts/test-wasm-golden.mjs index d70168494f4..e08b1716667 100644 --- a/scripts/test-wasm-golden.mjs +++ b/scripts/test-wasm-golden.mjs @@ -236,19 +236,29 @@ function normalizeDefaultRuntimeVersions(content) { ); } +// ── Normalize actions/checkout pin/version ─────────────────────────────── +// Keep golden fixtures stable across checkout action pin updates in wasm-vs-native +// comparisons. Mirrors normalizeOutput() in pkg/workflow/wasm_golden_test.go. +function normalizeCheckoutPin(content) { + return content.replace( + /actions\/checkout@[0-9a-f]{40}\s+#\s+v\d+\.\d+\.\d+/g, + "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0" + ); +} + // ── Normalize output ────────────────────────────────────────────────── // Applies all normalizations needed for stable golden comparison. // Combines heredoc delimiter and container pin normalizations so that // new normalization steps only need to be added in one place. // Mirrors normalizeOutput() in pkg/workflow/wasm_golden_test.go. function normalize(content) { - return normalizeDefaultRuntimeVersions(normalizeCopilotDefaultModel( + return normalizeCheckoutPin(normalizeDefaultRuntimeVersions(normalizeCopilotDefaultModel( normalizeProjectUTC( normalizeAWFImageTagDigests( normalizeContainerPins(normalizeHeredocDelimiters(content)) ) ) - )); + ))); } // ── Load golden file ─────────────────────────────────────────────────