diff --git a/.github/workflows/scenario-builds.yml b/.github/workflows/scenario-builds.yml deleted file mode 100644 index 8114176e7..000000000 --- a/.github/workflows/scenario-builds.yml +++ /dev/null @@ -1,237 +0,0 @@ -name: "Scenario Build Verification" - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths: - - "test/scenarios/**" - - "nodejs/src/**" - - "python/copilot/**" - - "go/**/*.go" - - "dotnet/src/**" - - "rust/src/**" - - "rust/Cargo.toml" - - ".github/workflows/scenario-builds.yml" - push: - branches: - - main - paths: - - "test/scenarios/**" - - ".github/workflows/scenario-builds.yml" - workflow_dispatch: - merge_group: - -permissions: - contents: read - -jobs: - # ── TypeScript ────────────────────────────────────────────────────── - build-typescript: - name: "TypeScript scenarios" - if: github.event.repository.fork == false - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-node@v6 - with: - node-version: 22 - - - uses: actions/cache@v4 - with: - path: ~/.npm - key: ${{ runner.os }}-npm-scenarios-${{ hashFiles('test/scenarios/**/package.json') }} - restore-keys: | - ${{ runner.os }}-npm-scenarios- - - # Build the SDK so local file: references resolve - - name: Build SDK - working-directory: nodejs - run: npm ci --ignore-scripts - - - name: Build all TypeScript scenarios - run: | - PASS=0; FAIL=0; FAILURES="" - for dir in $(find test/scenarios -path '*/typescript/package.json' -exec dirname {} \; | sort); do - scenario="${dir#test/scenarios/}" - echo "::group::$scenario" - if (cd "$dir" && npm install --ignore-scripts 2>&1); then - echo "✅ $scenario" - PASS=$((PASS + 1)) - else - echo "❌ $scenario" - FAIL=$((FAIL + 1)) - FAILURES="$FAILURES\n $scenario" - fi - echo "::endgroup::" - done - echo "" - echo "TypeScript builds: $PASS passed, $FAIL failed" - if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$FAILURES" - exit 1 - fi - - # ── Python ────────────────────────────────────────────────────────── - build-python: - name: "Python scenarios" - if: github.event.repository.fork == false - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install Python SDK - run: pip install -e python/ - - - name: Compile and import-check all Python scenarios - run: | - PASS=0; FAIL=0; FAILURES="" - for main in $(find test/scenarios -path '*/python/main.py' | sort); do - dir=$(dirname "$main") - scenario="${dir#test/scenarios/}" - echo "::group::$scenario" - if python3 -m py_compile "$main" 2>&1 && python3 -c "import copilot" 2>&1; then - echo "✅ $scenario" - PASS=$((PASS + 1)) - else - echo "❌ $scenario" - FAIL=$((FAIL + 1)) - FAILURES="$FAILURES\n $scenario" - fi - echo "::endgroup::" - done - echo "" - echo "Python builds: $PASS passed, $FAIL failed" - if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$FAILURES" - exit 1 - fi - - # ── Go ────────────────────────────────────────────────────────────── - build-go: - name: "Go scenarios" - if: github.event.repository.fork == false - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-go@v6 - with: - go-version: "1.24" - cache: true - cache-dependency-path: test/scenarios/**/go.sum - - - name: Build all Go scenarios - run: | - PASS=0; FAIL=0; FAILURES="" - for mod in $(find test/scenarios -path '*/go/go.mod' | sort); do - dir=$(dirname "$mod") - scenario="${dir#test/scenarios/}" - echo "::group::$scenario" - if (cd "$dir" && go build ./... 2>&1); then - echo "✅ $scenario" - PASS=$((PASS + 1)) - else - echo "❌ $scenario" - FAIL=$((FAIL + 1)) - FAILURES="$FAILURES\n $scenario" - fi - echo "::endgroup::" - done - echo "" - echo "Go builds: $PASS passed, $FAIL failed" - if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$FAILURES" - exit 1 - fi - - # ── C# ───────────────────────────────────────────────────────────── - build-csharp: - name: "C# scenarios" - if: github.event.repository.fork == false - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-dotnet@v5 - with: - dotnet-version: "10.0.x" - - - uses: actions/cache@v4 - with: - path: ~/.nuget/packages - key: ${{ runner.os }}-nuget-scenarios-${{ hashFiles('test/scenarios/**/*.csproj') }} - restore-keys: | - ${{ runner.os }}-nuget-scenarios- - - - name: Build all C# scenarios - run: | - PASS=0; FAIL=0; FAILURES="" - for proj in $(find test/scenarios -name '*.csproj' | sort); do - dir=$(dirname "$proj") - scenario="${dir#test/scenarios/}" - echo "::group::$scenario" - if (cd "$dir" && dotnet build --nologo 2>&1); then - echo "✅ $scenario" - PASS=$((PASS + 1)) - else - echo "❌ $scenario" - FAIL=$((FAIL + 1)) - FAILURES="$FAILURES\n $scenario" - fi - echo "::endgroup::" - done - echo "" - echo "C# builds: $PASS passed, $FAIL failed" - if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$FAILURES" - exit 1 - fi - - # ── Rust ──────────────────────────────────────────────────────────── - build-rust: - name: "Rust scenarios" - if: github.event.repository.fork == false - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - uses: dtolnay/rust-toolchain@1.94.0 - - - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - test/scenarios/**/rust/target - key: ${{ runner.os }}-cargo-scenarios-${{ hashFiles('rust/Cargo.toml', 'test/scenarios/**/rust/Cargo.toml') }} - restore-keys: | - ${{ runner.os }}-cargo-scenarios- - - - name: Build all Rust scenarios - run: | - PASS=0; FAIL=0; FAILURES="" - for manifest in $(find test/scenarios -path '*/rust/Cargo.toml' | sort); do - dir=$(dirname "$manifest") - scenario="${dir#test/scenarios/}" - echo "::group::$scenario" - if (cd "$dir" && cargo build --quiet 2>&1); then - echo "✅ $scenario" - PASS=$((PASS + 1)) - else - echo "❌ $scenario" - FAIL=$((FAIL + 1)) - FAILURES="$FAILURES\n $scenario" - fi - echo "::endgroup::" - done - echo "" - echo "Rust builds: $PASS passed, $FAIL failed" - if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$FAILURES" - exit 1 - fi diff --git a/.gitignore b/.gitignore index 6a8a9d545..1485d3a9c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,6 @@ docs/.validation/ .DS_Store -# Rust scenario build artifacts -test/scenarios/**/rust/target/ -test/scenarios/**/rust/Cargo.lock # Visual Studio .vs/ diff --git a/justfile b/justfile index ab97c1d3d..d23e155c8 100644 --- a/justfile +++ b/justfile @@ -169,111 +169,3 @@ validate-docs-cs: @echo "=== Validating C# documentation ===" @cd scripts/docs-validation && npm run validate:cs -# Build all scenario samples (all languages) -scenario-build: - #!/usr/bin/env bash - set -euo pipefail - echo "=== Building all scenario samples ===" - TOTAL=0; PASS=0; FAIL=0 - - build_lang() { - local lang="$1" find_expr="$2" build_cmd="$3" - echo "" - echo "── $lang ──" - while IFS= read -r target; do - [ -z "$target" ] && continue - dir=$(dirname "$target") - scenario="${dir#test/scenarios/}" - TOTAL=$((TOTAL + 1)) - if (cd "$dir" && eval "$build_cmd" >/dev/null 2>&1); then - printf " ✅ %s\n" "$scenario" - PASS=$((PASS + 1)) - else - printf " ❌ %s\n" "$scenario" - FAIL=$((FAIL + 1)) - fi - done < <(find test/scenarios $find_expr | sort) - } - - # TypeScript: npm install - (cd nodejs && npm ci --ignore-scripts --silent 2>/dev/null) || true - build_lang "TypeScript" "-path '*/typescript/package.json'" "npm install --ignore-scripts" - - # Python: syntax check - build_lang "Python" "-path '*/python/main.py'" "python3 -c \"import ast; ast.parse(open('main.py').read())\"" - - # Go: go build - build_lang "Go" "-path '*/go/go.mod'" "go build ./..." - - # C#: dotnet build - build_lang "C#" "-name '*.csproj' -path '*/csharp/*'" "dotnet build --nologo -v quiet" - - echo "" - echo "══════════════════════════════════════" - echo " Scenario build summary: $PASS passed, $FAIL failed (of $TOTAL)" - echo "══════════════════════════════════════" - [ "$FAIL" -eq 0 ] - -# Run the full scenario verify orchestrator (build + E2E, needs real CLI) -scenario-verify: - @echo "=== Running scenario verification ===" - @bash test/scenarios/verify.sh - -# Build scenarios for a single language (typescript, python, go, csharp) -scenario-build-lang LANG: - #!/usr/bin/env bash - set -euo pipefail - echo "=== Building {{LANG}} scenarios ===" - PASS=0; FAIL=0 - - case "{{LANG}}" in - typescript) - (cd nodejs && npm ci --ignore-scripts --silent 2>/dev/null) || true - for target in $(find test/scenarios -path '*/typescript/package.json' | sort); do - dir=$(dirname "$target"); scenario="${dir#test/scenarios/}" - if (cd "$dir" && npm install --ignore-scripts >/dev/null 2>&1); then - printf " ✅ %s\n" "$scenario"; PASS=$((PASS + 1)) - else - printf " ❌ %s\n" "$scenario"; FAIL=$((FAIL + 1)) - fi - done - ;; - python) - for target in $(find test/scenarios -path '*/python/main.py' | sort); do - dir=$(dirname "$target"); scenario="${dir#test/scenarios/}" - if python3 -c "import ast; ast.parse(open('$target').read())" 2>/dev/null; then - printf " ✅ %s\n" "$scenario"; PASS=$((PASS + 1)) - else - printf " ❌ %s\n" "$scenario"; FAIL=$((FAIL + 1)) - fi - done - ;; - go) - for target in $(find test/scenarios -path '*/go/go.mod' | sort); do - dir=$(dirname "$target"); scenario="${dir#test/scenarios/}" - if (cd "$dir" && go build ./... >/dev/null 2>&1); then - printf " ✅ %s\n" "$scenario"; PASS=$((PASS + 1)) - else - printf " ❌ %s\n" "$scenario"; FAIL=$((FAIL + 1)) - fi - done - ;; - csharp) - for target in $(find test/scenarios -name '*.csproj' -path '*/csharp/*' | sort); do - dir=$(dirname "$target"); scenario="${dir#test/scenarios/}" - if (cd "$dir" && dotnet build --nologo -v quiet >/dev/null 2>&1); then - printf " ✅ %s\n" "$scenario"; PASS=$((PASS + 1)) - else - printf " ❌ %s\n" "$scenario"; FAIL=$((FAIL + 1)) - fi - done - ;; - *) - echo "Unknown language: {{LANG}}. Use: typescript, python, go, csharp" - exit 1 - ;; - esac - - echo "" - echo "{{LANG}} scenarios: $PASS passed, $FAIL failed" - [ "$FAIL" -eq 0 ] diff --git a/test/scenarios/.gitignore b/test/scenarios/.gitignore deleted file mode 100644 index b56abbd20..000000000 --- a/test/scenarios/.gitignore +++ /dev/null @@ -1,86 +0,0 @@ -# Dependencies -node_modules/ -.venv/ -vendor/ - -# E2E run artifacts (agents may create files during verify.sh runs) -**/sessions/**/plan.md -**/tools/**/plan.md -**/callbacks/**/plan.md -**/prompts/**/plan.md - -# Build output -dist/ -target/ -build/ -*.exe -*.dll -*.so -*.dylib - -# Go -*.test -fully-bundled-go -app-direct-server-go -container-proxy-go -container-relay-go -app-backend-to-server-go -custom-agents-go -mcp-servers-go -no-tools-go -virtual-filesystem-go -system-message-go -skills-go -streaming-go -attachments-go -tool-filtering-go -permissions-go -hooks-go -user-input-go -concurrent-sessions-go -session-resume-go -stdio-go -tcp-go -gh-app-go -cli-preset-go -filesystem-preset-go -minimal-preset-go -default-go -minimal-go - -# Python -__pycache__/ -*.pyc -*.pyo -*.egg-info/ -*.egg -.eggs/ - -# TypeScript -*.tsbuildinfo -package-lock.json - -# C# / .NET -bin/ -obj/ -*.csproj.nuget.* - -# IDE / OS -.DS_Store -.idea/ -.vscode/ -*.swp -*.swo -*~ - -# Multi-user scenario temp directories -**/sessions/multi-user-long-lived/tmp/ - -# Logs -*.log -npm-debug.log* -infinite-sessions-go -reasoning-effort-go -reconnect-go -byok-openai-go -token-sources-go diff --git a/test/scenarios/Directory.Build.props b/test/scenarios/Directory.Build.props deleted file mode 100644 index 8350045c9..000000000 --- a/test/scenarios/Directory.Build.props +++ /dev/null @@ -1,5 +0,0 @@ - - - $(NoWarn);GHCP001 - - diff --git a/test/scenarios/README.md b/test/scenarios/README.md deleted file mode 100644 index 30be29c44..000000000 --- a/test/scenarios/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# SDK E2E Scenario Tests - -End-to-end scenario tests for the Copilot SDK. Each scenario demonstrates a specific SDK capability with implementations in TypeScript, Python, Go, C#, and Rust. - -## Structure - -``` -scenarios/ -├── auth/ # Authentication flows (OAuth, BYOK, token sources) -├── bundling/ # Deployment architectures (stdio, TCP, containers) -├── callbacks/ # Lifecycle hooks, permissions, user input -├── modes/ # Preset modes (CLI, filesystem, minimal) -├── prompts/ # Prompt configuration (attachments, system messages, reasoning) -├── sessions/ # Session management (streaming, resume, concurrent, infinite) -├── tools/ # Tool capabilities (custom agents, MCP, skills, filtering) -├── transport/ # Wire protocols (stdio, TCP, WASM, reconnect) -└── verify.sh # Run all scenarios -``` - -## Running - -Run all scenarios: - -```bash -COPILOT_CLI_PATH=/path/to/copilot GITHUB_TOKEN=$(gh auth token) bash verify.sh -``` - -Run a single scenario: - -```bash -COPILOT_CLI_PATH=/path/to/copilot GITHUB_TOKEN=$(gh auth token) bash //verify.sh -``` - -## Prerequisites - -- **Copilot CLI** — set `COPILOT_CLI_PATH` -- **GitHub token** — set `GITHUB_TOKEN` or use `gh auth login` -- **Node.js 20+**, **Python 3.11+**, **Go 1.24+**, **.NET 8+**, **Rust 1.94+** (per language) diff --git a/test/scenarios/RUST_COVERAGE.md b/test/scenarios/RUST_COVERAGE.md deleted file mode 100644 index f0c61979f..000000000 --- a/test/scenarios/RUST_COVERAGE.md +++ /dev/null @@ -1,61 +0,0 @@ -# Rust scenario coverage - -Rust SDK scenario samples live alongside the TypeScript / Python / Go / C# samples under -`test/scenarios/*//rust/`. The monorepo's `scenario-builds.yml` workflow -auto-discovers any `*/rust/Cargo.toml` under `test/scenarios/` and verifies it builds. - -## Coverage - -| Category | Scenario | Status | -|----------------|-------------------------|--------| -| `transport/` | `stdio` | ✅ | -| `transport/` | `tcp` | ✅ | -| `transport/` | `external` | ❌ deferred (needs `from_streams`-style sample) | -| `sessions/` | `streaming` | ✅ | -| `sessions/` | `session-resume` | ✅ | -| `sessions/` | `infinite-sessions` | ✅ | -| `sessions/` | `concurrent-sessions` | ✅ | -| `sessions/` | `multi-user-*` | ❌ deferred (multi-client orchestration) | -| `modes/` | `default` | ✅ | -| `modes/` | non-default | ❌ deferred (plan mode, read-only) | -| `tools/` | `no-tools` | ✅ | -| `tools/` | `mcp-servers` | ✅ | -| `tools/` | `skills` | ✅ | -| `tools/` | `tool-filtering` | ✅ | -| `tools/` | `custom-agents` | ✅ | -| `tools/` | `tool-overrides` | ✅ | -| `tools/` | `virtual-filesystem` | ❌ deferred (needs `VirtualFilesystem` hook port) | -| `callbacks/` | `hooks` | ✅ | -| `callbacks/` | `permissions` | ✅ | -| `callbacks/` | `user-input` | ✅ | -| `prompts/` | `system-message` | ✅ | -| `prompts/` | `reasoning-effort` | ✅ | -| `prompts/` | `attachments` | ✅ | -| `bundling/` | * | ❌ app-level concern, not an SDK gap | -| `auth/` | * | ❌ deferred (GitHub-App / token-exchange) | - -## Remaining gaps - -- `transport/external` — needs a sample using an externally-managed CLI process (parity with Node's `from_streams`). -- `tools/virtual-filesystem` — depends on a future `VirtualFilesystem` hook port. -- `modes/*` (non-default) — plan-mode and read-only-mode samples. -- `sessions/multi-user-*` — multi-client orchestration. -- `auth/*` — GitHub-App / token-exchange sample programs. -- `bundling/*` — process bundling is application-level, not an SDK concern. - -## Running the samples locally - -Each scenario's `verify.sh` runs the Rust build + run phase alongside the other -languages. With a token in place (`GITHUB_TOKEN`, or `gh auth login`): - -```sh -cd test/scenarios/transport/stdio && ./verify.sh -``` - -To build all Rust scenario samples without running them (what CI does): - -```sh -for d in $(find test/scenarios -path '*/rust/Cargo.toml'); do - (cd "$(dirname "$d")" && cargo build --quiet) || echo "FAILED: $d" -done -``` diff --git a/test/scenarios/auth/byok-anthropic/README.md b/test/scenarios/auth/byok-anthropic/README.md deleted file mode 100644 index 5fd4511dc..000000000 --- a/test/scenarios/auth/byok-anthropic/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Auth Sample: BYOK Anthropic - -This sample shows how to use Copilot SDK in **BYOK** mode with an Anthropic provider. - -## What this sample does - -1. Creates a session with a custom provider (`type: "anthropic"`) -2. Uses your `ANTHROPIC_API_KEY` instead of GitHub auth -3. Sends a prompt and prints the response - -## Prerequisites - -- `copilot` binary (`COPILOT_CLI_PATH`, or auto-detected by SDK) -- Node.js 20+ -- `ANTHROPIC_API_KEY` - -## Run - -```bash -cd typescript -npm install --ignore-scripts -npm run build -ANTHROPIC_API_KEY=sk-ant-... node dist/index.js -``` - -Optional environment variables: - -- `ANTHROPIC_BASE_URL` (default: `https://api.anthropic.com`) -- `ANTHROPIC_MODEL` (default: `claude-sonnet-4-20250514`) - -## Verify - -```bash -./verify.sh -``` - -Build checks run by default. E2E run is optional and requires both `BYOK_SAMPLE_RUN_E2E=1` and `ANTHROPIC_API_KEY`. diff --git a/test/scenarios/auth/byok-anthropic/csharp/Program.cs b/test/scenarios/auth/byok-anthropic/csharp/Program.cs deleted file mode 100644 index 3aac89577..000000000 --- a/test/scenarios/auth/byok-anthropic/csharp/Program.cs +++ /dev/null @@ -1,51 +0,0 @@ -using GitHub.Copilot; - -var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"); -var model = Environment.GetEnvironmentVariable("ANTHROPIC_MODEL") ?? "claude-sonnet-4-20250514"; -var baseUrl = Environment.GetEnvironmentVariable("ANTHROPIC_BASE_URL") ?? "https://api.anthropic.com"; - -if (string.IsNullOrEmpty(apiKey)) -{ - Console.Error.WriteLine("Missing ANTHROPIC_API_KEY."); - return 1; -} - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = model, - Provider = new ProviderConfig - { - Type = "anthropic", - BaseUrl = baseUrl, - ApiKey = apiKey, - }, - AvailableTools = [], - SystemMessage = new SystemMessageConfig - { - Mode = SystemMessageMode.Replace, - Content = "You are a helpful assistant. Answer concisely.", - }, - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} -return 0; - diff --git a/test/scenarios/auth/byok-anthropic/csharp/csharp.csproj b/test/scenarios/auth/byok-anthropic/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/auth/byok-anthropic/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/auth/byok-anthropic/go/go.mod b/test/scenarios/auth/byok-anthropic/go/go.mod deleted file mode 100644 index 995f34927..000000000 --- a/test/scenarios/auth/byok-anthropic/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/auth/byok-anthropic/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/auth/byok-anthropic/go/go.sum b/test/scenarios/auth/byok-anthropic/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/auth/byok-anthropic/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/auth/byok-anthropic/go/main.go b/test/scenarios/auth/byok-anthropic/go/main.go deleted file mode 100644 index efe7d5b4d..000000000 --- a/test/scenarios/auth/byok-anthropic/go/main.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - apiKey := os.Getenv("ANTHROPIC_API_KEY") - if apiKey == "" { - log.Fatal("Missing ANTHROPIC_API_KEY.") - } - - baseUrl := os.Getenv("ANTHROPIC_BASE_URL") - if baseUrl == "" { - baseUrl = "https://api.anthropic.com" - } - - model := os.Getenv("ANTHROPIC_MODEL") - if model == "" { - model = "claude-sonnet-4-20250514" - } - - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: model, - Provider: &copilot.ProviderConfig{ - Type: "anthropic", - BaseURL: baseUrl, - APIKey: apiKey, - }, - AvailableTools: []string{}, - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: "You are a helpful assistant. Answer concisely.", - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/auth/byok-anthropic/python/main.py b/test/scenarios/auth/byok-anthropic/python/main.py deleted file mode 100644 index 5b623ff87..000000000 --- a/test/scenarios/auth/byok-anthropic/python/main.py +++ /dev/null @@ -1,44 +0,0 @@ -import asyncio -import os -import sys - -from copilot import CopilotClient - -ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY") -ANTHROPIC_MODEL = os.environ.get("ANTHROPIC_MODEL", "claude-sonnet-4-20250514") -ANTHROPIC_BASE_URL = os.environ.get("ANTHROPIC_BASE_URL", "https://api.anthropic.com") - -if not ANTHROPIC_API_KEY: - print("Missing ANTHROPIC_API_KEY.", file=sys.stderr) - sys.exit(1) - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model=ANTHROPIC_MODEL, - provider={ - "type": "anthropic", - "base_url": ANTHROPIC_BASE_URL, - "api_key": ANTHROPIC_API_KEY, - }, - available_tools=[], - system_message={ - "mode": "replace", - "content": "You are a helpful assistant. Answer concisely.", - }, - ) - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/auth/byok-anthropic/python/requirements.txt b/test/scenarios/auth/byok-anthropic/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/auth/byok-anthropic/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/auth/byok-anthropic/typescript/package.json b/test/scenarios/auth/byok-anthropic/typescript/package.json deleted file mode 100644 index 4bb834ff2..000000000 --- a/test/scenarios/auth/byok-anthropic/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "auth-byok-anthropic-typescript", - "version": "1.0.0", - "private": true, - "description": "Auth sample — BYOK with Anthropic", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/auth/byok-anthropic/typescript/src/index.ts b/test/scenarios/auth/byok-anthropic/typescript/src/index.ts deleted file mode 100644 index 67eb27dd8..000000000 --- a/test/scenarios/auth/byok-anthropic/typescript/src/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const apiKey = process.env.ANTHROPIC_API_KEY; - const model = process.env.ANTHROPIC_MODEL || "claude-sonnet-4-20250514"; - - if (!apiKey) { - console.error("Required: ANTHROPIC_API_KEY"); - process.exit(1); - } - - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model, - provider: { - type: "anthropic", - baseUrl: process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com", - apiKey, - }, - availableTools: [], - systemMessage: { - mode: "replace", - content: "You are a helpful assistant. Answer concisely.", - }, - }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/auth/byok-anthropic/verify.sh b/test/scenarios/auth/byok-anthropic/verify.sh deleted file mode 100755 index 24a8c7ca9..000000000 --- a/test/scenarios/auth/byok-anthropic/verify.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying auth/byok-anthropic" -echo "══════════════════════════════════════" -echo "" - -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - -if [ "${BYOK_SAMPLE_RUN_E2E:-}" = "1" ] && [ -n "${ANTHROPIC_API_KEY:-}" ]; then - run_with_timeout "TypeScript (run)" bash -c " - cd '$SCRIPT_DIR/typescript' && \ - output=\$(node dist/index.js 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response\|hello' - " - run_with_timeout "C# (run)" bash -c " - cd '$SCRIPT_DIR/csharp' && \ - output=\$(dotnet run --no-build 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response\|hello' - " -else - echo "⚠️ WARNING: E2E run was SKIPPED — only build was verified, not runtime behavior." - echo " To run fully: set BYOK_SAMPLE_RUN_E2E=1 and ANTHROPIC_API_KEY." - echo "" -fi - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/auth/byok-azure/README.md b/test/scenarios/auth/byok-azure/README.md deleted file mode 100644 index 86843355f..000000000 --- a/test/scenarios/auth/byok-azure/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Auth Sample: BYOK Azure OpenAI - -This sample shows how to use Copilot SDK in **BYOK** mode with an Azure OpenAI provider. - -## What this sample does - -1. Creates a session with a custom provider (`type: "azure"`) -2. Uses your Azure OpenAI endpoint and API key instead of GitHub auth -3. Configures the Azure-specific `apiVersion` field -4. Sends a prompt and prints the response - -## Prerequisites - -- `copilot` binary (`COPILOT_CLI_PATH`, or auto-detected by SDK) -- Node.js 20+ -- An Azure OpenAI resource with a deployed model - -## Run - -```bash -cd typescript -npm install --ignore-scripts -npm run build -AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com AZURE_OPENAI_API_KEY=... node dist/index.js -``` - -### Environment variables - -| Variable | Required | Default | Description | -|---|---|---|---| -| `AZURE_OPENAI_ENDPOINT` | Yes | — | Azure OpenAI resource endpoint URL | -| `AZURE_OPENAI_API_KEY` | Yes | — | Azure OpenAI API key | -| `AZURE_OPENAI_MODEL` | No | `gpt-4.1` | Deployment / model name | -| `AZURE_API_VERSION` | No | `2024-10-21` | Azure OpenAI API version | -| `COPILOT_CLI_PATH` | No | auto-detected | Path to `copilot` binary | - -## Provider configuration - -The key difference from standard OpenAI BYOK is the `azure` block in the provider config: - -```typescript -provider: { - type: "azure", - baseUrl: endpoint, - apiKey, - azure: { - apiVersion: "2024-10-21", - }, -} -``` - -## Verify - -```bash -./verify.sh -``` - -Build checks run by default. E2E run requires `AZURE_OPENAI_ENDPOINT` and `AZURE_OPENAI_API_KEY` to be set. diff --git a/test/scenarios/auth/byok-azure/csharp/Program.cs b/test/scenarios/auth/byok-azure/csharp/Program.cs deleted file mode 100644 index 635404843..000000000 --- a/test/scenarios/auth/byok-azure/csharp/Program.cs +++ /dev/null @@ -1,56 +0,0 @@ -using GitHub.Copilot; - -var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); -var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); -var model = Environment.GetEnvironmentVariable("AZURE_OPENAI_MODEL") ?? "claude-haiku-4.5"; -var apiVersion = Environment.GetEnvironmentVariable("AZURE_API_VERSION") ?? "2024-10-21"; - -if (string.IsNullOrEmpty(endpoint) || string.IsNullOrEmpty(apiKey)) -{ - Console.Error.WriteLine("Required: AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY"); - return 1; -} - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = model, - Provider = new ProviderConfig - { - Type = "azure", - BaseUrl = endpoint, - ApiKey = apiKey, - Azure = new AzureOptions - { - ApiVersion = apiVersion, - }, - }, - AvailableTools = [], - SystemMessage = new SystemMessageConfig - { - Mode = SystemMessageMode.Replace, - Content = "You are a helpful assistant. Answer concisely.", - }, - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} -return 0; - diff --git a/test/scenarios/auth/byok-azure/csharp/csharp.csproj b/test/scenarios/auth/byok-azure/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/auth/byok-azure/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/auth/byok-azure/go/go.mod b/test/scenarios/auth/byok-azure/go/go.mod deleted file mode 100644 index 760cb8f62..000000000 --- a/test/scenarios/auth/byok-azure/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/auth/byok-azure/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/auth/byok-azure/go/go.sum b/test/scenarios/auth/byok-azure/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/auth/byok-azure/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/auth/byok-azure/go/main.go b/test/scenarios/auth/byok-azure/go/main.go deleted file mode 100644 index eea3fb8d6..000000000 --- a/test/scenarios/auth/byok-azure/go/main.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - endpoint := os.Getenv("AZURE_OPENAI_ENDPOINT") - apiKey := os.Getenv("AZURE_OPENAI_API_KEY") - if endpoint == "" || apiKey == "" { - log.Fatal("Required: AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY") - } - - model := os.Getenv("AZURE_OPENAI_MODEL") - if model == "" { - model = "claude-haiku-4.5" - } - - apiVersion := os.Getenv("AZURE_API_VERSION") - if apiVersion == "" { - apiVersion = "2024-10-21" - } - - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: model, - Provider: &copilot.ProviderConfig{ - Type: "azure", - BaseURL: endpoint, - APIKey: apiKey, - Azure: &copilot.AzureProviderOptions{ - APIVersion: apiVersion, - }, - }, - AvailableTools: []string{}, - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: "You are a helpful assistant. Answer concisely.", - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/auth/byok-azure/python/main.py b/test/scenarios/auth/byok-azure/python/main.py deleted file mode 100644 index 031ff47ee..000000000 --- a/test/scenarios/auth/byok-azure/python/main.py +++ /dev/null @@ -1,48 +0,0 @@ -import asyncio -import os -import sys - -from copilot import CopilotClient - -AZURE_OPENAI_ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT") -AZURE_OPENAI_API_KEY = os.environ.get("AZURE_OPENAI_API_KEY") -AZURE_OPENAI_MODEL = os.environ.get("AZURE_OPENAI_MODEL", "claude-haiku-4.5") -AZURE_API_VERSION = os.environ.get("AZURE_API_VERSION", "2024-10-21") - -if not AZURE_OPENAI_ENDPOINT or not AZURE_OPENAI_API_KEY: - print("Required: AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY", file=sys.stderr) - sys.exit(1) - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model=AZURE_OPENAI_MODEL, - provider={ - "type": "azure", - "base_url": AZURE_OPENAI_ENDPOINT, - "api_key": AZURE_OPENAI_API_KEY, - "azure": { - "api_version": AZURE_API_VERSION, - }, - }, - available_tools=[], - system_message={ - "mode": "replace", - "content": "You are a helpful assistant. Answer concisely.", - }, - ) - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/auth/byok-azure/python/requirements.txt b/test/scenarios/auth/byok-azure/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/auth/byok-azure/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/auth/byok-azure/typescript/package.json b/test/scenarios/auth/byok-azure/typescript/package.json deleted file mode 100644 index 2643625fd..000000000 --- a/test/scenarios/auth/byok-azure/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "auth-byok-azure-typescript", - "version": "1.0.0", - "private": true, - "description": "Auth sample — BYOK with Azure OpenAI", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/auth/byok-azure/typescript/src/index.ts b/test/scenarios/auth/byok-azure/typescript/src/index.ts deleted file mode 100644 index 8df0e4de3..000000000 --- a/test/scenarios/auth/byok-azure/typescript/src/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const endpoint = process.env.AZURE_OPENAI_ENDPOINT; - const apiKey = process.env.AZURE_OPENAI_API_KEY; - const model = process.env.AZURE_OPENAI_MODEL || "claude-haiku-4.5"; - - if (!endpoint || !apiKey) { - console.error("Required: AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY"); - process.exit(1); - } - - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model, - provider: { - type: "azure", - baseUrl: endpoint, - apiKey, - azure: { - apiVersion: process.env.AZURE_API_VERSION || "2024-10-21", - }, - }, - availableTools: [], - systemMessage: { - mode: "replace", - content: "You are a helpful assistant. Answer concisely.", - }, - }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/auth/byok-azure/verify.sh b/test/scenarios/auth/byok-azure/verify.sh deleted file mode 100755 index bc43a68db..000000000 --- a/test/scenarios/auth/byok-azure/verify.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying auth/byok-azure" -echo "══════════════════════════════════════" -echo "" - -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - -if [ -n "${AZURE_OPENAI_ENDPOINT:-}" ] && [ -n "${AZURE_OPENAI_API_KEY:-}" ]; then - run_with_timeout "TypeScript (run)" bash -c " - cd '$SCRIPT_DIR/typescript' && \ - output=\$(node dist/index.js 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response\|hello' - " - run_with_timeout "C# (run)" bash -c " - cd '$SCRIPT_DIR/csharp' && \ - output=\$(dotnet run --no-build 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response\|hello' - " -else - echo "⚠️ WARNING: E2E run was SKIPPED — only build was verified, not runtime behavior." - echo " To run fully: set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY." - echo "" -fi - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/auth/byok-ollama/README.md b/test/scenarios/auth/byok-ollama/README.md deleted file mode 100644 index 74d4f237b..000000000 --- a/test/scenarios/auth/byok-ollama/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Auth Sample: BYOK Ollama (Compact Context) - -This sample shows BYOK with **local Ollama** and intentionally trims session context so it works better with smaller local models. - -## What this sample does - -1. Uses a custom provider pointed at Ollama (`http://localhost:11434/v1`) -2. Replaces the default system prompt with a short compact prompt -3. Sets `availableTools: []` to remove built-in tool definitions from model context -4. Sends a prompt and prints the response - -This creates a small assistant profile suitable for constrained context windows. - -## Prerequisites - -- `copilot` binary (`COPILOT_CLI_PATH`, or auto-detected by SDK) -- Node.js 20+ -- Ollama running locally (`ollama serve`) -- A local model pulled (for example: `ollama pull llama3.2:3b`) - -## Run - -```bash -cd typescript -npm install --ignore-scripts -npm run build -node dist/index.js -``` - -Optional environment variables: - -- `OLLAMA_BASE_URL` (default: `http://localhost:11434/v1`) -- `OLLAMA_MODEL` (default: `llama3.2:3b`) - -## Verify - -```bash -./verify.sh -``` - -Build checks run by default. E2E run is optional and requires `BYOK_SAMPLE_RUN_E2E=1`. diff --git a/test/scenarios/auth/byok-ollama/csharp/Program.cs b/test/scenarios/auth/byok-ollama/csharp/Program.cs deleted file mode 100644 index 62b000af1..000000000 --- a/test/scenarios/auth/byok-ollama/csharp/Program.cs +++ /dev/null @@ -1,44 +0,0 @@ -using GitHub.Copilot; - -var baseUrl = Environment.GetEnvironmentVariable("OLLAMA_BASE_URL") ?? "http://localhost:11434/v1"; -var model = Environment.GetEnvironmentVariable("OLLAMA_MODEL") ?? "llama3.2:3b"; - -var compactSystemPrompt = - "You are a compact local assistant. Keep answers short, concrete, and under 80 words."; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = model, - Provider = new ProviderConfig - { - Type = "openai", - BaseUrl = baseUrl, - }, - AvailableTools = [], - SystemMessage = new SystemMessageConfig - { - Mode = SystemMessageMode.Replace, - Content = compactSystemPrompt, - }, - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/auth/byok-ollama/csharp/csharp.csproj b/test/scenarios/auth/byok-ollama/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/auth/byok-ollama/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/auth/byok-ollama/go/go.mod b/test/scenarios/auth/byok-ollama/go/go.mod deleted file mode 100644 index dfa1f94bc..000000000 --- a/test/scenarios/auth/byok-ollama/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/auth/byok-ollama/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/auth/byok-ollama/go/go.sum b/test/scenarios/auth/byok-ollama/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/auth/byok-ollama/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/auth/byok-ollama/go/main.go b/test/scenarios/auth/byok-ollama/go/main.go deleted file mode 100644 index c776da27b..000000000 --- a/test/scenarios/auth/byok-ollama/go/main.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - - copilot "github.com/github/copilot-sdk/go" -) - -const compactSystemPrompt = "You are a compact local assistant. Keep answers short, concrete, and under 80 words." - -func main() { - baseUrl := os.Getenv("OLLAMA_BASE_URL") - if baseUrl == "" { - baseUrl = "http://localhost:11434/v1" - } - - model := os.Getenv("OLLAMA_MODEL") - if model == "" { - model = "llama3.2:3b" - } - - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: model, - Provider: &copilot.ProviderConfig{ - Type: "openai", - BaseURL: baseUrl, - }, - AvailableTools: []string{}, - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: compactSystemPrompt, - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/auth/byok-ollama/python/main.py b/test/scenarios/auth/byok-ollama/python/main.py deleted file mode 100644 index 90c4838f8..000000000 --- a/test/scenarios/auth/byok-ollama/python/main.py +++ /dev/null @@ -1,41 +0,0 @@ -import asyncio -import os - -from copilot import CopilotClient - -OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434/v1") -OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "llama3.2:3b") - -COMPACT_SYSTEM_PROMPT = ( - "You are a compact local assistant. Keep answers short, concrete, and under 80 words." -) - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model=OLLAMA_MODEL, - provider={ - "type": "openai", - "base_url": OLLAMA_BASE_URL, - }, - available_tools=[], - system_message={ - "mode": "replace", - "content": COMPACT_SYSTEM_PROMPT, - }, - ) - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/auth/byok-ollama/python/requirements.txt b/test/scenarios/auth/byok-ollama/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/auth/byok-ollama/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/auth/byok-ollama/typescript/package.json b/test/scenarios/auth/byok-ollama/typescript/package.json deleted file mode 100644 index e6ed3752d..000000000 --- a/test/scenarios/auth/byok-ollama/typescript/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "auth-byok-ollama-typescript", - "version": "1.0.0", - "private": true, - "description": "BYOK Ollama sample with compact context settings", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0", - "typescript": "^5.5.0" - } -} diff --git a/test/scenarios/auth/byok-ollama/typescript/src/index.ts b/test/scenarios/auth/byok-ollama/typescript/src/index.ts deleted file mode 100644 index af2d71a44..000000000 --- a/test/scenarios/auth/byok-ollama/typescript/src/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -const OLLAMA_BASE_URL = - process.env.OLLAMA_BASE_URL ?? "http://localhost:11434/v1"; -const OLLAMA_MODEL = process.env.OLLAMA_MODEL ?? "llama3.2:3b"; - -const COMPACT_SYSTEM_PROMPT = - "You are a compact local assistant. Keep answers short, concrete, and under 80 words."; - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: OLLAMA_MODEL, - provider: { - type: "openai", - baseUrl: OLLAMA_BASE_URL, - }, - // Use a compact replacement prompt and no tools to minimize request context. - systemMessage: { mode: "replace", content: COMPACT_SYSTEM_PROMPT }, - availableTools: [], - }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/auth/byok-ollama/verify.sh b/test/scenarios/auth/byok-ollama/verify.sh deleted file mode 100755 index c9a132a93..000000000 --- a/test/scenarios/auth/byok-ollama/verify.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying auth/byok-ollama" -echo "══════════════════════════════════════" -echo "" - -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - -if [ "${BYOK_SAMPLE_RUN_E2E:-}" = "1" ]; then - run_with_timeout "TypeScript (run)" bash -c " - cd '$SCRIPT_DIR/typescript' && \ - output=\$(node dist/index.js 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response\|hello' - " - run_with_timeout "C# (run)" bash -c " - cd '$SCRIPT_DIR/csharp' && \ - output=\$(dotnet run --no-build 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response\|hello' - " -else - echo "⚠️ WARNING: E2E run was SKIPPED — only build was verified, not runtime behavior." - echo " To run fully: set BYOK_SAMPLE_RUN_E2E=1 (and ensure Ollama is running)." - echo "" -fi - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/auth/byok-openai/README.md b/test/scenarios/auth/byok-openai/README.md deleted file mode 100644 index ace65cace..000000000 --- a/test/scenarios/auth/byok-openai/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Auth Sample: BYOK OpenAI - -This sample shows how to use Copilot SDK in **BYOK** mode with an OpenAI-compatible provider. - -## What this sample does - -1. Creates a session with a custom provider (`type: "openai"`) -2. Uses your `OPENAI_API_KEY` instead of GitHub auth -3. Sends a prompt and prints the response - -## Prerequisites - -- `copilot` binary (`COPILOT_CLI_PATH`, or auto-detected by SDK) -- Node.js 20+ -- `OPENAI_API_KEY` - -## Run - -```bash -cd typescript -npm install --ignore-scripts -npm run build -OPENAI_API_KEY=sk-... node dist/index.js -``` - -Optional environment variables: - -- `OPENAI_BASE_URL` (default: `https://api.openai.com/v1`) -- `OPENAI_MODEL` (default: `gpt-4.1-mini`) - -## Verify - -```bash -./verify.sh -``` - -Build checks run by default. E2E run is optional and requires both `BYOK_SAMPLE_RUN_E2E=1` and `OPENAI_API_KEY`. diff --git a/test/scenarios/auth/byok-openai/csharp/Program.cs b/test/scenarios/auth/byok-openai/csharp/Program.cs deleted file mode 100644 index 826e35443..000000000 --- a/test/scenarios/auth/byok-openai/csharp/Program.cs +++ /dev/null @@ -1,45 +0,0 @@ -using GitHub.Copilot; - -var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); -var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "claude-haiku-4.5"; -var baseUrl = Environment.GetEnvironmentVariable("OPENAI_BASE_URL") ?? "https://api.openai.com/v1"; - -if (string.IsNullOrEmpty(apiKey)) -{ - Console.Error.WriteLine("Missing OPENAI_API_KEY."); - return 1; -} - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = model, - Provider = new ProviderConfig - { - Type = "openai", - BaseUrl = baseUrl, - ApiKey = apiKey, - }, - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} -return 0; - diff --git a/test/scenarios/auth/byok-openai/csharp/csharp.csproj b/test/scenarios/auth/byok-openai/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/auth/byok-openai/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/auth/byok-openai/go/go.mod b/test/scenarios/auth/byok-openai/go/go.mod deleted file mode 100644 index 7c9eff1e5..000000000 --- a/test/scenarios/auth/byok-openai/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/auth/byok-openai/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/auth/byok-openai/go/go.sum b/test/scenarios/auth/byok-openai/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/auth/byok-openai/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/auth/byok-openai/go/main.go b/test/scenarios/auth/byok-openai/go/main.go deleted file mode 100644 index d3221523d..000000000 --- a/test/scenarios/auth/byok-openai/go/main.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - apiKey := os.Getenv("OPENAI_API_KEY") - if apiKey == "" { - log.Fatal("Missing OPENAI_API_KEY.") - } - - baseUrl := os.Getenv("OPENAI_BASE_URL") - if baseUrl == "" { - baseUrl = "https://api.openai.com/v1" - } - - model := os.Getenv("OPENAI_MODEL") - if model == "" { - model = "claude-haiku-4.5" - } - - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: model, - Provider: &copilot.ProviderConfig{ - Type: "openai", - BaseURL: baseUrl, - APIKey: apiKey, - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/auth/byok-openai/python/main.py b/test/scenarios/auth/byok-openai/python/main.py deleted file mode 100644 index e9c673aa0..000000000 --- a/test/scenarios/auth/byok-openai/python/main.py +++ /dev/null @@ -1,39 +0,0 @@ -import asyncio -import os -import sys - -from copilot import CopilotClient - -OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1") -OPENAI_MODEL = os.environ.get("OPENAI_MODEL", "claude-haiku-4.5") -OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") - -if not OPENAI_API_KEY: - print("Missing OPENAI_API_KEY.", file=sys.stderr) - sys.exit(1) - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model=OPENAI_MODEL, - provider={ - "type": "openai", - "base_url": OPENAI_BASE_URL, - "api_key": OPENAI_API_KEY, - }, - ) - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/auth/byok-openai/python/requirements.txt b/test/scenarios/auth/byok-openai/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/auth/byok-openai/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/auth/byok-openai/typescript/package.json b/test/scenarios/auth/byok-openai/typescript/package.json deleted file mode 100644 index ecfaae878..000000000 --- a/test/scenarios/auth/byok-openai/typescript/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "auth-byok-openai-typescript", - "version": "1.0.0", - "private": true, - "description": "BYOK OpenAI provider sample for Copilot SDK", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0", - "typescript": "^5.5.0" - } -} diff --git a/test/scenarios/auth/byok-openai/typescript/src/index.ts b/test/scenarios/auth/byok-openai/typescript/src/index.ts deleted file mode 100644 index 268f1d201..000000000 --- a/test/scenarios/auth/byok-openai/typescript/src/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -const OPENAI_BASE_URL = - process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1"; -const OPENAI_MODEL = process.env.OPENAI_MODEL ?? "claude-haiku-4.5"; -const OPENAI_API_KEY = process.env.OPENAI_API_KEY; - -if (!OPENAI_API_KEY) { - console.error("Missing OPENAI_API_KEY."); - process.exit(1); -} - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: OPENAI_MODEL, - provider: { - type: "openai", - baseUrl: OPENAI_BASE_URL, - apiKey: OPENAI_API_KEY, - }, - }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/auth/byok-openai/verify.sh b/test/scenarios/auth/byok-openai/verify.sh deleted file mode 100755 index 1fa205e2b..000000000 --- a/test/scenarios/auth/byok-openai/verify.sh +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying auth/byok-openai" -echo "══════════════════════════════════════" -echo "" - -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o byok-openai-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - -if [ "${BYOK_SAMPLE_RUN_E2E:-}" = "1" ] && [ -n "${OPENAI_API_KEY:-}" ]; then - run_with_timeout "TypeScript (run)" bash -c " - cd '$SCRIPT_DIR/typescript' && \ - output=\$(node dist/index.js 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response\|hello' - " - run_with_timeout "Python (run)" bash -c " - cd '$SCRIPT_DIR/python' && \ - output=\$(python3 main.py 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response\|hello' - " - run_with_timeout "Go (run)" bash -c " - cd '$SCRIPT_DIR/go' && \ - output=\$(./byok-openai-go 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response\|hello' - " - run_with_timeout "C# (run)" bash -c " - cd '$SCRIPT_DIR/csharp' && \ - output=\$(dotnet run --no-build 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response\|hello' - " -else - echo "⚠️ WARNING: E2E run was SKIPPED — only build was verified, not runtime behavior." - echo " To run fully: set BYOK_SAMPLE_RUN_E2E=1 and OPENAI_API_KEY." - echo "" -fi - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/auth/gh-app/README.md b/test/scenarios/auth/gh-app/README.md deleted file mode 100644 index 0b1bf4f1f..000000000 --- a/test/scenarios/auth/gh-app/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Auth Sample: GitHub OAuth App (Scenario 1) - -This scenario demonstrates how a packaged app can let end users sign in with GitHub using OAuth Device Flow, then use that user token to call Copilot with their own subscription. - -## What this sample does - -1. Starts GitHub OAuth Device Flow -2. Prompts the user to open the verification URL and enter the code -3. Polls for the access token -4. Fetches the signed-in user profile -5. Calls Copilot with that OAuth token (SDK clients in TypeScript/Python/Go) - -## Prerequisites - -- A GitHub OAuth App client ID (`GITHUB_OAUTH_CLIENT_ID`) -- `copilot` binary (`COPILOT_CLI_PATH`, or auto-detected by SDK) -- Node.js 20+ -- Python 3.10+ -- Go 1.24+ - -## Run - -### TypeScript - -```bash -cd typescript -npm install --ignore-scripts -npm run build -GITHUB_OAUTH_CLIENT_ID=Ivxxxxxxxxxxxx node dist/index.js -``` - -### Python - -```bash -cd python -pip3 install -r requirements.txt --quiet -GITHUB_OAUTH_CLIENT_ID=Ivxxxxxxxxxxxx python3 main.py -``` - -### Go - -```bash -cd go -go run main.go -``` - -## Verify - -```bash -./verify.sh -``` - -`verify.sh` checks install/build for all languages. Interactive runs are skipped by default and can be enabled by setting both `GITHUB_OAUTH_CLIENT_ID` and `AUTH_SAMPLE_RUN_INTERACTIVE=1`. - -To include this sample in the full suite, run `./verify.sh` from the `samples/` root. diff --git a/test/scenarios/auth/gh-app/csharp/Program.cs b/test/scenarios/auth/gh-app/csharp/Program.cs deleted file mode 100644 index f16c8236e..000000000 --- a/test/scenarios/auth/gh-app/csharp/Program.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Net.Http.Json; -using System.Text.Json; -using GitHub.Copilot; - -// GitHub OAuth Device Flow -var clientId = Environment.GetEnvironmentVariable("GITHUB_OAUTH_CLIENT_ID") - ?? throw new InvalidOperationException("Missing GITHUB_OAUTH_CLIENT_ID"); - -var httpClient = new HttpClient(); -httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); -httpClient.DefaultRequestHeaders.Add("User-Agent", "copilot-sdk-csharp"); - -// Step 1: Request device code -var deviceCodeResponse = await httpClient.PostAsync( - "https://github.com/login/device/code", - new FormUrlEncodedContent(new Dictionary { { "client_id", clientId } })); -var deviceCode = await deviceCodeResponse.Content.ReadFromJsonAsync(); - -var userCode = deviceCode.GetProperty("user_code").GetString(); -var verificationUri = deviceCode.GetProperty("verification_uri").GetString(); -var code = deviceCode.GetProperty("device_code").GetString(); -var interval = deviceCode.GetProperty("interval").GetInt32(); - -Console.WriteLine($"Please visit: {verificationUri}"); -Console.WriteLine($"Enter code: {userCode}"); - -// Step 2: Poll for access token -string? accessToken = null; -while (accessToken == null) -{ - await Task.Delay(interval * 1000); - var tokenResponse = await httpClient.PostAsync( - "https://github.com/login/oauth/access_token", - new FormUrlEncodedContent(new Dictionary - { - { "client_id", clientId }, - { "device_code", code! }, - { "grant_type", "urn:ietf:params:oauth:grant-type:device_code" }, - })); - var tokenData = await tokenResponse.Content.ReadFromJsonAsync(); - - if (tokenData.TryGetProperty("access_token", out var token)) - { - accessToken = token.GetString(); - } - else if (tokenData.TryGetProperty("error", out var error)) - { - var err = error.GetString(); - if (err == "authorization_pending") continue; - if (err == "slow_down") { interval += 5; continue; } - throw new Exception($"OAuth error: {err}"); - } -} - -// Step 3: Verify authentication -httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}"); -var userResponse = await httpClient.GetFromJsonAsync("https://api.github.com/user"); -Console.WriteLine($"Authenticated as: {userResponse.GetProperty("login").GetString()}"); - -// Step 4: Use the token with Copilot -using var client = new CopilotClient(new CopilotClientOptions -{ - GitHubToken = accessToken, -}); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/auth/gh-app/csharp/csharp.csproj b/test/scenarios/auth/gh-app/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/auth/gh-app/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/auth/gh-app/go/go.mod b/test/scenarios/auth/gh-app/go/go.mod deleted file mode 100644 index 13caa4a2d..000000000 --- a/test/scenarios/auth/gh-app/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/auth/gh-app/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/auth/gh-app/go/go.sum b/test/scenarios/auth/gh-app/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/auth/gh-app/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/auth/gh-app/go/main.go b/test/scenarios/auth/gh-app/go/main.go deleted file mode 100644 index 5f774ef2c..000000000 --- a/test/scenarios/auth/gh-app/go/main.go +++ /dev/null @@ -1,193 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "os" - "time" - - copilot "github.com/github/copilot-sdk/go" -) - -const ( - deviceCodeURL = "https://github.com/login/device/code" - accessTokenURL = "https://github.com/login/oauth/access_token" - userURL = "https://api.github.com/user" -) - -type deviceCodeResponse struct { - DeviceCode string `json:"device_code"` - UserCode string `json:"user_code"` - VerificationURI string `json:"verification_uri"` - Interval int `json:"interval"` -} - -type tokenResponse struct { - AccessToken string `json:"access_token"` - Error string `json:"error"` - ErrorDescription string `json:"error_description"` - Interval int `json:"interval"` -} - -type githubUser struct { - Login string `json:"login"` - Name string `json:"name"` -} - -func postJSON(url string, payload any, target any) error { - body, err := json.Marshal(payload) - if err != nil { - return err - } - req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) - if err != nil { - return err - } - req.Header.Set("Accept", "application/json") - req.Header.Set("Content-Type", "application/json") - resp, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - if resp.StatusCode < 200 || resp.StatusCode > 299 { - responseBody, _ := io.ReadAll(resp.Body) - return fmt.Errorf("request failed: %s %s", resp.Status, string(responseBody)) - } - return json.NewDecoder(resp.Body).Decode(target) -} - -func getUser(token string) (*githubUser, error) { - req, err := http.NewRequest(http.MethodGet, userURL, nil) - if err != nil { - return nil, err - } - req.Header.Set("Accept", "application/json") - req.Header.Set("Authorization", "Bearer "+token) - req.Header.Set("User-Agent", "copilot-sdk-samples-auth-gh-app") - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode < 200 || resp.StatusCode > 299 { - responseBody, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("github API failed: %s %s", resp.Status, string(responseBody)) - } - var user githubUser - if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { - return nil, err - } - return &user, nil -} - -func startDeviceFlow(clientID string) (*deviceCodeResponse, error) { - var resp deviceCodeResponse - err := postJSON(deviceCodeURL, map[string]any{ - "client_id": clientID, - "scope": "read:user", - }, &resp) - return &resp, err -} - -func pollForToken(clientID, deviceCode string, interval int) (string, error) { - delaySeconds := interval - for { - time.Sleep(time.Duration(delaySeconds) * time.Second) - var resp tokenResponse - if err := postJSON(accessTokenURL, map[string]any{ - "client_id": clientID, - "device_code": deviceCode, - "grant_type": "urn:ietf:params:oauth:grant-type:device_code", - }, &resp); err != nil { - return "", err - } - if resp.AccessToken != "" { - return resp.AccessToken, nil - } - if resp.Error == "authorization_pending" { - continue - } - if resp.Error == "slow_down" { - if resp.Interval > 0 { - delaySeconds = resp.Interval - } else { - delaySeconds += 5 - } - continue - } - if resp.ErrorDescription != "" { - return "", fmt.Errorf(resp.ErrorDescription) - } - if resp.Error != "" { - return "", fmt.Errorf(resp.Error) - } - return "", fmt.Errorf("OAuth polling failed") - } -} - -func main() { - clientID := os.Getenv("GITHUB_OAUTH_CLIENT_ID") - if clientID == "" { - log.Fatal("Missing GITHUB_OAUTH_CLIENT_ID") - } - - fmt.Println("Starting GitHub OAuth device flow...") - device, err := startDeviceFlow(clientID) - if err != nil { - log.Fatal(err) - } - fmt.Printf("Open %s and enter code: %s\n", device.VerificationURI, device.UserCode) - fmt.Print("Press Enter after you authorize this app...") - fmt.Scanln() - - token, err := pollForToken(clientID, device.DeviceCode, device.Interval) - if err != nil { - log.Fatal(err) - } - - user, err := getUser(token) - if err != nil { - log.Fatal(err) - } - if user.Name != "" { - fmt.Printf("Authenticated as: %s (%s)\n", user.Login, user.Name) - } else { - fmt.Printf("Authenticated as: %s\n", user.Login) - } - - client := copilot.NewClient(&copilot.ClientOptions{ - GitHubToken: token, - }) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/auth/gh-app/python/main.py b/test/scenarios/auth/gh-app/python/main.py deleted file mode 100644 index 0d5a5ee9d..000000000 --- a/test/scenarios/auth/gh-app/python/main.py +++ /dev/null @@ -1,95 +0,0 @@ -import asyncio -import json -import os -import time -import urllib.request - -from copilot import CopilotClient - -DEVICE_CODE_URL = "https://github.com/login/device/code" -ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token" -USER_URL = "https://api.github.com/user" - - -def post_json(url: str, payload: dict) -> dict: - req = urllib.request.Request( - url=url, - data=json.dumps(payload).encode("utf-8"), - headers={"Accept": "application/json", "Content-Type": "application/json"}, - method="POST", - ) - with urllib.request.urlopen(req) as response: - return json.loads(response.read().decode("utf-8")) - - -def get_json(url: str, token: str) -> dict: - req = urllib.request.Request( - url=url, - headers={ - "Accept": "application/json", - "Authorization": f"Bearer {token}", - "User-Agent": "copilot-sdk-samples-auth-gh-app", - }, - method="GET", - ) - with urllib.request.urlopen(req) as response: - return json.loads(response.read().decode("utf-8")) - - -def start_device_flow(client_id: str) -> dict: - return post_json(DEVICE_CODE_URL, {"client_id": client_id, "scope": "read:user"}) - - -def poll_for_access_token(client_id: str, device_code: str, interval: int) -> str: - delay_seconds = interval - while True: - time.sleep(delay_seconds) - data = post_json( - ACCESS_TOKEN_URL, - { - "client_id": client_id, - "device_code": device_code, - "grant_type": "urn:ietf:params:oauth:grant-type:device_code", - }, - ) - if data.get("access_token"): - return data["access_token"] - if data.get("error") == "authorization_pending": - continue - if data.get("error") == "slow_down": - delay_seconds = int(data.get("interval", delay_seconds + 5)) - continue - raise RuntimeError( - data.get("error_description") or data.get("error") or "OAuth polling failed" - ) - - -async def main(): - client_id = os.environ.get("GITHUB_OAUTH_CLIENT_ID") - if not client_id: - raise RuntimeError("Missing GITHUB_OAUTH_CLIENT_ID") - - print("Starting GitHub OAuth device flow...") - device = start_device_flow(client_id) - print(f"Open {device['verification_uri']} and enter code: {device['user_code']}") - input("Press Enter after you authorize this app...") - - token = poll_for_access_token(client_id, device["device_code"], int(device["interval"])) - user = get_json(USER_URL, token) - display_name = f" ({user.get('name')})" if user.get("name") else "" - print(f"Authenticated as: {user.get('login')}{display_name}") - - client = CopilotClient(github_token=token) - - try: - session = await client.create_session(model="claude-haiku-4.5") - response = await session.send_and_wait("What is the capital of France?") - if response: - print(response.data.content) - await session.disconnect() - finally: - await client.stop() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/test/scenarios/auth/gh-app/python/requirements.txt b/test/scenarios/auth/gh-app/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/auth/gh-app/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/auth/gh-app/typescript/package.json b/test/scenarios/auth/gh-app/typescript/package.json deleted file mode 100644 index 1cdcd9602..000000000 --- a/test/scenarios/auth/gh-app/typescript/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "auth-gh-app-typescript", - "version": "1.0.0", - "private": true, - "description": "GitHub OAuth App device flow sample for Copilot SDK", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0", - "typescript": "^5.5.0" - } -} diff --git a/test/scenarios/auth/gh-app/typescript/src/index.ts b/test/scenarios/auth/gh-app/typescript/src/index.ts deleted file mode 100644 index b76fdc0a2..000000000 --- a/test/scenarios/auth/gh-app/typescript/src/index.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; -import readline from "node:readline/promises"; -import { stdin as input, stdout as output } from "node:process"; - -type DeviceCodeResponse = { - device_code: string; - user_code: string; - verification_uri: string; - expires_in: number; - interval: number; -}; - -type OAuthTokenResponse = { - access_token?: string; - error?: string; - error_description?: string; - interval?: number; -}; - -type GitHubUser = { - login: string; - name: string | null; -}; - -const DEVICE_CODE_URL = "https://github.com/login/device/code"; -const ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"; -const USER_URL = "https://api.github.com/user"; - -const CLIENT_ID = process.env.GITHUB_OAUTH_CLIENT_ID; - -if (!CLIENT_ID) { - console.error("Missing GITHUB_OAUTH_CLIENT_ID."); - process.exit(1); -} - -async function postJson( - url: string, - body: Record, -): Promise { - const response = await fetch(url, { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify(body), - }); - - if (!response.ok) { - throw new Error( - `Request failed: ${response.status} ${response.statusText}`, - ); - } - - return (await response.json()) as T; -} - -async function getJson(url: string, token: string): Promise { - const response = await fetch(url, { - headers: { - Accept: "application/json", - Authorization: `Bearer ${token}`, - "User-Agent": "copilot-sdk-samples-auth-gh-app", - }, - }); - - if (!response.ok) { - throw new Error( - `GitHub API failed: ${response.status} ${response.statusText}`, - ); - } - - return (await response.json()) as T; -} - -async function startDeviceFlow(): Promise { - return postJson(DEVICE_CODE_URL, { - client_id: CLIENT_ID, - scope: "read:user", - }); -} - -async function pollForAccessToken( - deviceCode: string, - intervalSeconds: number, -): Promise { - let interval = intervalSeconds; - - while (true) { - await new Promise((resolve) => setTimeout(resolve, interval * 1000)); - - const data = await postJson(ACCESS_TOKEN_URL, { - client_id: CLIENT_ID, - device_code: deviceCode, - grant_type: "urn:ietf:params:oauth:grant-type:device_code", - }); - - if (data.access_token) return data.access_token; - if (data.error === "authorization_pending") continue; - if (data.error === "slow_down") { - interval = data.interval ?? interval + 5; - continue; - } - - throw new Error( - data.error_description ?? data.error ?? "OAuth token polling failed", - ); - } -} - -async function main() { - console.log("Starting GitHub OAuth device flow..."); - const device = await startDeviceFlow(); - - console.log( - `Open ${device.verification_uri} and enter code: ${device.user_code}`, - ); - const rl = readline.createInterface({ input, output }); - await rl.question("Press Enter after you authorize this app..."); - rl.close(); - - const accessToken = await pollForAccessToken( - device.device_code, - device.interval, - ); - const user = await getJson(USER_URL, accessToken); - console.log( - `Authenticated as: ${user.login}${user.name ? ` (${user.name})` : ""}`, - ); - - const client = new CopilotClient({ - gitHubToken: accessToken, - }); - - try { - const session = await client.createSession({ model: "claude-haiku-4.5" }); - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response) console.log(response.data.content); - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((error) => { - console.error(error); - process.exit(1); -}); diff --git a/test/scenarios/auth/gh-app/verify.sh b/test/scenarios/auth/gh-app/verify.sh deleted file mode 100755 index 5d2ae20c0..000000000 --- a/test/scenarios/auth/gh-app/verify.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=180 - -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying auth/gh-app scenario 1" -echo "══════════════════════════════════════" -echo "" - -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go mod tidy && go build -o gh-app-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - -if [ -n "${GITHUB_OAUTH_CLIENT_ID:-}" ] && [ "${AUTH_SAMPLE_RUN_INTERACTIVE:-}" = "1" ]; then - run_with_timeout "TypeScript (run)" bash -c " - cd '$SCRIPT_DIR/typescript' && \ - output=\$(printf '\\n' | node dist/index.js 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'device\|code\|http\|login\|verify\|oauth\|github' - " - run_with_timeout "Python (run)" bash -c " - cd '$SCRIPT_DIR/python' && \ - output=\$(printf '\\n' | python3 main.py 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'device\|code\|http\|login\|verify\|oauth\|github' - " - run_with_timeout "Go (run)" bash -c " - cd '$SCRIPT_DIR/go' && \ - output=\$(printf '\\n' | ./gh-app-go 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'device\|code\|http\|login\|verify\|oauth\|github' - " - run_with_timeout "C# (run)" bash -c " - cd '$SCRIPT_DIR/csharp' && \ - output=\$(printf '\\n' | dotnet run --no-build 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'device\|code\|http\|login\|verify\|oauth\|github' - " -else - echo "⚠️ WARNING: E2E run was SKIPPED — only build was verified, not runtime behavior." - echo " To run fully: set GITHUB_OAUTH_CLIENT_ID and AUTH_SAMPLE_RUN_INTERACTIVE=1." - echo "" -fi - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/bundling/app-backend-to-server/README.md b/test/scenarios/bundling/app-backend-to-server/README.md deleted file mode 100644 index dd4e4b7f6..000000000 --- a/test/scenarios/bundling/app-backend-to-server/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# App-Backend-to-Server Samples - -Samples that demonstrate the **app-backend-to-server** deployment architecture of the Copilot SDK. In this scenario a web backend connects to a **pre-running** `copilot` TCP server and exposes a `POST /chat` HTTP endpoint. The HTTP server receives a prompt from the client, forwards it to Copilot CLI, and returns the response. - -``` -┌────────┐ HTTP POST /chat ┌─────────────┐ TCP (JSON-RPC) ┌──────────────┐ -│ Client │ ──────────────────▶ │ Web Backend │ ─────────────────▶ │ Copilot CLI │ -│ (curl) │ ◀────────────────── │ (HTTP server)│ ◀───────────────── │ (TCP server) │ -└────────┘ └─────────────┘ └──────────────┘ -``` - -Each sample follows the same flow: - -1. **Start** an HTTP server with a `POST /chat` endpoint -2. **Receive** a JSON request `{ "prompt": "..." }` -3. **Connect** to a running `copilot` server via TCP -4. **Open a session** targeting the `gpt-4.1` model -5. **Forward the prompt** and collect the response -6. **Return** a JSON response `{ "response": "..." }` - -## Languages - -| Directory | SDK / Approach | Language | HTTP Framework | -|-----------|---------------|----------|----------------| -| `typescript/` | `@github/copilot-sdk` | TypeScript (Node.js) | Express | -| `python/` | `github-copilot-sdk` | Python | Flask | -| `go/` | `github.com/github/copilot-sdk/go` | Go | net/http | - -## Prerequisites - -- **Copilot CLI** — set `COPILOT_CLI_PATH` -- **Authentication** — set `GITHUB_TOKEN`, or run `gh auth login` -- **Node.js 20+** (TypeScript sample) -- **Python 3.10+** (Python sample) -- **Go 1.24+** (Go sample) - -## Starting the Server - -Start `copilot` as a TCP server before running any sample: - -```bash -copilot --port 3000 --headless --auth-token-env GITHUB_TOKEN -``` - -## Quick Start - -**TypeScript** -```bash -cd typescript -npm install && npm run build -CLI_URL=localhost:3000 npm start -# In another terminal: -curl -X POST http://localhost:8080/chat \ - -H "Content-Type: application/json" \ - -d '{"prompt": "What is the capital of France?"}' -``` - -**Python** -```bash -cd python -pip install -r requirements.txt -CLI_URL=localhost:3000 python main.py -# In another terminal: -curl -X POST http://localhost:8080/chat \ - -H "Content-Type: application/json" \ - -d '{"prompt": "What is the capital of France?"}' -``` - -**Go** -```bash -cd go -CLI_URL=localhost:3000 go run main.go -# In another terminal: -curl -X POST http://localhost:8080/chat \ - -H "Content-Type: application/json" \ - -d '{"prompt": "What is the capital of France?"}' -``` - -All samples default to `localhost:3000` for the Copilot CLI and port `8080` for the HTTP server. Override with `CLI_URL` (or `COPILOT_CLI_URL`) and `PORT` environment variables: - -```bash -CLI_URL=localhost:4000 PORT=9090 npm start -``` - -## Verification - -A script is included that starts the server, builds, and end-to-end tests every sample: - -```bash -./verify.sh -``` - -It runs in three phases: - -1. **Server** — starts `copilot` on a random port -2. **Build** — installs dependencies and compiles each sample -3. **E2E Run** — starts each HTTP server, sends a `POST /chat` request via curl, and verifies it returns a response - -The server is automatically stopped when the script exits. diff --git a/test/scenarios/bundling/app-backend-to-server/csharp/Program.cs b/test/scenarios/bundling/app-backend-to-server/csharp/Program.cs deleted file mode 100644 index f6e993f0c..000000000 --- a/test/scenarios/bundling/app-backend-to-server/csharp/Program.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Text.Json; -using GitHub.Copilot; - -var port = Environment.GetEnvironmentVariable("PORT") ?? "8080"; -var cliUrl = Environment.GetEnvironmentVariable("CLI_URL") - ?? Environment.GetEnvironmentVariable("COPILOT_CLI_URL") - ?? "localhost:3000"; - -var builder = WebApplication.CreateBuilder(args); -builder.WebHost.UseUrls($"http://0.0.0.0:{port}"); -var app = builder.Build(); - -app.MapPost("/chat", async (HttpContext ctx) => -{ - var body = await JsonSerializer.DeserializeAsync(ctx.Request.Body); - var prompt = body.TryGetProperty("prompt", out var p) ? p.GetString() : null; - if (string.IsNullOrEmpty(prompt)) - { - ctx.Response.StatusCode = 400; - await ctx.Response.WriteAsJsonAsync(new { error = "Missing 'prompt' in request body" }); - return; - } - - using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl) }); - await client.StartAsync(); - - try - { - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = prompt, - }); - - if (response?.Data?.Content != null) - { - await ctx.Response.WriteAsJsonAsync(new { response = response.Data.Content }); - } - else - { - ctx.Response.StatusCode = 502; - await ctx.Response.WriteAsJsonAsync(new { error = "No response content from Copilot CLI" }); - } - } - finally - { - await client.StopAsync(); - } -}); - -Console.WriteLine($"Listening on port {port}"); -app.Run(); diff --git a/test/scenarios/bundling/app-backend-to-server/csharp/csharp.csproj b/test/scenarios/bundling/app-backend-to-server/csharp/csharp.csproj deleted file mode 100644 index b62a989b3..000000000 --- a/test/scenarios/bundling/app-backend-to-server/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/bundling/app-backend-to-server/go/go.mod b/test/scenarios/bundling/app-backend-to-server/go/go.mod deleted file mode 100644 index 2afb521a3..000000000 --- a/test/scenarios/bundling/app-backend-to-server/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/bundling/app-backend-to-server/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/bundling/app-backend-to-server/go/go.sum b/test/scenarios/bundling/app-backend-to-server/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/bundling/app-backend-to-server/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/bundling/app-backend-to-server/go/main.go b/test/scenarios/bundling/app-backend-to-server/go/main.go deleted file mode 100644 index 6e0bc7f30..000000000 --- a/test/scenarios/bundling/app-backend-to-server/go/main.go +++ /dev/null @@ -1,139 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "io" - "log" - "net" - "net/http" - "os" - "strings" - "time" - - copilot "github.com/github/copilot-sdk/go" -) - -func cliURL() string { - if u := os.Getenv("CLI_URL"); u != "" { - return u - } - if u := os.Getenv("COPILOT_CLI_URL"); u != "" { - return u - } - return "localhost:3000" -} - -type chatRequest struct { - Prompt string `json:"prompt"` -} - -type chatResponse struct { - Response string `json:"response,omitempty"` - Error string `json:"error,omitempty"` -} - -func chatHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - body, err := io.ReadAll(r.Body) - if err != nil { - writeJSON(w, http.StatusBadRequest, chatResponse{Error: "Failed to read body"}) - return - } - - var req chatRequest - if err := json.Unmarshal(body, &req); err != nil || req.Prompt == "" { - writeJSON(w, http.StatusBadRequest, chatResponse{Error: "Missing 'prompt' in request body"}) - return - } - - client := copilot.NewClient(&copilot.ClientOptions{ - Connection: copilot.UriConnection{URL: cliURL()}, - }) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - writeJSON(w, http.StatusInternalServerError, chatResponse{Error: err.Error()}) - return - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - }) - if err != nil { - writeJSON(w, http.StatusInternalServerError, chatResponse{Error: err.Error()}) - return - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: req.Prompt, - }) - if err != nil { - writeJSON(w, http.StatusInternalServerError, chatResponse{Error: err.Error()}) - return - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - writeJSON(w, http.StatusOK, chatResponse{Response: d.Content}) - } else { - writeJSON(w, http.StatusBadGateway, chatResponse{Error: "No response content from Copilot CLI"}) - } - } else { - writeJSON(w, http.StatusBadGateway, chatResponse{Error: "No response content from Copilot CLI"}) - } -} - -func writeJSON(w http.ResponseWriter, status int, v interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - json.NewEncoder(w).Encode(v) -} - -func main() { - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - - mux := http.NewServeMux() - mux.HandleFunc("/chat", chatHandler) - - listener, err := net.Listen("tcp", ":"+port) - if err != nil { - log.Fatal(err) - } - fmt.Printf("Listening on port %s\n", port) - - if os.Getenv("SELF_TEST") == "1" { - go func() { - http.Serve(listener, mux) - }() - - time.Sleep(500 * time.Millisecond) - url := fmt.Sprintf("http://localhost:%s/chat", port) - resp, err := http.Post(url, "application/json", - strings.NewReader(`{"prompt":"What is the capital of France?"}`)) - if err != nil { - log.Fatal("Self-test error:", err) - } - defer resp.Body.Close() - - var result chatResponse - json.NewDecoder(resp.Body).Decode(&result) - if result.Response != "" { - fmt.Println(result.Response) - } else { - log.Fatal("Self-test failed:", result.Error) - } - } else { - http.Serve(listener, mux) - } -} diff --git a/test/scenarios/bundling/app-backend-to-server/python/main.py b/test/scenarios/bundling/app-backend-to-server/python/main.py deleted file mode 100644 index d53d89854..000000000 --- a/test/scenarios/bundling/app-backend-to-server/python/main.py +++ /dev/null @@ -1,77 +0,0 @@ -import asyncio -import json -import os -import sys -import urllib.request - -from flask import Flask, jsonify, request - -from copilot import CopilotClient, RuntimeConnection - -app = Flask(__name__) - -CLI_URL = os.environ.get("CLI_URL", os.environ.get("COPILOT_CLI_URL", "localhost:3000")) - - -async def ask_copilot(prompt: str) -> str: - client = CopilotClient(connection=RuntimeConnection.for_uri(CLI_URL)) - - try: - session = await client.create_session(model="claude-haiku-4.5") - - response = await session.send_and_wait(prompt) - - await session.disconnect() - - if response: - return response.data.content - return "" - finally: - await client.stop() - - -@app.route("/chat", methods=["POST"]) -def chat(): - data = request.get_json(force=True) - prompt = data.get("prompt", "") - if not prompt: - return jsonify({"error": "Missing 'prompt' in request body"}), 400 - - content = asyncio.run(ask_copilot(prompt)) - if content: - return jsonify({"response": content}) - return jsonify({"error": "No response content from Copilot CLI"}), 502 - - -def self_test(port: int): - """Send a test request to ourselves and print the response.""" - url = f"http://localhost:{port}/chat" - payload = json.dumps({"prompt": "What is the capital of France?"}).encode() - req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"}) - with urllib.request.urlopen(req) as resp: - result = json.loads(resp.read().decode()) - if result.get("response"): - print(result["response"]) - else: - print("Self-test failed:", result, file=sys.stderr) - sys.exit(1) - - -if __name__ == "__main__": - import threading - - port = int(os.environ.get("PORT", "8080")) - - if os.environ.get("SELF_TEST") == "1": - # Start server in a background thread, run self-test, then exit - server_thread = threading.Thread( - target=lambda: app.run(host="0.0.0.0", port=port, debug=False), - daemon=True, - ) - server_thread.start() - import time - - time.sleep(1) - self_test(port) - else: - app.run(host="0.0.0.0", port=port, debug=False) diff --git a/test/scenarios/bundling/app-backend-to-server/python/requirements.txt b/test/scenarios/bundling/app-backend-to-server/python/requirements.txt deleted file mode 100644 index c6b6d06c1..000000000 --- a/test/scenarios/bundling/app-backend-to-server/python/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -flask --e ../../../../../python diff --git a/test/scenarios/bundling/app-backend-to-server/typescript/package.json b/test/scenarios/bundling/app-backend-to-server/typescript/package.json deleted file mode 100644 index eca6e68ce..000000000 --- a/test/scenarios/bundling/app-backend-to-server/typescript/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "bundling-app-backend-to-server-typescript", - "version": "1.0.0", - "private": true, - "description": "App-backend-to-server Copilot SDK sample — web backend proxies to Copilot CLI TCP server", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs", - "express": "^4.21.0" - }, - "devDependencies": { - "@types/express": "^4.17.0", - "@types/node": "^20.0.0", - "esbuild": "^0.24.0", - "typescript": "^5.5.0" - } -} diff --git a/test/scenarios/bundling/app-backend-to-server/typescript/src/index.ts b/test/scenarios/bundling/app-backend-to-server/typescript/src/index.ts deleted file mode 100644 index c169e7f89..000000000 --- a/test/scenarios/bundling/app-backend-to-server/typescript/src/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -import express from "express"; -import { CopilotClient } from "@github/copilot-sdk"; - -const PORT = parseInt(process.env.PORT || "8080", 10); -const CLI_URL = - process.env.CLI_URL || process.env.COPILOT_CLI_URL || "localhost:3000"; - -const app = express(); -app.use(express.json()); - -app.post("/chat", async (req, res) => { - const { prompt } = req.body; - if (!prompt || typeof prompt !== "string") { - res.status(400).json({ error: "Missing 'prompt' in request body" }); - return; - } - - const client = new CopilotClient({ cliUrl: CLI_URL }); - - try { - const session = await client.createSession({ model: "claude-haiku-4.5" }); - - const response = await session.sendAndWait({ prompt }); - - await session.disconnect(); - - if (response?.data.content) { - res.json({ response: response.data.content }); - } else { - res.status(502).json({ error: "No response content from Copilot CLI" }); - } - } catch (err) { - res.status(500).json({ error: String(err) }); - } finally { - await client.stop(); - } -}); - -// When run directly, start server and optionally self-test -const server = app.listen(PORT, async () => { - console.log(`Listening on port ${PORT}`); - - // Self-test mode: send a request and exit - if (process.env.SELF_TEST === "1") { - try { - const resp = await fetch(`http://localhost:${PORT}/chat`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ prompt: "What is the capital of France?" }), - }); - const data = await resp.json(); - if (data.response) { - console.log(data.response); - } else { - console.error("Self-test failed:", data); - process.exit(1); - } - } catch (err) { - console.error("Self-test error:", err); - process.exit(1); - } finally { - server.close(); - } - } -}); diff --git a/test/scenarios/bundling/app-backend-to-server/verify.sh b/test/scenarios/bundling/app-backend-to-server/verify.sh deleted file mode 100755 index 812a2cda4..000000000 --- a/test/scenarios/bundling/app-backend-to-server/verify.sh +++ /dev/null @@ -1,291 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 -SERVER_PID="" -SERVER_PORT_FILE="" -APP_PID="" - -cleanup() { - if [ -n "${APP_PID:-}" ] && kill -0 "$APP_PID" 2>/dev/null; then - kill "$APP_PID" 2>/dev/null || true - wait "$APP_PID" 2>/dev/null || true - fi - if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then - echo "" - echo "Stopping Copilot CLI server (PID $SERVER_PID)..." - kill "$SERVER_PID" 2>/dev/null || true - wait "$SERVER_PID" 2>/dev/null || true - fi - [ -n "$SERVER_PORT_FILE" ] && rm -f "$SERVER_PORT_FILE" -} -trap cleanup EXIT - -# Resolve Copilot CLI binary: use COPILOT_CLI_PATH env var or find the SDK bundled CLI. -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - # Try to resolve from the TypeScript sample node_modules - TS_DIR="$SCRIPT_DIR/typescript" - if [ -d "$TS_DIR/node_modules/@github/copilot" ]; then - COPILOT_CLI_PATH="$(node -e "console.log(require.resolve('@github/copilot'))" 2>/dev/null || true)" - fi - # Fallback: check PATH - if [ -z "${COPILOT_CLI_PATH:-}" ]; then - COPILOT_CLI_PATH="$(command -v copilot 2>/dev/null || true)" - fi -fi -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - echo "❌ Could not find Copilot CLI binary." - echo " Set COPILOT_CLI_PATH or run: cd typescript && npm install" - exit 1 -fi -echo "Using CLI: $COPILOT_CLI_PATH" - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - echo "✅ $name passed (got response)" - PASS=$((PASS + 1)) - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -# Helper: start an HTTP server, curl it, stop it -run_http_test() { - local name="$1" - local start_cmd="$2" - local app_port="$3" - local max_retries="${4:-15}" - - printf "━━━ %s ━━━\n" "$name" - - # Start the HTTP server in the background - eval "$start_cmd" & - APP_PID=$! - - # Wait for server to be ready - local ready=false - for i in $(seq 1 "$max_retries"); do - if curl -sf "http://localhost:${app_port}/chat" -X POST \ - -H "Content-Type: application/json" \ - -d '{"prompt":"ping"}' >/dev/null 2>&1; then - ready=true - break - fi - if ! kill -0 "$APP_PID" 2>/dev/null; then - break - fi - sleep 1 - done - - if [ "$ready" = false ]; then - echo "Server did not become ready" - echo "❌ $name failed (server not ready)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (server not ready)" - kill "$APP_PID" 2>/dev/null || true - wait "$APP_PID" 2>/dev/null || true - APP_PID="" - echo "" - return - fi - - # Send the real test request with timeout - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" curl -sf "http://localhost:${app_port}/chat" \ - -X POST -H "Content-Type: application/json" \ - -d '{"prompt":"What is the capital of France?"}' 2>&1) && code=0 || code=$? - else - output=$(curl -sf "http://localhost:${app_port}/chat" \ - -X POST -H "Content-Type: application/json" \ - -d '{"prompt":"What is the capital of France?"}' 2>&1) && code=0 || code=$? - fi - - # Stop the HTTP server - kill "$APP_PID" 2>/dev/null || true - wait "$APP_PID" 2>/dev/null || true - APP_PID="" - - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - if echo "$output" | grep -qi 'Paris\|capital\|France'; then - echo "✅ $name passed (got response with expected content)" - PASS=$((PASS + 1)) - else - echo "❌ $name failed (response missing expected content)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (no expected content)" - fi - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -# Kill any stale processes on the test ports from previous interrupted runs -for test_port in 18081 18082 18083 18084; do - stale_pid=$(lsof -ti ":$test_port" 2>/dev/null || true) - if [ -n "$stale_pid" ]; then - echo "Killing stale process on port $test_port (PID $stale_pid)" - kill $stale_pid 2>/dev/null || true - fi -done - -echo "══════════════════════════════════════" -echo " Starting Copilot CLI TCP server" -echo "══════════════════════════════════════" -echo "" - -SERVER_PORT_FILE=$(mktemp) -"$COPILOT_CLI_PATH" --headless --auth-token-env GITHUB_TOKEN > "$SERVER_PORT_FILE" 2>&1 & -SERVER_PID=$! - -# Wait for server to announce its port -echo "Waiting for server to be ready..." -PORT="" -for i in $(seq 1 30); do - if ! kill -0 "$SERVER_PID" 2>/dev/null; then - echo "❌ Server process exited unexpectedly" - cat "$SERVER_PORT_FILE" 2>/dev/null - exit 1 - fi - PORT=$(grep -o 'listening on port [0-9]*' "$SERVER_PORT_FILE" 2>/dev/null | grep -o '[0-9]*' || true) - if [ -n "$PORT" ]; then - break - fi - if [ "$i" -eq 30 ]; then - echo "❌ Server did not announce port within 30 seconds" - exit 1 - fi - sleep 1 -done -export COPILOT_CLI_URL="localhost:$PORT" -echo "Server is ready on port $PORT (PID $SERVER_PID)" -echo "" - -echo "══════════════════════════════════════" -echo " Verifying app-backend-to-server samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o app-backend-to-server-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: start server, curl, stop -run_http_test "TypeScript (run)" \ - "cd '$SCRIPT_DIR/typescript' && PORT=18081 CLI_URL=$COPILOT_CLI_URL node dist/index.js" \ - 18081 - -# Python: start server, curl, stop -run_http_test "Python (run)" \ - "cd '$SCRIPT_DIR/python' && PORT=18082 CLI_URL=$COPILOT_CLI_URL python3 main.py" \ - 18082 - -# Go: start server, curl, stop -run_http_test "Go (run)" \ - "cd '$SCRIPT_DIR/go' && PORT=18083 CLI_URL=$COPILOT_CLI_URL ./app-backend-to-server-go" \ - 18083 - -# C#: start server, curl, stop (extra retries for JIT startup) -run_http_test "C# (run)" \ - "cd '$SCRIPT_DIR/csharp' && PORT=18084 COPILOT_CLI_URL=$COPILOT_CLI_URL dotnet run --no-build" \ - 18084 \ - 30 - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/bundling/app-direct-server/README.md b/test/scenarios/bundling/app-direct-server/README.md deleted file mode 100644 index 1b396dced..000000000 --- a/test/scenarios/bundling/app-direct-server/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# App-Direct-Server Samples - -Samples that demonstrate the **app-direct-server** deployment architecture of the Copilot SDK. In this scenario the SDK connects to a **pre-running** `copilot` TCP server — the app does not spawn or manage the server process. - -``` -┌─────────────┐ TCP (JSON-RPC) ┌──────────────┐ -│ Your App │ ─────────────────▶ │ Copilot CLI │ -│ (SDK) │ ◀───────────────── │ (TCP server) │ -└─────────────┘ └──────────────┘ -``` - -Each sample follows the same flow: - -1. **Connect** to a running `copilot` server via TCP -2. **Open a session** targeting the `gpt-4.1` model -3. **Send a prompt** ("What is the capital of France?") -4. **Print the response** and clean up - -## Languages - -| Directory | SDK / Approach | Language | -|-----------|---------------|----------| -| `typescript/` | `@github/copilot-sdk` | TypeScript (Node.js) | -| `python/` | `github-copilot-sdk` | Python | -| `go/` | `github.com/github/copilot-sdk/go` | Go | - -## Prerequisites - -- **Copilot CLI** — set `COPILOT_CLI_PATH` -- **Authentication** — set `GITHUB_TOKEN`, or run `gh auth login` -- **Node.js 20+** (TypeScript sample) -- **Python 3.10+** (Python sample) -- **Go 1.24+** (Go sample) - -## Starting the Server - -Start `copilot` as a TCP server before running any sample: - -```bash -copilot --port 3000 --headless --auth-token-env GITHUB_TOKEN -``` - -## Quick Start - -**TypeScript** -```bash -cd typescript -npm install && npm run build && npm start -``` - -**Python** -```bash -cd python -pip install -r requirements.txt -python main.py -``` - -**Go** -```bash -cd go -go run main.go -``` - -All samples default to `localhost:3000`. Override with the `COPILOT_CLI_URL` environment variable: - -```bash -COPILOT_CLI_URL=localhost:8080 npm start -``` - -## Verification - -A script is included that starts the server, builds, and end-to-end tests every sample: - -```bash -./verify.sh -``` - -It runs in three phases: - -1. **Server** — starts `copilot` on a random port (auto-detected from server output) -2. **Build** — installs dependencies and compiles each sample -3. **E2E Run** — executes each sample with a 60-second timeout and verifies it produces output - -The server is automatically stopped when the script exits. diff --git a/test/scenarios/bundling/app-direct-server/csharp/Program.cs b/test/scenarios/bundling/app-direct-server/csharp/Program.cs deleted file mode 100644 index c7b9fec70..000000000 --- a/test/scenarios/bundling/app-direct-server/csharp/Program.cs +++ /dev/null @@ -1,33 +0,0 @@ -using GitHub.Copilot; - -var cliUrl = Environment.GetEnvironmentVariable("COPILOT_CLI_URL") ?? "localhost:3000"; - -using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl) }); -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response?.Data?.Content != null) - { - Console.WriteLine(response.Data.Content); - } - else - { - Console.Error.WriteLine("No response content received"); - Environment.Exit(1); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/bundling/app-direct-server/csharp/csharp.csproj b/test/scenarios/bundling/app-direct-server/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/bundling/app-direct-server/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/bundling/app-direct-server/go/go.mod b/test/scenarios/bundling/app-direct-server/go/go.mod deleted file mode 100644 index 950890c46..000000000 --- a/test/scenarios/bundling/app-direct-server/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/bundling/app-direct-server/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/bundling/app-direct-server/go/go.sum b/test/scenarios/bundling/app-direct-server/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/bundling/app-direct-server/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/bundling/app-direct-server/go/main.go b/test/scenarios/bundling/app-direct-server/go/main.go deleted file mode 100644 index 95da9cf68..000000000 --- a/test/scenarios/bundling/app-direct-server/go/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - cliUrl := os.Getenv("COPILOT_CLI_URL") - if cliUrl == "" { - cliUrl = "localhost:3000" - } - - client := copilot.NewClient(&copilot.ClientOptions{ - Connection: copilot.UriConnection{URL: cliUrl}, - }) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/bundling/app-direct-server/python/main.py b/test/scenarios/bundling/app-direct-server/python/main.py deleted file mode 100644 index 1bf32b475..000000000 --- a/test/scenarios/bundling/app-direct-server/python/main.py +++ /dev/null @@ -1,27 +0,0 @@ -import asyncio -import os - -from copilot import CopilotClient, RuntimeConnection - - -async def main(): - client = CopilotClient( - connection=RuntimeConnection.for_uri( - os.environ.get("COPILOT_CLI_URL", "localhost:3000"), - ), - ) - - try: - session = await client.create_session(model="claude-haiku-4.5") - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/bundling/app-direct-server/python/requirements.txt b/test/scenarios/bundling/app-direct-server/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/bundling/app-direct-server/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/bundling/app-direct-server/typescript/package.json b/test/scenarios/bundling/app-direct-server/typescript/package.json deleted file mode 100644 index 5ceb5c16f..000000000 --- a/test/scenarios/bundling/app-direct-server/typescript/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "bundling-app-direct-server-typescript", - "version": "1.0.0", - "private": true, - "description": "App-direct-server Copilot SDK sample — connects to a running Copilot CLI TCP server", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0", - "typescript": "^5.5.0" - } -} diff --git a/test/scenarios/bundling/app-direct-server/typescript/src/index.ts b/test/scenarios/bundling/app-direct-server/typescript/src/index.ts deleted file mode 100644 index 29a19dd10..000000000 --- a/test/scenarios/bundling/app-direct-server/typescript/src/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient({ - cliUrl: process.env.COPILOT_CLI_URL || "localhost:3000", - }); - - try { - const session = await client.createSession({ model: "claude-haiku-4.5" }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response?.data.content) { - console.log(response.data.content); - } else { - console.error("No response content received"); - process.exit(1); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/bundling/app-direct-server/typescript/tsconfig.json b/test/scenarios/bundling/app-direct-server/typescript/tsconfig.json deleted file mode 100644 index 8e7a1798c..000000000 --- a/test/scenarios/bundling/app-direct-server/typescript/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "outDir": "dist", - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": ["src"] -} diff --git a/test/scenarios/bundling/app-direct-server/verify.sh b/test/scenarios/bundling/app-direct-server/verify.sh deleted file mode 100755 index 6a4bbcc39..000000000 --- a/test/scenarios/bundling/app-direct-server/verify.sh +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 -SERVER_PID="" -SERVER_PORT_FILE="" - -cleanup() { - if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then - echo "" - echo "Stopping Copilot CLI server (PID $SERVER_PID)..." - kill "$SERVER_PID" 2>/dev/null || true - wait "$SERVER_PID" 2>/dev/null || true - fi - [ -n "$SERVER_PORT_FILE" ] && rm -f "$SERVER_PORT_FILE" -} -trap cleanup EXIT - -# Resolve Copilot CLI binary: use COPILOT_CLI_PATH env var or find the SDK bundled CLI. -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - # Try to resolve from the TypeScript sample node_modules - TS_DIR="$SCRIPT_DIR/typescript" - if [ -d "$TS_DIR/node_modules/@github/copilot" ]; then - COPILOT_CLI_PATH="$(node -e "console.log(require.resolve('@github/copilot'))" 2>/dev/null || true)" - fi - # Fallback: check PATH - if [ -z "${COPILOT_CLI_PATH:-}" ]; then - COPILOT_CLI_PATH="$(command -v copilot 2>/dev/null || true)" - fi -fi -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - echo "❌ Could not find Copilot CLI binary." - echo " Set COPILOT_CLI_PATH or run: cd typescript && npm install" - exit 1 -fi -echo "Using CLI: $COPILOT_CLI_PATH" - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - echo "✅ $name passed (got response)" - PASS=$((PASS + 1)) - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Starting Copilot CLI TCP server" -echo "══════════════════════════════════════" -echo "" - -SERVER_PORT_FILE=$(mktemp) -"$COPILOT_CLI_PATH" --headless --auth-token-env GITHUB_TOKEN > "$SERVER_PORT_FILE" 2>&1 & -SERVER_PID=$! - -# Wait for server to announce its port -echo "Waiting for server to be ready..." -PORT="" -for i in $(seq 1 30); do - if ! kill -0 "$SERVER_PID" 2>/dev/null; then - echo "❌ Server process exited unexpectedly" - cat "$SERVER_PORT_FILE" 2>/dev/null - exit 1 - fi - PORT=$(grep -o 'listening on port [0-9]*' "$SERVER_PORT_FILE" 2>/dev/null | grep -o '[0-9]*' || true) - if [ -n "$PORT" ]; then - break - fi - if [ "$i" -eq 30 ]; then - echo "❌ Server did not announce port within 30 seconds" - exit 1 - fi - sleep 1 -done -export COPILOT_CLI_URL="localhost:$PORT" -echo "Server is ready on port $PORT (PID $SERVER_PID)" -echo "" - -echo "══════════════════════════════════════" -echo " Verifying app-direct-server samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o app-direct-server-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c " - cd '$SCRIPT_DIR/typescript' && \ - output=\$(node dist/index.js 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response' -" - -# Python: run -run_with_timeout "Python (run)" bash -c " - cd '$SCRIPT_DIR/python' && \ - output=\$(python3 main.py 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response' -" - -# Go: run -run_with_timeout "Go (run)" bash -c " - cd '$SCRIPT_DIR/go' && \ - output=\$(./app-direct-server-go 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response' -" - -# C#: run -run_with_timeout "C# (run)" bash -c " - cd '$SCRIPT_DIR/csharp' && \ - output=\$(COPILOT_CLI_URL=$COPILOT_CLI_URL dotnet run --no-build 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response' -" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/bundling/container-proxy/.dockerignore b/test/scenarios/bundling/container-proxy/.dockerignore deleted file mode 100644 index df91b0e65..000000000 --- a/test/scenarios/bundling/container-proxy/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!experimental-copilot-server/ -experimental-copilot-server/target/ diff --git a/test/scenarios/bundling/container-proxy/Dockerfile b/test/scenarios/bundling/container-proxy/Dockerfile deleted file mode 100644 index 34c0ac3a7..000000000 --- a/test/scenarios/bundling/container-proxy/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -# syntax=docker/dockerfile:1 - -# Runtime image for Copilot CLI -# The final image contains ONLY the binary — no source code, no credentials. -# Requires a pre-built Copilot CLI binary to be copied in. - -FROM debian:bookworm-slim - -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* - -# Copy a pre-built Copilot CLI binary -# Set COPILOT_CLI_PATH build arg or provide the binary at build context root -ARG COPILOT_CLI_PATH=copilot -COPY ${COPILOT_CLI_PATH} /usr/local/bin/copilot -RUN chmod +x /usr/local/bin/copilot - -EXPOSE 3000 - -ENTRYPOINT ["copilot", "--headless", "--port", "3000", "--host", "0.0.0.0", "--auth-token-env", "GITHUB_TOKEN"] diff --git a/test/scenarios/bundling/container-proxy/README.md b/test/scenarios/bundling/container-proxy/README.md deleted file mode 100644 index dcc8b15c6..000000000 --- a/test/scenarios/bundling/container-proxy/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# Container-Proxy Samples - -Run the Copilot CLI inside a Docker container with a simple proxy on the host that returns canned responses. This demonstrates the deployment pattern where an external service intercepts the agent's LLM calls — in production the proxy would add credentials and forward to a real provider; here it just returns a fixed reply as proof-of-concept. - -``` - Host Machine -┌──────────────────────────────────────────────────────┐ -│ │ -│ ┌─────────────┐ │ -│ │ Your App │ TCP :3000 │ -│ │ (SDK) │ ────────────────┐ │ -│ └─────────────┘ │ │ -│ ▼ │ -│ ┌──────────────────────────┐ │ -│ │ Docker Container │ │ -│ │ Copilot CLI │ │ -│ │ --port 3000 --headless │ │ -│ │ --host 0.0.0.0 │ │ -│ │ --auth-token-env │ │ -│ └────────────┬─────────────┘ │ -│ │ │ -│ HTTP to host.docker.internal:4000 │ -│ │ │ -│ ┌───────────▼──────────────┐ │ -│ │ proxy.py │ │ -│ │ (port 4000) │ │ -│ │ Returns canned response │ │ -│ └─────────────────────────-┘ │ -│ │ -└──────────────────────────────────────────────────────┘ -``` - -## Why This Pattern? - -The agent runtime (Copilot CLI) has **no access to API keys**. All LLM traffic flows through a proxy on the host. In production you would replace `proxy.py` with a real proxy that injects credentials and forwards to OpenAI/Anthropic/etc. This means: - -- **No secrets in the image** — safe to share, scan, deploy anywhere -- **No secrets at runtime** — even if the container is compromised, there are no tokens to steal -- **Swap providers freely** — change the proxy target without rebuilding the container -- **Centralized key management** — one proxy manages keys for all your agents/services - -## Prerequisites - -- **Docker** with Docker Compose -- **Python 3** (for the proxy — uses only stdlib, no pip install needed) - -## Setup - -### 1. Start the proxy - -```bash -python3 proxy.py 4000 -``` - -This starts a minimal OpenAI-compatible HTTP server on port 4000 that returns a canned "The capital of France is Paris." response for every request. - -### 2. Start the Copilot CLI in Docker - -```bash -docker compose up -d --build -``` - -This builds the Copilot CLI from source and starts it on port 3000. It sends LLM requests to `host.docker.internal:4000` — no API keys are passed into the container. - -### 3. Run a client sample - -**TypeScript** -```bash -cd typescript && npm install && npm run build && npm start -``` - -**Python** -```bash -cd python && pip install -r requirements.txt && python main.py -``` - -**Go** -```bash -cd go && go run main.go -``` - -All samples connect to `localhost:3000` by default. Override with `COPILOT_CLI_URL`. - -## Verification - -Run all samples end-to-end: - -```bash -chmod +x verify.sh -./verify.sh -``` - -## Languages - -| Directory | SDK / Approach | Language | -|-----------|---------------|----------| -| `typescript/` | `@github/copilot-sdk` | TypeScript (Node.js) | -| `python/` | `github-copilot-sdk` | Python | -| `go/` | `github.com/github/copilot-sdk/go` | Go | - -## How It Works - -1. **Copilot CLI** starts in Docker with `COPILOT_API_URL=http://host.docker.internal:4000` — this overrides the default Copilot API endpoint to point at the proxy -2. When the agent needs to call an LLM, it sends a standard OpenAI-format request to the proxy -3. **proxy.py** receives the request and returns a canned response (in production, this would inject credentials and forward to a real provider) -4. The response flows back: proxy → Copilot CLI → your app - -The container never sees or needs any API credentials. diff --git a/test/scenarios/bundling/container-proxy/csharp/Program.cs b/test/scenarios/bundling/container-proxy/csharp/Program.cs deleted file mode 100644 index c7b9fec70..000000000 --- a/test/scenarios/bundling/container-proxy/csharp/Program.cs +++ /dev/null @@ -1,33 +0,0 @@ -using GitHub.Copilot; - -var cliUrl = Environment.GetEnvironmentVariable("COPILOT_CLI_URL") ?? "localhost:3000"; - -using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl) }); -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response?.Data?.Content != null) - { - Console.WriteLine(response.Data.Content); - } - else - { - Console.Error.WriteLine("No response content received"); - Environment.Exit(1); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/bundling/container-proxy/csharp/csharp.csproj b/test/scenarios/bundling/container-proxy/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/bundling/container-proxy/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/bundling/container-proxy/docker-compose.yml b/test/scenarios/bundling/container-proxy/docker-compose.yml deleted file mode 100644 index fe2291031..000000000 --- a/test/scenarios/bundling/container-proxy/docker-compose.yml +++ /dev/null @@ -1,24 +0,0 @@ -# Container-proxy sample: Copilot CLI in Docker, simple proxy on host. -# -# The proxy (proxy.py) runs on the host and returns canned responses. -# This demonstrates the network path without needing real LLM credentials. -# -# Usage: -# 1. Start the proxy on the host: python3 proxy.py 4000 -# 2. Start the container: docker compose up -d -# 3. Run client samples against localhost:3000 - -services: - copilot-cli: - build: - context: ../../../.. - dockerfile: test/scenarios/bundling/container-proxy/Dockerfile - ports: - - "3000:3000" - environment: - # Point LLM requests at the host proxy — returns canned responses - COPILOT_API_URL: "http://host.docker.internal:4000" - # Dummy token so Copilot CLI enters the Token auth path - GITHUB_TOKEN: "not-used" - extra_hosts: - - "host.docker.internal:host-gateway" diff --git a/test/scenarios/bundling/container-proxy/go/go.mod b/test/scenarios/bundling/container-proxy/go/go.mod deleted file mode 100644 index 37c7c04bd..000000000 --- a/test/scenarios/bundling/container-proxy/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/bundling/container-proxy/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/bundling/container-proxy/go/go.sum b/test/scenarios/bundling/container-proxy/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/bundling/container-proxy/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/bundling/container-proxy/go/main.go b/test/scenarios/bundling/container-proxy/go/main.go deleted file mode 100644 index 95da9cf68..000000000 --- a/test/scenarios/bundling/container-proxy/go/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - cliUrl := os.Getenv("COPILOT_CLI_URL") - if cliUrl == "" { - cliUrl = "localhost:3000" - } - - client := copilot.NewClient(&copilot.ClientOptions{ - Connection: copilot.UriConnection{URL: cliUrl}, - }) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/bundling/container-proxy/proxy.py b/test/scenarios/bundling/container-proxy/proxy.py deleted file mode 100644 index 688b9f8c1..000000000 --- a/test/scenarios/bundling/container-proxy/proxy.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python3 -""" -Minimal OpenAI-compatible proxy for the container-proxy sample. - -This replaces a real LLM provider — Copilot CLI (running in Docker) sends -its model requests here and gets back a canned response. The point is to -prove the network path: - - client → Copilot CLI (container :3000) → this proxy (host :4000) -""" - -import json -import sys -import time -from http.server import BaseHTTPRequestHandler, HTTPServer - - -class ProxyHandler(BaseHTTPRequestHandler): - def do_POST(self): - length = int(self.headers.get("Content-Length", 0)) - body = json.loads(self.rfile.read(length)) if length else {} - - model = body.get("model", "claude-haiku-4.5") - stream = body.get("stream", False) - - if stream: - self._handle_stream(model) - else: - self._handle_non_stream(model) - - def do_GET(self): - # Health check - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.end_headers() - self.wfile.write(json.dumps({"status": "ok"}).encode()) - - # ── Non-streaming ──────────────────────────────────────────────── - - def _handle_non_stream(self, model: str): - resp = { - "id": "chatcmpl-proxy-0001", - "object": "chat.completion", - "created": int(time.time()), - "model": model, - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "The capital of France is Paris.", - }, - "finish_reason": "stop", - } - ], - "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}, - } - payload = json.dumps(resp).encode() - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.send_header("Content-Length", str(len(payload))) - self.end_headers() - self.wfile.write(payload) - - # ── Streaming (SSE) ────────────────────────────────────────────── - - def _handle_stream(self, model: str): - self.send_response(200) - self.send_header("Content-Type", "text/event-stream") - self.send_header("Cache-Control", "no-cache") - self.end_headers() - - ts = int(time.time()) - - # Single content chunk - chunk = { - "id": "chatcmpl-proxy-0001", - "object": "chat.completion.chunk", - "created": ts, - "model": model, - "choices": [ - { - "index": 0, - "delta": {"role": "assistant", "content": "The capital of France is Paris."}, - "finish_reason": None, - } - ], - } - self.wfile.write(f"data: {json.dumps(chunk)}\n\n".encode()) - self.wfile.flush() - - # Final chunk with finish_reason - done_chunk = { - "id": "chatcmpl-proxy-0001", - "object": "chat.completion.chunk", - "created": ts, - "model": model, - "choices": [ - { - "index": 0, - "delta": {}, - "finish_reason": "stop", - } - ], - } - self.wfile.write(f"data: {json.dumps(done_chunk)}\n\n".encode()) - self.wfile.write(b"data: [DONE]\n\n") - self.wfile.flush() - - def log_message(self, format, *args): - print(f"[proxy] {args[0]}", file=sys.stderr) - - -def main(): - port = int(sys.argv[1]) if len(sys.argv) > 1 else 4000 - server = HTTPServer(("0.0.0.0", port), ProxyHandler) - print(f"Proxy listening on :{port}", flush=True) - server.serve_forever() - - -if __name__ == "__main__": - main() diff --git a/test/scenarios/bundling/container-proxy/python/main.py b/test/scenarios/bundling/container-proxy/python/main.py deleted file mode 100644 index 1bf32b475..000000000 --- a/test/scenarios/bundling/container-proxy/python/main.py +++ /dev/null @@ -1,27 +0,0 @@ -import asyncio -import os - -from copilot import CopilotClient, RuntimeConnection - - -async def main(): - client = CopilotClient( - connection=RuntimeConnection.for_uri( - os.environ.get("COPILOT_CLI_URL", "localhost:3000"), - ), - ) - - try: - session = await client.create_session(model="claude-haiku-4.5") - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/bundling/container-proxy/python/requirements.txt b/test/scenarios/bundling/container-proxy/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/bundling/container-proxy/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/bundling/container-proxy/typescript/package.json b/test/scenarios/bundling/container-proxy/typescript/package.json deleted file mode 100644 index 31b6d1ed0..000000000 --- a/test/scenarios/bundling/container-proxy/typescript/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "bundling-container-proxy-typescript", - "version": "1.0.0", - "private": true, - "description": "Container-proxy Copilot SDK sample — connects to Copilot CLI running in Docker", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0", - "typescript": "^5.5.0" - } -} diff --git a/test/scenarios/bundling/container-proxy/typescript/src/index.ts b/test/scenarios/bundling/container-proxy/typescript/src/index.ts deleted file mode 100644 index 29a19dd10..000000000 --- a/test/scenarios/bundling/container-proxy/typescript/src/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient({ - cliUrl: process.env.COPILOT_CLI_URL || "localhost:3000", - }); - - try { - const session = await client.createSession({ model: "claude-haiku-4.5" }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response?.data.content) { - console.log(response.data.content); - } else { - console.error("No response content received"); - process.exit(1); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/bundling/container-proxy/typescript/tsconfig.json b/test/scenarios/bundling/container-proxy/typescript/tsconfig.json deleted file mode 100644 index 8e7a1798c..000000000 --- a/test/scenarios/bundling/container-proxy/typescript/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "outDir": "dist", - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": ["src"] -} diff --git a/test/scenarios/bundling/container-proxy/verify.sh b/test/scenarios/bundling/container-proxy/verify.sh deleted file mode 100755 index f47fa2ad9..000000000 --- a/test/scenarios/bundling/container-proxy/verify.sh +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# Skip if runtime source not available (needed for Docker build) -if [ ! -d "$ROOT_DIR/runtime" ]; then - echo "SKIP: runtime/ directory not found — cannot build Copilot CLI Docker image" - exit 0 -fi - -cleanup() { - echo "" - if [ -n "${PROXY_PID:-}" ] && kill -0 "$PROXY_PID" 2>/dev/null; then - echo "Stopping proxy (PID $PROXY_PID)..." - kill "$PROXY_PID" 2>/dev/null || true - fi - echo "Stopping Docker container..." - docker compose -f "$SCRIPT_DIR/docker-compose.yml" down --timeout 5 2>/dev/null || true -} -trap cleanup EXIT - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - echo "✅ $name passed (got response)" - PASS=$((PASS + 1)) - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -# Kill any stale processes on test ports from previous interrupted runs -for test_port in 3000 4000; do - stale_pid=$(lsof -ti ":$test_port" 2>/dev/null || true) - if [ -n "$stale_pid" ]; then - echo "Cleaning up stale process on port $test_port (PID $stale_pid)" - kill $stale_pid 2>/dev/null || true - fi -done -docker compose -f "$SCRIPT_DIR/docker-compose.yml" down --timeout 5 2>/dev/null || true - -# ── Start the simple proxy ─────────────────────────────────────────── -PROXY_PORT=4000 -PROXY_PID="" - -echo "══════════════════════════════════════" -echo " Starting proxy on port $PROXY_PORT" -echo "══════════════════════════════════════" -echo "" - -python3 "$SCRIPT_DIR/proxy.py" "$PROXY_PORT" & -PROXY_PID=$! -sleep 1 - -if kill -0 "$PROXY_PID" 2>/dev/null; then - echo "✅ Proxy running (PID $PROXY_PID)" -else - echo "❌ Proxy failed to start" - exit 1 -fi -echo "" - -# ── Build and start container ──────────────────────────────────────── -echo "══════════════════════════════════════" -echo " Building and starting Copilot CLI container" -echo "══════════════════════════════════════" -echo "" - -docker compose -f "$SCRIPT_DIR/docker-compose.yml" up -d --build - -# Wait for Copilot CLI to be ready -echo "Waiting for Copilot CLI to be ready..." -for i in $(seq 1 30); do - if (echo > /dev/tcp/localhost/3000) 2>/dev/null; then - echo "✅ Copilot CLI is ready on port 3000" - break - fi - if [ "$i" -eq 30 ]; then - echo "❌ Copilot CLI did not become ready within 30 seconds" - docker compose -f "$SCRIPT_DIR/docker-compose.yml" logs - exit 1 - fi - sleep 1 -done -echo "" - -export COPILOT_CLI_URL="localhost:3000" - -echo "══════════════════════════════════════" -echo " Phase 1: Build client samples" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o container-proxy-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c " - cd '$SCRIPT_DIR/typescript' && \ - output=\$(node dist/index.js 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital' -" - -# Python: run -run_with_timeout "Python (run)" bash -c " - cd '$SCRIPT_DIR/python' && \ - output=\$(python3 main.py 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital' -" - -# Go: run -run_with_timeout "Go (run)" bash -c " - cd '$SCRIPT_DIR/go' && \ - output=\$(./container-proxy-go 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital' -" - -# C#: run -run_with_timeout "C# (run)" bash -c " - cd '$SCRIPT_DIR/csharp' && \ - output=\$(COPILOT_CLI_URL=$COPILOT_CLI_URL dotnet run --no-build 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital' -" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/bundling/fully-bundled/README.md b/test/scenarios/bundling/fully-bundled/README.md deleted file mode 100644 index 6d99e0d85..000000000 --- a/test/scenarios/bundling/fully-bundled/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# Fully-Bundled Samples - -Self-contained samples that demonstrate the **fully-bundled** deployment architecture of the Copilot SDK. In this scenario the SDK spawns `copilot` as a child process over stdio — no external server or container is required. - -Each sample follows the same flow: - -1. **Create a client** that spawns `copilot` automatically -2. **Open a session** targeting the `gpt-4.1` model -3. **Send a prompt** ("What is the capital of France?") -4. **Print the response** and clean up - -## Languages - -| Directory | SDK / Approach | Language | -|-----------|---------------|----------| -| `typescript/` | `@github/copilot-sdk` | TypeScript (Node.js) | -| `typescript-wasm/` | `@github/copilot-sdk` with WASM runtime | TypeScript (Node.js) | -| `python/` | `github-copilot-sdk` | Python | -| `go/` | `github.com/github/copilot-sdk/go` | Go | - -## Prerequisites - -- **Copilot CLI** — set `COPILOT_CLI_PATH` -- **Authentication** — set `GITHUB_TOKEN`, or run `gh auth login` -- **Node.js 20+** (TypeScript samples) -- **Python 3.10+** (Python sample) -- **Go 1.24+** (Go sample) - -## Quick Start - -**TypeScript** -```bash -cd typescript -npm install && npm run build && npm start -``` - -**TypeScript (WASM)** -```bash -cd typescript-wasm -npm install && npm run build && npm start -``` - -**Python** -```bash -cd python -pip install -r requirements.txt -python main.py -``` - -**Go** -```bash -cd go -go run main.go -``` - -## Verification - -A script is included to build and end-to-end test every sample: - -```bash -./verify.sh -``` - -It runs in two phases: - -1. **Build** — installs dependencies and compiles each sample -2. **E2E Run** — executes each sample with a 60-second timeout and verifies it produces output - -Set `COPILOT_CLI_PATH` to point at your `copilot` binary if it isn't in the default location. diff --git a/test/scenarios/bundling/fully-bundled/csharp/Program.cs b/test/scenarios/bundling/fully-bundled/csharp/Program.cs deleted file mode 100644 index 576ca5518..000000000 --- a/test/scenarios/bundling/fully-bundled/csharp/Program.cs +++ /dev/null @@ -1,30 +0,0 @@ -using GitHub.Copilot; - -using var client = new CopilotClient(new CopilotClientOptions -{ - Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), -}); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/bundling/fully-bundled/csharp/csharp.csproj b/test/scenarios/bundling/fully-bundled/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/bundling/fully-bundled/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/bundling/fully-bundled/go/go.mod b/test/scenarios/bundling/fully-bundled/go/go.mod deleted file mode 100644 index c3bb7d0ea..000000000 --- a/test/scenarios/bundling/fully-bundled/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/bundling/fully-bundled/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/bundling/fully-bundled/go/go.sum b/test/scenarios/bundling/fully-bundled/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/bundling/fully-bundled/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/bundling/fully-bundled/go/main.go b/test/scenarios/bundling/fully-bundled/go/main.go deleted file mode 100644 index 51b592431..000000000 --- a/test/scenarios/bundling/fully-bundled/go/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - // Go SDK auto-reads COPILOT_CLI_PATH from env - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/bundling/fully-bundled/python/main.py b/test/scenarios/bundling/fully-bundled/python/main.py deleted file mode 100644 index 6be1d4294..000000000 --- a/test/scenarios/bundling/fully-bundled/python/main.py +++ /dev/null @@ -1,22 +0,0 @@ -import asyncio - -from copilot import CopilotClient - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session(model="claude-haiku-4.5") - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/bundling/fully-bundled/python/requirements.txt b/test/scenarios/bundling/fully-bundled/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/bundling/fully-bundled/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/bundling/fully-bundled/typescript/package.json b/test/scenarios/bundling/fully-bundled/typescript/package.json deleted file mode 100644 index c4d7a93b6..000000000 --- a/test/scenarios/bundling/fully-bundled/typescript/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "bundling-fully-bundled-typescript", - "version": "1.0.0", - "private": true, - "description": "Fully-bundled Copilot SDK sample — spawns Copilot CLI via stdio", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0", - "typescript": "^5.5.0" - } -} diff --git a/test/scenarios/bundling/fully-bundled/typescript/src/index.ts b/test/scenarios/bundling/fully-bundled/typescript/src/index.ts deleted file mode 100644 index 7df9cd888..000000000 --- a/test/scenarios/bundling/fully-bundled/typescript/src/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient({ - connection: RuntimeConnection.forStdio({ - path: process.env.COPILOT_CLI_PATH, - }), - }); - - try { - const session = await client.createSession({ model: "claude-haiku-4.5" }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/bundling/fully-bundled/typescript/tsconfig.json b/test/scenarios/bundling/fully-bundled/typescript/tsconfig.json deleted file mode 100644 index 8e7a1798c..000000000 --- a/test/scenarios/bundling/fully-bundled/typescript/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "outDir": "dist", - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": ["src"] -} diff --git a/test/scenarios/bundling/fully-bundled/verify.sh b/test/scenarios/bundling/fully-bundled/verify.sh deleted file mode 100755 index fe7c8087e..000000000 --- a/test/scenarios/bundling/fully-bundled/verify.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - echo "✅ $name passed (got response)" - PASS=$((PASS + 1)) - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying fully-bundled samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o fully-bundled-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c " - cd '$SCRIPT_DIR/typescript' && \ - output=\$(node dist/index.js 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response' -" - -# Python: run -run_with_timeout "Python (run)" bash -c " - cd '$SCRIPT_DIR/python' && \ - output=\$(python3 main.py 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response' -" - -# Go: run -run_with_timeout "Go (run)" bash -c " - cd '$SCRIPT_DIR/go' && \ - output=\$(./fully-bundled-go 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response' -" - -# C#: run -run_with_timeout "C# (run)" bash -c " - cd '$SCRIPT_DIR/csharp' && \ - output=\$(dotnet run --no-build 2>&1) && \ - echo \"\$output\" && \ - echo \"\$output\" | grep -qi 'Paris\|capital\|France\|response' -" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/callbacks/hooks/README.md b/test/scenarios/callbacks/hooks/README.md deleted file mode 100644 index 14f4d3784..000000000 --- a/test/scenarios/callbacks/hooks/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# configs/hooks — Session Lifecycle Hooks - -Demonstrates all SDK session lifecycle hooks firing during a typical prompt–tool–response cycle. - -## Hooks Tested - -| Hook | When It Fires | Purpose | -|------|---------------|---------| -| `onSessionStart` | Session is created | Initialize logging, metrics, or state | -| `onSessionEnd` | Session is destroyed | Clean up resources, flush logs | -| `onPreToolUse` | Before a tool executes | Approve/deny tool calls, audit usage | -| `onPostToolUse` | After a tool executes | Log results, collect metrics | -| `onUserPromptSubmitted` | User sends a prompt | Transform, validate, or log prompts | -| `onErrorOccurred` | An error is raised | Centralized error handling | - -## What This Scenario Does - -1. Creates a session with **all** lifecycle hooks registered. -2. Each hook appends its name to a log list when invoked. -3. Sends a prompt that triggers tool use (glob file listing). -4. Prints the model's response followed by the hook execution log showing which hooks fired and in what order. - -## Run - -```bash -# TypeScript -cd typescript && npm install && npm run build && node dist/index.js - -# Python -cd python && pip install -r requirements.txt && python3 main.py - -# Go -cd go && go run . -``` - -## Verify All - -```bash -./verify.sh -``` diff --git a/test/scenarios/callbacks/hooks/csharp/Program.cs b/test/scenarios/callbacks/hooks/csharp/Program.cs deleted file mode 100644 index b20addf39..000000000 --- a/test/scenarios/callbacks/hooks/csharp/Program.cs +++ /dev/null @@ -1,72 +0,0 @@ -using GitHub.Copilot; -using GitHub.Copilot.Rpc; - -var hookLog = new List(); - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - OnPermissionRequest = (request, invocation) => - Task.FromResult(PermissionDecision.ApproveOnce()), - Hooks = new SessionHooks - { - OnSessionStart = (input, invocation) => - { - hookLog.Add("onSessionStart"); - return Task.FromResult(null); - }, - OnSessionEnd = (input, invocation) => - { - hookLog.Add("onSessionEnd"); - return Task.FromResult(null); - }, - OnPreToolUse = (input, invocation) => - { - hookLog.Add($"onPreToolUse:{input.ToolName}"); - return Task.FromResult(new PreToolUseHookOutput { PermissionDecision = "allow" }); - }, - OnPostToolUse = (input, invocation) => - { - hookLog.Add($"onPostToolUse:{input.ToolName}"); - return Task.FromResult(null); - }, - OnUserPromptSubmitted = (input, invocation) => - { - hookLog.Add("onUserPromptSubmitted"); - return Task.FromResult(null); - }, - OnErrorOccurred = (input, invocation) => - { - hookLog.Add($"onErrorOccurred:{input.Error}"); - return Task.FromResult(null); - }, - }, - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "List the files in the current directory using the glob tool with pattern '*.md'.", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } - - Console.WriteLine("\n--- Hook execution log ---"); - foreach (var entry in hookLog) - { - Console.WriteLine($" {entry}"); - } - Console.WriteLine($"\nTotal hooks fired: {hookLog.Count}"); -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/callbacks/hooks/csharp/csharp.csproj b/test/scenarios/callbacks/hooks/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/callbacks/hooks/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/callbacks/hooks/go/go.mod b/test/scenarios/callbacks/hooks/go/go.mod deleted file mode 100644 index 0454868a0..000000000 --- a/test/scenarios/callbacks/hooks/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/callbacks/hooks/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/callbacks/hooks/go/go.sum b/test/scenarios/callbacks/hooks/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/callbacks/hooks/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/callbacks/hooks/go/main.go b/test/scenarios/callbacks/hooks/go/main.go deleted file mode 100644 index d080a6ae1..000000000 --- a/test/scenarios/callbacks/hooks/go/main.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "sync" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - var ( - hookLog []string - hookLogMu sync.Mutex - ) - - appendLog := func(entry string) { - hookLogMu.Lock() - hookLog = append(hookLog, entry) - hookLogMu.Unlock() - } - - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - Hooks: &copilot.SessionHooks{ - OnSessionStart: func(input copilot.SessionStartHookInput, inv copilot.HookInvocation) (*copilot.SessionStartHookOutput, error) { - appendLog("onSessionStart") - return nil, nil - }, - OnSessionEnd: func(input copilot.SessionEndHookInput, inv copilot.HookInvocation) (*copilot.SessionEndHookOutput, error) { - appendLog("onSessionEnd") - return nil, nil - }, - OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) { - appendLog(fmt.Sprintf("onPreToolUse:%s", input.ToolName)) - return &copilot.PreToolUseHookOutput{PermissionDecision: "allow"}, nil - }, - OnPostToolUse: func(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) { - appendLog(fmt.Sprintf("onPostToolUse:%s", input.ToolName)) - return nil, nil - }, - OnUserPromptSubmitted: func(input copilot.UserPromptSubmittedHookInput, inv copilot.HookInvocation) (*copilot.UserPromptSubmittedHookOutput, error) { - appendLog("onUserPromptSubmitted") - return &copilot.UserPromptSubmittedHookOutput{ModifiedPrompt: input.Prompt}, nil - }, - OnErrorOccurred: func(input copilot.ErrorOccurredHookInput, inv copilot.HookInvocation) (*copilot.ErrorOccurredHookOutput, error) { - appendLog(fmt.Sprintf("onErrorOccurred:%s", input.Error)) - return nil, nil - }, - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "List the files in the current directory using the glob tool with pattern '*.md'.", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } - - fmt.Println("\n--- Hook execution log ---") - hookLogMu.Lock() - for _, entry := range hookLog { - fmt.Printf(" %s\n", entry) - } - fmt.Printf("\nTotal hooks fired: %d\n", len(hookLog)) - hookLogMu.Unlock() -} diff --git a/test/scenarios/callbacks/hooks/python/main.py b/test/scenarios/callbacks/hooks/python/main.py deleted file mode 100644 index 434c86509..000000000 --- a/test/scenarios/callbacks/hooks/python/main.py +++ /dev/null @@ -1,76 +0,0 @@ -import asyncio - -from copilot import CopilotClient -from copilot.generated.rpc import PermissionDecisionApproveOnce - -hook_log: list[str] = [] - - -async def auto_approve_permission(request, invocation): - return PermissionDecisionApproveOnce() - - -async def on_session_start(input_data, invocation): - hook_log.append("onSessionStart") - - -async def on_session_end(input_data, invocation): - hook_log.append("onSessionEnd") - - -async def on_pre_tool_use(input_data, invocation): - tool_name = input_data.get("toolName", "unknown") - hook_log.append(f"onPreToolUse:{tool_name}") - return {"permissionDecision": "allow"} - - -async def on_post_tool_use(input_data, invocation): - tool_name = input_data.get("toolName", "unknown") - hook_log.append(f"onPostToolUse:{tool_name}") - - -async def on_user_prompt_submitted(input_data, invocation): - hook_log.append("onUserPromptSubmitted") - return input_data - - -async def on_error_occurred(input_data, invocation): - error = input_data.get("error", "unknown") - hook_log.append(f"onErrorOccurred:{error}") - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model="claude-haiku-4.5", - on_permission_request=auto_approve_permission, - hooks={ - "on_session_start": on_session_start, - "on_session_end": on_session_end, - "on_pre_tool_use": on_pre_tool_use, - "on_post_tool_use": on_post_tool_use, - "on_user_prompt_submitted": on_user_prompt_submitted, - "on_error_occurred": on_error_occurred, - }, - ) - - response = await session.send_and_wait( - "List the files in the current directory using the glob tool with pattern '*.md'." - ) - - if response: - print(response.data.content) - - await session.disconnect() - - print("\n--- Hook execution log ---") - for entry in hook_log: - print(f" {entry}") - print(f"\nTotal hooks fired: {len(hook_log)}") - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/callbacks/hooks/python/requirements.txt b/test/scenarios/callbacks/hooks/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/callbacks/hooks/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/callbacks/hooks/rust/Cargo.toml b/test/scenarios/callbacks/hooks/rust/Cargo.toml deleted file mode 100644 index 4c16a91b5..000000000 --- a/test/scenarios/callbacks/hooks/rust/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "hooks-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -async-trait = "0.1" -tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync"] } diff --git a/test/scenarios/callbacks/hooks/rust/src/main.rs b/test/scenarios/callbacks/hooks/rust/src/main.rs deleted file mode 100644 index 3f1cd056e..000000000 --- a/test/scenarios/callbacks/hooks/rust/src/main.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! Session hooks — intercept lifecycle events (session start/end, pre/post -//! tool use, user prompt, errors) and log every firing. - -use std::sync::Arc; - -use async_trait::async_trait; -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::hooks::{ - ErrorOccurredInput, ErrorOccurredOutput, HookContext, PostToolUseInput, PostToolUseOutput, - PreToolUseInput, PreToolUseOutput, SessionEndInput, SessionEndOutput, SessionHooks, - SessionStartInput, SessionStartOutput, UserPromptSubmittedInput, UserPromptSubmittedOutput, -}; -use github_copilot_sdk::types::SessionConfig; -use github_copilot_sdk::{Client, ClientOptions}; -use tokio::sync::Mutex; - -struct HookLogger { - log: Arc>>, -} - -impl HookLogger { - async fn append(&self, entry: String) { - self.log.lock().await.push(entry); - } -} - -#[async_trait] -impl SessionHooks for HookLogger { - async fn on_session_start( - &self, - _input: SessionStartInput, - _ctx: HookContext, - ) -> Option { - self.append("onSessionStart".to_string()).await; - None - } - - async fn on_session_end( - &self, - _input: SessionEndInput, - _ctx: HookContext, - ) -> Option { - self.append("onSessionEnd".to_string()).await; - None - } - - async fn on_pre_tool_use( - &self, - input: PreToolUseInput, - _ctx: HookContext, - ) -> Option { - self.append(format!("onPreToolUse:{}", input.tool_name)) - .await; - let mut out = PreToolUseOutput::default(); - out.permission_decision = Some("allow".to_string()); - Some(out) - } - - async fn on_post_tool_use( - &self, - input: PostToolUseInput, - _ctx: HookContext, - ) -> Option { - self.append(format!("onPostToolUse:{}", input.tool_name)) - .await; - None - } - - async fn on_user_prompt_submitted( - &self, - input: UserPromptSubmittedInput, - _ctx: HookContext, - ) -> Option { - self.append("onUserPromptSubmitted".to_string()).await; - let mut out = UserPromptSubmittedOutput::default(); - out.modified_prompt = Some(input.prompt); - Some(out) - } - - async fn on_error_occurred( - &self, - input: ErrorOccurredInput, - _ctx: HookContext, - ) -> Option { - self.append(format!("onErrorOccurred:{}", input.error)) - .await; - None - } -} - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let hook_log = Arc::new(Mutex::new(Vec::::new())); - let hooks = Arc::new(HookLogger { - log: hook_log.clone(), - }); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - let config = config - .with_permission_handler(Arc::new(ApproveAllHandler)) - .with_hooks(hooks); - - let session = client.create_session(config).await?; - - let response = session - .send_and_wait( - "List the files in the current directory using the glob tool with pattern '*.md'.", - ) - .await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - println!("\n--- Hook execution log ---"); - let log = hook_log.lock().await; - for entry in log.iter() { - println!(" {entry}"); - } - println!("\nTotal hooks fired: {}", log.len()); - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/callbacks/hooks/typescript/package.json b/test/scenarios/callbacks/hooks/typescript/package.json deleted file mode 100644 index 54c2d4ed0..000000000 --- a/test/scenarios/callbacks/hooks/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "callbacks-hooks-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — session lifecycle hooks", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/callbacks/hooks/typescript/src/index.ts b/test/scenarios/callbacks/hooks/typescript/src/index.ts deleted file mode 100644 index c712994a9..000000000 --- a/test/scenarios/callbacks/hooks/typescript/src/index.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const hookLog: string[] = []; - - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - onPermissionRequest: async () => ({ kind: "approve-once" as const }), - hooks: { - onSessionStart: async () => { - hookLog.push("onSessionStart"); - }, - onSessionEnd: async () => { - hookLog.push("onSessionEnd"); - }, - onPreToolUse: async (input) => { - hookLog.push(`onPreToolUse:${input.toolName}`); - return { permissionDecision: "allow" as const }; - }, - onPostToolUse: async (input) => { - hookLog.push(`onPostToolUse:${input.toolName}`); - }, - onUserPromptSubmitted: async (input) => { - hookLog.push("onUserPromptSubmitted"); - return input; - }, - onErrorOccurred: async (input) => { - hookLog.push(`onErrorOccurred:${input.error}`); - }, - }, - }); - - const response = await session.sendAndWait({ - prompt: - "List the files in the current directory using the glob tool with pattern '*.md'.", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - - console.log("\n--- Hook execution log ---"); - for (const entry of hookLog) { - console.log(` ${entry}`); - } - console.log(`\nTotal hooks fired: ${hookLog.length}`); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/callbacks/hooks/verify.sh b/test/scenarios/callbacks/hooks/verify.sh deleted file mode 100755 index e6f706e61..000000000 --- a/test/scenarios/callbacks/hooks/verify.sh +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - local missing="" - if ! echo "$output" | grep -q "onSessionStart\|on_session_start\|OnSessionStart"; then - missing="$missing onSessionStart" - fi - if ! echo "$output" | grep -q "onPreToolUse\|on_pre_tool_use\|OnPreToolUse"; then - missing="$missing onPreToolUse" - fi - if ! echo "$output" | grep -q "onPostToolUse\|on_post_tool_use\|OnPostToolUse"; then - missing="$missing onPostToolUse" - fi - if ! echo "$output" | grep -q "onSessionEnd\|on_session_end\|OnSessionEnd"; then - missing="$missing onSessionEnd" - fi - if [ -z "$missing" ]; then - echo "✅ $name passed (all hooks confirmed)" - PASS=$((PASS + 1)) - else - echo "❌ $name failed (missing hooks:$missing)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (missing:$missing)" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying callbacks/hooks" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + build -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o hooks-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./hooks-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/callbacks/permissions/README.md b/test/scenarios/callbacks/permissions/README.md deleted file mode 100644 index 2fcb7a67c..000000000 --- a/test/scenarios/callbacks/permissions/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Config Sample: Permissions - -Demonstrates the **permission request flow** — the runtime asks the SDK for permission before executing tools, and the SDK can approve or deny each request. This sample approves all requests while logging which tools were invoked. - -This pattern is the foundation for: -- **Enterprise policy enforcement** where certain tools are restricted -- **Audit logging** where all tool invocations must be recorded -- **Interactive approval UIs** where a human confirms sensitive operations -- **Fine-grained access control** based on tool name, arguments, or context - -## How It Works - -1. **Enable `onPermissionRequest` handler** on the session config -2. **Track which tools requested permission** in a log array -3. **Approve all permission requests** (return `kind: "approve-once"`) -4. **Send a prompt that triggers tool use** (e.g., listing files via glob) -5. **Print the permission log** showing which tools were approved - -## What Each Sample Does - -1. Creates a session with an `onPermissionRequest` callback that logs and approves -2. Sends: _"List the files in the current directory using glob with pattern '*'."_ -3. The runtime calls `onPermissionRequest` before each tool execution -4. The callback records `approved:` and returns approval -5. Prints the agent's response -6. Dumps the permission log showing all approved tool invocations - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `onPermissionRequest` | Log + approve | Records tool name, returns `approve-once` | -| `hooks.onPreToolUse` | Auto-allow | No tool confirmation prompts | - -## Key Insight - -The `onPermissionRequest` handler gives the integrator full control over which tools the agent can execute. By inspecting the request (tool name, arguments), you can implement allow/deny lists, require human approval for dangerous operations, or log every action for compliance. Returning `{ kind: "reject" }` blocks the tool from running. - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/callbacks/permissions/csharp/Program.cs b/test/scenarios/callbacks/permissions/csharp/Program.cs deleted file mode 100644 index 7818580fb..000000000 --- a/test/scenarios/callbacks/permissions/csharp/Program.cs +++ /dev/null @@ -1,56 +0,0 @@ -using GitHub.Copilot; -using GitHub.Copilot.Rpc; - -var permissionLog = new List(); - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - OnPermissionRequest = (request, invocation) => - { - var toolName = request switch - { - PermissionRequestCustomTool ct => ct.ToolName, - PermissionRequestShell sh => "shell", - PermissionRequestWrite wr => wr.FileName ?? "write", - PermissionRequestRead rd => rd.Path ?? "read", - PermissionRequestMcp mcp => mcp.ToolName ?? "mcp", - _ => request.Kind, - }; - permissionLog.Add($"approved:{toolName}"); - return Task.FromResult(PermissionDecision.ApproveOnce()); - }, - Hooks = new SessionHooks - { - OnPreToolUse = (input, invocation) => - Task.FromResult(new PreToolUseHookOutput { PermissionDecision = "allow" }), - }, - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "List the files in the current directory using glob with pattern '*.md'.", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } - - Console.WriteLine("\n--- Permission request log ---"); - foreach (var entry in permissionLog) - { - Console.WriteLine($" {entry}"); - } - Console.WriteLine($"\nTotal permission requests: {permissionLog.Count}"); -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/callbacks/permissions/csharp/csharp.csproj b/test/scenarios/callbacks/permissions/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/callbacks/permissions/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/callbacks/permissions/go/go.mod b/test/scenarios/callbacks/permissions/go/go.mod deleted file mode 100644 index d8157e589..000000000 --- a/test/scenarios/callbacks/permissions/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/callbacks/permissions/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/callbacks/permissions/go/go.sum b/test/scenarios/callbacks/permissions/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/callbacks/permissions/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/callbacks/permissions/go/main.go b/test/scenarios/callbacks/permissions/go/main.go deleted file mode 100644 index 428ab0a70..000000000 --- a/test/scenarios/callbacks/permissions/go/main.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "sync" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - var ( - permissionLog []string - permissionLogMu sync.Mutex - ) - - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - permissionLogMu.Lock() - permissionName := string(req.Kind()) - switch request := req.(type) { - case *copilot.PermissionRequestCustomTool: - permissionName = request.ToolName - case *copilot.PermissionRequestHook: - permissionName = request.ToolName - case *copilot.PermissionRequestMcp: - permissionName = request.ToolName - } - permissionLog = append(permissionLog, fmt.Sprintf("approved:%s", permissionName)) - permissionLogMu.Unlock() - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - Hooks: &copilot.SessionHooks{ - OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) { - return &copilot.PreToolUseHookOutput{PermissionDecision: "allow"}, nil - }, - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "List the files in the current directory using glob with pattern '*.md'.", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } - - fmt.Println("\n--- Permission request log ---") - for _, entry := range permissionLog { - fmt.Printf(" %s\n", entry) - } - fmt.Printf("\nTotal permission requests: %d\n", len(permissionLog)) -} diff --git a/test/scenarios/callbacks/permissions/python/main.py b/test/scenarios/callbacks/permissions/python/main.py deleted file mode 100644 index 2e8e5c3e5..000000000 --- a/test/scenarios/callbacks/permissions/python/main.py +++ /dev/null @@ -1,46 +0,0 @@ -import asyncio - -from copilot import CopilotClient -from copilot.generated.rpc import PermissionDecisionApproveOnce - -# Track which tools requested permission -permission_log: list[str] = [] - - -async def log_permission(request, invocation): - permission_log.append(f"approved:{request.tool_name}") - return PermissionDecisionApproveOnce() - - -async def auto_approve_tool(input_data, invocation): - return {"permissionDecision": "allow"} - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model="claude-haiku-4.5", - on_permission_request=log_permission, - hooks={"on_pre_tool_use": auto_approve_tool}, - ) - - response = await session.send_and_wait( - "List the files in the current directory using glob with pattern '*.md'." - ) - - if response: - print(response.data.content) - - await session.disconnect() - - print("\n--- Permission request log ---") - for entry in permission_log: - print(f" {entry}") - print(f"\nTotal permission requests: {len(permission_log)}") - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/callbacks/permissions/python/requirements.txt b/test/scenarios/callbacks/permissions/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/callbacks/permissions/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/callbacks/permissions/rust/Cargo.toml b/test/scenarios/callbacks/permissions/rust/Cargo.toml deleted file mode 100644 index a30a94162..000000000 --- a/test/scenarios/callbacks/permissions/rust/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "permissions-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -async-trait = "0.1" -tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync"] } diff --git a/test/scenarios/callbacks/permissions/rust/src/main.rs b/test/scenarios/callbacks/permissions/rust/src/main.rs deleted file mode 100644 index 69c0037a6..000000000 --- a/test/scenarios/callbacks/permissions/rust/src/main.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Permission callback — log every `permission.request` from the CLI and -//! approve all of them. - -use std::sync::Arc; - -use async_trait::async_trait; -use github_copilot_sdk::handler::{PermissionHandler, PermissionResult}; -use github_copilot_sdk::hooks::{HookContext, PreToolUseInput, PreToolUseOutput, SessionHooks}; -use github_copilot_sdk::types::{PermissionRequestData, RequestId, SessionConfig, SessionId}; -use github_copilot_sdk::{Client, ClientOptions}; -use tokio::sync::Mutex; - -struct PermissionLogger { - log: Arc>>, -} - -#[async_trait] -impl PermissionHandler for PermissionLogger { - async fn handle( - &self, - _session_id: SessionId, - _request_id: RequestId, - data: PermissionRequestData, - ) -> PermissionResult { - let tool_name = data - .extra - .get("tool") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - self.log.lock().await.push(format!("approved:{tool_name}")); - PermissionResult::approve_once() - } -} - -struct AllowAllHooks; - -#[async_trait] -impl SessionHooks for AllowAllHooks { - async fn on_pre_tool_use( - &self, - _input: PreToolUseInput, - _ctx: HookContext, - ) -> Option { - let mut out = PreToolUseOutput::default(); - out.permission_decision = Some("allow".to_string()); - Some(out) - } -} - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let permission_log = Arc::new(Mutex::new(Vec::::new())); - let handler = Arc::new(PermissionLogger { - log: permission_log.clone(), - }); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - let config = config - .with_permission_handler(handler) - .with_hooks(Arc::new(AllowAllHooks)); - - let session = client.create_session(config).await?; - - let response = session - .send_and_wait( - "List the files in the current directory using glob with pattern '*.md'.", - ) - .await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - println!("\n--- Permission request log ---"); - let log = permission_log.lock().await; - for entry in log.iter() { - println!(" {entry}"); - } - println!("\nTotal permission requests: {}", log.len()); - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/callbacks/permissions/typescript/package.json b/test/scenarios/callbacks/permissions/typescript/package.json deleted file mode 100644 index a88b00e73..000000000 --- a/test/scenarios/callbacks/permissions/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "callbacks-permissions-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — permission request flow for tool execution", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/callbacks/permissions/typescript/src/index.ts b/test/scenarios/callbacks/permissions/typescript/src/index.ts deleted file mode 100644 index 861f5a654..000000000 --- a/test/scenarios/callbacks/permissions/typescript/src/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const permissionLog: string[] = []; - - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - onPermissionRequest: async (request) => { - permissionLog.push(`approved:${request.toolName}`); - return { kind: "approve-once" as const }; - }, - hooks: { - onPreToolUse: async () => ({ permissionDecision: "allow" as const }), - }, - }); - - const response = await session.sendAndWait({ - prompt: - "List the files in the current directory using glob with pattern '*.md'.", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - - console.log("\n--- Permission request log ---"); - for (const entry of permissionLog) { - console.log(` ${entry}`); - } - console.log(`\nTotal permission requests: ${permissionLog.length}`); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/callbacks/permissions/verify.sh b/test/scenarios/callbacks/permissions/verify.sh deleted file mode 100755 index e63438a6e..000000000 --- a/test/scenarios/callbacks/permissions/verify.sh +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - local missing="" - if ! echo "$output" | grep -qi "approved:"; then - missing="$missing approved-string" - fi - if ! echo "$output" | grep -qE "Total permission requests: [1-9]"; then - missing="$missing permission-count>0" - fi - if [ -z "$missing" ]; then - echo "✅ $name passed (permission flow confirmed)" - PASS=$((PASS + 1)) - else - echo "❌ $name failed (missing:$missing)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (missing:$missing)" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying callbacks/permissions" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o permissions-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./permissions-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/callbacks/user-input/README.md b/test/scenarios/callbacks/user-input/README.md deleted file mode 100644 index fc1482df1..000000000 --- a/test/scenarios/callbacks/user-input/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Config Sample: User Input Request - -Demonstrates the **user input request flow** — the runtime's `ask_user` tool triggers a callback to the SDK, allowing the host application to programmatically respond to agent questions without human interaction. - -This pattern is useful for: -- **Automated pipelines** where answers are predetermined or fetched from config -- **Custom UIs** that intercept user input requests and present their own dialogs -- **Testing** agent flows that require user interaction - -## How It Works - -1. **Enable `onUserInputRequest` callback** on the session -2. The callback auto-responds with `"Paris"` whenever the agent asks a question via `ask_user` -3. **Send a prompt** that instructs the agent to use `ask_user` to ask which city the user is interested in -4. The agent receives `"Paris"` as the answer and tells us about it -5. Print the response and confirm the user input flow worked via a log - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `onUserInputRequest` | Returns `{ answer: "Paris", wasFreeform: true }` | Auto-responds to `ask_user` tool calls | -| `onPermissionRequest` | Auto-approve | No permission dialogs | -| `hooks.onPreToolUse` | Auto-allow | No tool confirmation prompts | - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/callbacks/user-input/csharp/Program.cs b/test/scenarios/callbacks/user-input/csharp/Program.cs deleted file mode 100644 index e6988c6cb..000000000 --- a/test/scenarios/callbacks/user-input/csharp/Program.cs +++ /dev/null @@ -1,49 +0,0 @@ -using GitHub.Copilot; -using GitHub.Copilot.Rpc; - -var inputLog = new List(); - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - OnPermissionRequest = (request, invocation) => - Task.FromResult(PermissionDecision.ApproveOnce()), - OnUserInputRequest = (request, invocation) => - { - inputLog.Add($"question: {request.Question}"); - return Task.FromResult(new UserInputResponse { Answer = "Paris", WasFreeform = true }); - }, - Hooks = new SessionHooks - { - OnPreToolUse = (input, invocation) => - Task.FromResult(new PreToolUseHookOutput { PermissionDecision = "allow" }), - }, - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "I want to learn about a city. Use the ask_user tool to ask me which city I'm interested in. Then tell me about that city.", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } - - Console.WriteLine("\n--- User input log ---"); - foreach (var entry in inputLog) - { - Console.WriteLine($" {entry}"); - } - Console.WriteLine($"\nTotal user input requests: {inputLog.Count}"); -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/callbacks/user-input/csharp/csharp.csproj b/test/scenarios/callbacks/user-input/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/callbacks/user-input/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/callbacks/user-input/go/go.mod b/test/scenarios/callbacks/user-input/go/go.mod deleted file mode 100644 index 3dc18ebab..000000000 --- a/test/scenarios/callbacks/user-input/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/callbacks/user-input/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/callbacks/user-input/go/go.sum b/test/scenarios/callbacks/user-input/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/callbacks/user-input/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/callbacks/user-input/go/main.go b/test/scenarios/callbacks/user-input/go/main.go deleted file mode 100644 index 673667058..000000000 --- a/test/scenarios/callbacks/user-input/go/main.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "sync" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -var ( - inputLog []string - inputLogMu sync.Mutex -) - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - OnUserInputRequest: func(req copilot.UserInputRequest, inv copilot.UserInputInvocation) (copilot.UserInputResponse, error) { - inputLogMu.Lock() - inputLog = append(inputLog, fmt.Sprintf("question: %s", req.Question)) - inputLogMu.Unlock() - return copilot.UserInputResponse{Answer: "Paris", WasFreeform: true}, nil - }, - Hooks: &copilot.SessionHooks{ - OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) { - return &copilot.PreToolUseHookOutput{PermissionDecision: "allow"}, nil - }, - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "I want to learn about a city. Use the ask_user tool to ask me " + - "which city I'm interested in. Then tell me about that city.", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } - - fmt.Println("\n--- User input log ---") - for _, entry := range inputLog { - fmt.Printf(" %s\n", entry) - } - fmt.Printf("\nTotal user input requests: %d\n", len(inputLog)) -} diff --git a/test/scenarios/callbacks/user-input/python/main.py b/test/scenarios/callbacks/user-input/python/main.py deleted file mode 100644 index 2d5bdb1e9..000000000 --- a/test/scenarios/callbacks/user-input/python/main.py +++ /dev/null @@ -1,51 +0,0 @@ -import asyncio - -from copilot import CopilotClient -from copilot.generated.rpc import PermissionDecisionApproveOnce - -input_log: list[str] = [] - - -async def auto_approve_permission(request, invocation): - return PermissionDecisionApproveOnce() - - -async def auto_approve_tool(input_data, invocation): - return {"permissionDecision": "allow"} - - -async def handle_user_input(request, invocation): - input_log.append(f"question: {request['question']}") - return {"answer": "Paris", "wasFreeform": True} - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model="claude-haiku-4.5", - on_permission_request=auto_approve_permission, - on_user_input_request=handle_user_input, - hooks={"on_pre_tool_use": auto_approve_tool}, - ) - - response = await session.send_and_wait( - "I want to learn about a city. Use the ask_user tool to ask me " - "which city I'm interested in. Then tell me about that city." - ) - - if response: - print(response.data.content) - - await session.disconnect() - - print("\n--- User input log ---") - for entry in input_log: - print(f" {entry}") - print(f"\nTotal user input requests: {len(input_log)}") - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/callbacks/user-input/python/requirements.txt b/test/scenarios/callbacks/user-input/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/callbacks/user-input/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/callbacks/user-input/rust/Cargo.toml b/test/scenarios/callbacks/user-input/rust/Cargo.toml deleted file mode 100644 index 83430f128..000000000 --- a/test/scenarios/callbacks/user-input/rust/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "user-input-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -async-trait = "0.1" -tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync"] } diff --git a/test/scenarios/callbacks/user-input/rust/src/main.rs b/test/scenarios/callbacks/user-input/rust/src/main.rs deleted file mode 100644 index 348248389..000000000 --- a/test/scenarios/callbacks/user-input/rust/src/main.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! User-input callback — answer the agent's `ask_user` prompts and log -//! every question. - -use std::sync::Arc; - -use async_trait::async_trait; -use github_copilot_sdk::handler::{ - PermissionHandler, PermissionResult, UserInputHandler, UserInputResponse, -}; -use github_copilot_sdk::hooks::{HookContext, PreToolUseInput, PreToolUseOutput, SessionHooks}; -use github_copilot_sdk::types::{PermissionRequestData, RequestId, SessionConfig, SessionId}; -use github_copilot_sdk::{Client, ClientOptions}; -use tokio::sync::Mutex; - -struct InputResponder { - log: Arc>>, -} - -#[async_trait] -impl PermissionHandler for InputResponder { - async fn handle( - &self, - _session_id: SessionId, - _request_id: RequestId, - _data: PermissionRequestData, - ) -> PermissionResult { - PermissionResult::approve_once() - } -} - -#[async_trait] -impl UserInputHandler for InputResponder { - async fn handle( - &self, - _session_id: SessionId, - question: String, - _choices: Option>, - _allow_freeform: Option, - ) -> Option { - self.log - .lock() - .await - .push(format!("question: {question}")); - Some(UserInputResponse { - answer: "Paris".to_string(), - was_freeform: true, - }) - } -} - -struct AllowAllHooks; - -#[async_trait] -impl SessionHooks for AllowAllHooks { - async fn on_pre_tool_use( - &self, - _input: PreToolUseInput, - _ctx: HookContext, - ) -> Option { - let mut out = PreToolUseOutput::default(); - out.permission_decision = Some("allow".to_string()); - Some(out) - } -} - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let input_log = Arc::new(Mutex::new(Vec::::new())); - let handler = Arc::new(InputResponder { - log: input_log.clone(), - }); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - let config = config - .with_permission_handler(handler.clone()) - .with_user_input_handler(handler) - .with_hooks(Arc::new(AllowAllHooks)); - - let session = client.create_session(config).await?; - - let response = session - .send_and_wait( - "I want to learn about a city. Use the ask_user tool to ask me \ - which city I'm interested in. Then tell me about that city.", - ) - .await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - println!("\n--- User input log ---"); - let log = input_log.lock().await; - for entry in log.iter() { - println!(" {entry}"); - } - println!("\nTotal user input requests: {}", log.len()); - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/callbacks/user-input/typescript/package.json b/test/scenarios/callbacks/user-input/typescript/package.json deleted file mode 100644 index e6c0e3c73..000000000 --- a/test/scenarios/callbacks/user-input/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "callbacks-user-input-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — user input request flow via ask_user tool", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/callbacks/user-input/typescript/src/index.ts b/test/scenarios/callbacks/user-input/typescript/src/index.ts deleted file mode 100644 index e7e3d0bca..000000000 --- a/test/scenarios/callbacks/user-input/typescript/src/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const inputLog: string[] = []; - - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - onPermissionRequest: async () => ({ kind: "approve-once" as const }), - onUserInputRequest: async (request) => { - inputLog.push(`question: ${request.question}`); - return { answer: "Paris", wasFreeform: true }; - }, - hooks: { - onPreToolUse: async () => ({ permissionDecision: "allow" as const }), - }, - }); - - const response = await session.sendAndWait({ - prompt: - "I want to learn about a city. Use the ask_user tool to ask me which city I'm interested in. Then tell me about that city.", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - - console.log("\n--- User input log ---"); - for (const entry of inputLog) { - console.log(` ${entry}`); - } - console.log(`\nTotal user input requests: ${inputLog.length}`); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/callbacks/user-input/verify.sh b/test/scenarios/callbacks/user-input/verify.sh deleted file mode 100755 index 5e35eb67c..000000000 --- a/test/scenarios/callbacks/user-input/verify.sh +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - local missing="" - if ! echo "$output" | grep -qE "Total user input requests: [1-9]"; then - missing="$missing input-count>0" - fi - if ! echo "$output" | grep -qi "Paris"; then - missing="$missing Paris-in-output" - fi - if [ -z "$missing" ]; then - echo "✅ $name passed (user input flow confirmed)" - PASS=$((PASS + 1)) - else - echo "❌ $name failed (missing:$missing)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (missing:$missing)" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying callbacks/user-input" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + build -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o user-input-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./user-input-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/modes/default/README.md b/test/scenarios/modes/default/README.md deleted file mode 100644 index 8bf51cd1e..000000000 --- a/test/scenarios/modes/default/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# modes/default - -Demonstrates the default agent mode with standard built-in tools. - -Creates a session with only a model specified (no tool overrides), sends a prompt, -and prints the response. The agent has access to all default tools provided by the -Copilot CLI. diff --git a/test/scenarios/modes/default/csharp/Program.cs b/test/scenarios/modes/default/csharp/Program.cs deleted file mode 100644 index 71ea173aa..000000000 --- a/test/scenarios/modes/default/csharp/Program.cs +++ /dev/null @@ -1,30 +0,0 @@ -using GitHub.Copilot; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "Use the grep tool to search for the word 'SDK' in README.md and show the matching lines.", - }); - - if (response != null) - { - Console.WriteLine($"Response: {response.Data?.Content}"); - } - - Console.WriteLine("Default mode test complete"); - -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/modes/default/csharp/csharp.csproj b/test/scenarios/modes/default/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/modes/default/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/modes/default/go/go.mod b/test/scenarios/modes/default/go/go.mod deleted file mode 100644 index 85ba2d6b8..000000000 --- a/test/scenarios/modes/default/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/modes/default/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/modes/default/go/go.sum b/test/scenarios/modes/default/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/modes/default/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/modes/default/go/main.go b/test/scenarios/modes/default/go/main.go deleted file mode 100644 index 7cefdcf26..000000000 --- a/test/scenarios/modes/default/go/main.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "Use the grep tool to search for the word 'SDK' in README.md and show the matching lines.", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Printf("Response: %s\n", d.Content) - } - } - - fmt.Println("Default mode test complete") -} diff --git a/test/scenarios/modes/default/python/main.py b/test/scenarios/modes/default/python/main.py deleted file mode 100644 index 54d937be0..000000000 --- a/test/scenarios/modes/default/python/main.py +++ /dev/null @@ -1,25 +0,0 @@ -import asyncio - -from copilot import CopilotClient - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session(model="claude-haiku-4.5") - - response = await session.send_and_wait( - "Use the grep tool to search for the word 'SDK' in README.md and show the matching lines." - ) - if response: - print(f"Response: {response.data.content}") - - print("Default mode test complete") - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/modes/default/python/requirements.txt b/test/scenarios/modes/default/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/modes/default/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/modes/default/rust/Cargo.toml b/test/scenarios/modes/default/rust/Cargo.toml deleted file mode 100644 index d3483ec64..000000000 --- a/test/scenarios/modes/default/rust/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "default-mode-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/modes/default/rust/src/main.rs b/test/scenarios/modes/default/rust/src/main.rs deleted file mode 100644 index d5d51ce8d..000000000 --- a/test/scenarios/modes/default/rust/src/main.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Default agent mode — the agent has access to built-in tools (grep, view, etc.) -//! and can use them to complete a task. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::SessionConfig; -use github_copilot_sdk::{Client, ClientOptions}; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - let session = client.create_session(config).await?; - - let response = session - .send_and_wait( - "Use the grep tool to search for the word 'SDK' in README.md and show the matching lines.", - ) - .await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("Response: {content}"); - } - } - - println!("Default mode test complete"); - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/modes/default/typescript/package.json b/test/scenarios/modes/default/typescript/package.json deleted file mode 100644 index 0696bad60..000000000 --- a/test/scenarios/modes/default/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "modes-default-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — default agent mode with standard built-in tools", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/modes/default/typescript/src/index.ts b/test/scenarios/modes/default/typescript/src/index.ts deleted file mode 100644 index c6db7562d..000000000 --- a/test/scenarios/modes/default/typescript/src/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - }); - - const response = await session.sendAndWait({ - prompt: - "Use the grep tool to search for the word 'SDK' in README.md and show the matching lines.", - }); - - if (response) { - console.log(`Response: ${response.data.content}`); - } - - console.log("Default mode test complete"); - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/modes/default/verify.sh b/test/scenarios/modes/default/verify.sh deleted file mode 100755 index e8811d0d9..000000000 --- a/test/scenarios/modes/default/verify.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - # Check that the response shows evidence of tool usage or SDK-related content - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -qi "SDK\|readme\|grep\|match\|search"; then - echo "✅ $name passed (confirmed tool usage or SDK content)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name ran but response may not confirm tool usage" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying modes/default samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o default-go . 2>&1" - -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./default-go" - -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/modes/minimal/README.md b/test/scenarios/modes/minimal/README.md deleted file mode 100644 index 9881fbcc7..000000000 --- a/test/scenarios/modes/minimal/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# modes/minimal - -Demonstrates a locked-down agent with all tools removed. - -Creates a session with `availableTools: []` and a custom system message instructing -the agent to respond with text only. Sends a prompt and verifies a text-only response -is returned. diff --git a/test/scenarios/modes/minimal/csharp/Program.cs b/test/scenarios/modes/minimal/csharp/Program.cs deleted file mode 100644 index 166048d34..000000000 --- a/test/scenarios/modes/minimal/csharp/Program.cs +++ /dev/null @@ -1,36 +0,0 @@ -using GitHub.Copilot; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - AvailableTools = new List(), - SystemMessage = new SystemMessageConfig - { - Mode = SystemMessageMode.Replace, - Content = "You have no tools. Respond with text only.", - }, - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "Use the grep tool to search for 'SDK' in README.md.", - }); - - if (response != null) - { - Console.WriteLine($"Response: {response.Data?.Content}"); - } - - Console.WriteLine("Minimal mode test complete"); - -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/modes/minimal/csharp/csharp.csproj b/test/scenarios/modes/minimal/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/modes/minimal/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/modes/minimal/go/go.mod b/test/scenarios/modes/minimal/go/go.mod deleted file mode 100644 index 4ce0a27ce..000000000 --- a/test/scenarios/modes/minimal/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/modes/minimal/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/modes/minimal/go/go.sum b/test/scenarios/modes/minimal/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/modes/minimal/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/modes/minimal/go/main.go b/test/scenarios/modes/minimal/go/main.go deleted file mode 100644 index 4cb891885..000000000 --- a/test/scenarios/modes/minimal/go/main.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - AvailableTools: []string{}, - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: "You have no tools. Respond with text only.", - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "Use the grep tool to search for 'SDK' in README.md.", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Printf("Response: %s\n", d.Content) - } - } - - fmt.Println("Minimal mode test complete") -} diff --git a/test/scenarios/modes/minimal/python/main.py b/test/scenarios/modes/minimal/python/main.py deleted file mode 100644 index e9455daee..000000000 --- a/test/scenarios/modes/minimal/python/main.py +++ /dev/null @@ -1,32 +0,0 @@ -import asyncio - -from copilot import CopilotClient - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model="claude-haiku-4.5", - available_tools=[], - system_message={ - "mode": "replace", - "content": "You have no tools. Respond with text only.", - }, - ) - - response = await session.send_and_wait( - "Use the grep tool to search for 'SDK' in README.md." - ) - if response: - print(f"Response: {response.data.content}") - - print("Minimal mode test complete") - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/modes/minimal/python/requirements.txt b/test/scenarios/modes/minimal/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/modes/minimal/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/modes/minimal/typescript/package.json b/test/scenarios/modes/minimal/typescript/package.json deleted file mode 100644 index 4f531cfa0..000000000 --- a/test/scenarios/modes/minimal/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "modes-minimal-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — locked-down agent with all tools removed", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/modes/minimal/typescript/src/index.ts b/test/scenarios/modes/minimal/typescript/src/index.ts deleted file mode 100644 index 68fa73752..000000000 --- a/test/scenarios/modes/minimal/typescript/src/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - availableTools: [], - systemMessage: { - mode: "replace", - content: "You have no tools. Respond with text only.", - }, - }); - - const response = await session.sendAndWait({ - prompt: "Use the grep tool to search for 'SDK' in README.md.", - }); - - if (response) { - console.log(`Response: ${response.data.content}`); - } - - console.log("Minimal mode test complete"); - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/modes/minimal/verify.sh b/test/scenarios/modes/minimal/verify.sh deleted file mode 100755 index b72b42520..000000000 --- a/test/scenarios/modes/minimal/verify.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - # Check that the response indicates it can't use tools - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -qi "no tool\|can't\|cannot\|unable\|don't have\|do not have\|not available\|not have access\|no access"; then - echo "✅ $name passed (confirmed no tools)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name ran but response may not confirm tool-less state" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying modes/minimal samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o minimal-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./minimal-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/prompts/attachments/README.md b/test/scenarios/prompts/attachments/README.md deleted file mode 100644 index 4c6918f35..000000000 --- a/test/scenarios/prompts/attachments/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Config Sample: File Attachments - -Demonstrates sending **file attachments** alongside a prompt using the Copilot SDK. This validates that the SDK correctly passes file content to the model and the model can reference it in its response. - -## What Each Sample Does - -1. Creates a session with a custom system prompt in `replace` mode -2. Resolves the path to `sample-data.txt` (a small text file in the scenario root) -3. Sends: _"What languages are listed in the attached file?"_ with the file as an attachment -4. Prints the response — which should list TypeScript, Python, Go, C#, and Rust - -## Attachment Format - -### File Attachment - -| Field | Value | Description | -|-------|-------|-------------| -| `type` | `"file"` | Indicates a local file attachment | -| `path` | Absolute path to file | The SDK reads and sends the file content to the model | - -### Blob Attachment - -| Field | Value | Description | -|-------|-------|-------------| -| `type` | `"blob"` | Indicates an inline data attachment | -| `data` | Base64-encoded string | The file content encoded as base64 | -| `mimeType` | MIME type string | The MIME type of the data (e.g., `"image/png"`) | -| `displayName` | *(optional)* string | User-facing display name for the attachment | - -### Language-Specific Usage - -| Language | File Attachment Syntax | -|----------|------------------------| -| TypeScript | `attachments: [{ type: "file", path: sampleFile }]` | -| Python | `"attachments": [{"type": "file", "path": sample_file}]` | -| Go | `Attachments: []copilot.Attachment{&copilot.UserMessageAttachmentFile{Path: sampleFile}}` | -| Rust | `Attachment::File { path, display_name: None, line_range: None }` | - -| Language | Blob Attachment Syntax | -|----------|------------------------| -| TypeScript | `attachments: [{ type: "blob", data: base64Data, mimeType: "image/png" }]` | -| Python | `"attachments": [{"type": "blob", "data": base64_data, "mimeType": "image/png"}]` | -| Go | `Attachments: []copilot.Attachment{&copilot.UserMessageAttachmentBlob{Data: base64Data, MIMEType: "image/png"}}` | -| Rust | `Attachment::Blob { data, mime_type, display_name: None }` | - -## Sample Data - -The `sample-data.txt` file contains basic project metadata used as the attachment target: - -``` -Project: Copilot SDK Samples -Version: 1.0.0 -Description: Minimal buildable samples demonstrating the Copilot SDK -Languages: TypeScript, Python, Go, C#, Rust -``` - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/prompts/attachments/csharp/Program.cs b/test/scenarios/prompts/attachments/csharp/Program.cs deleted file mode 100644 index 8983af2d9..000000000 --- a/test/scenarios/prompts/attachments/csharp/Program.cs +++ /dev/null @@ -1,35 +0,0 @@ -using GitHub.Copilot; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - SystemMessage = new SystemMessageConfig { Mode = SystemMessageMode.Replace, Content = "You are a helpful assistant. Answer questions about attached files concisely." }, - AvailableTools = [], - }); - - var sampleFile = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "sample-data.txt")); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What languages are listed in the attached file?", - Attachments = - [ - new UserMessageAttachmentFile { Path = sampleFile, DisplayName = "sample-data.txt" }, - ], - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/prompts/attachments/csharp/csharp.csproj b/test/scenarios/prompts/attachments/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/prompts/attachments/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/prompts/attachments/go/go.mod b/test/scenarios/prompts/attachments/go/go.mod deleted file mode 100644 index 663655657..000000000 --- a/test/scenarios/prompts/attachments/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/prompts/attachments/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/prompts/attachments/go/go.sum b/test/scenarios/prompts/attachments/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/prompts/attachments/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/prompts/attachments/go/main.go b/test/scenarios/prompts/attachments/go/main.go deleted file mode 100644 index 4acfee001..000000000 --- a/test/scenarios/prompts/attachments/go/main.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - "path/filepath" - - copilot "github.com/github/copilot-sdk/go" -) - -const systemPrompt = `You are a helpful assistant. Answer questions about attached files concisely.` - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: systemPrompt, - }, - AvailableTools: []string{}, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - exe, err := os.Executable() - if err != nil { - log.Fatal(err) - } - sampleFile := filepath.Join(filepath.Dir(exe), "..", "sample-data.txt") - sampleFile, err = filepath.Abs(sampleFile) - if err != nil { - log.Fatal(err) - } - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What languages are listed in the attached file?", - Attachments: []copilot.Attachment{ - copilot.UserMessageAttachmentFile{DisplayName: filepath.Base(sampleFile), Path: sampleFile}, - }, - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/prompts/attachments/python/main.py b/test/scenarios/prompts/attachments/python/main.py deleted file mode 100644 index 584d86ae9..000000000 --- a/test/scenarios/prompts/attachments/python/main.py +++ /dev/null @@ -1,35 +0,0 @@ -import asyncio -import os - -from copilot import CopilotClient - -SYSTEM_PROMPT = """You are a helpful assistant. Answer questions about attached files concisely.""" - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model="claude-haiku-4.5", - system_message={"mode": "replace", "content": SYSTEM_PROMPT}, - available_tools=[], - ) - - sample_file = os.path.join(os.path.dirname(__file__), "..", "sample-data.txt") - sample_file = os.path.abspath(sample_file) - - response = await session.send_and_wait( - "What languages are listed in the attached file?", - attachments=[{"type": "file", "path": sample_file}], - ) - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/prompts/attachments/python/requirements.txt b/test/scenarios/prompts/attachments/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/prompts/attachments/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/prompts/attachments/rust/Cargo.toml b/test/scenarios/prompts/attachments/rust/Cargo.toml deleted file mode 100644 index e87952f14..000000000 --- a/test/scenarios/prompts/attachments/rust/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "attachments-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/prompts/attachments/rust/src/main.rs b/test/scenarios/prompts/attachments/rust/src/main.rs deleted file mode 100644 index 040ee5cde..000000000 --- a/test/scenarios/prompts/attachments/rust/src/main.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! File attachments — send a prompt alongside a file attachment so the -//! model can reference the file's content in its response. - -use std::path::PathBuf; -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::{Attachment, MessageOptions, SessionConfig, SystemMessageConfig}; -use github_copilot_sdk::{Client, ClientOptions}; - -const SYSTEM_PROMPT: &str = - "You are a helpful assistant. Answer questions about attached files concisely."; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let mut sysmsg = SystemMessageConfig::default(); - sysmsg.mode = Some("replace".to_string()); - sysmsg.content = Some(SYSTEM_PROMPT.to_string()); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - config.system_message = Some(sysmsg); - config.available_tools = Some(Vec::new()); - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - - let session = client.create_session(config).await?; - - // CARGO_MANIFEST_DIR resolves to .../prompts/attachments/rust at compile time. - let sample_file: PathBuf = [env!("CARGO_MANIFEST_DIR"), "..", "sample-data.txt"] - .iter() - .collect(); - let sample_file = sample_file.canonicalize().unwrap_or(sample_file); - - let response = session - .send_and_wait( - MessageOptions::new("What languages are listed in the attached file?").with_attachments( - vec![Attachment::File { - path: sample_file, - display_name: None, - line_range: None, - }], - ), - ) - .await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/prompts/attachments/sample-data.txt b/test/scenarios/prompts/attachments/sample-data.txt deleted file mode 100644 index 449dec12c..000000000 --- a/test/scenarios/prompts/attachments/sample-data.txt +++ /dev/null @@ -1,4 +0,0 @@ -Project: Copilot SDK Samples -Version: 1.0.0 -Description: Minimal buildable samples demonstrating the Copilot SDK -Languages: TypeScript, Python, Go, C#, Rust diff --git a/test/scenarios/prompts/attachments/typescript/package.json b/test/scenarios/prompts/attachments/typescript/package.json deleted file mode 100644 index 4553a73b3..000000000 --- a/test/scenarios/prompts/attachments/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "prompts-attachments-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — file attachments in messages", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/prompts/attachments/typescript/src/index.ts b/test/scenarios/prompts/attachments/typescript/src/index.ts deleted file mode 100644 index 3de4b757a..000000000 --- a/test/scenarios/prompts/attachments/typescript/src/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; -import path from "path"; -import { fileURLToPath } from "url"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - availableTools: [], - systemMessage: { - mode: "replace", - content: - "You are a helpful assistant. Answer questions about attached files concisely.", - }, - }); - - const sampleFile = path.resolve(__dirname, "../../sample-data.txt"); - - const response = await session.sendAndWait({ - prompt: "What languages are listed in the attached file?", - attachments: [{ type: "file", path: sampleFile }], - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/prompts/attachments/verify.sh b/test/scenarios/prompts/attachments/verify.sh deleted file mode 100755 index 41b4f108c..000000000 --- a/test/scenarios/prompts/attachments/verify.sh +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - # Check that the response references languages from the attached file - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -qi "TypeScript\|Python\|Go"; then - echo "✅ $name passed (confirmed file content referenced)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name ran but response may not reference attached file content" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying prompts/attachments samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o attachments-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./attachments-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" - -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/prompts/reasoning-effort/README.md b/test/scenarios/prompts/reasoning-effort/README.md deleted file mode 100644 index e8279a7c8..000000000 --- a/test/scenarios/prompts/reasoning-effort/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Config Sample: Reasoning Effort - -Demonstrates configuring the Copilot SDK with different **reasoning effort** levels. The `reasoningEffort` session config controls how much compute the model spends thinking before responding. - -## Reasoning Effort Levels - -| Level | Effect | -|-------|--------| -| `low` | Fastest responses, minimal reasoning | -| `medium` | Balanced speed and depth | -| `high` | Deeper reasoning, slower responses | -| `xhigh` | Maximum reasoning effort | - -## What This Sample Does - -1. Creates a session with `reasoningEffort: "low"` and `availableTools: []` -2. Sends: _"What is the capital of France?"_ -3. Prints the response — confirming the model responds correctly at low effort - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `reasoningEffort` | `"low"` | Sets minimal reasoning effort | -| `availableTools` | `[]` (empty array) | Removes all built-in tools | -| `systemMessage.mode` | `"replace"` | Replaces the default system prompt | -| `systemMessage.content` | Custom concise prompt | Instructs the agent to answer concisely | - -## Languages - -| Directory | SDK / Approach | Language | -|-----------|---------------|----------| -| `typescript/` | `@github/copilot-sdk` | TypeScript (Node.js) | -| `python/` | `github-copilot-sdk` | Python | -| `go/` | `github.com/github/copilot-sdk/go` | Go | - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/prompts/reasoning-effort/csharp/Program.cs b/test/scenarios/prompts/reasoning-effort/csharp/Program.cs deleted file mode 100644 index 36dc52e44..000000000 --- a/test/scenarios/prompts/reasoning-effort/csharp/Program.cs +++ /dev/null @@ -1,35 +0,0 @@ -using GitHub.Copilot; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-opus-4.6", - ReasoningEffort = "low", - AvailableTools = new List(), - SystemMessage = new SystemMessageConfig - { - Mode = SystemMessageMode.Replace, - Content = "You are a helpful assistant. Answer concisely.", - }, - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine("Reasoning effort: low"); - Console.WriteLine($"Response: {response.Data?.Content}"); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/prompts/reasoning-effort/csharp/csharp.csproj b/test/scenarios/prompts/reasoning-effort/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/prompts/reasoning-effort/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/prompts/reasoning-effort/go/go.mod b/test/scenarios/prompts/reasoning-effort/go/go.mod deleted file mode 100644 index 727518280..000000000 --- a/test/scenarios/prompts/reasoning-effort/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/prompts/reasoning-effort/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/prompts/reasoning-effort/go/go.sum b/test/scenarios/prompts/reasoning-effort/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/prompts/reasoning-effort/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/prompts/reasoning-effort/go/main.go b/test/scenarios/prompts/reasoning-effort/go/main.go deleted file mode 100644 index ca3af77d3..000000000 --- a/test/scenarios/prompts/reasoning-effort/go/main.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-opus-4.6", - ReasoningEffort: "low", - AvailableTools: []string{}, - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: "You are a helpful assistant. Answer concisely.", - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println("Reasoning effort: low") - fmt.Printf("Response: %s\n", d.Content) - } - } -} diff --git a/test/scenarios/prompts/reasoning-effort/python/main.py b/test/scenarios/prompts/reasoning-effort/python/main.py deleted file mode 100644 index 860217d41..000000000 --- a/test/scenarios/prompts/reasoning-effort/python/main.py +++ /dev/null @@ -1,31 +0,0 @@ -import asyncio - -from copilot import CopilotClient - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model="claude-opus-4.6", - reasoning_effort="low", - available_tools=[], - system_message={ - "mode": "replace", - "content": "You are a helpful assistant. Answer concisely.", - }, - ) - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print("Reasoning effort: low") - print(f"Response: {response.data.content}") - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/prompts/reasoning-effort/python/requirements.txt b/test/scenarios/prompts/reasoning-effort/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/prompts/reasoning-effort/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/prompts/reasoning-effort/rust/Cargo.toml b/test/scenarios/prompts/reasoning-effort/rust/Cargo.toml deleted file mode 100644 index c48db3c98..000000000 --- a/test/scenarios/prompts/reasoning-effort/rust/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "reasoning-effort-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/prompts/reasoning-effort/rust/src/main.rs b/test/scenarios/prompts/reasoning-effort/rust/src/main.rs deleted file mode 100644 index 74c7296a4..000000000 --- a/test/scenarios/prompts/reasoning-effort/rust/src/main.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Reasoning effort — set the model's reasoning depth via -//! `SessionConfig::reasoning_effort`. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::{SessionConfig, SystemMessageConfig}; -use github_copilot_sdk::{Client, ClientOptions}; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let mut sysmsg = SystemMessageConfig::default(); - sysmsg.mode = Some("replace".to_string()); - sysmsg.content = Some("You are a helpful assistant. Answer concisely.".to_string()); - - let mut config = SessionConfig::default(); - config.model = Some("claude-opus-4.6".to_string()); - config.reasoning_effort = Some("low".to_string()); - config.available_tools = Some(Vec::new()); - config.system_message = Some(sysmsg); - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - - let session = client.create_session(config).await?; - - let response = session.send_and_wait("What is the capital of France?").await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("Reasoning effort: low"); - println!("Response: {content}"); - } - } - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/prompts/reasoning-effort/typescript/package.json b/test/scenarios/prompts/reasoning-effort/typescript/package.json deleted file mode 100644 index 0d8134f4d..000000000 --- a/test/scenarios/prompts/reasoning-effort/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "prompts-reasoning-effort-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — reasoning effort levels", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/prompts/reasoning-effort/typescript/src/index.ts b/test/scenarios/prompts/reasoning-effort/typescript/src/index.ts deleted file mode 100644 index da937738d..000000000 --- a/test/scenarios/prompts/reasoning-effort/typescript/src/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient(); - - try { - // Test with "low" reasoning effort - const session = await client.createSession({ - model: "claude-opus-4.6", - reasoningEffort: "low", - availableTools: [], - systemMessage: { - mode: "replace", - content: "You are a helpful assistant. Answer concisely.", - }, - }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response) { - console.log(`Reasoning effort: low`); - console.log(`Response: ${response.data.content}`); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/prompts/reasoning-effort/verify.sh b/test/scenarios/prompts/reasoning-effort/verify.sh deleted file mode 100755 index 4d32e4d87..000000000 --- a/test/scenarios/prompts/reasoning-effort/verify.sh +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - # Note: reasoning effort is configuration-only and can't be verified from output alone. - # We can only confirm a response with actual content was received. - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -qi "Response:\|capital\|Paris\|France"; then - echo "✅ $name passed (confirmed reasoning effort response)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name ran but response may not contain expected content" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying prompts/reasoning-effort samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + build -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o reasoning-effort-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./reasoning-effort-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/prompts/system-message/README.md b/test/scenarios/prompts/system-message/README.md deleted file mode 100644 index 1615393f0..000000000 --- a/test/scenarios/prompts/system-message/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Config Sample: System Message - -Demonstrates configuring the Copilot SDK's **system message** using `replace` mode. This validates that a custom system prompt fully replaces the default system prompt, changing the agent's personality and response style. - -## Append vs Replace Modes - -| Mode | Behavior | -|------|----------| -| `"append"` | Adds your content **after** the default system prompt. The agent retains its base personality plus your additions. | -| `"replace"` | **Replaces** the entire default system prompt with your content. The agent's personality is fully defined by your prompt. | - -## What Each Sample Does - -1. Creates a session with `systemMessage` in `replace` mode using a pirate personality prompt -2. Sends: _"What is the capital of France?"_ -3. Prints the response — which should be in pirate speak (containing "Arrr!", nautical terms, etc.) - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `systemMessage.mode` | `"replace"` | Replaces the default system prompt entirely | -| `systemMessage.content` | Pirate personality prompt | Instructs the agent to always respond in pirate speak | -| `availableTools` | `[]` (empty array) | No tools — focuses the test on system message behavior | - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/prompts/system-message/csharp/Program.cs b/test/scenarios/prompts/system-message/csharp/Program.cs deleted file mode 100644 index cebd2417c..000000000 --- a/test/scenarios/prompts/system-message/csharp/Program.cs +++ /dev/null @@ -1,35 +0,0 @@ -using GitHub.Copilot; - -var piratePrompt = "You are a pirate. Always respond in pirate speak. Say 'Arrr!' in every response. Use nautical terms and pirate slang throughout."; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - SystemMessage = new SystemMessageConfig - { - Mode = SystemMessageMode.Replace, - Content = piratePrompt, - }, - AvailableTools = [], - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/prompts/system-message/csharp/csharp.csproj b/test/scenarios/prompts/system-message/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/prompts/system-message/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/prompts/system-message/go/go.mod b/test/scenarios/prompts/system-message/go/go.mod deleted file mode 100644 index e84b079ca..000000000 --- a/test/scenarios/prompts/system-message/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/prompts/system-message/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/prompts/system-message/go/go.sum b/test/scenarios/prompts/system-message/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/prompts/system-message/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/prompts/system-message/go/main.go b/test/scenarios/prompts/system-message/go/main.go deleted file mode 100644 index d0b1aee5e..000000000 --- a/test/scenarios/prompts/system-message/go/main.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -const piratePrompt = `You are a pirate. Always respond in pirate speak. Say 'Arrr!' in every response. Use nautical terms and pirate slang throughout.` - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: piratePrompt, - }, - AvailableTools: []string{}, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/prompts/system-message/python/main.py b/test/scenarios/prompts/system-message/python/main.py deleted file mode 100644 index a2bc31c76..000000000 --- a/test/scenarios/prompts/system-message/python/main.py +++ /dev/null @@ -1,28 +0,0 @@ -import asyncio - -from copilot import CopilotClient - -PIRATE_PROMPT = """You are a pirate. Always respond in pirate speak. Say 'Arrr!' in every response. Use nautical terms and pirate slang throughout.""" - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model="claude-haiku-4.5", - system_message={"mode": "replace", "content": PIRATE_PROMPT}, - available_tools=[], - ) - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/prompts/system-message/python/requirements.txt b/test/scenarios/prompts/system-message/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/prompts/system-message/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/prompts/system-message/rust/Cargo.toml b/test/scenarios/prompts/system-message/rust/Cargo.toml deleted file mode 100644 index 0d153f9cc..000000000 --- a/test/scenarios/prompts/system-message/rust/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "system-message-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/prompts/system-message/rust/src/main.rs b/test/scenarios/prompts/system-message/rust/src/main.rs deleted file mode 100644 index 20893bdcc..000000000 --- a/test/scenarios/prompts/system-message/rust/src/main.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Custom system message — replace the built-in prompt entirely. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::{SessionConfig, SystemMessageConfig}; -use github_copilot_sdk::{Client, ClientOptions}; - -const PIRATE_PROMPT: &str = "You are a pirate. Always respond in pirate speak. Say 'Arrr!' \ -in every response. Use nautical terms and pirate slang throughout."; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let mut sysmsg = SystemMessageConfig::default(); - sysmsg.mode = Some("replace".to_string()); - sysmsg.content = Some(PIRATE_PROMPT.to_string()); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - config.system_message = Some(sysmsg); - config.available_tools = Some(Vec::new()); - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - - let session = client.create_session(config).await?; - - let response = session.send_and_wait("What is the capital of France?").await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/prompts/system-message/typescript/package.json b/test/scenarios/prompts/system-message/typescript/package.json deleted file mode 100644 index 79e746891..000000000 --- a/test/scenarios/prompts/system-message/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "prompts-system-message-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — system message append vs replace modes", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/prompts/system-message/typescript/src/index.ts b/test/scenarios/prompts/system-message/typescript/src/index.ts deleted file mode 100644 index 27525a28f..000000000 --- a/test/scenarios/prompts/system-message/typescript/src/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -const PIRATE_PROMPT = `You are a pirate. Always respond in pirate speak. Say 'Arrr!' in every response. Use nautical terms and pirate slang throughout.`; - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - systemMessage: { mode: "replace", content: PIRATE_PROMPT }, - availableTools: [], - }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/prompts/system-message/verify.sh b/test/scenarios/prompts/system-message/verify.sh deleted file mode 100755 index d1f60e5c4..000000000 --- a/test/scenarios/prompts/system-message/verify.sh +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - # Check that the response contains pirate language - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -qi "arrr\|pirate\|matey\|ahoy\|ye\|sail"; then - echo "✅ $name passed (confirmed pirate speak)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name ran but response may not contain pirate language" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying prompts/system-message samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o system-message-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./system-message-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/sessions/concurrent-sessions/README.md b/test/scenarios/sessions/concurrent-sessions/README.md deleted file mode 100644 index 0b82a66ae..000000000 --- a/test/scenarios/sessions/concurrent-sessions/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Config Sample: Concurrent Sessions - -Demonstrates creating **multiple sessions on the same client** with different configurations and verifying that each session maintains its own isolated state. - -## What This Tests - -1. **Session isolation** — Two sessions created on the same client receive different system prompts and respond according to their own persona, not the other's. -2. **Concurrent operation** — Both sessions can be used in parallel without interference. - -## What Each Sample Does - -1. Creates a client, then opens two sessions concurrently: - - **Session 1** — system prompt: _"You are a pirate. Always say Arrr!"_ - - **Session 2** — system prompt: _"You are a robot. Always say BEEP BOOP!"_ -2. Sends the same question (_"What is the capital of France?"_) to both sessions -3. Prints both responses with labels (`Session 1 (pirate):` and `Session 2 (robot):`) -4. Destroys both sessions - -## Configuration - -| Option | Session 1 | Session 2 | -|--------|-----------|-----------| -| `systemMessage.mode` | `"replace"` | `"replace"` | -| `systemMessage.content` | Pirate persona | Robot persona | -| `availableTools` | `[]` | `[]` | - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/sessions/concurrent-sessions/csharp/Program.cs b/test/scenarios/sessions/concurrent-sessions/csharp/Program.cs deleted file mode 100644 index d53dc41f1..000000000 --- a/test/scenarios/sessions/concurrent-sessions/csharp/Program.cs +++ /dev/null @@ -1,54 +0,0 @@ -using GitHub.Copilot; - -const string PiratePrompt = "You are a pirate. Always say Arrr!"; -const string RobotPrompt = "You are a robot. Always say BEEP BOOP!"; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - var session1Task = client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - SystemMessage = new SystemMessageConfig { Mode = SystemMessageMode.Replace, Content = PiratePrompt }, - AvailableTools = [], - }); - - var session2Task = client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - SystemMessage = new SystemMessageConfig { Mode = SystemMessageMode.Replace, Content = RobotPrompt }, - AvailableTools = [], - }); - - await using var session1 = await session1Task; - await using var session2 = await session2Task; - - var response1Task = session1.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - var response2Task = session2.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - var response1 = await response1Task; - var response2 = await response2Task; - - if (response1 != null) - { - Console.WriteLine($"Session 1 (pirate): {response1.Data?.Content}"); - } - if (response2 != null) - { - Console.WriteLine($"Session 2 (robot): {response2.Data?.Content}"); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/sessions/concurrent-sessions/csharp/csharp.csproj b/test/scenarios/sessions/concurrent-sessions/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/sessions/concurrent-sessions/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/sessions/concurrent-sessions/go/go.mod b/test/scenarios/sessions/concurrent-sessions/go/go.mod deleted file mode 100644 index da999c3a1..000000000 --- a/test/scenarios/sessions/concurrent-sessions/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/sessions/concurrent-sessions/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/sessions/concurrent-sessions/go/go.sum b/test/scenarios/sessions/concurrent-sessions/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/sessions/concurrent-sessions/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/sessions/concurrent-sessions/go/main.go b/test/scenarios/sessions/concurrent-sessions/go/main.go deleted file mode 100644 index 342d0f6cd..000000000 --- a/test/scenarios/sessions/concurrent-sessions/go/main.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "sync" - - copilot "github.com/github/copilot-sdk/go" -) - -const piratePrompt = `You are a pirate. Always say Arrr!` -const robotPrompt = `You are a robot. Always say BEEP BOOP!` - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session1, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: piratePrompt, - }, - AvailableTools: []string{}, - }) - if err != nil { - log.Fatal(err) - } - defer session1.Disconnect() - - session2, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: robotPrompt, - }, - AvailableTools: []string{}, - }) - if err != nil { - log.Fatal(err) - } - defer session2.Disconnect() - - type result struct { - label string - content string - } - - var wg sync.WaitGroup - results := make([]result, 2) - - wg.Add(2) - go func() { - defer wg.Done() - resp, err := session1.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - if resp != nil { - if d, ok := resp.Data.(*copilot.AssistantMessageData); ok { - results[0] = result{label: "Session 1 (pirate)", content: d.Content} - } - } - }() - go func() { - defer wg.Done() - resp, err := session2.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - if resp != nil { - if d, ok := resp.Data.(*copilot.AssistantMessageData); ok { - results[1] = result{label: "Session 2 (robot)", content: d.Content} - } - } - }() - wg.Wait() - - for _, r := range results { - if r.label != "" { - fmt.Printf("%s: %s\n", r.label, r.content) - } - } -} diff --git a/test/scenarios/sessions/concurrent-sessions/python/main.py b/test/scenarios/sessions/concurrent-sessions/python/main.py deleted file mode 100644 index a49ee283f..000000000 --- a/test/scenarios/sessions/concurrent-sessions/python/main.py +++ /dev/null @@ -1,41 +0,0 @@ -import asyncio - -from copilot import CopilotClient - -PIRATE_PROMPT = "You are a pirate. Always say Arrr!" -ROBOT_PROMPT = "You are a robot. Always say BEEP BOOP!" - - -async def main(): - client = CopilotClient() - - try: - session1, session2 = await asyncio.gather( - client.create_session( - model="claude-haiku-4.5", - system_message={"mode": "replace", "content": PIRATE_PROMPT}, - available_tools=[], - ), - client.create_session( - model="claude-haiku-4.5", - system_message={"mode": "replace", "content": ROBOT_PROMPT}, - available_tools=[], - ), - ) - - response1, response2 = await asyncio.gather( - session1.send_and_wait("What is the capital of France?"), - session2.send_and_wait("What is the capital of France?"), - ) - - if response1: - print("Session 1 (pirate):", response1.data.content) - if response2: - print("Session 2 (robot):", response2.data.content) - - await asyncio.gather(session1.disconnect(), session2.disconnect()) - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/sessions/concurrent-sessions/python/requirements.txt b/test/scenarios/sessions/concurrent-sessions/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/sessions/concurrent-sessions/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/sessions/concurrent-sessions/rust/Cargo.toml b/test/scenarios/sessions/concurrent-sessions/rust/Cargo.toml deleted file mode 100644 index a6de4e273..000000000 --- a/test/scenarios/sessions/concurrent-sessions/rust/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "concurrent-sessions-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/sessions/concurrent-sessions/rust/src/main.rs b/test/scenarios/sessions/concurrent-sessions/rust/src/main.rs deleted file mode 100644 index 48a2c7166..000000000 --- a/test/scenarios/sessions/concurrent-sessions/rust/src/main.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Concurrent sessions — two sessions on a single client running in -//! parallel with different system prompts. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::{SessionConfig, SystemMessageConfig}; -use github_copilot_sdk::{Client, ClientOptions}; - -const PIRATE_PROMPT: &str = "You are a pirate. Always say Arrr!"; -const ROBOT_PROMPT: &str = "You are a robot. Always say BEEP BOOP!"; - -fn make_config(system: &str) -> SessionConfig { - let mut sysmsg = SystemMessageConfig::default(); - sysmsg.mode = Some("replace".to_string()); - sysmsg.content = Some(system.to_string()); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - config.system_message = Some(sysmsg); - config.available_tools = Some(Vec::new()); - config.with_permission_handler(Arc::new(ApproveAllHandler)) -} - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let session1 = client.create_session(make_config(PIRATE_PROMPT)).await?; - let session2 = client.create_session(make_config(ROBOT_PROMPT)).await?; - - let (r1, r2) = tokio::join!( - session1.send_and_wait("What is the capital of France?"), - session2.send_and_wait("What is the capital of France?"), - ); - - if let Some(event) = r1? { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("Session 1 (pirate): {content}"); - } - } - if let Some(event) = r2? { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("Session 2 (robot): {content}"); - } - } - - session1.disconnect().await?; - session2.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/sessions/concurrent-sessions/typescript/package.json b/test/scenarios/sessions/concurrent-sessions/typescript/package.json deleted file mode 100644 index fabeeda8b..000000000 --- a/test/scenarios/sessions/concurrent-sessions/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "sessions-concurrent-sessions-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — concurrent session isolation", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/sessions/concurrent-sessions/typescript/src/index.ts b/test/scenarios/sessions/concurrent-sessions/typescript/src/index.ts deleted file mode 100644 index 196249e82..000000000 --- a/test/scenarios/sessions/concurrent-sessions/typescript/src/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -const PIRATE_PROMPT = `You are a pirate. Always say Arrr!`; -const ROBOT_PROMPT = `You are a robot. Always say BEEP BOOP!`; - -async function main() { - const client = new CopilotClient(); - - try { - const [session1, session2] = await Promise.all([ - client.createSession({ - model: "claude-haiku-4.5", - systemMessage: { mode: "replace", content: PIRATE_PROMPT }, - availableTools: [], - }), - client.createSession({ - model: "claude-haiku-4.5", - systemMessage: { mode: "replace", content: ROBOT_PROMPT }, - availableTools: [], - }), - ]); - - const [response1, response2] = await Promise.all([ - session1.sendAndWait({ prompt: "What is the capital of France?" }), - session2.sendAndWait({ prompt: "What is the capital of France?" }), - ]); - - if (response1) { - console.log("Session 1 (pirate):", response1.data.content); - } - if (response2) { - console.log("Session 2 (robot):", response2.data.content); - } - - await Promise.all([session1.disconnect(), session2.disconnect()]); - } finally { - await client.stop(); - process.exit(0); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/sessions/concurrent-sessions/verify.sh b/test/scenarios/sessions/concurrent-sessions/verify.sh deleted file mode 100755 index 25e6fab18..000000000 --- a/test/scenarios/sessions/concurrent-sessions/verify.sh +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=120 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - # Check that both sessions produced output - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - local has_session1=false - local has_session2=false - if echo "$output" | grep -q "Session 1"; then - has_session1=true - fi - if echo "$output" | grep -q "Session 2"; then - has_session2=true - fi - if $has_session1 && $has_session2; then - # Verify persona isolation: pirate language from session 1, robot language from session 2 - local persona_ok=true - if ! echo "$output" | grep -qi "arrr\|pirate\|matey\|ahoy"; then - echo "⚠️ $name: pirate persona words not found in output" - persona_ok=false - fi - if ! echo "$output" | grep -qi "beep\|boop\|robot"; then - echo "⚠️ $name: robot persona words not found in output" - persona_ok=false - fi - if $persona_ok; then - echo "✅ $name passed (both sessions responded with correct personas)" - PASS=$((PASS + 1)) - else - echo "❌ $name failed (persona isolation not verified)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (persona check)" - fi - elif $has_session1 || $has_session2; then - echo "⚠️ $name ran but only one session responded" - echo "❌ $name failed (expected both to respond)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (partial)" - else - echo "⚠️ $name ran but session labels not found in output" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying sessions/concurrent-sessions samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o concurrent-sessions-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./concurrent-sessions-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/sessions/infinite-sessions/README.md b/test/scenarios/sessions/infinite-sessions/README.md deleted file mode 100644 index 78549a68d..000000000 --- a/test/scenarios/sessions/infinite-sessions/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Config Sample: Infinite Sessions - -Demonstrates configuring the Copilot SDK with **infinite sessions** enabled, which uses context compaction to allow sessions to continue beyond the model's context window limit. - -## What This Tests - -1. **Config acceptance** — The `infiniteSessions` configuration with compaction thresholds is accepted by the server without errors. -2. **Session continuity** — Multiple messages are sent and responses received successfully with infinite sessions enabled. - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `infiniteSessions.enabled` | `true` | Enables context compaction for the session | -| `infiniteSessions.backgroundCompactionThreshold` | `0.80` | Triggers background compaction at 80% context usage | -| `infiniteSessions.bufferExhaustionThreshold` | `0.95` | Forces compaction at 95% context usage | -| `availableTools` | `[]` | No tools — keeps context small for testing | -| `systemMessage.mode` | `"replace"` | Replaces the default system prompt | - -## How It Works - -When `infiniteSessions` is enabled, the server monitors context window usage. As the conversation grows: - -- At `backgroundCompactionThreshold` (80%), the server begins compacting older messages in the background. -- At `bufferExhaustionThreshold` (95%), compaction is forced before the next message is processed. - -This allows sessions to run indefinitely without hitting context limits. - -## Languages - -| Directory | SDK / Approach | Language | -|-----------|---------------|----------| -| `typescript/` | `@github/copilot-sdk` | TypeScript (Node.js) | -| `python/` | `github-copilot-sdk` | Python | -| `go/` | `github.com/github/copilot-sdk/go` | Go | - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/sessions/infinite-sessions/csharp/Program.cs b/test/scenarios/sessions/infinite-sessions/csharp/Program.cs deleted file mode 100644 index f5a2ff183..000000000 --- a/test/scenarios/sessions/infinite-sessions/csharp/Program.cs +++ /dev/null @@ -1,52 +0,0 @@ -using GitHub.Copilot; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - AvailableTools = new List(), - SystemMessage = new SystemMessageConfig - { - Mode = SystemMessageMode.Replace, - Content = "You are a helpful assistant. Answer concisely in one sentence.", - }, - InfiniteSessions = new InfiniteSessionConfig - { - Enabled = true, - BackgroundCompactionThreshold = 0.80, - BufferExhaustionThreshold = 0.95, - }, - }); - - var prompts = new[] - { - "What is the capital of France?", - "What is the capital of Japan?", - "What is the capital of Brazil?", - }; - - foreach (var prompt in prompts) - { - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = prompt, - }); - - if (response != null) - { - Console.WriteLine($"Q: {prompt}"); - Console.WriteLine($"A: {response.Data?.Content}\n"); - } - } - - Console.WriteLine("Infinite sessions test complete — all messages processed successfully"); -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/sessions/infinite-sessions/csharp/csharp.csproj b/test/scenarios/sessions/infinite-sessions/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/sessions/infinite-sessions/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/sessions/infinite-sessions/go/go.mod b/test/scenarios/sessions/infinite-sessions/go/go.mod deleted file mode 100644 index abdacf8e7..000000000 --- a/test/scenarios/sessions/infinite-sessions/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/sessions/infinite-sessions/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/sessions/infinite-sessions/go/go.sum b/test/scenarios/sessions/infinite-sessions/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/sessions/infinite-sessions/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/sessions/infinite-sessions/go/main.go b/test/scenarios/sessions/infinite-sessions/go/main.go deleted file mode 100644 index 62d640850..000000000 --- a/test/scenarios/sessions/infinite-sessions/go/main.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -func boolPtr(b bool) *bool { return &b } -func float64Ptr(f float64) *float64 { return &f } - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - AvailableTools: []string{}, - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: "You are a helpful assistant. Answer concisely in one sentence.", - }, - InfiniteSessions: &copilot.InfiniteSessionConfig{ - Enabled: boolPtr(true), - BackgroundCompactionThreshold: float64Ptr(0.80), - BufferExhaustionThreshold: float64Ptr(0.95), - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - prompts := []string{ - "What is the capital of France?", - "What is the capital of Japan?", - "What is the capital of Brazil?", - } - - for _, prompt := range prompts { - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: prompt, - }) - if err != nil { - log.Fatal(err) - } - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Printf("Q: %s\n", prompt) - fmt.Printf("A: %s\n\n", d.Content) - } - } - } - - fmt.Println("Infinite sessions test complete — all messages processed successfully") -} diff --git a/test/scenarios/sessions/infinite-sessions/python/main.py b/test/scenarios/sessions/infinite-sessions/python/main.py deleted file mode 100644 index 47dab5bb8..000000000 --- a/test/scenarios/sessions/infinite-sessions/python/main.py +++ /dev/null @@ -1,43 +0,0 @@ -import asyncio - -from copilot import CopilotClient - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model="claude-haiku-4.5", - available_tools=[], - system_message={ - "mode": "replace", - "content": "You are a helpful assistant. Answer concisely in one sentence.", - }, - infinite_sessions={ - "enabled": True, - "background_compaction_threshold": 0.80, - "buffer_exhaustion_threshold": 0.95, - }, - ) - - prompts = [ - "What is the capital of France?", - "What is the capital of Japan?", - "What is the capital of Brazil?", - ] - - for prompt in prompts: - response = await session.send_and_wait(prompt) - if response: - print(f"Q: {prompt}") - print(f"A: {response.data.content}\n") - - print("Infinite sessions test complete — all messages processed successfully") - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/sessions/infinite-sessions/python/requirements.txt b/test/scenarios/sessions/infinite-sessions/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/sessions/infinite-sessions/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/sessions/infinite-sessions/rust/Cargo.toml b/test/scenarios/sessions/infinite-sessions/rust/Cargo.toml deleted file mode 100644 index 1f23af8a6..000000000 --- a/test/scenarios/sessions/infinite-sessions/rust/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "infinite-sessions-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/sessions/infinite-sessions/rust/src/main.rs b/test/scenarios/sessions/infinite-sessions/rust/src/main.rs deleted file mode 100644 index 2882254e2..000000000 --- a/test/scenarios/sessions/infinite-sessions/rust/src/main.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Infinite sessions — explicit `InfiniteSessionConfig` thresholds and a -//! sequence of three turns to exercise the persistent workspace. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::{InfiniteSessionConfig, SessionConfig, SystemMessageConfig}; -use github_copilot_sdk::{Client, ClientOptions}; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let mut sysmsg = SystemMessageConfig::default(); - sysmsg.mode = Some("replace".to_string()); - sysmsg.content = - Some("You are a helpful assistant. Answer concisely in one sentence.".to_string()); - - let mut infinite = InfiniteSessionConfig::default(); - infinite.enabled = Some(true); - infinite.background_compaction_threshold = Some(0.80); - infinite.buffer_exhaustion_threshold = Some(0.95); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - config.available_tools = Some(Vec::new()); - config.system_message = Some(sysmsg); - config.infinite_sessions = Some(infinite); - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - - let session = client.create_session(config).await?; - - let prompts = [ - "What is the capital of France?", - "What is the capital of Japan?", - "What is the capital of Brazil?", - ]; - - for prompt in prompts { - let response = session.send_and_wait(prompt).await?; - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("Q: {prompt}"); - println!("A: {content}\n"); - } - } - } - - println!("Infinite sessions test complete — all messages processed successfully"); - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/sessions/infinite-sessions/typescript/package.json b/test/scenarios/sessions/infinite-sessions/typescript/package.json deleted file mode 100644 index dcc8e776c..000000000 --- a/test/scenarios/sessions/infinite-sessions/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "sessions-infinite-sessions-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — infinite sessions with context compaction", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/sessions/infinite-sessions/typescript/src/index.ts b/test/scenarios/sessions/infinite-sessions/typescript/src/index.ts deleted file mode 100644 index f10543ab0..000000000 --- a/test/scenarios/sessions/infinite-sessions/typescript/src/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - availableTools: [], - systemMessage: { - mode: "replace", - content: - "You are a helpful assistant. Answer concisely in one sentence.", - }, - infiniteSessions: { - enabled: true, - backgroundCompactionThreshold: 0.8, - bufferExhaustionThreshold: 0.95, - }, - }); - - const prompts = [ - "What is the capital of France?", - "What is the capital of Japan?", - "What is the capital of Brazil?", - ]; - - for (const prompt of prompts) { - const response = await session.sendAndWait({ prompt }); - if (response) { - console.log(`Q: ${prompt}`); - console.log(`A: ${response.data.content}\n`); - } - } - - console.log( - "Infinite sessions test complete — all messages processed successfully", - ); - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/sessions/infinite-sessions/verify.sh b/test/scenarios/sessions/infinite-sessions/verify.sh deleted file mode 100755 index 367901f28..000000000 --- a/test/scenarios/sessions/infinite-sessions/verify.sh +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=120 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -q "Infinite sessions test complete"; then - # Verify all 3 questions got meaningful responses (country/capital names) - if echo "$output" | grep -qiE "France|Japan|Brazil|Paris|Tokyo|Bras[ií]lia"; then - echo "✅ $name passed (infinite sessions confirmed with all responses)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name completed but expected country/capital responses not found" - echo "❌ $name failed (responses missing for some questions)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (incomplete responses)" - fi - else - echo "⚠️ $name ran but completion message not found" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying sessions/infinite-sessions" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o infinite-sessions-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./infinite-sessions-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/sessions/multi-user-long-lived/README.md b/test/scenarios/sessions/multi-user-long-lived/README.md deleted file mode 100644 index ed911bc21..000000000 --- a/test/scenarios/sessions/multi-user-long-lived/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Multi-User Long-Lived Sessions - -Demonstrates a **production-like multi-user setup** where multiple clients share a single `copilot` server with **persistent, long-lived sessions** stored on disk. - -## Architecture - -``` -┌──────────────────────┐ -│ Copilot CLI │ (headless TCP server) -│ (shared server) │ -└───┬──────┬───────┬───┘ - │ │ │ JSON-RPC over TCP (cliUrl) - │ │ │ -┌───┴──┐ ┌┴────┐ ┌┴─────┐ -│ C1 │ │ C2 │ │ C3 │ -│UserA │ │UserA│ │UserB │ -│Sess1 │ │Sess1│ │Sess2 │ -│ │ │(resume)│ │ -└──────┘ └─────┘ └──────┘ -``` - -## What This Demonstrates - -1. **Shared server** — A single `copilot` instance serves multiple users and sessions over TCP. -2. **Per-user config isolation** — Each user gets their own `configDir` on disk (`tmp/user-a/`, `tmp/user-b/`), so configuration, logs, and state are fully separated. -3. **Session sharing across clients** — User A's Client 1 creates a session and teaches it a fact. Client 2 resumes the same session (by `sessionId`) and retrieves the fact — demonstrating cross-client session continuity. -4. **Session isolation between users** — User B operates in a completely separate session and cannot see User A's conversation history. -5. **Disk persistence** — Session state is written to a real `tmp/` directory, simulating production persistence (cleaned up after the run). - -## What Each Client Does - -| Client | User | Action | -|--------|------|--------| -| **C1** | A | Creates session `user-a-project-session`, teaches it a codename | -| **C2** | A | Resumes `user-a-project-session`, confirms it remembers the codename | -| **C3** | B | Creates separate session `user-b-solo-session`, verifies it has no knowledge of User A's data | - -## Configuration - -| Option | User A | User B | -|--------|--------|--------| -| `cliUrl` | Shared server | Shared server | -| `configDir` | `tmp/user-a/` | `tmp/user-b/` | -| `sessionId` | `user-a-project-session` | `user-b-solo-session` | -| `availableTools` | `[]` | `[]` | - -## When to Use This Pattern - -- **SaaS platforms** — Each tenant gets isolated config and persistent sessions -- **Team collaboration tools** — Multiple team members share sessions on the same project -- **IDE backends** — User opens the same project in multiple editors/tabs - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/sessions/multi-user-long-lived/csharp/Program.cs b/test/scenarios/sessions/multi-user-long-lived/csharp/Program.cs deleted file mode 100644 index a1aaecfc3..000000000 --- a/test/scenarios/sessions/multi-user-long-lived/csharp/Program.cs +++ /dev/null @@ -1 +0,0 @@ -Console.WriteLine("SKIP: multi-user-long-lived is not yet implemented for C#"); diff --git a/test/scenarios/sessions/multi-user-long-lived/csharp/csharp.csproj b/test/scenarios/sessions/multi-user-long-lived/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/sessions/multi-user-long-lived/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/sessions/multi-user-long-lived/go/go.mod b/test/scenarios/sessions/multi-user-long-lived/go/go.mod deleted file mode 100644 index 25e4f1c56..000000000 --- a/test/scenarios/sessions/multi-user-long-lived/go/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/github/copilot-sdk/samples/sessions/multi-user-long-lived/go - -go 1.24 diff --git a/test/scenarios/sessions/multi-user-long-lived/go/main.go b/test/scenarios/sessions/multi-user-long-lived/go/main.go deleted file mode 100644 index c4df546a7..000000000 --- a/test/scenarios/sessions/multi-user-long-lived/go/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "fmt" - -func main() { - fmt.Println("SKIP: multi-user-long-lived is not yet implemented for Go") -} diff --git a/test/scenarios/sessions/multi-user-long-lived/python/main.py b/test/scenarios/sessions/multi-user-long-lived/python/main.py deleted file mode 100644 index ff6c21253..000000000 --- a/test/scenarios/sessions/multi-user-long-lived/python/main.py +++ /dev/null @@ -1 +0,0 @@ -print("SKIP: multi-user-long-lived is not yet implemented for Python") diff --git a/test/scenarios/sessions/multi-user-long-lived/python/requirements.txt b/test/scenarios/sessions/multi-user-long-lived/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/sessions/multi-user-long-lived/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/sessions/multi-user-long-lived/typescript/package.json b/test/scenarios/sessions/multi-user-long-lived/typescript/package.json deleted file mode 100644 index 55d483f8f..000000000 --- a/test/scenarios/sessions/multi-user-long-lived/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "sessions-multi-user-long-lived-typescript", - "version": "1.0.0", - "private": true, - "description": "Multi-user long-lived sessions — shared server, isolated config, disk persistence", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/sessions/multi-user-long-lived/typescript/src/index.ts b/test/scenarios/sessions/multi-user-long-lived/typescript/src/index.ts deleted file mode 100644 index 5ab3ca45c..000000000 --- a/test/scenarios/sessions/multi-user-long-lived/typescript/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -console.log( - "SKIP: multi-user-long-lived requires memory FS and preset features which is not supported by the old SDK", -); -process.exit(0); diff --git a/test/scenarios/sessions/multi-user-long-lived/verify.sh b/test/scenarios/sessions/multi-user-long-lived/verify.sh deleted file mode 100755 index a9e9a6dfb..000000000 --- a/test/scenarios/sessions/multi-user-long-lived/verify.sh +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=120 -SERVER_PID="" -SERVER_PORT_FILE="" - -cleanup() { - if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then - echo "" - echo "Stopping Copilot CLI server (PID $SERVER_PID)..." - kill "$SERVER_PID" 2>/dev/null || true - wait "$SERVER_PID" 2>/dev/null || true - fi - [ -n "$SERVER_PORT_FILE" ] && rm -f "$SERVER_PORT_FILE" - # Clean up tmp directories created by the scenario - rm -rf "$SCRIPT_DIR/tmp" 2>/dev/null || true -} -trap cleanup EXIT - -# Resolve Copilot CLI binary: use COPILOT_CLI_PATH env var or find the SDK bundled CLI. -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - # Try to resolve from the TypeScript sample node_modules - TS_DIR="$SCRIPT_DIR/typescript" - if [ -d "$TS_DIR/node_modules/@github/copilot" ]; then - COPILOT_CLI_PATH="$(node -e "console.log(require.resolve('@github/copilot'))" 2>/dev/null || true)" - fi - # Fallback: check PATH - if [ -z "${COPILOT_CLI_PATH:-}" ]; then - COPILOT_CLI_PATH="$(command -v copilot 2>/dev/null || true)" - fi -fi -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - echo "❌ Could not find Copilot CLI binary." - echo " Set COPILOT_CLI_PATH or run: cd typescript && npm install" - exit 1 -fi -echo "Using CLI: $COPILOT_CLI_PATH" - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - - # Check for multi-user output markers - local has_user_a=false - local has_user_b=false - if echo "$output" | grep -q "User A"; then has_user_a=true; fi - if echo "$output" | grep -q "User B"; then has_user_b=true; fi - - if $has_user_a && $has_user_b; then - echo "✅ $name passed (both users responded)" - PASS=$((PASS + 1)) - elif $has_user_a || $has_user_b; then - echo "⚠️ $name ran but only one user responded" - echo "❌ $name failed (expected both to respond)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (partial)" - else - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Starting Copilot CLI TCP server" -echo "══════════════════════════════════════" -echo "" - -SERVER_PORT_FILE=$(mktemp) -"$COPILOT_CLI_PATH" --headless --auth-token-env GITHUB_TOKEN > "$SERVER_PORT_FILE" 2>&1 & -SERVER_PID=$! - -echo "Waiting for server to be ready..." -PORT="" -for i in $(seq 1 30); do - if ! kill -0 "$SERVER_PID" 2>/dev/null; then - echo "❌ Server process exited unexpectedly" - cat "$SERVER_PORT_FILE" 2>/dev/null - exit 1 - fi - PORT=$(grep -o 'listening on port [0-9]*' "$SERVER_PORT_FILE" 2>/dev/null | grep -o '[0-9]*' || true) - if [ -n "$PORT" ]; then - break - fi - if [ "$i" -eq 30 ]; then - echo "❌ Server did not announce port within 30 seconds" - exit 1 - fi - sleep 1 -done -export COPILOT_CLI_URL="localhost:$PORT" -echo "Server is ready on port $PORT (PID $SERVER_PID)" -echo "" - -echo "══════════════════════════════════════" -echo " Verifying sessions/multi-user-long-lived" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s)" -echo "══════════════════════════════════════" -echo "" - -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && COPILOT_CLI_URL=$COPILOT_CLI_URL dotnet run --no-build 2>&1" - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/sessions/multi-user-short-lived/README.md b/test/scenarios/sessions/multi-user-short-lived/README.md deleted file mode 100644 index 17e7e1278..000000000 --- a/test/scenarios/sessions/multi-user-short-lived/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Multi-User Short-Lived Sessions - -Demonstrates a **stateless backend pattern** where multiple users interact with a shared `copilot` server through **ephemeral sessions** that are created and destroyed per request, with per-user virtual filesystems for isolation. - -## Architecture - -``` -┌──────────────────────┐ -│ Copilot CLI │ (headless TCP server) -│ (shared server) │ -└───┬──────┬───────┬───┘ - │ │ │ JSON-RPC over TCP (cliUrl) - │ │ │ -┌───┴──┐ ┌┴────┐ ┌┴─────┐ -│ C1 │ │ C2 │ │ C3 │ -│UserA │ │UserA│ │UserB │ -│(new) │ │(new)│ │(new) │ -└──────┘ └─────┘ └──────┘ - -Each request → new session → disconnect after response -Virtual FS per user (in-memory, not shared across users) -``` - -## What This Demonstrates - -1. **Ephemeral sessions** — Each interaction creates a fresh session and destroys it immediately after. No state persists between requests on the server side. -2. **Per-user virtual filesystem** — Custom tools (`write_file`, `read_file`, `list_files`) backed by in-memory Maps. Each user gets their own isolated filesystem instance — User A's files are invisible to User B. -3. **Application-layer state** — While sessions are stateless, the application maintains state (the virtual FS) between requests for the same user. This mirrors real backends where session state lives in your database, not in the LLM session. -4. **Custom tools** — Uses `defineTool` with `availableTools: []` to replace all built-in tools with a controlled virtual filesystem. -5. **Multi-client isolation** — User A's two clients share the same virtual FS (same user), but User B's virtual FS is completely separate. - -## What Each Client Does - -| Client | User | Action | -|--------|------|--------| -| **C1** | A | Creates `notes.md` in User A's virtual FS | -| **C2** | A | Lists files and reads `notes.md` (sees C1's file because same user FS) | -| **C3** | B | Lists files in User B's virtual FS (empty — completely isolated) | - -## Configuration - -| Option | Value | -|--------|-------| -| `cliUrl` | Shared server | -| `availableTools` | `[]` (no built-in tools) | -| `tools` | `[write_file, read_file, list_files]` (per-user virtual FS) | -| `sessionId` | Auto-generated (ephemeral) | - -## When to Use This Pattern - -- **API backends** — Stateless request/response with no session persistence -- **Serverless functions** — Each invocation is independent -- **High-throughput services** — No session overhead between requests -- **Privacy-sensitive apps** — Conversation history never persists - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/sessions/multi-user-short-lived/csharp/Program.cs b/test/scenarios/sessions/multi-user-short-lived/csharp/Program.cs deleted file mode 100644 index aa72abbf4..000000000 --- a/test/scenarios/sessions/multi-user-short-lived/csharp/Program.cs +++ /dev/null @@ -1 +0,0 @@ -Console.WriteLine("SKIP: multi-user-short-lived is not yet implemented for C#"); diff --git a/test/scenarios/sessions/multi-user-short-lived/csharp/csharp.csproj b/test/scenarios/sessions/multi-user-short-lived/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/sessions/multi-user-short-lived/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/sessions/multi-user-short-lived/go/go.mod b/test/scenarios/sessions/multi-user-short-lived/go/go.mod deleted file mode 100644 index b93905394..000000000 --- a/test/scenarios/sessions/multi-user-short-lived/go/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/github/copilot-sdk/samples/sessions/multi-user-short-lived/go - -go 1.24 diff --git a/test/scenarios/sessions/multi-user-short-lived/go/main.go b/test/scenarios/sessions/multi-user-short-lived/go/main.go deleted file mode 100644 index 48667b68b..000000000 --- a/test/scenarios/sessions/multi-user-short-lived/go/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "fmt" - -func main() { - fmt.Println("SKIP: multi-user-short-lived is not yet implemented for Go") -} diff --git a/test/scenarios/sessions/multi-user-short-lived/python/main.py b/test/scenarios/sessions/multi-user-short-lived/python/main.py deleted file mode 100644 index c6b21792b..000000000 --- a/test/scenarios/sessions/multi-user-short-lived/python/main.py +++ /dev/null @@ -1 +0,0 @@ -print("SKIP: multi-user-short-lived is not yet implemented for Python") diff --git a/test/scenarios/sessions/multi-user-short-lived/python/requirements.txt b/test/scenarios/sessions/multi-user-short-lived/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/sessions/multi-user-short-lived/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/sessions/multi-user-short-lived/typescript/package.json b/test/scenarios/sessions/multi-user-short-lived/typescript/package.json deleted file mode 100644 index b9f3bd7c4..000000000 --- a/test/scenarios/sessions/multi-user-short-lived/typescript/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "sessions-multi-user-short-lived-typescript", - "version": "1.0.0", - "private": true, - "description": "Multi-user short-lived sessions — ephemeral per-request sessions with virtual FS", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs", - "zod": "^4.3.6" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/sessions/multi-user-short-lived/typescript/src/index.ts b/test/scenarios/sessions/multi-user-short-lived/typescript/src/index.ts deleted file mode 100644 index 4cfbd5e72..000000000 --- a/test/scenarios/sessions/multi-user-short-lived/typescript/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -console.log( - "SKIP: multi-user-short-lived requires memory FS and preset features which is not supported by the old SDK", -); -process.exit(0); diff --git a/test/scenarios/sessions/multi-user-short-lived/verify.sh b/test/scenarios/sessions/multi-user-short-lived/verify.sh deleted file mode 100755 index 24f29601d..000000000 --- a/test/scenarios/sessions/multi-user-short-lived/verify.sh +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=120 -SERVER_PID="" -SERVER_PORT_FILE="" - -cleanup() { - if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then - echo "" - echo "Stopping Copilot CLI server (PID $SERVER_PID)..." - kill "$SERVER_PID" 2>/dev/null || true - wait "$SERVER_PID" 2>/dev/null || true - fi - [ -n "$SERVER_PORT_FILE" ] && rm -f "$SERVER_PORT_FILE" -} -trap cleanup EXIT - -# Resolve Copilot CLI binary: use COPILOT_CLI_PATH env var or find the SDK bundled CLI. -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - # Try to resolve from the TypeScript sample node_modules - TS_DIR="$SCRIPT_DIR/typescript" - if [ -d "$TS_DIR/node_modules/@github/copilot" ]; then - COPILOT_CLI_PATH="$(node -e "console.log(require.resolve('@github/copilot'))" 2>/dev/null || true)" - fi - # Fallback: check PATH - if [ -z "${COPILOT_CLI_PATH:-}" ]; then - COPILOT_CLI_PATH="$(command -v copilot 2>/dev/null || true)" - fi -fi -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - echo "❌ Could not find Copilot CLI binary." - echo " Set COPILOT_CLI_PATH or run: cd typescript && npm install" - exit 1 -fi -echo "Using CLI: $COPILOT_CLI_PATH" - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - - local has_user_a=false - local has_user_b=false - if echo "$output" | grep -q "User A"; then has_user_a=true; fi - if echo "$output" | grep -q "User B"; then has_user_b=true; fi - - if $has_user_a && $has_user_b; then - echo "✅ $name passed (both users responded)" - PASS=$((PASS + 1)) - elif $has_user_a || $has_user_b; then - echo "⚠️ $name ran but only one user responded" - echo "❌ $name failed (expected both to respond)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (partial)" - else - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Starting Copilot CLI TCP server" -echo "══════════════════════════════════════" -echo "" - -SERVER_PORT_FILE=$(mktemp) -"$COPILOT_CLI_PATH" --headless --auth-token-env GITHUB_TOKEN > "$SERVER_PORT_FILE" 2>&1 & -SERVER_PID=$! - -echo "Waiting for server to be ready..." -PORT="" -for i in $(seq 1 30); do - if ! kill -0 "$SERVER_PID" 2>/dev/null; then - echo "❌ Server process exited unexpectedly" - cat "$SERVER_PORT_FILE" 2>/dev/null - exit 1 - fi - PORT=$(grep -o 'listening on port [0-9]*' "$SERVER_PORT_FILE" 2>/dev/null | grep -o '[0-9]*' || true) - if [ -n "$PORT" ]; then - break - fi - if [ "$i" -eq 30 ]; then - echo "❌ Server did not announce port within 30 seconds" - exit 1 - fi - sleep 1 -done -export COPILOT_CLI_URL="localhost:$PORT" -echo "Server is ready on port $PORT (PID $SERVER_PID)" -echo "" - -echo "══════════════════════════════════════" -echo " Verifying sessions/multi-user-short-lived" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s)" -echo "══════════════════════════════════════" -echo "" - -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && COPILOT_CLI_URL=$COPILOT_CLI_URL dotnet run --no-build 2>&1" - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/sessions/session-resume/README.md b/test/scenarios/sessions/session-resume/README.md deleted file mode 100644 index abc47ad09..000000000 --- a/test/scenarios/sessions/session-resume/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Config Sample: Session Resume - -Demonstrates session persistence and resume with the Copilot SDK. This validates that a destroyed session can be resumed by its ID, retaining full conversation history. - -## What Each Sample Does - -1. Creates a session with `availableTools: []` and model `gpt-4.1` -2. Sends: _"Remember this: the secret word is PINEAPPLE."_ -3. Captures the session ID and destroys the session -4. Resumes the session using the same session ID -5. Sends: _"What was the secret word I told you?"_ -6. Prints the response — which should mention **PINEAPPLE** - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `availableTools` | `[]` (empty array) | Keeps the session simple with no tools | -| `model` | `"gpt-4.1"` | Uses GPT-4.1 for both the initial and resumed session | - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/sessions/session-resume/csharp/Program.cs b/test/scenarios/sessions/session-resume/csharp/Program.cs deleted file mode 100644 index 4f8bbdb5a..000000000 --- a/test/scenarios/sessions/session-resume/csharp/Program.cs +++ /dev/null @@ -1,47 +0,0 @@ -using GitHub.Copilot; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - // 1. Create a session - await using var session = await client.CreateSessionAsync(new SessionConfig - { - OnPermissionRequest = PermissionHandler.ApproveAll, - Model = "claude-haiku-4.5", - AvailableTools = new List(), - }); - - // 2. Send the secret word - await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "Remember this: the secret word is PINEAPPLE.", - }); - - // 3. Get the session ID - var sessionId = session.SessionId; - - // 4. Resume the session with the same ID - await using var resumed = await client.ResumeSessionAsync(sessionId, new ResumeSessionConfig - { - OnPermissionRequest = PermissionHandler.ApproveAll, - }); - Console.WriteLine("Session resumed"); - - // 5. Ask for the secret word - var response = await resumed.SendAndWaitAsync(new MessageOptions - { - Prompt = "What was the secret word I told you?", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/sessions/session-resume/csharp/csharp.csproj b/test/scenarios/sessions/session-resume/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/sessions/session-resume/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/sessions/session-resume/go/go.mod b/test/scenarios/sessions/session-resume/go/go.mod deleted file mode 100644 index 9d87af808..000000000 --- a/test/scenarios/sessions/session-resume/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/sessions/session-resume/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/sessions/session-resume/go/go.sum b/test/scenarios/sessions/session-resume/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/sessions/session-resume/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/sessions/session-resume/go/main.go b/test/scenarios/sessions/session-resume/go/main.go deleted file mode 100644 index 365c58ec6..000000000 --- a/test/scenarios/sessions/session-resume/go/main.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - // 1. Create a session - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Model: "claude-haiku-4.5", - AvailableTools: []string{}, - }) - if err != nil { - log.Fatal(err) - } - - // 2. Send the secret word - _, err = session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "Remember this: the secret word is PINEAPPLE.", - }) - if err != nil { - log.Fatal(err) - } - - // 3. Get the session ID (don't disconnect — resume needs the session to persist) - sessionID := session.SessionID - - // 4. Resume the session with the same ID - resumed, err := client.ResumeSession(ctx, sessionID, &copilot.ResumeSessionConfig{ - OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - }) - if err != nil { - log.Fatal(err) - } - fmt.Println("Session resumed") - defer resumed.Disconnect() - - // 5. Ask for the secret word - response, err := resumed.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What was the secret word I told you?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/sessions/session-resume/python/main.py b/test/scenarios/sessions/session-resume/python/main.py deleted file mode 100644 index 7dab1b309..000000000 --- a/test/scenarios/sessions/session-resume/python/main.py +++ /dev/null @@ -1,34 +0,0 @@ -import asyncio - -from copilot import CopilotClient - - -async def main(): - client = CopilotClient() - - try: - # 1. Create a session - session = await client.create_session(model="claude-haiku-4.5", available_tools=[]) - - # 2. Send the secret word - await session.send_and_wait("Remember this: the secret word is PINEAPPLE.") - - # 3. Get the session ID (don't disconnect — resume needs the session to persist) - session_id = session.session_id - - # 4. Resume the session with the same ID - resumed = await client.resume_session(session_id) - print("Session resumed") - - # 5. Ask for the secret word - response = await resumed.send_and_wait("What was the secret word I told you?") - - if response: - print(response.data.content) - - await resumed.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/sessions/session-resume/python/requirements.txt b/test/scenarios/sessions/session-resume/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/sessions/session-resume/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/sessions/session-resume/rust/Cargo.toml b/test/scenarios/sessions/session-resume/rust/Cargo.toml deleted file mode 100644 index ed6207260..000000000 --- a/test/scenarios/sessions/session-resume/rust/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "session-resume-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/sessions/session-resume/rust/src/main.rs b/test/scenarios/sessions/session-resume/rust/src/main.rs deleted file mode 100644 index 2f1815a78..000000000 --- a/test/scenarios/sessions/session-resume/rust/src/main.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Session resume — create a session, plant a memory, then resume by ID -//! and verify the agent recalls it. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::{ResumeSessionConfig, SessionConfig}; -use github_copilot_sdk::{Client, ClientOptions}; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - config.available_tools = Some(Vec::new()); - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - let session = client.create_session(config).await?; - - session - .send_and_wait("Remember this: the secret word is PINEAPPLE.") - .await?; - - let session_id = session.id().clone(); - // Note: do NOT destroy — `resume_session` needs the session to persist. - - let resume_config = - ResumeSessionConfig::new(session_id).with_permission_handler(Arc::new(ApproveAllHandler)); - let resumed = client.resume_session(resume_config).await?; - println!("Session resumed"); - - let response = resumed - .send_and_wait("What was the secret word I told you?") - .await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - resumed.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/sessions/session-resume/typescript/package.json b/test/scenarios/sessions/session-resume/typescript/package.json deleted file mode 100644 index 11dfd6865..000000000 --- a/test/scenarios/sessions/session-resume/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "sessions-session-resume-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — session persistence and resume", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/sessions/session-resume/typescript/src/index.ts b/test/scenarios/sessions/session-resume/typescript/src/index.ts deleted file mode 100644 index 125b89341..000000000 --- a/test/scenarios/sessions/session-resume/typescript/src/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient(); - - try { - // 1. Create a session - const session = await client.createSession({ - model: "claude-haiku-4.5", - availableTools: [], - }); - - // 2. Send the secret word - await session.sendAndWait({ - prompt: "Remember this: the secret word is PINEAPPLE.", - }); - - // 3. Get the session ID (don't disconnect — resume needs the session to persist) - const sessionId = session.sessionId; - - // 4. Resume the session with the same ID - const resumed = await client.resumeSession(sessionId); - console.log("Session resumed"); - - // 5. Ask for the secret word - const response = await resumed.sendAndWait({ - prompt: "What was the secret word I told you?", - }); - - if (response) { - console.log(response.data.content); - } - - await resumed.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/sessions/session-resume/verify.sh b/test/scenarios/sessions/session-resume/verify.sh deleted file mode 100755 index 07a5992e9..000000000 --- a/test/scenarios/sessions/session-resume/verify.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=120 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - # Check that the response mentions the secret word - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -qi "pineapple"; then - # Also verify session resume indication in output - if echo "$output" | grep -qi "session.*resum\|resum.*session\|Session resumed"; then - echo "✅ $name passed (confirmed session resume — found PINEAPPLE and session resume)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name found PINEAPPLE but no session resume indication in output" - echo "❌ $name failed (session resume not confirmed)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (no resume indication)" - fi - else - echo "⚠️ $name ran but response does not mention PINEAPPLE" - echo "❌ $name failed (secret word not recalled)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (PINEAPPLE not found)" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying sessions/session-resume samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o session-resume-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./session-resume-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/sessions/streaming/README.md b/test/scenarios/sessions/streaming/README.md deleted file mode 100644 index 377b3670a..000000000 --- a/test/scenarios/sessions/streaming/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# Config Sample: Streaming - -Demonstrates configuring the Copilot SDK with **`streaming: true`** to receive incremental response chunks. This validates that the server sends multiple `assistant.message_delta` events before the final `assistant.message` event. - -## What Each Sample Does - -1. Creates a session with `streaming: true` -2. Registers an event listener to count `assistant.message_delta` events -3. Sends: _"What is the capital of France?"_ -4. Prints the final response and the number of streaming chunks received - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `streaming` | `true` | Enables incremental streaming — the server emits `assistant.message_delta` events as tokens are generated | - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/sessions/streaming/csharp/Program.cs b/test/scenarios/sessions/streaming/csharp/Program.cs deleted file mode 100644 index 706960901..000000000 --- a/test/scenarios/sessions/streaming/csharp/Program.cs +++ /dev/null @@ -1,38 +0,0 @@ -using GitHub.Copilot; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - Streaming = true, - }); - - var chunkCount = 0; - using var subscription = session.On(evt => - { - if (evt is AssistantMessageDeltaEvent) - { - chunkCount++; - } - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine(response.Data.Content); - } - Console.WriteLine($"\nStreaming chunks received: {chunkCount}"); -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/sessions/streaming/csharp/csharp.csproj b/test/scenarios/sessions/streaming/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/sessions/streaming/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/sessions/streaming/go/go.mod b/test/scenarios/sessions/streaming/go/go.mod deleted file mode 100644 index 7e4c67004..000000000 --- a/test/scenarios/sessions/streaming/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/sessions/streaming/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/sessions/streaming/go/go.sum b/test/scenarios/sessions/streaming/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/sessions/streaming/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/sessions/streaming/go/main.go b/test/scenarios/sessions/streaming/go/main.go deleted file mode 100644 index e2a11029a..000000000 --- a/test/scenarios/sessions/streaming/go/main.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - Streaming: copilot.Bool(true), - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - chunkCount := 0 - session.On(func(event copilot.SessionEvent) { - if event.Type() == "assistant.message_delta" { - chunkCount++ - } - }) - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } - fmt.Printf("\nStreaming chunks received: %d\n", chunkCount) -} diff --git a/test/scenarios/sessions/streaming/python/main.py b/test/scenarios/sessions/streaming/python/main.py deleted file mode 100644 index 8def95f1f..000000000 --- a/test/scenarios/sessions/streaming/python/main.py +++ /dev/null @@ -1,32 +0,0 @@ -import asyncio - -from copilot import CopilotClient - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session(model="claude-haiku-4.5", streaming=True) - - chunk_count = 0 - - def on_event(event): - nonlocal chunk_count - if event.type.value == "assistant.message_delta": - chunk_count += 1 - - session.on(on_event) - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - print(f"\nStreaming chunks received: {chunk_count}") - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/sessions/streaming/python/requirements.txt b/test/scenarios/sessions/streaming/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/sessions/streaming/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/sessions/streaming/rust/Cargo.toml b/test/scenarios/sessions/streaming/rust/Cargo.toml deleted file mode 100644 index 31acc381b..000000000 --- a/test/scenarios/sessions/streaming/rust/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "streaming-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -async-trait = "0.1" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/sessions/streaming/rust/src/main.rs b/test/scenarios/sessions/streaming/rust/src/main.rs deleted file mode 100644 index da0cba5e3..000000000 --- a/test/scenarios/sessions/streaming/rust/src/main.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Streaming session — count `assistant.message_delta` events while waiting -//! for the final response. - -use std::sync::Arc; -use std::sync::atomic::{AtomicUsize, Ordering}; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::SessionConfig; -use github_copilot_sdk::{Client, ClientOptions}; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let chunks = Arc::new(AtomicUsize::new(0)); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - config.streaming = Some(true); - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - let session = client.create_session(config).await?; - - let mut events = session.subscribe(); - let chunks_clone = chunks.clone(); - let counter = tokio::spawn(async move { - while let Ok(event) = events.recv().await { - if event.event_type == "assistant.message_delta" { - chunks_clone.fetch_add(1, Ordering::Relaxed); - } - } - }); - - let response = session.send_and_wait("What is the capital of France?").await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - println!( - "\nStreaming chunks received: {}", - chunks.load(Ordering::Relaxed) - ); - - session.disconnect().await?; - drop(counter); - Ok(()) -} diff --git a/test/scenarios/sessions/streaming/typescript/package.json b/test/scenarios/sessions/streaming/typescript/package.json deleted file mode 100644 index 4418925d4..000000000 --- a/test/scenarios/sessions/streaming/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "sessions-streaming-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — streaming response chunks", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/sessions/streaming/typescript/src/index.ts b/test/scenarios/sessions/streaming/typescript/src/index.ts deleted file mode 100644 index 25df8cb4b..000000000 --- a/test/scenarios/sessions/streaming/typescript/src/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - streaming: true, - }); - - let chunkCount = 0; - session.on("assistant.message_delta", () => { - chunkCount++; - }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response) { - console.log(response.data.content); - } - console.log(`\nStreaming chunks received: ${chunkCount}`); - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/sessions/streaming/verify.sh b/test/scenarios/sessions/streaming/verify.sh deleted file mode 100755 index 828f42a43..000000000 --- a/test/scenarios/sessions/streaming/verify.sh +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -qE "Streaming chunks received: [1-9]"; then - # Also verify a final response was received (content printed before chunk count) - if echo "$output" | grep -qiE "Paris|France|capital"; then - echo "✅ $name passed (confirmed streaming chunks and final response)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name had streaming chunks but no final response content detected" - echo "❌ $name failed (final response not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (no final response)" - fi - else - echo "⚠️ $name ran but response may not confirm streaming" - echo "❌ $name failed (expected streaming chunk pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying sessions/streaming samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o streaming-go . 2>&1" - -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./streaming-go" - -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/tools/custom-agents/README.md b/test/scenarios/tools/custom-agents/README.md deleted file mode 100644 index 391345454..000000000 --- a/test/scenarios/tools/custom-agents/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Config Sample: Custom Agents - -Demonstrates configuring the Copilot SDK with **custom agent definitions** that restrict which tools an agent can use, and **agent-exclusive tools** that are hidden from the main agent. This validates: - -1. **Agent definition** — The `customAgents` session config accepts agent definitions with name, description, tool lists, and custom prompts. -2. **Tool scoping** — Each custom agent can be restricted to a subset of available tools (e.g. read-only tools like `grep`, `glob`, `view`). -3. **Agent-exclusive tools** — The `defaultAgent.excludedTools` option hides tools from the main agent while keeping them available to sub-agents. -4. **Agent awareness** — The model recognizes and can describe the configured custom agents. - -## What Each Sample Does - -1. Creates a session with a custom `analyze-codebase` tool and a `customAgents` array containing a "researcher" agent -2. Uses `defaultAgent.excludedTools` to hide `analyze-codebase` from the main agent -3. The researcher agent is scoped to read-only tools plus `analyze-codebase`: `grep`, `glob`, `view`, `analyze-codebase` -4. Sends: _"What custom agents are available? Describe the researcher agent and its capabilities."_ -5. Prints the response — which should describe the researcher agent and its tool restrictions - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `tools` | `[analyze-codebase]` | Registers custom tool at session level | -| `defaultAgent.excludedTools` | `["analyze-codebase"]` | Hides tool from main agent | -| `customAgents[0].name` | `"researcher"` | Internal identifier for the agent | -| `customAgents[0].displayName` | `"Research Agent"` | Human-readable name | -| `customAgents[0].description` | Custom text | Describes agent purpose | -| `customAgents[0].tools` | `["grep", "glob", "view", "analyze-codebase"]` | Restricts agent to read-only tools + analysis | -| `customAgents[0].prompt` | Custom text | Sets agent behavior instructions | - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/tools/custom-agents/csharp/Program.cs b/test/scenarios/tools/custom-agents/csharp/Program.cs deleted file mode 100644 index f2db5fe5b..000000000 --- a/test/scenarios/tools/custom-agents/csharp/Program.cs +++ /dev/null @@ -1,52 +0,0 @@ -using GitHub.Copilot; -using Microsoft.Extensions.AI; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - var analyzeCodebase = AIFunctionFactory.Create( - (string query) => $"Analysis result for: {query}", - new AIFunctionFactoryOptions - { - Name = "analyze-codebase", - Description = "Performs deep analysis of the codebase", - }); - - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - Tools = [analyzeCodebase], - DefaultAgent = new DefaultAgentConfig - { - ExcludedTools = ["analyze-codebase"], - }, - CustomAgents = - [ - new CustomAgentConfig - { - Name = "researcher", - DisplayName = "Research Agent", - Description = "A research agent that can only read and search files, not modify them", - Tools = ["grep", "glob", "view", "analyze-codebase"], - Prompt = "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", - }, - ], - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What custom agents are available? Describe the researcher agent and its capabilities.", - }); - - if (response != null) - { - Console.WriteLine(response.Data.Content); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/tools/custom-agents/csharp/csharp.csproj b/test/scenarios/tools/custom-agents/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/tools/custom-agents/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/tools/custom-agents/go/go.mod b/test/scenarios/tools/custom-agents/go/go.mod deleted file mode 100644 index 5b267a1f8..000000000 --- a/test/scenarios/tools/custom-agents/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/tools/custom-agents/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/tools/custom-agents/go/go.sum b/test/scenarios/tools/custom-agents/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/tools/custom-agents/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/tools/custom-agents/go/main.go b/test/scenarios/tools/custom-agents/go/main.go deleted file mode 100644 index e52404a6a..000000000 --- a/test/scenarios/tools/custom-agents/go/main.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - type AnalyzeParams struct { - Query string `json:"query" jsonschema:"the analysis query"` - } - - analyzeCodebase := copilot.DefineTool("analyze-codebase", - "Performs deep analysis of the codebase", - func(params AnalyzeParams, inv copilot.ToolInvocation) (string, error) { - return fmt.Sprintf("Analysis result for: %s", params.Query), nil - }, - ) - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - Tools: []copilot.Tool{analyzeCodebase}, - DefaultAgent: &copilot.DefaultAgentConfig{ - ExcludedTools: []string{"analyze-codebase"}, - }, - CustomAgents: []copilot.CustomAgentConfig{ - { - Name: "researcher", - DisplayName: "Research Agent", - Description: "A research agent that can only read and search files, not modify them", - Tools: []string{"grep", "glob", "view", "analyze-codebase"}, - Prompt: "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", - }, - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What custom agents are available? Describe the researcher agent and its capabilities.", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/tools/custom-agents/python/main.py b/test/scenarios/tools/custom-agents/python/main.py deleted file mode 100644 index 1aaae199c..000000000 --- a/test/scenarios/tools/custom-agents/python/main.py +++ /dev/null @@ -1,53 +0,0 @@ -import asyncio - -from copilot import CopilotClient -from copilot.tools import Tool - - -async def analyze_handler(args): - return f"Analysis result for: {args.get('query', '')}" - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model="claude-haiku-4.5", - tools=[ - Tool( - name="analyze-codebase", - description="Performs deep analysis of the codebase", - handler=analyze_handler, - parameters={ - "type": "object", - "properties": {"query": {"type": "string"}}, - }, - ), - ], - default_agent={"excluded_tools": ["analyze-codebase"]}, - custom_agents=[ - { - "name": "researcher", - "display_name": "Research Agent", - "description": "A research agent that can only read and search files, not modify them", - "tools": ["grep", "glob", "view", "analyze-codebase"], - "prompt": "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", - }, - ], - on_permission_request=lambda _: {"action": "allow"}, - ) - - response = await session.send_and_wait( - "What custom agents are available? Describe the researcher agent and its capabilities." - ) - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/tools/custom-agents/python/requirements.txt b/test/scenarios/tools/custom-agents/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/tools/custom-agents/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/tools/custom-agents/rust/Cargo.toml b/test/scenarios/tools/custom-agents/rust/Cargo.toml deleted file mode 100644 index 6d536052c..000000000 --- a/test/scenarios/tools/custom-agents/rust/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "custom-agents-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust", features = ["derive"] } -schemars = "1" -serde = { version = "1", features = ["derive"] } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/tools/custom-agents/rust/src/main.rs b/test/scenarios/tools/custom-agents/rust/src/main.rs deleted file mode 100644 index 016ff47e6..000000000 --- a/test/scenarios/tools/custom-agents/rust/src/main.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Custom agents — define a sub-agent ("researcher") with its own prompt -//! and tool allowlist, alongside a client-defined `analyze-codebase` tool. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::tool::define_tool; -use github_copilot_sdk::types::{CustomAgentConfig, DefaultAgentConfig, SessionConfig, ToolResult}; -use github_copilot_sdk::{Client, ClientOptions}; -use schemars::JsonSchema; -use serde::Deserialize; - -#[derive(Deserialize, JsonSchema)] -#[schemars(description = "Parameters for analyze-codebase")] -struct AnalyzeParams { - /// the analysis query - query: String, -} - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let analyze_codebase = define_tool( - "analyze-codebase", - "Performs deep analysis of the codebase", - |_inv, params: AnalyzeParams| async move { - Ok(ToolResult::Text(format!( - "Analysis result for: {}", - params.query - ))) - }, - ); - - let mut researcher = CustomAgentConfig::default(); - researcher.name = "researcher".to_string(); - researcher.display_name = Some("Research Agent".to_string()); - researcher.description = Some( - "A research agent that can only read and search files, not modify them".to_string(), - ); - researcher.tools = Some(vec![ - "grep".to_string(), - "glob".to_string(), - "view".to_string(), - "analyze-codebase".to_string(), - ]); - researcher.prompt = - "You are a research assistant. You can search and read files but cannot modify \ - anything. When asked about your capabilities, list the tools you have access to." - .to_string(); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - config.default_agent = Some(DefaultAgentConfig { - excluded_tools: Some(vec!["analyze-codebase".to_string()]), - }); - config.custom_agents = Some(vec![researcher]); - let config = config - .with_permission_handler(Arc::new(ApproveAllHandler)) - .with_tools(vec![analyze_codebase]); - - let session = client.create_session(config).await?; - - let response = session - .send_and_wait( - "What custom agents are available? Describe the researcher agent and its capabilities.", - ) - .await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/tools/custom-agents/typescript/package.json b/test/scenarios/tools/custom-agents/typescript/package.json deleted file mode 100644 index abb893d67..000000000 --- a/test/scenarios/tools/custom-agents/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "tools-custom-agents-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — custom agent definitions with tool scoping", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/tools/custom-agents/typescript/src/index.ts b/test/scenarios/tools/custom-agents/typescript/src/index.ts deleted file mode 100644 index 4a48902f8..000000000 --- a/test/scenarios/tools/custom-agents/typescript/src/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { CopilotClient, defineTool } from "@github/copilot-sdk"; -import { z } from "zod"; - -const analyzeCodebase = defineTool("analyze-codebase", { - description: - "Performs deep analysis of the codebase, generating extensive context", - parameters: z.object({ query: z.string().describe("The analysis query") }), - handler: async ({ query }) => { - return `Analysis result for: ${query}`; - }, -}); - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - tools: [analyzeCodebase], - defaultAgent: { - excludedTools: ["analyze-codebase"], - }, - customAgents: [ - { - name: "researcher", - displayName: "Research Agent", - description: - "A research agent that can only read and search files, not modify them", - tools: ["grep", "glob", "view", "analyze-codebase"], - prompt: - "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", - }, - ], - }); - - const response = await session.sendAndWait({ - prompt: - "What custom agents are available? Describe the researcher agent and its capabilities.", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/tools/custom-agents/verify.sh b/test/scenarios/tools/custom-agents/verify.sh deleted file mode 100755 index 4d295b47f..000000000 --- a/test/scenarios/tools/custom-agents/verify.sh +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - # Check that the response mentions the researcher agent or its tools - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -qi "researcher\|Research"; then - echo "✅ $name passed (confirmed custom agent)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name ran but response may not confirm custom agent" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying tools/custom-agents samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o custom-agents-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./custom-agents-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/tools/mcp-servers/README.md b/test/scenarios/tools/mcp-servers/README.md deleted file mode 100644 index 706e50e9e..000000000 --- a/test/scenarios/tools/mcp-servers/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Config Sample: MCP Servers - -Demonstrates configuring the Copilot SDK with **MCP (Model Context Protocol) server** integration. This validates that the SDK correctly passes `mcpServers` configuration to the runtime for connecting to external tool providers via stdio. - -## What Each Sample Does - -1. Checks for `MCP_SERVER_CMD` environment variable -2. If set, configures an MCP server entry of type `stdio` in the session config -3. Creates a session with `availableTools: []` and optionally `mcpServers` -4. Sends: _"What is the capital of France?"_ as a fallback test prompt -5. Prints the response and whether MCP servers were configured - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `mcpServers` | Map of server configs | Connects to external MCP servers that expose tools | -| `mcpServers.*.type` | `"stdio"` | Communicates with the MCP server via stdin/stdout | -| `mcpServers.*.command` | Executable path | The MCP server binary to spawn | -| `mcpServers.*.args` | String array | Arguments passed to the MCP server | -| `availableTools` | `[]` (empty array) | No built-in tools; MCP tools used if available | - -## Environment Variables - -| Variable | Required | Description | -|----------|----------|-------------| -| `COPILOT_CLI_PATH` | No | Path to `copilot` binary (auto-detected) | -| `GITHUB_TOKEN` | Yes | GitHub auth token (falls back to `gh auth token`) | -| `MCP_SERVER_CMD` | No | MCP server executable — when set, enables MCP integration | -| `MCP_SERVER_ARGS` | No | Space-separated arguments for the MCP server command | - -## Run - -```bash -# Without MCP server (build + basic integration test) -./verify.sh - -# With a real MCP server -MCP_SERVER_CMD=npx MCP_SERVER_ARGS="@modelcontextprotocol/server-filesystem /tmp" ./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/tools/mcp-servers/csharp/Program.cs b/test/scenarios/tools/mcp-servers/csharp/Program.cs deleted file mode 100644 index 7d7fe4738..000000000 --- a/test/scenarios/tools/mcp-servers/csharp/Program.cs +++ /dev/null @@ -1,62 +0,0 @@ -using GitHub.Copilot; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - var mcpServers = new Dictionary(); - var mcpServerCmd = Environment.GetEnvironmentVariable("MCP_SERVER_CMD"); - if (!string.IsNullOrEmpty(mcpServerCmd)) - { - var mcpArgs = Environment.GetEnvironmentVariable("MCP_SERVER_ARGS"); - mcpServers["example"] = new McpStdioServerConfig - { - Command = mcpServerCmd, - Args = string.IsNullOrEmpty(mcpArgs) ? [] : [.. mcpArgs.Split(' ')], - Tools = ["*"], - }; - } - - var config = new SessionConfig - { - Model = "claude-haiku-4.5", - AvailableTools = new List(), - SystemMessage = new SystemMessageConfig - { - Mode = SystemMessageMode.Replace, - Content = "You are a helpful assistant. Answer questions concisely.", - }, - }; - - if (mcpServers.Count > 0) - { - config.McpServers = mcpServers; - } - - await using var session = await client.CreateSessionAsync(config); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } - - if (mcpServers.Count > 0) - { - Console.WriteLine($"\nMCP servers configured: {string.Join(", ", mcpServers.Keys)}"); - } - else - { - Console.WriteLine("\nNo MCP servers configured (set MCP_SERVER_CMD to test with a real server)"); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/tools/mcp-servers/csharp/csharp.csproj b/test/scenarios/tools/mcp-servers/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/tools/mcp-servers/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/tools/mcp-servers/go/go.mod b/test/scenarios/tools/mcp-servers/go/go.mod deleted file mode 100644 index 39050b710..000000000 --- a/test/scenarios/tools/mcp-servers/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/tools/mcp-servers/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/tools/mcp-servers/go/go.sum b/test/scenarios/tools/mcp-servers/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/tools/mcp-servers/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/tools/mcp-servers/go/main.go b/test/scenarios/tools/mcp-servers/go/main.go deleted file mode 100644 index dd6ec5219..000000000 --- a/test/scenarios/tools/mcp-servers/go/main.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - "strings" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - // MCP server config — demonstrates the configuration pattern. - // When MCP_SERVER_CMD is set, connects to a real MCP server. - // Otherwise, runs without MCP tools as a build/integration test. - mcpServers := map[string]copilot.MCPServerConfig{} - if cmd := os.Getenv("MCP_SERVER_CMD"); cmd != "" { - var args []string - if argsStr := os.Getenv("MCP_SERVER_ARGS"); argsStr != "" { - args = strings.Split(argsStr, " ") - } - mcpServers["example"] = copilot.MCPStdioServerConfig{ - Command: cmd, - Args: args, - Tools: &[]string{"*"}, - } - } - - sessionConfig := &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: "You are a helpful assistant. Answer questions concisely.", - }, - AvailableTools: []string{}, - } - if len(mcpServers) > 0 { - sessionConfig.MCPServers = mcpServers - } - - session, err := client.CreateSession(ctx, sessionConfig) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } - - if len(mcpServers) > 0 { - keys := make([]string, 0, len(mcpServers)) - for k := range mcpServers { - keys = append(keys, k) - } - fmt.Printf("\nMCP servers configured: %s\n", strings.Join(keys, ", ")) - } else { - fmt.Println("\nNo MCP servers configured (set MCP_SERVER_CMD to test with a real server)") - } -} diff --git a/test/scenarios/tools/mcp-servers/python/main.py b/test/scenarios/tools/mcp-servers/python/main.py deleted file mode 100644 index 706094ac9..000000000 --- a/test/scenarios/tools/mcp-servers/python/main.py +++ /dev/null @@ -1,55 +0,0 @@ -import asyncio -import os - -from copilot import CopilotClient - - -async def main(): - client = CopilotClient() - - try: - # MCP server config — demonstrates the configuration pattern. - # When MCP_SERVER_CMD is set, connects to a real MCP server. - # Otherwise, runs without MCP tools as a build/integration test. - mcp_servers = {} - if os.environ.get("MCP_SERVER_CMD"): - args = ( - os.environ.get("MCP_SERVER_ARGS", "").split() - if os.environ.get("MCP_SERVER_ARGS") - else [] - ) - mcp_servers["example"] = { - "type": "stdio", - "command": os.environ["MCP_SERVER_CMD"], - "args": args, - } - - session_config = { - "model": "claude-haiku-4.5", - "available_tools": [], - "system_message": { - "mode": "replace", - "content": "You are a helpful assistant. Answer questions concisely.", - }, - } - if mcp_servers: - session_config["mcp_servers"] = mcp_servers - - session = await client.create_session(session_config) - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - - if mcp_servers: - print(f"\nMCP servers configured: {', '.join(mcp_servers.keys())}") - else: - print("\nNo MCP servers configured (set MCP_SERVER_CMD to test with a real server)") - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/tools/mcp-servers/python/requirements.txt b/test/scenarios/tools/mcp-servers/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/tools/mcp-servers/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/tools/mcp-servers/rust/Cargo.toml b/test/scenarios/tools/mcp-servers/rust/Cargo.toml deleted file mode 100644 index 84c40e3be..000000000 --- a/test/scenarios/tools/mcp-servers/rust/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "mcp-servers-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -serde_json = "1" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/tools/mcp-servers/rust/src/main.rs b/test/scenarios/tools/mcp-servers/rust/src/main.rs deleted file mode 100644 index a1b043854..000000000 --- a/test/scenarios/tools/mcp-servers/rust/src/main.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! MCP servers — configure an MCP server from env and pass it through to -//! the CLI via `SessionConfig::mcp_servers`. Build-only when -//! `MCP_SERVER_CMD` is unset. - -use std::collections::HashMap; -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::{ - McpServerConfig, McpStdioServerConfig, SessionConfig, SystemMessageConfig, -}; -use github_copilot_sdk::{Client, ClientOptions}; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let mcp_cmd = std::env::var("MCP_SERVER_CMD").ok(); - let mcp_args_env = std::env::var("MCP_SERVER_ARGS").ok(); - let mcp_servers = mcp_cmd.as_ref().map(|cmd| { - let args: Vec = mcp_args_env - .as_deref() - .map(|s| s.split(' ').map(str::to_string).collect()) - .unwrap_or_default(); - let stdio = McpStdioServerConfig { - command: cmd.clone(), - args, - ..Default::default() - }; - let mut map = HashMap::new(); - map.insert("example".to_string(), McpServerConfig::Stdio(stdio)); - map - }); - - let mut sysmsg = SystemMessageConfig::default(); - sysmsg.mode = Some("replace".to_string()); - sysmsg.content = - Some("You are a helpful assistant. Answer questions concisely.".to_string()); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - config.system_message = Some(sysmsg); - config.available_tools = Some(Vec::new()); - config.mcp_servers = mcp_servers; - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - - let session = client.create_session(config).await?; - - let response = session.send_and_wait("What is the capital of France?").await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - if mcp_cmd.is_some() { - println!("\nMCP servers configured: example"); - } else { - println!("\nNo MCP servers configured (set MCP_SERVER_CMD to test with a real server)"); - } - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/tools/mcp-servers/typescript/package.json b/test/scenarios/tools/mcp-servers/typescript/package.json deleted file mode 100644 index eaf810cee..000000000 --- a/test/scenarios/tools/mcp-servers/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "tools-mcp-servers-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — MCP server integration", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/tools/mcp-servers/typescript/src/index.ts b/test/scenarios/tools/mcp-servers/typescript/src/index.ts deleted file mode 100644 index 838094c8d..000000000 --- a/test/scenarios/tools/mcp-servers/typescript/src/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient(); - - try { - // MCP server config — demonstrates the configuration pattern. - // When MCP_SERVER_CMD is set, connects to a real MCP server. - // Otherwise, runs without MCP tools as a build/integration test. - const mcpServers: Record = {}; - if (process.env.MCP_SERVER_CMD) { - mcpServers["example"] = { - type: "stdio", - command: process.env.MCP_SERVER_CMD, - args: process.env.MCP_SERVER_ARGS - ? process.env.MCP_SERVER_ARGS.split(" ") - : [], - }; - } - - const session = await client.createSession({ - model: "claude-haiku-4.5", - ...(Object.keys(mcpServers).length > 0 && { mcpServers }), - availableTools: [], - systemMessage: { - mode: "replace", - content: "You are a helpful assistant. Answer questions concisely.", - }, - }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response) { - console.log(response.data.content); - } - - if (Object.keys(mcpServers).length > 0) { - console.log( - "\nMCP servers configured: " + Object.keys(mcpServers).join(", "), - ); - } else { - console.log( - "\nNo MCP servers configured (set MCP_SERVER_CMD to test with a real server)", - ); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/tools/mcp-servers/verify.sh b/test/scenarios/tools/mcp-servers/verify.sh deleted file mode 100755 index abde4508e..000000000 --- a/test/scenarios/tools/mcp-servers/verify.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - if [ "$code" -eq 0 ] && [ -n "$output" ] && echo "$output" | grep -qi "MCP\|mcp\|capital\|France\|Paris\|configured"; then - echo "✅ $name passed (got meaningful response)" - PASS=$((PASS + 1)) - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - elif [ "$code" -eq 0 ]; then - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying tools/mcp-servers samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o mcp-servers-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./mcp-servers-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/tools/no-tools/README.md b/test/scenarios/tools/no-tools/README.md deleted file mode 100644 index 3cfac6baa..000000000 --- a/test/scenarios/tools/no-tools/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Config Sample: No Tools - -Demonstrates configuring the Copilot SDK with **zero tools** and a custom system prompt that reflects the tool-less state. This validates two things: - -1. **Tool removal** — Setting `availableTools: []` removes all built-in tools (bash, view, edit, grep, glob, etc.) from the agent's capabilities. -2. **Agent awareness** — The replaced system prompt tells the agent it has no tools, and the agent's response confirms this. - -## What Each Sample Does - -1. Creates a session with `availableTools: []` and a `systemMessage` in `replace` mode -2. Sends: _"What tools do you have available? List them."_ -3. Prints the response — which should confirm the agent has no tools - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `availableTools` | `[]` (empty array) | Whitelists zero tools — all built-in tools are removed | -| `systemMessage.mode` | `"replace"` | Replaces the default system prompt entirely | -| `systemMessage.content` | Custom minimal prompt | Tells the agent it has no tools and can only respond with text | - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/tools/no-tools/csharp/Program.cs b/test/scenarios/tools/no-tools/csharp/Program.cs deleted file mode 100644 index a9a2e0308..000000000 --- a/test/scenarios/tools/no-tools/csharp/Program.cs +++ /dev/null @@ -1,40 +0,0 @@ -using GitHub.Copilot; - -const string SystemPrompt = """ - You are a minimal assistant with no tools available. - You cannot execute code, read files, edit files, search, or perform any actions. - You can only respond with text based on your training data. - If asked about your capabilities or tools, clearly state that you have no tools available. - """; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - SystemMessage = new SystemMessageConfig - { - Mode = SystemMessageMode.Replace, - Content = SystemPrompt, - }, - AvailableTools = [], - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "Use the bash tool to run 'echo hello'.", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/tools/no-tools/csharp/csharp.csproj b/test/scenarios/tools/no-tools/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/tools/no-tools/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/tools/no-tools/go/go.mod b/test/scenarios/tools/no-tools/go/go.mod deleted file mode 100644 index 678915fda..000000000 --- a/test/scenarios/tools/no-tools/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/tools/no-tools/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/tools/no-tools/go/go.sum b/test/scenarios/tools/no-tools/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/tools/no-tools/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/tools/no-tools/go/main.go b/test/scenarios/tools/no-tools/go/main.go deleted file mode 100644 index 9698ebded..000000000 --- a/test/scenarios/tools/no-tools/go/main.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -const systemPrompt = `You are a minimal assistant with no tools available. -You cannot execute code, read files, edit files, search, or perform any actions. -You can only respond with text based on your training data. -If asked about your capabilities or tools, clearly state that you have no tools available.` - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: systemPrompt, - }, - AvailableTools: []string{}, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "Use the bash tool to run 'echo hello'.", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/tools/no-tools/python/main.py b/test/scenarios/tools/no-tools/python/main.py deleted file mode 100644 index 35448e9a2..000000000 --- a/test/scenarios/tools/no-tools/python/main.py +++ /dev/null @@ -1,31 +0,0 @@ -import asyncio - -from copilot import CopilotClient - -SYSTEM_PROMPT = """You are a minimal assistant with no tools available. -You cannot execute code, read files, edit files, search, or perform any actions. -You can only respond with text based on your training data. -If asked about your capabilities or tools, clearly state that you have no tools available.""" - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model="claude-haiku-4.5", - system_message={"mode": "replace", "content": SYSTEM_PROMPT}, - available_tools=[], - ) - - response = await session.send_and_wait("Use the bash tool to run 'echo hello'.") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/tools/no-tools/python/requirements.txt b/test/scenarios/tools/no-tools/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/tools/no-tools/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/tools/no-tools/rust/Cargo.toml b/test/scenarios/tools/no-tools/rust/Cargo.toml deleted file mode 100644 index 461469946..000000000 --- a/test/scenarios/tools/no-tools/rust/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "no-tools-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/tools/no-tools/rust/src/main.rs b/test/scenarios/tools/no-tools/rust/src/main.rs deleted file mode 100644 index c2e13339f..000000000 --- a/test/scenarios/tools/no-tools/rust/src/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! No-tools session — replace the system prompt and empty the available tools -//! list so the agent cannot execute code, read files, or call any built-ins. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::{SessionConfig, SystemMessageConfig}; -use github_copilot_sdk::{Client, ClientOptions}; - -const SYSTEM_PROMPT: &str = "You are a minimal assistant with no tools available. -You cannot execute code, read files, edit files, search, or perform any actions. -You can only respond with text based on your training data. -If asked about your capabilities or tools, clearly state that you have no tools available."; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let mut sysmsg = SystemMessageConfig::default(); - sysmsg.mode = Some("replace".to_string()); - sysmsg.content = Some(SYSTEM_PROMPT.to_string()); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - config.system_message = Some(sysmsg); - config.available_tools = Some(Vec::new()); - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - let session = client.create_session(config).await?; - - let response = session - .send_and_wait("Use the bash tool to run 'echo hello'.") - .await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/tools/no-tools/typescript/package.json b/test/scenarios/tools/no-tools/typescript/package.json deleted file mode 100644 index 7c78e51ca..000000000 --- a/test/scenarios/tools/no-tools/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "tools-no-tools-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — no tools, minimal system prompt", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/tools/no-tools/typescript/src/index.ts b/test/scenarios/tools/no-tools/typescript/src/index.ts deleted file mode 100644 index 5756bb350..000000000 --- a/test/scenarios/tools/no-tools/typescript/src/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -const SYSTEM_PROMPT = `You are a minimal assistant with no tools available. -You cannot execute code, read files, edit files, search, or perform any actions. -You can only respond with text based on your training data. -If asked about your capabilities or tools, clearly state that you have no tools available.`; - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - systemMessage: { mode: "replace", content: SYSTEM_PROMPT }, - availableTools: [], - }); - - const response = await session.sendAndWait({ - prompt: "Use the bash tool to run 'echo hello'.", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/tools/no-tools/verify.sh b/test/scenarios/tools/no-tools/verify.sh deleted file mode 100755 index 286796b70..000000000 --- a/test/scenarios/tools/no-tools/verify.sh +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - # Check that the response indicates no tools are available - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -qi "no tool\|can't\|cannot\|unable\|don't have\|do not have\|not available"; then - echo "✅ $name passed (confirmed no tools)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name ran but response may not confirm tool-less state" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying tools/no-tools samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o no-tools-go . 2>&1" - -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./no-tools-go" - -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/tools/skills/README.md b/test/scenarios/tools/skills/README.md deleted file mode 100644 index 138dee2d0..000000000 --- a/test/scenarios/tools/skills/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Config Sample: Skills (SKILL.md Discovery) - -Demonstrates configuring the Copilot SDK with **skill directories** that contain `SKILL.md` files. The agent discovers and uses skills defined in these markdown files at runtime. - -## What This Tests - -1. **Skill discovery** — Setting `skillDirectories` points the agent to directories containing `SKILL.md` files that define available skills. -2. **Skill execution** — The agent reads the skill definition and follows its instructions when prompted to use the skill. -3. **SKILL.md format** — Skills are defined as markdown files with a name, description, and usage instructions. - -## SKILL.md Format - -A `SKILL.md` file is a markdown document placed in a named directory under a skills root: - -``` -sample-skills/ -└── greeting/ - └── SKILL.md # Defines the "greeting" skill -``` - -The file contains: -- **Title** (`# skill-name`) — The skill's identifier -- **Description** — What the skill does -- **Usage** — Instructions the agent follows when the skill is invoked - -## What Each Sample Does - -1. Creates a session with `skillDirectories` pointing to `sample-skills/` -2. Sends: _"Use the greeting skill to greet someone named Alice."_ -3. The agent discovers the greeting skill from `SKILL.md` and generates a personalized greeting -4. Prints the response and confirms skill directory configuration - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `skillDirectories` | `["path/to/sample-skills"]` | Points the agent to directories containing skill definitions | - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/tools/skills/csharp/Program.cs b/test/scenarios/tools/skills/csharp/Program.cs deleted file mode 100644 index 5e3f3c859..000000000 --- a/test/scenarios/tools/skills/csharp/Program.cs +++ /dev/null @@ -1,40 +0,0 @@ -using GitHub.Copilot; -using GitHub.Copilot.Rpc; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - var skillsDir = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "sample-skills")); - - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - SkillDirectories = [skillsDir], - OnPermissionRequest = (request, invocation) => - Task.FromResult(PermissionDecision.ApproveOnce()), - Hooks = new SessionHooks - { - OnPreToolUse = (input, invocation) => - Task.FromResult(new PreToolUseHookOutput { PermissionDecision = "allow" }), - }, - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "Use the greeting skill to greet someone named Alice.", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } - - Console.WriteLine("\nSkill directories configured successfully"); -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/tools/skills/csharp/csharp.csproj b/test/scenarios/tools/skills/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/tools/skills/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/tools/skills/go/go.mod b/test/scenarios/tools/skills/go/go.mod deleted file mode 100644 index a5e098a14..000000000 --- a/test/scenarios/tools/skills/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/tools/skills/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/tools/skills/go/go.sum b/test/scenarios/tools/skills/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/tools/skills/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/tools/skills/go/main.go b/test/scenarios/tools/skills/go/main.go deleted file mode 100644 index 21d9604f7..000000000 --- a/test/scenarios/tools/skills/go/main.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "path/filepath" - "runtime" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - _, thisFile, _, _ := runtime.Caller(0) - skillsDir := filepath.Join(filepath.Dir(thisFile), "..", "sample-skills") - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - SkillDirectories: []string{skillsDir}, - OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - Hooks: &copilot.SessionHooks{ - OnPreToolUse: func(input copilot.PreToolUseHookInput, invocation copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) { - return &copilot.PreToolUseHookOutput{PermissionDecision: "allow"}, nil - }, - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "Use the greeting skill to greet someone named Alice.", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } - - fmt.Println("\nSkill directories configured successfully") -} diff --git a/test/scenarios/tools/skills/python/main.py b/test/scenarios/tools/skills/python/main.py deleted file mode 100644 index 6b066bbf4..000000000 --- a/test/scenarios/tools/skills/python/main.py +++ /dev/null @@ -1,37 +0,0 @@ -import asyncio -from pathlib import Path - -from copilot import CopilotClient -from copilot.generated.rpc import PermissionDecisionApproveOnce - - -async def main(): - client = CopilotClient() - - try: - skills_dir = str(Path(__file__).resolve().parent.parent / "sample-skills") - - session = await client.create_session( - on_permission_request=lambda _, __: PermissionDecisionApproveOnce(), - model="claude-haiku-4.5", - skill_directories=[skills_dir], - hooks={ - "on_pre_tool_use": lambda _, __: {"permissionDecision": "allow"}, - }, - ) - - response = await session.send_and_wait( - "Use the greeting skill to greet someone named Alice." - ) - - if response: - print(response.data.content) - - print("\nSkill directories configured successfully") - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/tools/skills/python/requirements.txt b/test/scenarios/tools/skills/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/tools/skills/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/tools/skills/rust/Cargo.toml b/test/scenarios/tools/skills/rust/Cargo.toml deleted file mode 100644 index c2de4b20e..000000000 --- a/test/scenarios/tools/skills/rust/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "skills-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -async-trait = "0.1" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/tools/skills/rust/src/main.rs b/test/scenarios/tools/skills/rust/src/main.rs deleted file mode 100644 index 64cf689a4..000000000 --- a/test/scenarios/tools/skills/rust/src/main.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! Skills — point the CLI at a directory of user-defined skills via -//! `SessionConfig::skill_directories`. - -use std::path::PathBuf; -use std::sync::Arc; - -use async_trait::async_trait; -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::hooks::{HookContext, PreToolUseInput, PreToolUseOutput, SessionHooks}; -use github_copilot_sdk::types::SessionConfig; -use github_copilot_sdk::{Client, ClientOptions}; - -struct AllowAllHooks; - -#[async_trait] -impl SessionHooks for AllowAllHooks { - async fn on_pre_tool_use( - &self, - _input: PreToolUseInput, - _ctx: HookContext, - ) -> Option { - let mut out = PreToolUseOutput::default(); - out.permission_decision = Some("allow".to_string()); - Some(out) - } -} - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - // CARGO_MANIFEST_DIR resolves to .../tools/skills/rust at compile time. - let skills_dir: PathBuf = [env!("CARGO_MANIFEST_DIR"), "..", "sample-skills"] - .iter() - .collect(); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - config.skill_directories = Some(vec![skills_dir]); - let config = config - .with_permission_handler(Arc::new(ApproveAllHandler)) - .with_hooks(Arc::new(AllowAllHooks)); - - let session = client.create_session(config).await?; - - let response = session - .send_and_wait("Use the greeting skill to greet someone named Alice.") - .await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - println!("\nSkill directories configured successfully"); - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/tools/skills/sample-skills/greeting/SKILL.md b/test/scenarios/tools/skills/sample-skills/greeting/SKILL.md deleted file mode 100644 index feb816c84..000000000 --- a/test/scenarios/tools/skills/sample-skills/greeting/SKILL.md +++ /dev/null @@ -1,8 +0,0 @@ -# greeting - -A skill that generates personalized greetings. - -## Usage - -When asked to greet someone, generate a warm, personalized greeting message. -Always include the person's name and a fun fact about their name. diff --git a/test/scenarios/tools/skills/typescript/package.json b/test/scenarios/tools/skills/typescript/package.json deleted file mode 100644 index 77d8142b3..000000000 --- a/test/scenarios/tools/skills/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "tools-skills-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — skill discovery and execution via SKILL.md", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/tools/skills/typescript/src/index.ts b/test/scenarios/tools/skills/typescript/src/index.ts deleted file mode 100644 index 0a934eb39..000000000 --- a/test/scenarios/tools/skills/typescript/src/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; -import path from "path"; -import { fileURLToPath } from "url"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -async function main() { - const client = new CopilotClient(); - - try { - const skillsDir = path.resolve(__dirname, "../../sample-skills"); - - const session = await client.createSession({ - model: "claude-haiku-4.5", - skillDirectories: [skillsDir], - onPermissionRequest: async () => ({ kind: "approve-once" as const }), - hooks: { - onPreToolUse: async () => ({ permissionDecision: "allow" as const }), - }, - }); - - const response = await session.sendAndWait({ - prompt: "Use the greeting skill to greet someone named Alice.", - }); - - if (response) { - console.log(response.data.content); - } - - console.log("\nSkill directories configured successfully"); - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/tools/skills/verify.sh b/test/scenarios/tools/skills/verify.sh deleted file mode 100755 index 6d1881173..000000000 --- a/test/scenarios/tools/skills/verify.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=120 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -qi "skill\|Skill\|greeting\|Alice"; then - echo "✅ $name passed (confirmed skill execution)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name ran but response may not confirm skill execution" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying tools/skills samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o skills-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./skills-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/tools/tool-filtering/README.md b/test/scenarios/tools/tool-filtering/README.md deleted file mode 100644 index cb664a479..000000000 --- a/test/scenarios/tools/tool-filtering/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Config Sample: Tool Filtering - -Demonstrates advanced tool filtering using the `availableTools` whitelist. This restricts the agent to only the specified read-only tools, removing all others (bash, edit, create_file, etc.). - -The Copilot SDK supports two complementary filtering mechanisms: - -- **`availableTools`** (whitelist) — Only the listed tools are available. All others are removed. -- **`excludedTools`** (blacklist) — All tools are available *except* the listed ones. - -This sample tests the **whitelist** approach with `["grep", "glob", "view"]`. - -## What Each Sample Does - -1. Creates a session with `availableTools: ["grep", "glob", "view"]` and a `systemMessage` in `replace` mode -2. Sends: _"What tools do you have available? List each one by name."_ -3. Prints the response — which should list only grep, glob, and view - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `availableTools` | `["grep", "glob", "view"]` | Whitelists only read-only tools | -| `systemMessage.mode` | `"replace"` | Replaces the default system prompt entirely | -| `systemMessage.content` | Custom prompt | Instructs the agent to list its available tools | - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. - -## Verification - -The verify script checks that: -- The response mentions at least one whitelisted tool (grep, glob, or view) -- The response does **not** mention excluded tools (bash, edit, or create_file) diff --git a/test/scenarios/tools/tool-filtering/csharp/Program.cs b/test/scenarios/tools/tool-filtering/csharp/Program.cs deleted file mode 100644 index 23f6bf4b4..000000000 --- a/test/scenarios/tools/tool-filtering/csharp/Program.cs +++ /dev/null @@ -1,33 +0,0 @@ -using GitHub.Copilot; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - SystemMessage = new SystemMessageConfig - { - Mode = SystemMessageMode.Replace, - Content = "You are a helpful assistant. You have access to a limited set of tools. When asked about your tools, list exactly which tools you have available.", - }, - AvailableTools = ["grep", "glob", "view"], - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What tools do you have available? List each one by name.", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/tools/tool-filtering/csharp/csharp.csproj b/test/scenarios/tools/tool-filtering/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/tools/tool-filtering/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/tools/tool-filtering/go/go.mod b/test/scenarios/tools/tool-filtering/go/go.mod deleted file mode 100644 index 1084324fe..000000000 --- a/test/scenarios/tools/tool-filtering/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/tools/tool-filtering/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/tools/tool-filtering/go/go.sum b/test/scenarios/tools/tool-filtering/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/tools/tool-filtering/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/tools/tool-filtering/go/main.go b/test/scenarios/tools/tool-filtering/go/main.go deleted file mode 100644 index 646582bdf..000000000 --- a/test/scenarios/tools/tool-filtering/go/main.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -const systemPrompt = `You are a helpful assistant. You have access to a limited set of tools. When asked about your tools, list exactly which tools you have available.` - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - SystemMessage: &copilot.SystemMessageConfig{ - Mode: "replace", - Content: systemPrompt, - }, - AvailableTools: []string{"grep", "glob", "view"}, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What tools do you have available? List each one by name.", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/tools/tool-filtering/python/main.py b/test/scenarios/tools/tool-filtering/python/main.py deleted file mode 100644 index a38f73c78..000000000 --- a/test/scenarios/tools/tool-filtering/python/main.py +++ /dev/null @@ -1,30 +0,0 @@ -import asyncio - -from copilot import CopilotClient - -SYSTEM_PROMPT = """You are a helpful assistant. You have access to a limited set of tools. When asked about your tools, list exactly which tools you have available.""" - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - model="claude-haiku-4.5", - system_message={"mode": "replace", "content": SYSTEM_PROMPT}, - available_tools=["grep", "glob", "view"], - ) - - response = await session.send_and_wait( - "What tools do you have available? List each one by name." - ) - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/tools/tool-filtering/python/requirements.txt b/test/scenarios/tools/tool-filtering/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/tools/tool-filtering/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/tools/tool-filtering/rust/Cargo.toml b/test/scenarios/tools/tool-filtering/rust/Cargo.toml deleted file mode 100644 index 88e38073d..000000000 --- a/test/scenarios/tools/tool-filtering/rust/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "tool-filtering-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/tools/tool-filtering/rust/src/main.rs b/test/scenarios/tools/tool-filtering/rust/src/main.rs deleted file mode 100644 index bce9b3aba..000000000 --- a/test/scenarios/tools/tool-filtering/rust/src/main.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Tool filtering — restrict the agent to a subset of built-in tools via -//! `SessionConfig::available_tools`. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::{SessionConfig, SystemMessageConfig}; -use github_copilot_sdk::{Client, ClientOptions}; - -const SYSTEM_PROMPT: &str = "You are a helpful assistant. You have access to a limited set \ -of tools. When asked about your tools, list exactly which tools you have available."; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let mut sysmsg = SystemMessageConfig::default(); - sysmsg.mode = Some("replace".to_string()); - sysmsg.content = Some(SYSTEM_PROMPT.to_string()); - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - config.system_message = Some(sysmsg); - config.available_tools = Some(vec![ - "grep".to_string(), - "glob".to_string(), - "view".to_string(), - ]); - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - - let session = client.create_session(config).await?; - - let response = session - .send_and_wait("What tools do you have available? List each one by name.") - .await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/tools/tool-filtering/typescript/package.json b/test/scenarios/tools/tool-filtering/typescript/package.json deleted file mode 100644 index 5ff9537f8..000000000 --- a/test/scenarios/tools/tool-filtering/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "tools-tool-filtering-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — advanced tool filtering with availableTools whitelist", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/tools/tool-filtering/typescript/src/index.ts b/test/scenarios/tools/tool-filtering/typescript/src/index.ts deleted file mode 100644 index 7ab2d2f93..000000000 --- a/test/scenarios/tools/tool-filtering/typescript/src/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - systemMessage: { - mode: "replace", - content: - "You are a helpful assistant. You have access to a limited set of tools. When asked about your tools, list exactly which tools you have available.", - }, - availableTools: ["grep", "glob", "view"], - }); - - const response = await session.sendAndWait({ - prompt: "What tools do you have available? List each one by name.", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/tools/tool-filtering/verify.sh b/test/scenarios/tools/tool-filtering/verify.sh deleted file mode 100755 index d73377718..000000000 --- a/test/scenarios/tools/tool-filtering/verify.sh +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - # Check that whitelisted tools are mentioned and blacklisted tools are NOT - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - local has_whitelisted=false - local has_blacklisted=false - - if echo "$output" | grep -qi "grep\|glob\|view"; then - has_whitelisted=true - fi - if echo "$output" | grep -qiw "bash\|edit\|create_file"; then - has_blacklisted=true - fi - - if $has_whitelisted && ! $has_blacklisted; then - echo "✅ $name passed (confirmed whitelisted tools only)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name ran but response mentions excluded tools or missing whitelisted tools" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying tools/tool-filtering samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o tool-filtering-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./tool-filtering-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/tools/tool-overrides/README.md b/test/scenarios/tools/tool-overrides/README.md deleted file mode 100644 index cb15f45b5..000000000 --- a/test/scenarios/tools/tool-overrides/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# Config Sample: Tool Overrides - -Demonstrates how to override a built-in tool with a custom implementation using the `overridesBuiltInTool` flag. When this flag is set on a custom tool, the SDK knows to disable the corresponding built-in tool so your implementation is used instead. - -## What Each Sample Does - -1. Creates a session with a custom `grep` tool (with `overridesBuiltInTool` enabled) that returns `"CUSTOM_GREP_RESULT: "` -2. Sends: _"Use grep to search for the word 'hello'"_ -3. Prints the response — which should contain `CUSTOM_GREP_RESULT` (proving the custom tool ran, not the built-in) - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `tools` | Custom `grep` tool | Provides a custom grep implementation | -| `overridesBuiltInTool` | `true` | Tells the SDK to disable the built-in `grep` in favor of the custom one | - -The flag is set per-tool in TypeScript (`overridesBuiltInTool: true`), Python (`overrides_built_in_tool=True`), Go (`OverridesBuiltInTool: true`), and .NET (`new CopilotToolOptions { OverridesBuiltInTool = true }`). - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. - -## Verification - -The verify script checks that: -- The response contains `CUSTOM_GREP_RESULT` (custom tool was invoked) -- The response does **not** contain typical built-in grep output patterns diff --git a/test/scenarios/tools/tool-overrides/csharp/Program.cs b/test/scenarios/tools/tool-overrides/csharp/Program.cs deleted file mode 100644 index c88b8dc2c..000000000 --- a/test/scenarios/tools/tool-overrides/csharp/Program.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.ComponentModel; -using GitHub.Copilot; -using Microsoft.Extensions.AI; - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - OnPermissionRequest = PermissionHandler.ApproveAll, - Tools = [CopilotTool.DefineTool((Delegate)CustomGrep, new CopilotToolOptions - { - OverridesBuiltInTool = true - }, new AIFunctionFactoryOptions - { - Name = "grep", - Description = "A custom grep implementation that overrides the built-in", - })], - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "Use grep to search for the word 'hello'", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} - -[Description("A custom grep implementation that overrides the built-in")] -static string CustomGrep([Description("Search query")] string query) - => $"CUSTOM_GREP_RESULT: {query}"; diff --git a/test/scenarios/tools/tool-overrides/csharp/csharp.csproj b/test/scenarios/tools/tool-overrides/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/tools/tool-overrides/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/tools/tool-overrides/go/go.mod b/test/scenarios/tools/tool-overrides/go/go.mod deleted file mode 100644 index 49726e94b..000000000 --- a/test/scenarios/tools/tool-overrides/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/tools/tool-overrides/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/tools/tool-overrides/go/go.sum b/test/scenarios/tools/tool-overrides/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/tools/tool-overrides/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/tools/tool-overrides/go/main.go b/test/scenarios/tools/tool-overrides/go/main.go deleted file mode 100644 index 9f77fc56d..000000000 --- a/test/scenarios/tools/tool-overrides/go/main.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -type GrepParams struct { - Query string `json:"query" jsonschema:"Search query"` -} - -func main() { - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - grepTool := copilot.DefineTool("grep", "A custom grep implementation that overrides the built-in", - func(params GrepParams, inv copilot.ToolInvocation) (string, error) { - return "CUSTOM_GREP_RESULT: " + params.Query, nil - }) - grepTool.OverridesBuiltInTool = true - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Tools: []copilot.Tool{grepTool}, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "Use grep to search for the word 'hello'", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/tools/tool-overrides/python/main.py b/test/scenarios/tools/tool-overrides/python/main.py deleted file mode 100644 index aa31de170..000000000 --- a/test/scenarios/tools/tool-overrides/python/main.py +++ /dev/null @@ -1,42 +0,0 @@ -import asyncio - -from pydantic import BaseModel, Field - -from copilot import CopilotClient, define_tool -from copilot.session import PermissionHandler - - -class GrepParams(BaseModel): - query: str = Field(description="Search query") - - -@define_tool( - "grep", - description="A custom grep implementation that overrides the built-in", - overrides_built_in_tool=True, -) -def custom_grep(params: GrepParams) -> str: - return f"CUSTOM_GREP_RESULT: {params.query}" - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - on_permission_request=PermissionHandler.approve_all, - model="claude-haiku-4.5", - tools=[custom_grep], - ) - - response = await session.send_and_wait("Use grep to search for the word 'hello'") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/tools/tool-overrides/python/requirements.txt b/test/scenarios/tools/tool-overrides/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/tools/tool-overrides/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/tools/tool-overrides/rust/Cargo.toml b/test/scenarios/tools/tool-overrides/rust/Cargo.toml deleted file mode 100644 index f3b9d6aef..000000000 --- a/test/scenarios/tools/tool-overrides/rust/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "tool-overrides-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust", features = ["derive"] } -schemars = "1" -serde = { version = "1", features = ["derive"] } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/tools/tool-overrides/rust/src/main.rs b/test/scenarios/tools/tool-overrides/rust/src/main.rs deleted file mode 100644 index bfa46b1b3..000000000 --- a/test/scenarios/tools/tool-overrides/rust/src/main.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Tool overrides — replace the built-in `grep` tool with a custom -//! implementation that returns a distinct marker. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::tool::define_tool; -use github_copilot_sdk::types::{SessionConfig, ToolResult}; -use github_copilot_sdk::{Client, ClientOptions}; -use schemars::JsonSchema; -use serde::Deserialize; - -#[derive(Deserialize, JsonSchema)] -#[schemars(description = "Parameters for custom grep")] -struct GrepParams { - /// Search query - query: String, -} - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let mut grep_tool = define_tool( - "grep", - "A custom grep implementation that overrides the built-in", - |_inv, params: GrepParams| async move { - Ok(ToolResult::Text(format!("CUSTOM_GREP_RESULT: {}", params.query))) - }, - ); - grep_tool.overrides_built_in_tool = true; - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - let config = config - .with_permission_handler(Arc::new(ApproveAllHandler)) - .with_tools(vec![grep_tool]); - - let session = client.create_session(config).await?; - - let response = session - .send_and_wait("Use grep to search for the word 'hello'") - .await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/tools/tool-overrides/typescript/package.json b/test/scenarios/tools/tool-overrides/typescript/package.json deleted file mode 100644 index 64e958406..000000000 --- a/test/scenarios/tools/tool-overrides/typescript/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "tools-tool-overrides-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — custom tool overriding a built-in tool", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/tools/tool-overrides/typescript/src/index.ts b/test/scenarios/tools/tool-overrides/typescript/src/index.ts deleted file mode 100644 index fa3fc457b..000000000 --- a/test/scenarios/tools/tool-overrides/typescript/src/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { CopilotClient, defineTool, approveAll } from "@github/copilot-sdk"; -import { z } from "zod"; - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - onPermissionRequest: approveAll, - tools: [ - defineTool("grep", { - description: - "A custom grep implementation that overrides the built-in", - parameters: z.object({ - query: z.string().describe("Search query"), - }), - overridesBuiltInTool: true, - handler: ({ query }) => `CUSTOM_GREP_RESULT: ${query}`, - }), - ], - }); - - const response = await session.sendAndWait({ - prompt: "Use grep to search for the word 'hello'", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/tools/tool-overrides/verify.sh b/test/scenarios/tools/tool-overrides/verify.sh deleted file mode 100755 index cf9b34d51..000000000 --- a/test/scenarios/tools/tool-overrides/verify.sh +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - # Check that custom grep tool was used (not built-in) - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -q "CUSTOM_GREP_RESULT"; then - echo "✅ $name passed (confirmed custom tool override)" - PASS=$((PASS + 1)) - else - echo "⚠️ $name ran but response doesn't contain CUSTOM_GREP_RESULT" - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying tools/tool-overrides samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o tool-overrides-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./tool-overrides-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/tools/virtual-filesystem/README.md b/test/scenarios/tools/virtual-filesystem/README.md deleted file mode 100644 index 30665c97b..000000000 --- a/test/scenarios/tools/virtual-filesystem/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Config Sample: Virtual Filesystem - -Demonstrates running the Copilot agent with **custom tool implementations backed by an in-memory store** instead of the real filesystem. The agent doesn't know it's virtual — it sees `create_file`, `read_file`, and `list_files` tools that work normally, but zero bytes ever touch disk. - -This pattern is the foundation for: -- **WASM / browser agents** where there's no real filesystem -- **Cloud-hosted sandboxes** where file ops go to object storage -- **Multi-tenant platforms** where each user gets isolated virtual storage -- **Office add-ins** where "files" are document sections in memory - -## How It Works - -1. **Disable all built-in tools** with `availableTools: []` -2. **Provide custom tools** (`create_file`, `read_file`, `list_files`) whose handlers read/write a `Map` / `dict` / `HashMap` in the host process -3. **Auto-approve permissions** — no dialogs since the tools are entirely user-controlled -4. The agent uses the tools normally — it doesn't know they're virtual - -## What Each Sample Does - -1. Creates a session with no built-in tools + 3 custom virtual FS tools -2. Sends: _"Create a file called plan.md with a brief 3-item project plan for building a CLI tool. Then read it back and tell me what you wrote."_ -3. The agent calls `create_file` → writes to in-memory map -4. The agent calls `read_file` → reads from in-memory map -5. Prints the agent's response -6. Dumps the in-memory store to prove files exist only in memory - -## Configuration - -| Option | Value | Effect | -|--------|-------|--------| -| `availableTools` | `[]` (empty) | Removes all built-in tools (bash, view, edit, create_file, grep, glob, etc.) | -| `tools` | `[create_file, read_file, list_files]` | Custom tools backed by in-memory storage | -| `onPermissionRequest` | Auto-approve | No permission dialogs | -| `hooks.onPreToolUse` | Auto-allow | No tool confirmation prompts | - -## Key Insight - -The integrator controls the tool layer. By replacing built-in tools with custom implementations, you can swap the backing store to anything — `Map`, Redis, S3, SQLite, IndexedDB — without the agent knowing or caring. The system prompt stays the same. The agent plans and operates normally. - -Custom tools with the same name as a built-in automatically override the built-in — no need to explicitly exclude them. `availableTools: []` removes all built-ins while keeping your custom tools available. - -## Run - -```bash -./verify.sh -``` - -Requires the `copilot` binary (auto-detected or set `COPILOT_CLI_PATH`) and `GITHUB_TOKEN`. diff --git a/test/scenarios/tools/virtual-filesystem/csharp/Program.cs b/test/scenarios/tools/virtual-filesystem/csharp/Program.cs deleted file mode 100644 index 64704ff3f..000000000 --- a/test/scenarios/tools/virtual-filesystem/csharp/Program.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.ComponentModel; -using GitHub.Copilot; -using GitHub.Copilot.Rpc; -using Microsoft.Extensions.AI; - -// In-memory virtual filesystem -var virtualFs = new Dictionary(); - -using var client = new CopilotClient(); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - AvailableTools = [], - Tools = - [ - AIFunctionFactory.Create( - ([Description("File path")] string path, [Description("File content")] string content) => - { - virtualFs[path] = content; - return $"Created {path} ({content.Length} bytes)"; - }, - "create_file", - "Create or overwrite a file at the given path with the provided content"), - AIFunctionFactory.Create( - ([Description("File path")] string path) => - { - return virtualFs.TryGetValue(path, out var content) - ? content - : $"Error: file not found: {path}"; - }, - "read_file", - "Read the contents of a file at the given path"), - AIFunctionFactory.Create( - () => - { - return virtualFs.Count == 0 - ? "No files" - : string.Join("\n", virtualFs.Keys); - }, - "list_files", - "List all files in the virtual filesystem"), - ], - OnPermissionRequest = (request, invocation) => - Task.FromResult(PermissionDecision.ApproveOnce()), - Hooks = new SessionHooks - { - OnPreToolUse = (input, invocation) => - Task.FromResult(new PreToolUseHookOutput { PermissionDecision = "allow" }), - }, - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "Create a file called plan.md with a brief 3-item project plan for building a CLI tool. Then read it back and tell me what you wrote.", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } - - // Dump the virtual filesystem to prove nothing touched disk - Console.WriteLine("\n--- Virtual filesystem contents ---"); - foreach (var (path, content) in virtualFs) - { - Console.WriteLine($"\n[{path}]"); - Console.WriteLine(content); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/tools/virtual-filesystem/csharp/csharp.csproj b/test/scenarios/tools/virtual-filesystem/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/tools/virtual-filesystem/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/tools/virtual-filesystem/go/go.mod b/test/scenarios/tools/virtual-filesystem/go/go.mod deleted file mode 100644 index 38696a380..000000000 --- a/test/scenarios/tools/virtual-filesystem/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/tools/virtual-filesystem/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/tools/virtual-filesystem/go/go.sum b/test/scenarios/tools/virtual-filesystem/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/tools/virtual-filesystem/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/tools/virtual-filesystem/go/main.go b/test/scenarios/tools/virtual-filesystem/go/main.go deleted file mode 100644 index 84dccf7f4..000000000 --- a/test/scenarios/tools/virtual-filesystem/go/main.go +++ /dev/null @@ -1,123 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "strings" - "sync" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -// In-memory virtual filesystem -var ( - virtualFs = make(map[string]string) - virtualFsMu sync.Mutex -) - -type CreateFileArgs struct { - Path string `json:"path" description:"File path"` - Content string `json:"content" description:"File content"` -} - -type ReadFileArgs struct { - Path string `json:"path" description:"File path"` -} - -func main() { - createFile := copilot.DefineTool[CreateFileArgs, string]( - "create_file", - "Create or overwrite a file at the given path with the provided content", - func(args CreateFileArgs, inv copilot.ToolInvocation) (string, error) { - virtualFsMu.Lock() - virtualFs[args.Path] = args.Content - virtualFsMu.Unlock() - return fmt.Sprintf("Created %s (%d bytes)", args.Path, len(args.Content)), nil - }, - ) - - readFile := copilot.DefineTool[ReadFileArgs, string]( - "read_file", - "Read the contents of a file at the given path", - func(args ReadFileArgs, inv copilot.ToolInvocation) (string, error) { - virtualFsMu.Lock() - content, ok := virtualFs[args.Path] - virtualFsMu.Unlock() - if !ok { - return fmt.Sprintf("Error: file not found: %s", args.Path), nil - } - return content, nil - }, - ) - - listFiles := copilot.Tool{ - Name: "list_files", - Description: "List all files in the virtual filesystem", - Parameters: map[string]any{ - "type": "object", - "properties": map[string]any{}, - }, - Handler: func(inv copilot.ToolInvocation) (copilot.ToolResult, error) { - virtualFsMu.Lock() - defer virtualFsMu.Unlock() - if len(virtualFs) == 0 { - return copilot.ToolResult{TextResultForLLM: "No files"}, nil - } - paths := make([]string, 0, len(virtualFs)) - for p := range virtualFs { - paths = append(paths, p) - } - return copilot.ToolResult{TextResultForLLM: strings.Join(paths, "\n")}, nil - }, - } - - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - // Remove all built-in tools — only our custom virtual FS tools are available - AvailableTools: []string{}, - Tools: []copilot.Tool{createFile, readFile, listFiles}, - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - Hooks: &copilot.SessionHooks{ - OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) { - return &copilot.PreToolUseHookOutput{PermissionDecision: "allow"}, nil - }, - }, - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "Create a file called plan.md with a brief 3-item project plan " + - "for building a CLI tool. Then read it back and tell me what you wrote.", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } - - // Dump the virtual filesystem to prove nothing touched disk - fmt.Println("\n--- Virtual filesystem contents ---") - for path, content := range virtualFs { - fmt.Printf("\n[%s]\n", path) - fmt.Println(content) - } -} diff --git a/test/scenarios/tools/virtual-filesystem/python/main.py b/test/scenarios/tools/virtual-filesystem/python/main.py deleted file mode 100644 index eeafa22ce..000000000 --- a/test/scenarios/tools/virtual-filesystem/python/main.py +++ /dev/null @@ -1,81 +0,0 @@ -import asyncio - -from pydantic import BaseModel, Field - -from copilot import CopilotClient, define_tool -from copilot.generated.rpc import PermissionDecisionApproveOnce - -# In-memory virtual filesystem -virtual_fs: dict[str, str] = {} - - -class CreateFileParams(BaseModel): - path: str = Field(description="File path") - content: str = Field(description="File content") - - -class ReadFileParams(BaseModel): - path: str = Field(description="File path") - - -@define_tool(description="Create or overwrite a file at the given path with the provided content") -def create_file(params: CreateFileParams) -> str: - virtual_fs[params.path] = params.content - return f"Created {params.path} ({len(params.content)} bytes)" - - -@define_tool(description="Read the contents of a file at the given path") -def read_file(params: ReadFileParams) -> str: - content = virtual_fs.get(params.path) - if content is None: - return f"Error: file not found: {params.path}" - return content - - -@define_tool(description="List all files in the virtual filesystem") -def list_files() -> str: - if not virtual_fs: - return "No files" - return "\n".join(virtual_fs.keys()) - - -async def auto_approve_permission(request, invocation): - return PermissionDecisionApproveOnce() - - -async def auto_approve_tool(input_data, invocation): - return {"permissionDecision": "allow"} - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session( - on_permission_request=auto_approve_permission, - model="claude-haiku-4.5", - available_tools=[], - tools=[create_file, read_file, list_files], - hooks={"on_pre_tool_use": auto_approve_tool}, - ) - - response = await session.send_and_wait( - "Create a file called plan.md with a brief 3-item project plan " - "for building a CLI tool. Then read it back and tell me what you wrote." - ) - - if response: - print(response.data.content) - - # Dump the virtual filesystem to prove nothing touched disk - print("\n--- Virtual filesystem contents ---") - for path, content in virtual_fs.items(): - print(f"\n[{path}]") - print(content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/tools/virtual-filesystem/python/requirements.txt b/test/scenarios/tools/virtual-filesystem/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/tools/virtual-filesystem/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/tools/virtual-filesystem/typescript/package.json b/test/scenarios/tools/virtual-filesystem/typescript/package.json deleted file mode 100644 index 9f1415d83..000000000 --- a/test/scenarios/tools/virtual-filesystem/typescript/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "tools-virtual-filesystem-typescript", - "version": "1.0.0", - "private": true, - "description": "Config sample — virtual filesystem sandbox with auto-approved permissions", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs", - "zod": "^4.3.6" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0" - } -} diff --git a/test/scenarios/tools/virtual-filesystem/typescript/src/index.ts b/test/scenarios/tools/virtual-filesystem/typescript/src/index.ts deleted file mode 100644 index 432d91da9..000000000 --- a/test/scenarios/tools/virtual-filesystem/typescript/src/index.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { CopilotClient, defineTool } from "@github/copilot-sdk"; -import { z } from "zod"; - -// In-memory virtual filesystem -const virtualFs = new Map(); - -const createFile = defineTool("create_file", { - description: - "Create or overwrite a file at the given path with the provided content", - parameters: z.object({ - path: z.string().describe("File path"), - content: z.string().describe("File content"), - }), - handler: async (args) => { - virtualFs.set(args.path, args.content); - return `Created ${args.path} (${args.content.length} bytes)`; - }, -}); - -const readFile = defineTool("read_file", { - description: "Read the contents of a file at the given path", - parameters: z.object({ - path: z.string().describe("File path"), - }), - handler: async (args) => { - const content = virtualFs.get(args.path); - if (content === undefined) return `Error: file not found: ${args.path}`; - return content; - }, -}); - -const listFiles = defineTool("list_files", { - description: "List all files in the virtual filesystem", - parameters: z.object({}), - handler: async () => { - if (virtualFs.size === 0) return "No files"; - return [...virtualFs.keys()].join("\n"); - }, -}); - -async function main() { - const client = new CopilotClient(); - - try { - const session = await client.createSession({ - model: "claude-haiku-4.5", - // Remove all built-in tools — only our custom virtual FS tools are available - availableTools: [], - tools: [createFile, readFile, listFiles], - onPermissionRequest: async () => ({ kind: "approve-once" as const }), - hooks: { - onPreToolUse: async () => ({ permissionDecision: "allow" as const }), - }, - }); - - const response = await session.sendAndWait({ - prompt: - "Create a file called plan.md with a brief 3-item project plan for building a CLI tool. " + - "Then read it back and tell me what you wrote.", - }); - - if (response) { - console.log(response.data.content); - } - - // Dump the virtual filesystem to prove nothing touched disk - console.log("\n--- Virtual filesystem contents ---"); - for (const [path, content] of virtualFs) { - console.log(`\n[${path}]`); - console.log(content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/tools/virtual-filesystem/verify.sh b/test/scenarios/tools/virtual-filesystem/verify.sh deleted file mode 100755 index 30fd1fd37..000000000 --- a/test/scenarios/tools/virtual-filesystem/verify.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=120 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - - echo "$output" - - if [ "$code" -eq 0 ] && [ -n "$output" ]; then - if echo "$output" | grep -qi "Virtual filesystem contents" && echo "$output" | grep -qi "plan\.md"; then - echo "✅ $name passed (virtual FS operations confirmed)" - PASS=$((PASS + 1)) - else - echo "❌ $name failed (expected pattern not found)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - elif [ "$code" -eq 124 ]; then - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying tools/virtual-filesystem" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o virtual-filesystem-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./virtual-filesystem-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/transport/README.md b/test/scenarios/transport/README.md deleted file mode 100644 index 25067158a..000000000 --- a/test/scenarios/transport/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Transport Samples - -Minimal samples organized by **transport model** — the wire protocol used to communicate with `copilot`. Each subfolder demonstrates one transport with the same "What is the capital of France?" flow. - -## Transport Models - -| Transport | Description | Languages | -|-----------|-------------|-----------| -| **[stdio](stdio/)** | SDK spawns `copilot` as a child process and communicates via stdin/stdout | TypeScript, Python, Go, C#, Rust | -| **[tcp](tcp/)** | SDK connects to a pre-running `copilot` TCP server | TypeScript, Python, Go, C#, Rust | -| **[wasm](wasm/)** | SDK loads `copilot` as an in-process WASM module | TypeScript | - -## How They Differ - -| | stdio | tcp | wasm | -|---|---|---|---| -| **Process model** | Child process | External server | In-process | -| **Binary required** | Yes (auto-spawned) | Yes (pre-started) | No (WASM module) | -| **Wire protocol** | Content-Length framed JSON-RPC over pipes | Content-Length framed JSON-RPC over TCP | In-memory function calls | -| **Best for** | CLI tools, desktop apps | Shared servers, multi-tenant | Serverless, edge, sandboxed | - -## Prerequisites - -- **Authentication** — set `GITHUB_TOKEN`, or run `gh auth login` -- **Copilot CLI** — required for stdio and tcp (set `COPILOT_CLI_PATH`) -- Language toolchains as needed (Node.js 20+, Python 3.11+, Go 1.24+, .NET 8+, Rust 1.94+) - -## Verification - -Each transport has its own `verify.sh` that builds and runs all language samples: - -```bash -cd stdio && ./verify.sh -cd tcp && ./verify.sh -cd wasm && ./verify.sh -``` diff --git a/test/scenarios/transport/reconnect/README.md b/test/scenarios/transport/reconnect/README.md deleted file mode 100644 index c2ed0d2fa..000000000 --- a/test/scenarios/transport/reconnect/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# TCP Reconnection Sample - -Tests that a **pre-running** `copilot` TCP server correctly handles **multiple sequential sessions**. The SDK connects, creates a session, exchanges a message, destroys the session, then repeats the process — verifying the server remains responsive across session lifecycles. - -``` -┌─────────────┐ TCP (JSON-RPC) ┌──────────────┐ -│ Your App │ ─────────────────▶ │ Copilot CLI │ -│ (SDK) │ ◀───────────────── │ (TCP server) │ -└─────────────┘ └──────────────┘ - Session 1: create → send → disconnect - Session 2: create → send → disconnect -``` - -## What This Tests - -- The TCP server accepts a new session after a previous session is destroyed -- Server state is properly cleaned up between sessions -- The SDK client can reuse the same connection for multiple session lifecycles -- No resource leaks or port conflicts across sequential sessions - -## Languages - -| Directory | SDK / Approach | Language | -|-----------|---------------|----------| -| `typescript/` | `@github/copilot-sdk` | TypeScript (Node.js) | - -> **TypeScript-only:** This scenario tests SDK-level session lifecycle over TCP. The reconnection behavior is an SDK concern, so only one language is needed to verify it. - -## Prerequisites - -- **Copilot CLI** — set `COPILOT_CLI_PATH` -- **Authentication** — set `GITHUB_TOKEN`, or run `gh auth login` -- **Node.js 20+** (TypeScript sample) - -## Quick Start - -Start the TCP server: - -```bash -copilot --port 3000 --headless --auth-token-env GITHUB_TOKEN -``` - -Run the sample: - -```bash -cd typescript -npm install && npm run build -COPILOT_CLI_URL=localhost:3000 npm start -``` - -## Verification - -```bash -./verify.sh -``` - -Runs in three phases: - -1. **Server** — starts `copilot` as a TCP server (auto-detects port) -2. **Build** — installs dependencies and compiles the TypeScript sample -3. **E2E Run** — executes the sample with a 120-second timeout, verifies both sessions complete and prints "Reconnect test passed" - -The server is automatically stopped when the script exits. diff --git a/test/scenarios/transport/reconnect/csharp/Program.cs b/test/scenarios/transport/reconnect/csharp/Program.cs deleted file mode 100644 index 99e9b91dd..000000000 --- a/test/scenarios/transport/reconnect/csharp/Program.cs +++ /dev/null @@ -1,61 +0,0 @@ -using GitHub.Copilot; - -var cliUrl = Environment.GetEnvironmentVariable("COPILOT_CLI_URL") ?? "localhost:3000"; - -using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(cliUrl) }); -await client.StartAsync(); - -try -{ - // First session - Console.WriteLine("--- Session 1 ---"); - await using var session1 = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - }); - - var response1 = await session1.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response1?.Data?.Content != null) - { - Console.WriteLine(response1.Data.Content); - } - else - { - Console.Error.WriteLine("No response content received for session 1"); - Environment.Exit(1); - } - Console.WriteLine("Session 1 disconnected\n"); - - // Second session — tests that the server accepts new sessions - Console.WriteLine("--- Session 2 ---"); - await using var session2 = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - }); - - var response2 = await session2.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response2?.Data?.Content != null) - { - Console.WriteLine(response2.Data.Content); - } - else - { - Console.Error.WriteLine("No response content received for session 2"); - Environment.Exit(1); - } - Console.WriteLine("Session 2 disconnected"); - - Console.WriteLine("\nReconnect test passed — both sessions completed successfully"); -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/transport/reconnect/csharp/csharp.csproj b/test/scenarios/transport/reconnect/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/transport/reconnect/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/transport/reconnect/go/go.mod b/test/scenarios/transport/reconnect/go/go.mod deleted file mode 100644 index a9a9a34ee..000000000 --- a/test/scenarios/transport/reconnect/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/transport/reconnect/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/transport/reconnect/go/go.sum b/test/scenarios/transport/reconnect/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/transport/reconnect/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/transport/reconnect/go/main.go b/test/scenarios/transport/reconnect/go/main.go deleted file mode 100644 index 2efeaa2db..000000000 --- a/test/scenarios/transport/reconnect/go/main.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - cliUrl := os.Getenv("COPILOT_CLI_URL") - if cliUrl == "" { - cliUrl = "localhost:3000" - } - - client := copilot.NewClient(&copilot.ClientOptions{ - Connection: copilot.UriConnection{URL: cliUrl}, - }) - - ctx := context.Background() - - // Session 1 - fmt.Println("--- Session 1 ---") - session1, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - }) - if err != nil { - log.Fatal(err) - } - - response1, err := session1.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response1 != nil { - if d, ok := response1.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } else { - log.Fatal("No response content received for session 1") - } - - session1.Disconnect() - fmt.Println("Session 1 disconnected") - fmt.Println() - - // Session 2 — tests that the server accepts new sessions - fmt.Println("--- Session 2 ---") - session2, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - }) - if err != nil { - log.Fatal(err) - } - - response2, err := session2.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response2 != nil { - if d, ok := response2.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } else { - log.Fatal("No response content received for session 2") - } - - session2.Disconnect() - fmt.Println("Session 2 disconnected") - - fmt.Println("\nReconnect test passed — both sessions completed successfully") -} diff --git a/test/scenarios/transport/reconnect/python/main.py b/test/scenarios/transport/reconnect/python/main.py deleted file mode 100644 index cc79f9721..000000000 --- a/test/scenarios/transport/reconnect/python/main.py +++ /dev/null @@ -1,51 +0,0 @@ -import asyncio -import os -import sys - -from copilot import CopilotClient, RuntimeConnection - - -async def main(): - client = CopilotClient( - connection=RuntimeConnection.for_uri( - os.environ.get("COPILOT_CLI_URL", "localhost:3000"), - ), - ) - - try: - # First session - print("--- Session 1 ---") - session1 = await client.create_session(model="claude-haiku-4.5") - - response1 = await session1.send_and_wait("What is the capital of France?") - - if response1 and response1.data.content: - print(response1.data.content) - else: - print("No response content received for session 1", file=sys.stderr) - sys.exit(1) - - await session1.disconnect() - print("Session 1 disconnected\n") - - # Second session — tests that the server accepts new sessions - print("--- Session 2 ---") - session2 = await client.create_session(model="claude-haiku-4.5") - - response2 = await session2.send_and_wait("What is the capital of France?") - - if response2 and response2.data.content: - print(response2.data.content) - else: - print("No response content received for session 2", file=sys.stderr) - sys.exit(1) - - await session2.disconnect() - print("Session 2 disconnected") - - print("\nReconnect test passed — both sessions completed successfully") - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/transport/reconnect/python/requirements.txt b/test/scenarios/transport/reconnect/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/transport/reconnect/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/transport/reconnect/typescript/package.json b/test/scenarios/transport/reconnect/typescript/package.json deleted file mode 100644 index 9ef9163ca..000000000 --- a/test/scenarios/transport/reconnect/typescript/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "transport-reconnect-typescript", - "version": "1.0.0", - "private": true, - "description": "Transport sample — TCP reconnection and session reuse", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0", - "typescript": "^5.5.0" - } -} diff --git a/test/scenarios/transport/reconnect/typescript/src/index.ts b/test/scenarios/transport/reconnect/typescript/src/index.ts deleted file mode 100644 index e1ea21fd1..000000000 --- a/test/scenarios/transport/reconnect/typescript/src/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient({ - connection: RuntimeConnection.forUri( - process.env.COPILOT_CLI_URL || "localhost:3000", - ), - }); - - try { - // First session - console.log("--- Session 1 ---"); - const session1 = await client.createSession({ model: "claude-haiku-4.5" }); - - const response1 = await session1.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response1?.data.content) { - console.log(response1.data.content); - } else { - console.error("No response content received for session 1"); - process.exit(1); - } - - await session1.disconnect(); - console.log("Session 1 disconnected\n"); - - // Second session — tests that the server accepts new sessions - console.log("--- Session 2 ---"); - const session2 = await client.createSession({ model: "claude-haiku-4.5" }); - - const response2 = await session2.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response2?.data.content) { - console.log(response2.data.content); - } else { - console.error("No response content received for session 2"); - process.exit(1); - } - - await session2.disconnect(); - console.log("Session 2 disconnected"); - - console.log( - "\nReconnect test passed — both sessions completed successfully", - ); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/transport/reconnect/verify.sh b/test/scenarios/transport/reconnect/verify.sh deleted file mode 100755 index 28dd7326f..000000000 --- a/test/scenarios/transport/reconnect/verify.sh +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=120 -SERVER_PID="" -SERVER_PORT_FILE="" - -cleanup() { - if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then - echo "" - echo "Stopping Copilot CLI server (PID $SERVER_PID)..." - kill "$SERVER_PID" 2>/dev/null || true - wait "$SERVER_PID" 2>/dev/null || true - fi - [ -n "$SERVER_PORT_FILE" ] && rm -f "$SERVER_PORT_FILE" -} -trap cleanup EXIT - -# Resolve Copilot CLI binary: use COPILOT_CLI_PATH env var or find the SDK bundled CLI. -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - # Try to resolve from the TypeScript sample node_modules - TS_DIR="$SCRIPT_DIR/typescript" - if [ -d "$TS_DIR/node_modules/@github/copilot" ]; then - COPILOT_CLI_PATH="$(node -e "console.log(require.resolve('@github/copilot'))" 2>/dev/null || true)" - fi - # Fallback: check PATH - if [ -z "${COPILOT_CLI_PATH:-}" ]; then - COPILOT_CLI_PATH="$(command -v copilot 2>/dev/null || true)" - fi -fi -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - echo "❌ Could not find Copilot CLI binary." - echo " Set COPILOT_CLI_PATH or run: cd typescript && npm install" - exit 1 -fi -echo "Using CLI: $COPILOT_CLI_PATH" - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && echo "$output" | grep -q "Reconnect test passed"; then - echo "$output" - echo "✅ $name passed (reconnect verified)" - PASS=$((PASS + 1)) - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Starting Copilot CLI TCP server" -echo "══════════════════════════════════════" -echo "" - -SERVER_PORT_FILE=$(mktemp) -"$COPILOT_CLI_PATH" --headless --auth-token-env GITHUB_TOKEN > "$SERVER_PORT_FILE" 2>&1 & -SERVER_PID=$! - -# Wait for server to announce its port -echo "Waiting for server to be ready..." -PORT="" -for i in $(seq 1 30); do - if ! kill -0 "$SERVER_PID" 2>/dev/null; then - echo "❌ Server process exited unexpectedly" - cat "$SERVER_PORT_FILE" 2>/dev/null - exit 1 - fi - PORT=$(grep -o 'listening on port [0-9]*' "$SERVER_PORT_FILE" 2>/dev/null | grep -o '[0-9]*' || true) - if [ -n "$PORT" ]; then - break - fi - if [ "$i" -eq 30 ]; then - echo "❌ Server did not announce port within 30 seconds" - exit 1 - fi - sleep 1 -done -export COPILOT_CLI_URL="localhost:$PORT" -echo "Server is ready on port $PORT (PID $SERVER_PID)" -echo "" - -echo "══════════════════════════════════════" -echo " Verifying transport/reconnect" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o reconnect-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && CLI_URL=$COPILOT_CLI_URL node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && CLI_URL=$COPILOT_CLI_URL python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && CLI_URL=$COPILOT_CLI_URL ./reconnect-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && COPILOT_CLI_URL=$COPILOT_CLI_URL dotnet run --no-build 2>&1" - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/transport/stdio/README.md b/test/scenarios/transport/stdio/README.md deleted file mode 100644 index 7de2457ec..000000000 --- a/test/scenarios/transport/stdio/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Stdio Transport Samples - -Samples demonstrating the **stdio** transport model. The SDK spawns `copilot` as a child process and communicates over standard input/output using Content-Length-framed JSON-RPC 2.0 messages. - -``` -┌─────────────┐ stdin/stdout (JSON-RPC) ┌──────────────┐ -│ Your App │ ──────────────────────────▶ │ Copilot CLI │ -│ (SDK) │ ◀────────────────────────── │ (child proc) │ -└─────────────┘ └──────────────┘ -``` - -Each sample follows the same flow: - -1. **Create a client** that spawns `copilot` automatically -2. **Open a session** targeting the `gpt-4.1` model -3. **Send a prompt** ("What is the capital of France?") -4. **Print the response** and clean up - -## Languages - -| Directory | SDK / Approach | Language | -|-----------|---------------|----------| -| `typescript/` | `@github/copilot-sdk` | TypeScript (Node.js) | -| `python/` | `github-copilot-sdk` | Python | -| `go/` | `github.com/github/copilot-sdk/go` | Go | -| `rust/` | `copilot-sdk` | Rust | - -## Prerequisites - -- **Copilot CLI** — set `COPILOT_CLI_PATH` -- **Authentication** — set `GITHUB_TOKEN`, or run `gh auth login` -- **Node.js 20+** (TypeScript sample) -- **Python 3.10+** (Python sample) -- **Go 1.24+** (Go sample) - -## Quick Start - -**TypeScript** -```bash -cd typescript -npm install && npm run build && npm start -``` - -**Python** -```bash -cd python -pip install -r requirements.txt -python main.py -``` - -**Go** -```bash -cd go -go run main.go -``` - -## Verification - -```bash -./verify.sh -``` - -Runs in two phases: - -1. **Build** — installs dependencies and compiles each sample -2. **E2E Run** — executes each sample with a 60-second timeout and verifies it produces output diff --git a/test/scenarios/transport/stdio/csharp/Program.cs b/test/scenarios/transport/stdio/csharp/Program.cs deleted file mode 100644 index 576ca5518..000000000 --- a/test/scenarios/transport/stdio/csharp/Program.cs +++ /dev/null @@ -1,30 +0,0 @@ -using GitHub.Copilot; - -using var client = new CopilotClient(new CopilotClientOptions -{ - Connection = RuntimeConnection.ForStdio(path: Environment.GetEnvironmentVariable("COPILOT_CLI_PATH")), -}); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/transport/stdio/csharp/csharp.csproj b/test/scenarios/transport/stdio/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/transport/stdio/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/transport/stdio/go/go.mod b/test/scenarios/transport/stdio/go/go.mod deleted file mode 100644 index ea5192511..000000000 --- a/test/scenarios/transport/stdio/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/transport/stdio/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/transport/stdio/go/go.sum b/test/scenarios/transport/stdio/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/transport/stdio/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/transport/stdio/go/main.go b/test/scenarios/transport/stdio/go/main.go deleted file mode 100644 index 51b592431..000000000 --- a/test/scenarios/transport/stdio/go/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - // Go SDK auto-reads COPILOT_CLI_PATH from env - client := copilot.NewClient(nil) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/transport/stdio/python/main.py b/test/scenarios/transport/stdio/python/main.py deleted file mode 100644 index 6be1d4294..000000000 --- a/test/scenarios/transport/stdio/python/main.py +++ /dev/null @@ -1,22 +0,0 @@ -import asyncio - -from copilot import CopilotClient - - -async def main(): - client = CopilotClient() - - try: - session = await client.create_session(model="claude-haiku-4.5") - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/transport/stdio/python/requirements.txt b/test/scenarios/transport/stdio/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/transport/stdio/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/transport/stdio/rust/Cargo.toml b/test/scenarios/transport/stdio/rust/Cargo.toml deleted file mode 100644 index aa22474c0..000000000 --- a/test/scenarios/transport/stdio/rust/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "stdio-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/transport/stdio/rust/src/main.rs b/test/scenarios/transport/stdio/rust/src/main.rs deleted file mode 100644 index b3f92eaf9..000000000 --- a/test/scenarios/transport/stdio/rust/src/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Stdio transport — spawn the CLI as a child and exchange JSON-RPC over its stdio. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::SessionConfig; -use github_copilot_sdk::{Client, ClientOptions}; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let client = Client::start(ClientOptions::default()).await?; - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - let session = client.create_session(config).await?; - - let response = session.send_and_wait("What is the capital of France?").await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/transport/stdio/typescript/package.json b/test/scenarios/transport/stdio/typescript/package.json deleted file mode 100644 index bd56e8a38..000000000 --- a/test/scenarios/transport/stdio/typescript/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "transport-stdio-typescript", - "version": "1.0.0", - "private": true, - "description": "Stdio transport sample — spawns Copilot CLI as a child process", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0", - "typescript": "^5.5.0" - } -} diff --git a/test/scenarios/transport/stdio/typescript/src/index.ts b/test/scenarios/transport/stdio/typescript/src/index.ts deleted file mode 100644 index 7df9cd888..000000000 --- a/test/scenarios/transport/stdio/typescript/src/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient({ - connection: RuntimeConnection.forStdio({ - path: process.env.COPILOT_CLI_PATH, - }), - }); - - try { - const session = await client.createSession({ model: "claude-haiku-4.5" }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response) { - console.log(response.data.content); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/transport/stdio/verify.sh b/test/scenarios/transport/stdio/verify.sh deleted file mode 100755 index f9f004675..000000000 --- a/test/scenarios/transport/stdio/verify.sh +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 - -# COPILOT_CLI_PATH is optional — the SDK discovers the bundled CLI automatically. -# Set it only to override with a custom binary path. -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -fi - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi -echo "" - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && [ -n "$output" ] && echo "$output" | grep -qi "Paris\|capital\|France\|response"; then - echo "$output" - echo "✅ $name passed (content validated)" - PASS=$((PASS + 1)) - elif [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - echo "❌ $name failed (no meaningful content in response)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (no content match)" - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Verifying stdio transport samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o stdio-go . 2>&1" - -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./stdio-go" - -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/transport/tcp/README.md b/test/scenarios/transport/tcp/README.md deleted file mode 100644 index ea2df27cd..000000000 --- a/test/scenarios/transport/tcp/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# TCP Transport Samples - -Samples demonstrating the **TCP** transport model. The SDK connects to a **pre-running** `copilot` TCP server using Content-Length-framed JSON-RPC 2.0 messages over a TCP socket. - -``` -┌─────────────┐ TCP (JSON-RPC) ┌──────────────┐ -│ Your App │ ─────────────────▶ │ Copilot CLI │ -│ (SDK) │ ◀───────────────── │ (TCP server) │ -└─────────────┘ └──────────────┘ -``` - -Each sample follows the same flow: - -1. **Connect** to a running `copilot` server via TCP -2. **Open a session** targeting the `gpt-4.1` model -3. **Send a prompt** ("What is the capital of France?") -4. **Print the response** and clean up - -## Languages - -| Directory | SDK / Approach | Language | -|-----------|---------------|----------| -| `typescript/` | `@github/copilot-sdk` | TypeScript (Node.js) | -| `python/` | `github-copilot-sdk` | Python | -| `go/` | `github.com/github/copilot-sdk/go` | Go | - -## Prerequisites - -- **Copilot CLI** — set `COPILOT_CLI_PATH` -- **Authentication** — set `GITHUB_TOKEN`, or run `gh auth login` -- **Node.js 20+** (TypeScript sample) -- **Python 3.10+** (Python sample) -- **Go 1.24+** (Go sample) - -## Starting the Server - -Start `copilot` as a TCP server before running any sample: - -```bash -copilot --port 3000 --headless --auth-token-env GITHUB_TOKEN -``` - -## Quick Start - -**TypeScript** -```bash -cd typescript -npm install && npm run build && npm start -``` - -**Python** -```bash -cd python -pip install -r requirements.txt -python main.py -``` - -**Go** -```bash -cd go -go run main.go -``` - -All samples default to `localhost:3000`. Override with the `COPILOT_CLI_URL` environment variable: - -```bash -COPILOT_CLI_URL=localhost:8080 npm start -``` - -## Verification - -```bash -./verify.sh -``` - -Runs in three phases: - -1. **Server** — starts `copilot` as a TCP server (auto-detects port) -2. **Build** — installs dependencies and compiles each sample -3. **E2E Run** — executes each sample with a 60-second timeout and verifies it produces output - -The server is automatically stopped when the script exits. diff --git a/test/scenarios/transport/tcp/csharp/Program.cs b/test/scenarios/transport/tcp/csharp/Program.cs deleted file mode 100644 index 9fe2b176b..000000000 --- a/test/scenarios/transport/tcp/csharp/Program.cs +++ /dev/null @@ -1,36 +0,0 @@ -using GitHub.Copilot; - -var cliUrl = Environment.GetEnvironmentVariable("COPILOT_CLI_URL") ?? "localhost:3000"; - -using var client = new CopilotClient(new CopilotClientOptions -{ - Connection = RuntimeConnection.ForUri(cliUrl), -}); - -await client.StartAsync(); - -try -{ - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "claude-haiku-4.5", - }); - - var response = await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "What is the capital of France?", - }); - - if (response != null) - { - Console.WriteLine(response.Data?.Content); - } - else - { - Console.WriteLine("(no response)"); - } -} -finally -{ - await client.StopAsync(); -} diff --git a/test/scenarios/transport/tcp/csharp/csharp.csproj b/test/scenarios/transport/tcp/csharp/csharp.csproj deleted file mode 100644 index 48e375961..000000000 --- a/test/scenarios/transport/tcp/csharp/csharp.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - Exe - net8.0 - LatestMajor - enable - enable - true - - - - - diff --git a/test/scenarios/transport/tcp/go/go.mod b/test/scenarios/transport/tcp/go/go.mod deleted file mode 100644 index 83ca00bc9..000000000 --- a/test/scenarios/transport/tcp/go/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/github/copilot-sdk/samples/transport/tcp/go - -go 1.24 - -require github.com/github/copilot-sdk/go v0.0.0 - -require ( - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/uuid v1.6.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect -) - -replace github.com/github/copilot-sdk/go => ../../../../../go diff --git a/test/scenarios/transport/tcp/go/go.sum b/test/scenarios/transport/tcp/go/go.sum deleted file mode 100644 index 605b1f5d2..000000000 --- a/test/scenarios/transport/tcp/go/go.sum +++ /dev/null @@ -1,27 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/scenarios/transport/tcp/go/main.go b/test/scenarios/transport/tcp/go/main.go deleted file mode 100644 index 95da9cf68..000000000 --- a/test/scenarios/transport/tcp/go/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - cliUrl := os.Getenv("COPILOT_CLI_URL") - if cliUrl == "" { - cliUrl = "localhost:3000" - } - - client := copilot.NewClient(&copilot.ClientOptions{ - Connection: copilot.UriConnection{URL: cliUrl}, - }) - - ctx := context.Background() - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "claude-haiku-4.5", - }) - if err != nil { - log.Fatal(err) - } - defer session.Disconnect() - - response, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "What is the capital of France?", - }) - if err != nil { - log.Fatal(err) - } - - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} diff --git a/test/scenarios/transport/tcp/python/main.py b/test/scenarios/transport/tcp/python/main.py deleted file mode 100644 index 1bf32b475..000000000 --- a/test/scenarios/transport/tcp/python/main.py +++ /dev/null @@ -1,27 +0,0 @@ -import asyncio -import os - -from copilot import CopilotClient, RuntimeConnection - - -async def main(): - client = CopilotClient( - connection=RuntimeConnection.for_uri( - os.environ.get("COPILOT_CLI_URL", "localhost:3000"), - ), - ) - - try: - session = await client.create_session(model="claude-haiku-4.5") - - response = await session.send_and_wait("What is the capital of France?") - - if response: - print(response.data.content) - - await session.disconnect() - finally: - await client.stop() - - -asyncio.run(main()) diff --git a/test/scenarios/transport/tcp/python/requirements.txt b/test/scenarios/transport/tcp/python/requirements.txt deleted file mode 100644 index f9a8f4d60..000000000 --- a/test/scenarios/transport/tcp/python/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --e ../../../../../python diff --git a/test/scenarios/transport/tcp/rust/Cargo.toml b/test/scenarios/transport/tcp/rust/Cargo.toml deleted file mode 100644 index fe5d19a91..000000000 --- a/test/scenarios/transport/tcp/rust/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "tcp-rust" -version = "0.0.0" -edition = "2024" -publish = false - -[dependencies] -github-copilot-sdk = { path = "../../../../../rust" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/test/scenarios/transport/tcp/rust/src/main.rs b/test/scenarios/transport/tcp/rust/src/main.rs deleted file mode 100644 index f9ccfe5f3..000000000 --- a/test/scenarios/transport/tcp/rust/src/main.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! TCP transport — connect to an externally-running CLI server. Reads -//! `COPILOT_CLI_URL` (default `localhost:3000`) for `host:port`. - -use std::sync::Arc; - -use github_copilot_sdk::handler::ApproveAllHandler; -use github_copilot_sdk::types::SessionConfig; -use github_copilot_sdk::{Client, ClientOptions, Transport}; - -#[tokio::main] -async fn main() -> Result<(), github_copilot_sdk::Error> { - let cli_url = - std::env::var("COPILOT_CLI_URL").unwrap_or_else(|_| "localhost:3000".to_string()); - let (host, port_str) = cli_url - .split_once(':') - .expect("COPILOT_CLI_URL must be 'host:port'"); - let port: u16 = port_str.parse().expect("COPILOT_CLI_URL port must be u16"); - - let mut opts = ClientOptions::default(); - opts.transport = Transport::External { - host: host.to_string(), - port, - connection_token: None, - }; - let client = Client::start(opts).await?; - - let mut config = SessionConfig::default(); - config.model = Some("claude-haiku-4.5".to_string()); - let config = config.with_permission_handler(Arc::new(ApproveAllHandler)); - - let session = client.create_session(config).await?; - - let response = session.send_and_wait("What is the capital of France?").await?; - - if let Some(event) = response { - if let Some(content) = event.data.get("content").and_then(|c| c.as_str()) { - println!("{content}"); - } - } - - session.disconnect().await?; - Ok(()) -} diff --git a/test/scenarios/transport/tcp/typescript/package.json b/test/scenarios/transport/tcp/typescript/package.json deleted file mode 100644 index 98799b75a..000000000 --- a/test/scenarios/transport/tcp/typescript/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "transport-tcp-typescript", - "version": "1.0.0", - "private": true, - "description": "TCP transport sample — connects to a running Copilot CLI TCP server", - "type": "module", - "scripts": { - "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"", - "start": "node dist/index.js" - }, - "dependencies": { - "@github/copilot-sdk": "file:../../../../../nodejs" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "esbuild": "^0.24.0", - "typescript": "^5.5.0" - } -} diff --git a/test/scenarios/transport/tcp/typescript/src/index.ts b/test/scenarios/transport/tcp/typescript/src/index.ts deleted file mode 100644 index 9b6efd277..000000000 --- a/test/scenarios/transport/tcp/typescript/src/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient({ - connection: RuntimeConnection.forUri( - process.env.COPILOT_CLI_URL || "localhost:3000", - ), - }); - - try { - const session = await client.createSession({ model: "claude-haiku-4.5" }); - - const response = await session.sendAndWait({ - prompt: "What is the capital of France?", - }); - - if (response?.data.content) { - console.log(response.data.content); - } else { - console.error("No response content received"); - process.exit(1); - } - - await session.disconnect(); - } finally { - await client.stop(); - } -} - -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/test/scenarios/transport/tcp/verify.sh b/test/scenarios/transport/tcp/verify.sh deleted file mode 100755 index fd30b98f9..000000000 --- a/test/scenarios/transport/tcp/verify.sh +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" -PASS=0 -FAIL=0 -ERRORS="" -TIMEOUT=60 -SERVER_PID="" -SERVER_PORT_FILE="" - -cleanup() { - if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then - echo "" - echo "Stopping Copilot CLI server (PID $SERVER_PID)..." - kill "$SERVER_PID" 2>/dev/null || true - wait "$SERVER_PID" 2>/dev/null || true - fi - [ -n "$SERVER_PORT_FILE" ] && rm -f "$SERVER_PORT_FILE" -} -trap cleanup EXIT - -# Resolve Copilot CLI binary: use COPILOT_CLI_PATH env var or find the SDK bundled CLI. -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - # Try to resolve from the TypeScript sample node_modules - TS_DIR="$SCRIPT_DIR/typescript" - if [ -d "$TS_DIR/node_modules/@github/copilot" ]; then - COPILOT_CLI_PATH="$(node -e "console.log(require.resolve('@github/copilot'))" 2>/dev/null || true)" - fi - # Fallback: check PATH - if [ -z "${COPILOT_CLI_PATH:-}" ]; then - COPILOT_CLI_PATH="$(command -v copilot 2>/dev/null || true)" - fi -fi -if [ -z "${COPILOT_CLI_PATH:-}" ]; then - echo "❌ Could not find Copilot CLI binary." - echo " Set COPILOT_CLI_PATH or run: cd typescript && npm install" - exit 1 -fi -echo "Using CLI: $COPILOT_CLI_PATH" - -# Ensure GITHUB_TOKEN is set for auth -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set and gh auth not available. E2E runs will fail." -fi - -# Use gtimeout on macOS, timeout on Linux -if command -v gtimeout &>/dev/null; then - TIMEOUT_CMD="gtimeout" -elif command -v timeout &>/dev/null; then - TIMEOUT_CMD="timeout" -else - echo "⚠️ No timeout command found. Install coreutils (brew install coreutils)." - echo " Running without timeouts." - TIMEOUT_CMD="" -fi - -check() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - if output=$("$@" 2>&1); then - echo "$output" - echo "✅ $name passed" - PASS=$((PASS + 1)) - else - echo "$output" - echo "❌ $name failed" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -run_with_timeout() { - local name="$1" - shift - printf "━━━ %s ━━━\n" "$name" - local output="" - local code=0 - if [ -n "$TIMEOUT_CMD" ]; then - output=$($TIMEOUT_CMD "$TIMEOUT" "$@" 2>&1) && code=0 || code=$? - else - output=$("$@" 2>&1) && code=0 || code=$? - fi - if [ "$code" -eq 0 ] && [ -n "$output" ] && echo "$output" | grep -qi "Paris\|capital\|France\|response"; then - echo "$output" - echo "✅ $name passed (content validated)" - PASS=$((PASS + 1)) - elif [ "$code" -eq 0 ] && [ -n "$output" ]; then - echo "$output" - echo "❌ $name failed (no meaningful content in response)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (no content match)" - elif [ "$code" -eq 124 ]; then - echo "${output:-(no output)}" - echo "❌ $name failed (timed out after ${TIMEOUT}s)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name (timeout)" - else - echo "${output:-(empty output)}" - echo "❌ $name failed (exit code $code)" - FAIL=$((FAIL + 1)) - ERRORS="$ERRORS\n - $name" - fi - echo "" -} - -echo "══════════════════════════════════════" -echo " Starting Copilot CLI TCP server" -echo "══════════════════════════════════════" -echo "" - -SERVER_PORT_FILE=$(mktemp) -"$COPILOT_CLI_PATH" --headless --auth-token-env GITHUB_TOKEN > "$SERVER_PORT_FILE" 2>&1 & -SERVER_PID=$! - -# Wait for server to announce its port -echo "Waiting for server to be ready..." -PORT="" -for i in $(seq 1 30); do - if ! kill -0 "$SERVER_PID" 2>/dev/null; then - echo "❌ Server process exited unexpectedly" - cat "$SERVER_PORT_FILE" 2>/dev/null - exit 1 - fi - PORT=$(grep -o 'listening on port [0-9]*' "$SERVER_PORT_FILE" 2>/dev/null | grep -o '[0-9]*' || true) - if [ -n "$PORT" ]; then - break - fi - if [ "$i" -eq 30 ]; then - echo "❌ Server did not announce port within 30 seconds" - exit 1 - fi - sleep 1 -done -export COPILOT_CLI_URL="localhost:$PORT" -echo "Server is ready on port $PORT (PID $SERVER_PID)" -echo "" - -echo "══════════════════════════════════════" -echo " Verifying TCP transport samples" -echo " Phase 1: Build" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: install + compile -check "TypeScript (install)" bash -c "cd '$SCRIPT_DIR/typescript' && npm install --ignore-scripts 2>&1" -check "TypeScript (build)" bash -c "cd '$SCRIPT_DIR/typescript' && npm run build 2>&1" - -# Python: install + syntax -check "Python (install)" bash -c "python3 -c 'import copilot' 2>/dev/null || (cd '$SCRIPT_DIR/python' && pip3 install -r requirements.txt --quiet 2>&1)" -check "Python (syntax)" bash -c "python3 -c \"import ast; ast.parse(open('$SCRIPT_DIR/python/main.py').read()); print('Syntax OK')\"" - -# Go: build -check "Go (build)" bash -c "cd '$SCRIPT_DIR/go' && go build -o tcp-go . 2>&1" - -# C#: build -check "C# (build)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet build --nologo -v q 2>&1" -# Rust: build -check "Rust (build)" bash -c "cd '$SCRIPT_DIR/rust' && cargo build --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Phase 2: E2E Run (timeout ${TIMEOUT}s each)" -echo "══════════════════════════════════════" -echo "" - -# TypeScript: run -run_with_timeout "TypeScript (run)" bash -c "cd '$SCRIPT_DIR/typescript' && node dist/index.js" - -# Python: run -run_with_timeout "Python (run)" bash -c "cd '$SCRIPT_DIR/python' && python3 main.py" - -# Go: run -run_with_timeout "Go (run)" bash -c "cd '$SCRIPT_DIR/go' && ./tcp-go" - -# C#: run -run_with_timeout "C# (run)" bash -c "cd '$SCRIPT_DIR/csharp' && dotnet run --no-build 2>&1" -# Rust: run -run_with_timeout "Rust (run)" bash -c "cd '$SCRIPT_DIR/rust' && cargo run --quiet 2>&1" - - -echo "══════════════════════════════════════" -echo " Results: $PASS passed, $FAIL failed" -echo "══════════════════════════════════════" -if [ "$FAIL" -gt 0 ]; then - echo -e "Failures:$ERRORS" - exit 1 -fi diff --git a/test/scenarios/verify.sh b/test/scenarios/verify.sh deleted file mode 100755 index 7b6b066a0..000000000 --- a/test/scenarios/verify.sh +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" -TMP_DIR="$(mktemp -d)" -MAX_PARALLEL="${SCENARIO_PARALLEL:-6}" - -cleanup() { rm -rf "$TMP_DIR"; } -trap cleanup EXIT - -# ── CLI path (optional) ────────────────────────────────────────────── -if [ -n "${COPILOT_CLI_PATH:-}" ]; then - echo "Using CLI override: $COPILOT_CLI_PATH" -else - echo "No COPILOT_CLI_PATH set — SDKs will use their bundled CLI." -fi - -# ── Auth ──────────────────────────────────────────────────────────── -if [ -z "${GITHUB_TOKEN:-}" ]; then - if command -v gh &>/dev/null; then - export GITHUB_TOKEN=$(gh auth token 2>/dev/null || true) - fi -fi -if [ -z "${GITHUB_TOKEN:-}" ]; then - echo "⚠️ GITHUB_TOKEN not set" -fi - -# ── Pre-install shared dependencies ──────────────────────────────── -# Install Python SDK once to avoid parallel pip install races -if command -v pip3 &>/dev/null; then - pip3 install -e "$ROOT_DIR/python" --quiet 2>/dev/null || true -fi - -# ── Discover verify scripts ──────────────────────────────────────── -VERIFY_SCRIPTS=() -while IFS= read -r script; do - VERIFY_SCRIPTS+=("$script") -done < <(find "$SCRIPT_DIR" -mindepth 3 -maxdepth 3 -name verify.sh -type f | sort) - -TOTAL=${#VERIFY_SCRIPTS[@]} - -# ── SDK icon helpers ──────────────────────────────────────────────── -sdk_icons() { - local log="$1" - local ts py go cs rs - ts="$(sdk_status "$log" "TypeScript")" - py="$(sdk_status "$log" "Python")" - go="$(sdk_status "$log" "Go ")" - cs="$(sdk_status "$log" "C#")" - rs="$(sdk_status "$log" "Rust")" - printf "TS %s PY %s GO %s C# %s RS %s" "$ts" "$py" "$go" "$cs" "$rs" -} - -sdk_status() { - local log="$1" sdk="$2" - if ! grep -q "$sdk" "$log" 2>/dev/null; then - printf "·"; return - fi - if grep "$sdk" "$log" | grep -q "❌"; then - printf "✗"; return - fi - if grep "$sdk" "$log" | grep -q "⏭\|SKIP"; then - printf "⊘"; return - fi - printf "✓" -} - -# ── Display helpers ───────────────────────────────────────────────── -BOLD="\033[1m" -DIM="\033[2m" -RESET="\033[0m" -RED="\033[31m" -GREEN="\033[32m" -YELLOW="\033[33m" -CYAN="\033[36m" -CLR_LINE="\033[2K" - -BAR_WIDTH=20 - -progress_bar() { - local done_count="$1" total="$2" - local filled=$(( done_count * BAR_WIDTH / total )) - local empty=$(( BAR_WIDTH - filled )) - printf "${DIM}[" - [ "$filled" -gt 0 ] && printf "%0.s█" $(seq 1 "$filled") - [ "$empty" -gt 0 ] && printf "%0.s░" $(seq 1 "$empty") - printf "]${RESET}" -} - -declare -a SCENARIO_NAMES=() -declare -a SCENARIO_STATES=() # waiting | running | done -declare -a SCENARIO_RESULTS=() # "" | PASS | FAIL | SKIP -declare -a SCENARIO_PIDS=() -declare -a SCENARIO_ICONS=() - -for script in "${VERIFY_SCRIPTS[@]}"; do - rel="${script#"$SCRIPT_DIR"/}" - name="${rel%/verify.sh}" - SCENARIO_NAMES+=("$name") - SCENARIO_STATES+=("waiting") - SCENARIO_RESULTS+=("") - SCENARIO_PIDS+=("") - SCENARIO_ICONS+=("") -done - -# ── Execution ─────────────────────────────────────────────────────── -RUNNING_COUNT=0 -NEXT_IDX=0 -PASSED=0; FAILED=0; SKIPPED=0 -DONE_COUNT=0 - -# The progress line is the ONE line we update in-place via \r. -# When a scenario completes, we print its result as a permanent line -# above the progress line. -COLS="${COLUMNS:-$(tput cols 2>/dev/null || echo 80)}" - -print_progress() { - local running_names="" - for i in "${!SCENARIO_STATES[@]}"; do - if [ "${SCENARIO_STATES[$i]}" = "running" ]; then - [ -n "$running_names" ] && running_names="$running_names, " - running_names="$running_names${SCENARIO_NAMES[$i]}" - fi - done - # Build the prefix: " 3/33 [████░░░░░░░░░░░░░░░░] " - local prefix - prefix=$(printf " %d/%d " "$DONE_COUNT" "$TOTAL") - local prefix_len=$(( ${#prefix} + BAR_WIDTH + 4 )) # +4 for []+ spaces - # Truncate running names to fit in one terminal line - local max_names=$(( COLS - prefix_len - 1 )) - if [ "${#running_names}" -gt "$max_names" ] && [ "$max_names" -gt 3 ]; then - running_names="${running_names:0:$((max_names - 1))}…" - fi - printf "\r${CLR_LINE}" - printf "%s" "$prefix" - progress_bar "$DONE_COUNT" "$TOTAL" - printf " ${CYAN}%s${RESET}" "$running_names" -} - -print_result() { - local i="$1" - local name="${SCENARIO_NAMES[$i]}" - local result="${SCENARIO_RESULTS[$i]}" - local icons="${SCENARIO_ICONS[$i]}" - - # Clear the progress line, print result, then reprint progress below - printf "\r${CLR_LINE}" - case "$result" in - PASS) printf " ${GREEN}✅${RESET} %-36s %s\n" "$name" "$icons" ;; - FAIL) printf " ${RED}❌${RESET} %-36s %s\n" "$name" "$icons" ;; - SKIP) printf " ${YELLOW}⏭${RESET} %-36s %s\n" "$name" "$icons" ;; - esac -} - -start_scenario() { - local i="$1" - local script="${VERIFY_SCRIPTS[$i]}" - local name="${SCENARIO_NAMES[$i]}" - local log_file="$TMP_DIR/${name//\//__}.log" - - bash "$script" >"$log_file" 2>&1 & - SCENARIO_PIDS[$i]=$! - SCENARIO_STATES[$i]="running" - RUNNING_COUNT=$((RUNNING_COUNT + 1)) -} - -finish_scenario() { - local i="$1" exit_code="$2" - local name="${SCENARIO_NAMES[$i]}" - local log_file="$TMP_DIR/${name//\//__}.log" - - SCENARIO_STATES[$i]="done" - RUNNING_COUNT=$((RUNNING_COUNT - 1)) - DONE_COUNT=$((DONE_COUNT + 1)) - - if grep -q "^SKIP:" "$log_file" 2>/dev/null; then - SCENARIO_RESULTS[$i]="SKIP" - SKIPPED=$((SKIPPED + 1)) - elif [ "$exit_code" -eq 0 ]; then - SCENARIO_RESULTS[$i]="PASS" - PASSED=$((PASSED + 1)) - else - SCENARIO_RESULTS[$i]="FAIL" - FAILED=$((FAILED + 1)) - fi - - SCENARIO_ICONS[$i]="$(sdk_icons "$log_file")" - print_result "$i" -} - -echo "" - -# Launch initial batch -while [ "$NEXT_IDX" -lt "$TOTAL" ] && [ "$RUNNING_COUNT" -lt "$MAX_PARALLEL" ]; do - start_scenario "$NEXT_IDX" - NEXT_IDX=$((NEXT_IDX + 1)) -done -print_progress - -# Poll for completion and launch new scenarios -while [ "$RUNNING_COUNT" -gt 0 ]; do - for i in "${!SCENARIO_STATES[@]}"; do - if [ "${SCENARIO_STATES[$i]}" = "running" ]; then - pid="${SCENARIO_PIDS[$i]}" - if ! kill -0 "$pid" 2>/dev/null; then - wait "$pid" 2>/dev/null && exit_code=0 || exit_code=$? - finish_scenario "$i" "$exit_code" - - # Launch next if available - if [ "$NEXT_IDX" -lt "$TOTAL" ] && [ "$RUNNING_COUNT" -lt "$MAX_PARALLEL" ]; then - start_scenario "$NEXT_IDX" - NEXT_IDX=$((NEXT_IDX + 1)) - fi - - print_progress - fi - fi - done - sleep 0.2 -done - -# Clear the progress line -printf "\r${CLR_LINE}" -echo "" - -# ── Final summary ────────────────────────────────────────────────── -printf " ${BOLD}%d${RESET} scenarios" "$TOTAL" -[ "$PASSED" -gt 0 ] && printf " ${GREEN}${BOLD}%d passed${RESET}" "$PASSED" -[ "$FAILED" -gt 0 ] && printf " ${RED}${BOLD}%d failed${RESET}" "$FAILED" -[ "$SKIPPED" -gt 0 ] && printf " ${YELLOW}${BOLD}%d skipped${RESET}" "$SKIPPED" -echo "" - -# ── Failed scenario logs ─────────────────────────────────────────── -if [ "$FAILED" -gt 0 ]; then - echo "" - printf "${BOLD}══════════════════════════════════════════════════════════════════════════${RESET}\n" - printf "${RED}${BOLD} Failed Scenario Logs${RESET}\n" - printf "${BOLD}══════════════════════════════════════════════════════════════════════════${RESET}\n" - for i in "${!SCENARIO_NAMES[@]}"; do - if [ "${SCENARIO_RESULTS[$i]}" = "FAIL" ]; then - local_name="${SCENARIO_NAMES[$i]}" - local_log="$TMP_DIR/${local_name//\//__}.log" - echo "" - printf "${RED}━━━ %s ━━━${RESET}\n" "$local_name" - printf " %s\n" "${SCENARIO_ICONS[$i]}" - echo "" - tail -30 "$local_log" | sed 's/^/ /' - fi - done - exit 1 -fi