Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/aw/actions-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
"version": "v8",
"sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd"
},
"github/gh-aw-actions/setup@v0.81.6": {
"repo": "github/gh-aw-actions/setup",
"version": "v0.81.6",
"sha": "ba6380cc6e5be5d21677bebe04d52fb48e3abec7"
},
"githubnext/gh-aw/actions/setup@v0.36.0": {
"repo": "githubnext/gh-aw/actions/setup",
"version": "v0.36.0",
Expand Down
1 change: 1 addition & 0 deletions .github/skills/agent-framework-azure-ai-py
1 change: 1 addition & 0 deletions .github/skills/azure-ai-contentsafety-py
1 change: 1 addition & 0 deletions .github/skills/azure-ai-contentunderstanding-py
1 change: 1 addition & 0 deletions .github/skills/azure-ai-language-conversations-py
1 change: 1 addition & 0 deletions .github/skills/azure-ai-ml-py
1 change: 1 addition & 0 deletions .github/skills/azure-ai-projects-py
1 change: 1 addition & 0 deletions .github/skills/azure-ai-textanalytics-py
1 change: 1 addition & 0 deletions .github/skills/azure-ai-transcription-py
1 change: 1 addition & 0 deletions .github/skills/azure-ai-translation-document-py
1 change: 1 addition & 0 deletions .github/skills/azure-ai-translation-text-py
1 change: 1 addition & 0 deletions .github/skills/azure-ai-vision-imageanalysis-py
1 change: 1 addition & 0 deletions .github/skills/azure-ai-voicelive-py
1 change: 1 addition & 0 deletions .github/skills/azure-appconfiguration-py
1 change: 1 addition & 0 deletions .github/skills/azure-containerregistry-py
1 change: 1 addition & 0 deletions .github/skills/azure-cosmos-db-py
1 change: 1 addition & 0 deletions .github/skills/azure-cosmos-py
1 change: 1 addition & 0 deletions .github/skills/azure-data-tables-py
1 change: 1 addition & 0 deletions .github/skills/azure-eventgrid-py
1 change: 1 addition & 0 deletions .github/skills/azure-eventhub-py
1 change: 1 addition & 0 deletions .github/skills/azure-identity-py
1 change: 1 addition & 0 deletions .github/skills/azure-keyvault-py
1 change: 1 addition & 0 deletions .github/skills/azure-messaging-webpubsubservice-py
1 change: 1 addition & 0 deletions .github/skills/azure-mgmt-apicenter-py
1 change: 1 addition & 0 deletions .github/skills/azure-mgmt-apimanagement-py
1 change: 1 addition & 0 deletions .github/skills/azure-mgmt-botservice-py
1 change: 1 addition & 0 deletions .github/skills/azure-mgmt-fabric-py
1 change: 1 addition & 0 deletions .github/skills/azure-monitor-ingestion-py
1 change: 1 addition & 0 deletions .github/skills/azure-monitor-opentelemetry-exporter-py
1 change: 1 addition & 0 deletions .github/skills/azure-monitor-opentelemetry-py
1 change: 1 addition & 0 deletions .github/skills/azure-monitor-query-py
1 change: 1 addition & 0 deletions .github/skills/azure-search-documents-py
1 change: 1 addition & 0 deletions .github/skills/azure-servicebus-py
1 change: 1 addition & 0 deletions .github/skills/azure-speech-to-text-rest-py
1 change: 1 addition & 0 deletions .github/skills/azure-storage-blob-py
1 change: 1 addition & 0 deletions .github/skills/azure-storage-file-datalake-py
1 change: 1 addition & 0 deletions .github/skills/azure-storage-file-share-py
1 change: 1 addition & 0 deletions .github/skills/azure-storage-queue-py
1 change: 1 addition & 0 deletions .github/skills/fastapi-router-py
1 change: 1 addition & 0 deletions .github/skills/m365-agents-py
1 change: 1 addition & 0 deletions .github/skills/pydantic-models-py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
name: Vally Evaluation
name: Vally Workflow Evaluation

on:
pull_request:
paths:
- "tests/scenarios/**/vally/**"
- ".github/workflows/vally-evaluation.yml"
- ".github/workflows/run-vally-evaluations.yml"
push:
branches: [main]
paths:
- "tests/scenarios/**/vally/**"
- ".github/workflows/vally-evaluation.yml"
- ".github/workflows/run-vally-evaluations.yml"
workflow_dispatch:
inputs:
eval_spec:
Expand All @@ -19,24 +19,38 @@ on:

permissions:
contents: read
# eval.yaml's `executor: copilot-sdk` makes vally spawn the real GitHub
# Copilot CLI (`@github/copilot`, pulled in transitively via the vally npm
# dependency tree -- no extra install step needed) to run each stimulus.
# That CLI authenticates via the GH_TOKEN/GITHUB_TOKEN env var, which
# requires the GITHUB_TOKEN to carry the "Copilot Requests" permission.
copilot-requests: write

jobs:
vally:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.COPILOT_TOKEN || '' }}

# Authenticates Copilot CLI and Vally's Copilot SDK evaluator path.
GH_TOKEN: ${{ github.token }}
GITHUB_TOKEN: ${{ github.token }}
GITHUB_COPILOT_API_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-node@v4
- uses: actions/setup-node@v7
with:
node-version: "20"
node-version: "24"

- name: Install Vally CLI
run: npm install -g @microsoft/vally-cli@0.6.0
run: npm install -g @microsoft/vally-cli@0.7.0

- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true

- name: Verify Vally CLI
run: vally --version
Expand Down Expand Up @@ -87,7 +101,7 @@ jobs:
find tests/scenarios -type f \( -name 'eval.yaml' -o -name 'eval.yml' \) -path '*/vally/*' | sort -u > "$specs_file"
fi
elif [ "${{ github.event_name }}" = "pull_request" ]; then
collect_from_changed_dirs "${{ github.event.pull_request.base.sha }}" "${{ github.sha }}" | sort -u > "$specs_file"
collect_from_changed_dirs "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" | sort -u > "$specs_file"
else
base_sha="${{ github.event.before }}"
if [ -z "$base_sha" ] || [ "$base_sha" = '0000000000000000000000000000000000000000' ]; then
Expand Down Expand Up @@ -137,8 +151,18 @@ jobs:
--strict
done

- name: Vally PR scope notice (same-repo)
if: ${{ steps.specs.outputs.has_specs == 'true' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }}
run: |
echo "::notice::Running Vally evaluations for same-repo PR from ${{ github.event.pull_request.head.repo.full_name }}."

- name: Vally PR scope notice (cross-repo)
if: ${{ steps.specs.outputs.has_specs == 'true' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
run: |
echo "::notice::Skipping Vally evaluations for cross-repo PR from ${{ github.event.pull_request.head.repo.full_name }} to ${{ github.repository }}. Evaluation runs only on same-repo PRs and non-PR events."

- name: Run Vally evaluations
if: ${{ steps.specs.outputs.has_specs == 'true' && env.GH_TOKEN != '' }}
if: ${{ steps.specs.outputs.has_specs == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
shell: bash
run: |
set -euo pipefail
Expand All @@ -156,11 +180,6 @@ jobs:
--grader-plugin "$GITHUB_WORKSPACE/tests/scenarios/_shared/vally/grader-plugins/rust-cargo-build-failure" \
--junit --output-dir vally-results --workers 2

- name: Skip eval when token is unavailable
if: ${{ steps.specs.outputs.has_specs == 'true' && env.GH_TOKEN == '' }}
run: |
echo "::warning::COPILOT_TOKEN secret is not available. Skipping vally eval run and keeping lint-only validation."

- name: Upload Vally artifacts
if: always()
uses: actions/upload-artifact@v4
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/test-harness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ jobs:
cache: "pnpm"
cache-dependency-path: tests/pnpm-lock.yaml

- name: Verify Rust skill links are in sync
- name: Verify Skill links are in sync
run: |
python .github/scripts/sync_skill_links.py --plugin azure-sdk-rust --check
python .github/scripts/sync_skill_links.py --plugin azure-sdk-python --check

- name: Install dependencies
working-directory: tests
Expand Down
79 changes: 79 additions & 0 deletions tests/harness/copilot-client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { describe, expect, it } from "vitest";

import {
CopilotGenerationError,
SkillCopilotClient,
classifyCopilotError,
} from "./copilot-client.js";

describe("SkillCopilotClient.extractCode", () => {
it("keeps assignment line for multiline constructor call without fences", () => {
const client = new SkillCopilotClient(process.cwd(), true);

const response = [
"from azure.identity import DefaultAzureCredential",
"from azure.monitor.opentelemetry.exporter import AzureMonitorTraceExporter",
"",
"exporter = AzureMonitorTraceExporter(",
" credential=DefaultAzureCredential(),",
' storage_directory="/path/to/storage",',
" disable_offline_storage=False,",
")",
].join("\n");

const extracted = (
client as unknown as { extractCode: (r: string) => string }
).extractCode(response);

expect(extracted).toContain("exporter = AzureMonitorTraceExporter(");
expect(extracted).toContain("disable_offline_storage=False,");
expect(extracted.trim().endsWith(")")).toBe(true);
});

it("prefers fenced code blocks when present", () => {
const client = new SkillCopilotClient(process.cwd(), true);

const response = [
"Here is the implementation:",
"```python",
"x = 1",
"print(x)",
"```",
].join("\n");

const extracted = (
client as unknown as { extractCode: (r: string) => string }
).extractCode(response);

expect(extracted).toBe("x = 1\nprint(x)");
});
});

describe("classifyCopilotError", () => {
it("classifies timeout and marks retryable", () => {
const classified = classifyCopilotError(
new Error("Timeout after 120000ms waiting for session.idle"),
);

expect(classified.kind).toBe("timeout");
expect(classified.retryable).toBe(true);
});

it("classifies auth and marks non-retryable", () => {
const classified = classifyCopilotError(
new Error(
"Authentication failed: Failed to fetch GitHub CLI user login (401): Bad credentials",
),
);

expect(classified.kind).toBe("auth");
expect(classified.retryable).toBe(false);
});

it("returns existing classified errors unchanged", () => {
const original = new CopilotGenerationError("transient", "429", true);
const classified = classifyCopilotError(original);

expect(classified).toBe(original);
});
});
Loading
Loading