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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Copilot SDK service template — API + web UI deployed to Azure Container Apps.
| `src/web/components/ChatWindow.tsx` | Message display with markdown rendering |
| `infra/main.bicep` | Bicep orchestration — includes optional BYOM resources |
| `infra/resources.bicep` | Azure resources (Container Apps, ACR, Key Vault, optional OpenAI) |
| `scripts/get-github-token.mjs` | azd hook — injects GITHUB_TOKEN from `gh auth token` |
| `scripts/get-github-token.mjs` | azd hook — injects COPILOT_GITHUB_TOKEN from `gh auth token` |

## Model Configuration

Expand All @@ -38,7 +38,7 @@ Default: no env vars set → SDK picks default GitHub model.
## Environment

- Node ≥ 24, pnpm for package management. **Always use `pnpm`, never `npm` or `yarn`.**
- `gh` CLI required for provisioning (provides `GITHUB_TOKEN` via `scripts/get-github-token.mjs`).
- `gh` CLI required for provisioning (provides `COPILOT_GITHUB_TOKEN` via `scripts/get-github-token.mjs`).

## Commands

Expand All @@ -61,5 +61,5 @@ Default: no env vars set → SDK picks default GitHub model.

## Safety

- Never commit secrets. `GITHUB_TOKEN` is injected at deploy time via Key Vault.
- Never commit secrets. `COPILOT_GITHUB_TOKEN` is injected at deploy time via Key Vault.
- Dockerfile runs as non-root user (`app`).
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The following prerequisites are required to use this application. Please ensure
| [Azure Developer CLI (`azd`)](https://aka.ms/azd-install) | Latest | Provisions and deploys Azure resources |
| [Node.js](https://nodejs.org/) | 24+ | Runtime for the API and build tooling |
| [pnpm](https://pnpm.io/) | 10+ | Fast, disk-efficient package manager |
| [GitHub CLI (`gh`)](https://cli.github.com/) | Latest | Provides the `GITHUB_TOKEN` for the Copilot SDK |
| [GitHub CLI (`gh`)](https://cli.github.com/) | Latest | Provides the `COPILOT_GITHUB_TOKEN` for the Copilot SDK |
| [Docker](https://docs.docker.com/get-docker/) | Latest | Required for Azure deployment (container builds) |

**GitHub CLI setup:**
Expand Down Expand Up @@ -72,7 +72,7 @@ This application utilizes the following Azure resources:

- [**Azure Container Apps**](https://docs.microsoft.com/azure/container-apps/) to host the API backend and web frontend
- [**Azure Container Registry**](https://docs.microsoft.com/azure/container-registry/) for Docker image storage
- [**Azure Key Vault**](https://docs.microsoft.com/azure/key-vault/) for securing the `GITHUB_TOKEN`
- [**Azure Key Vault**](https://docs.microsoft.com/azure/key-vault/) for securing the `COPILOT_GITHUB_TOKEN`
- [**Azure Monitor**](https://docs.microsoft.com/azure/azure-monitor/) for monitoring and logging
- [**Azure OpenAI**](https://docs.microsoft.com/azure/ai-services/openai/) *(optional)* for Bring Your Own Model (BYOM)

Expand Down Expand Up @@ -135,7 +135,7 @@ No environment variables required — the SDK picks its default model:
azd app run

# Option B: manual
export GITHUB_TOKEN=$(gh auth token)
export COPILOT_GITHUB_TOKEN=$(gh auth token)
cd src/api && pnpm dev
```

Expand All @@ -149,7 +149,7 @@ azd env set MODEL_NAME gpt-4o
azd app run

# Option B: manual
export GITHUB_TOKEN=$(gh auth token)
export COPILOT_GITHUB_TOKEN=$(gh auth token)
export MODEL_NAME=gpt-4o
cd src/api && pnpm dev
```
Expand All @@ -167,7 +167,7 @@ azd env set AZURE_OPENAI_ENDPOINT https://<your-resource>.openai.azure.com
azd app run

# Option B: manual
export GITHUB_TOKEN=$(gh auth token)
export COPILOT_GITHUB_TOKEN=$(gh auth token)
export MODEL_PROVIDER=azure
export MODEL_NAME=<your-deployment-name>
export AZURE_OPENAI_ENDPOINT=https://<your-resource>.openai.azure.com
Expand Down Expand Up @@ -196,14 +196,14 @@ azd extension install jongio.azd.app
azd app run
```

The `prerun` hook automatically retrieves your `GITHUB_TOKEN` from the `gh` CLI via `scripts/get-github-token.mjs`. Open the URL shown in the dashboard output to start testing.
The `prerun` hook automatically retrieves your `COPILOT_GITHUB_TOKEN` from the `gh` CLI via `scripts/get-github-token.mjs`. Open the URL shown in the dashboard output to start testing.

<details>
<summary><b>Run services manually (without azd app)</b></summary>

```bash
# Set your GitHub token
export GITHUB_TOKEN=$(gh auth token)
export COPILOT_GITHUB_TOKEN=$(gh auth token)

# Install dependencies
cd src/api && pnpm install && cd ../web && pnpm install && cd ../..
Expand Down Expand Up @@ -239,7 +239,7 @@ import { CopilotClient } from "@github/copilot-sdk";
const router = Router();

router.post("/classify", async (req, res) => {
const client = new CopilotClient({ githubToken: process.env.GITHUB_TOKEN });
const client = new CopilotClient({ githubToken: process.env.COPILOT_GITHUB_TOKEN });
const { getSessionOptions } = await import("../model-config.js");
const options = await getSessionOptions();
const session = await client.createSession(options);
Expand Down Expand Up @@ -290,12 +290,12 @@ cd src/api && pnpm test:models
- ✅ Azure BYOM (auto-skipped if not configured)

**Prerequisites:**
- `GITHUB_TOKEN` — required for all tests (auto-resolved from `gh auth token` if not set)
- `COPILOT_GITHUB_TOKEN` — required for all tests (auto-resolved from `gh auth token` if not set)
- `AZURE_MODEL_NAME` and `AZURE_OPENAI_ENDPOINT` — optional, for Azure BYOM tests

**Local usage:** When running locally without Azure env vars, an interactive prompt offers to load values from `azd` environments or run `azd up`. To skip Azure tests, just don't set the Azure env vars.

**CI usage:** Set env vars (`GITHUB_TOKEN`, `AZURE_OPENAI_ENDPOINT`, `AZURE_MODEL_NAME`, `CI=true`) and run — no interactive prompts.
**CI usage:** Set env vars (`COPILOT_GITHUB_TOKEN`, `AZURE_OPENAI_ENDPOINT`, `AZURE_MODEL_NAME`, `CI=true`) and run — no interactive prompts.

See [`scripts/README.md`](scripts/README.md) for detailed setup instructions.

Expand All @@ -307,10 +307,10 @@ azd up

This single command handles the entire deployment pipeline:

1. **Preprovision hook** — Retrieves your `GITHUB_TOKEN` from the `gh` CLI and stores it in the `azd` environment
1. **Preprovision hook** — Retrieves your `COPILOT_GITHUB_TOKEN` from the `gh` CLI and stores it in the `azd` environment
2. **Provisions infrastructure** — Creates Azure Container Registry, Container Apps Environment, Key Vault, Application Insights, and a managed identity (using [Azure Verified Modules](https://azure.github.io/Azure-Verified-Modules/))
3. **Builds and pushes** — Builds the Docker images and pushes them to the provisioned ACR
4. **Deploys** — Deploys both containers to Azure Container Apps with the `GITHUB_TOKEN` securely referenced from Key Vault
4. **Deploys** — Deploys both containers to Azure Container Apps with the `COPILOT_GITHUB_TOKEN` securely referenced from Key Vault

### Verify Deployed App

Expand Down Expand Up @@ -343,7 +343,7 @@ This template creates a [managed identity](https://docs.microsoft.com/azure/acti

### Key Vault

This template uses [Azure Key Vault](https://docs.microsoft.com/azure/key-vault/general/overview) to securely store your `GITHUB_TOKEN` for the provisioned Copilot SDK service. Key Vault is a cloud service for securely storing and accessing secrets (API keys, passwords, certificates, cryptographic keys) and makes it simple to give other Azure services access to them. As you continue developing your solution, you may add as many secrets to your Key Vault as you require.
This template uses [Azure Key Vault](https://docs.microsoft.com/azure/key-vault/general/overview) to securely store your `COPILOT_GITHUB_TOKEN` for the provisioned Copilot SDK service. Key Vault is a cloud service for securely storing and accessing secrets (API keys, passwords, certificates, cryptographic keys) and makes it simple to give other Azure services access to them. As you continue developing your solution, you may add as many secrets to your Key Vault as you require.

## Reporting Issues and Feedback

Expand Down
2 changes: 1 addition & 1 deletion infra/main.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"parameters": {
"environmentName": { "value": "${AZURE_ENV_NAME}" },
"location": { "value": "${AZURE_LOCATION}" },
"githubToken": { "value": "${GITHUB_TOKEN}" },
"githubToken": { "value": "${COPILOT_GITHUB_TOKEN}" },
"useAzureModel": { "value": "${USE_AZURE_MODEL=false}" },
"azureModelName": { "value": "${AZURE_MODEL_NAME=o4-mini}" },
"azureModelVersion": { "value": "${AZURE_MODEL_VERSION=2025-04-16}" }
Expand Down
4 changes: 2 additions & 2 deletions infra/resources.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ module managedIdentity 'br/public:avm/res/managed-identity/user-assigned-identit
}

// ===================== //
// AVM Resource: Key Vault (stores GITHUB_TOKEN)
// AVM Resource: Key Vault (stores COPILOT_GITHUB_TOKEN)
// ===================== //

module keyVault 'br/public:avm/res/key-vault/vault:0.13.3' = {
Expand Down Expand Up @@ -190,7 +190,7 @@ module containerAppApi 'br/public:avm/ptn/azd/acr-container-app:0.4.0' = {
[
{ name: 'PORT', value: '3000' }
{ name: 'ALLOWED_ORIGINS', value: 'https://ca-web-${environmentName}-${resourceSuffix}.${containerAppsStack.outputs.defaultDomain}' }
{ name: 'GITHUB_TOKEN', secretRef: 'github-token' }
{ name: 'COPILOT_GITHUB_TOKEN', secretRef: 'github-token' }
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: monitoring.outputs.applicationInsightsConnectionString
Expand Down
2 changes: 1 addition & 1 deletion scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export AZURE_CONTAINER_APP_WEB_URL=$(azd env get-value AZURE_CONTAINER_APP_WEB_U

Set the required environment variables and run — there are no interactive prompts in CI:
```bash
export GITHUB_TOKEN=<token>
export COPILOT_GITHUB_TOKEN=<token>
export AZURE_OPENAI_ENDPOINT=<endpoint-url>
export AZURE_MODEL_NAME=<deployment-name>
cd tests/integration && pnpm install && pnpm test
Expand Down
12 changes: 6 additions & 6 deletions scripts/get-github-token.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ function fail(message) {
process.exit(1);
}

// 0. Skip if GITHUB_TOKEN is already set in azd environment
// 0. Skip if COPILOT_GITHUB_TOKEN is already set in azd environment
try {
const existing = execSync("azd env get-value GITHUB_TOKEN", {
const existing = execSync("azd env get-value COPILOT_GITHUB_TOKEN", {
encoding: "utf-8",
stdio: ["ignore", "pipe", "ignore"],
}).trim();
if (existing) {
console.log("✓ GITHUB_TOKEN already set in azd environment.");
console.log("✓ COPILOT_GITHUB_TOKEN already set in azd environment.");
process.exit(0);
}
} catch {
Expand Down Expand Up @@ -64,11 +64,11 @@ try {
// Token is passed via env var to avoid exposing it in process argument lists
const isWindows = process.platform === "win32";
const cmd = isWindows
? `azd env set GITHUB_TOKEN %__GH_TOKEN%`
: `azd env set GITHUB_TOKEN "$__GH_TOKEN"`;
? `azd env set COPILOT_GITHUB_TOKEN %__GH_TOKEN%`
: `azd env set COPILOT_GITHUB_TOKEN "$__GH_TOKEN"`;
execSync(cmd, {
env: { ...process.env, __GH_TOKEN: token },
stdio: "inherit",
shell: true,
});
console.log("✓ GITHUB_TOKEN set from gh CLI.");
console.log("✓ COPILOT_GITHUB_TOKEN set from gh CLI.");
4 changes: 2 additions & 2 deletions src/api/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ describe("getClient", () => {
});

it("returns a client instance with correct token", async () => {
vi.stubEnv("GITHUB_TOKEN", "test-token");
vi.stubEnv("COPILOT_GITHUB_TOKEN", "test-token");
const { getClient } = await import("./client.js");
const client = await getClient();
expect(client).toBeDefined();
expect((client as unknown as { _token: string })._token).toBe("test-token");
});

it("returns the same instance on subsequent calls (singleton)", async () => {
vi.stubEnv("GITHUB_TOKEN", "test-token");
vi.stubEnv("COPILOT_GITHUB_TOKEN", "test-token");
const { getClient } = await import("./client.js");
const client1 = await getClient();
const client2 = await getClient();
Expand Down
2 changes: 1 addition & 1 deletion src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let client: CopilotClient | null = null;
export async function getClient(): Promise<CopilotClient> {
if (!client) {
client = new CopilotClient({
githubToken: process.env.GITHUB_TOKEN,
githubToken: process.env.COPILOT_GITHUB_TOKEN,
});
}
return client;
Expand Down
4 changes: 2 additions & 2 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ app.use(healthRoutes);
app.use(summarizeRoutes);
app.use(chatRoutes);

if (!process.env.GITHUB_TOKEN) {
console.warn("⚠ GITHUB_TOKEN is not set. AI endpoints will not work.");
if (!process.env.COPILOT_GITHUB_TOKEN) {
console.warn("⚠ COPILOT_GITHUB_TOKEN is not set. AI endpoints will not work.");
}

const PORT = process.env.PORT || 3000;
Expand Down
22 changes: 11 additions & 11 deletions tests/integration/global-setup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Vitest globalSetup — resolves environment variables once before all test files run.
*
* - GITHUB_TOKEN: required for all tests (resolved from env or `gh auth token`)
* - COPILOT_GITHUB_TOKEN: required for all tests (resolved from env or `gh auth token`)
* - AZURE_OPENAI_ENDPOINT / AZURE_MODEL_NAME: auto-loaded from the default azd
* environment. Falls back to interactive prompt only when auto-load fails and
* a TTY is available.
Expand Down Expand Up @@ -53,11 +53,11 @@ function checkPrerequisites(): CliStatus {
return { gh, az, azd };
}

// ── GITHUB_TOKEN resolution ──────────────────────────────────────────
// ── COPILOT_GITHUB_TOKEN resolution ──────────────────────────────────

function resolveGitHubToken(): void {
if (process.env.GITHUB_TOKEN) {
console.log(" ✓ GITHUB_TOKEN already set");
if (process.env.COPILOT_GITHUB_TOKEN) {
console.log(" ✓ COPILOT_GITHUB_TOKEN already set");
return;
}

Expand All @@ -67,17 +67,17 @@ function resolveGitHubToken(): void {
stdio: ["ignore", "pipe", "ignore"],
}).trim();
if (token) {
process.env.GITHUB_TOKEN = token;
console.log(" ✓ GITHUB_TOKEN resolved from gh CLI");
process.env.COPILOT_GITHUB_TOKEN = token;
console.log(" ✓ COPILOT_GITHUB_TOKEN resolved from gh CLI");
return;
}
} catch {
// gh not installed or not authenticated
}

console.error("❌ GITHUB_TOKEN could not be resolved.");
console.error(" Set GITHUB_TOKEN or run: gh auth login");
throw new Error("GITHUB_TOKEN is required to run integration tests");
console.error("❌ COPILOT_GITHUB_TOKEN could not be resolved.");
console.error(" Set COPILOT_GITHUB_TOKEN or run: gh auth login");
throw new Error("COPILOT_GITHUB_TOKEN is required to run integration tests");
}

// ── azd environment helpers ──────────────────────────────────────────
Expand Down Expand Up @@ -269,7 +269,7 @@ export async function setup(): Promise<void> {
// 1. Check CLI prerequisites
const cli = checkPrerequisites();

// 2. Resolve GITHUB_TOKEN (required — throws on failure)
// 2. Resolve COPILOT_GITHUB_TOKEN (required — throws on failure)
resolveGitHubToken();

// 3. Resolve Azure environment: auto-load from default azd env, prompt only as fallback
Expand All @@ -283,7 +283,7 @@ export async function setup(): Promise<void> {
// 4. Log final environment state
const hasAzure = !!(process.env.AZURE_OPENAI_ENDPOINT && process.env.AZURE_MODEL_NAME);
console.log("\nEnvironment summary:");
console.log(` GITHUB_TOKEN: ${process.env.GITHUB_TOKEN ? "set" : "MISSING"}`);
console.log(` COPILOT_GITHUB_TOKEN: ${process.env.COPILOT_GITHUB_TOKEN ? "set" : "MISSING"}`);
console.log(` AZURE_OPENAI_ENDPOINT: ${process.env.AZURE_OPENAI_ENDPOINT || "(not set — Azure tests will be skipped)"}`);
console.log(` AZURE_MODEL_NAME: ${process.env.AZURE_MODEL_NAME || "(not set)"}`);
console.log(` CI: ${process.env.CI || "(not set)"}`);
Expand Down