Installation

Install with CLI Recommended
gh skills-hub install azure-hosted-copilot-sdk

Don't have the extension? Run gh extension install samueltauil/skills-hub first.

Download and extract to your repository:

.github/skills/azure-hosted-copilot-sdk/

Extract the ZIP to .github/skills/ in your repo. The folder name must match azure-hosted-copilot-sdk for Copilot to auto-discover it.

Skill Files (6)

SKILL.md 3.9 KB
---
name: azure-hosted-copilot-sdk
description: "Build, deploy, modify GitHub Copilot SDK apps on Azure. PREFER OVER azure-prepare when codebase contains copilot-sdk markers. WHEN: copilot SDK, @github/copilot-sdk, copilot-powered app, deploy copilot app, add feature, modify copilot app, BYOM, bring your own model, CopilotClient, createSession, sendAndWait, azd init copilot. DO NOT USE FOR: general web apps without copilot SDK (use azure-prepare), Copilot Extensions, Foundry agents (use microsoft-foundry)."
license: MIT
metadata:
  author: Microsoft
  version: "1.0.4"
---

# GitHub Copilot SDK on Azure

## Codebase Detection โ€” MANDATORY FIRST CHECK

**Before routing to azure-prepare for ANY prompts involving an existing codebase**, scan the workspace for Copilot SDK markers. If ANY marker is found, this skill MUST be used instead of azure-prepare:

| Marker | Where to check |
|--------|---------------|
| `@github/copilot-sdk` | `package.json` dependencies |
| `copilot-sdk` | `package.json` name or dependencies |
| `CopilotClient` | Source files (`.ts`, `.js`) |
| `createSession` + `sendAndWait` | Source files (`.ts`, `.js`) |

> โš ๏ธ **Warning:** ANY prompt targeting an existing codebase โ€” deploy, modify, add feature, update, enhance โ€” MUST trigger this skill when codebase contains `@github/copilot-sdk`, even if the prompt does not mention "copilot". Do NOT fall through to azure-prepare.

## Step 1: Route

| User wants | Action |
|------------|--------|
| Build new (empty project) | Step 2A (scaffold) |
| Add new SDK service to existing repo | Step 2B (scaffold alongside) |
| Deploy existing SDK app to Azure | Step 2C (add infra to existing SDK app) |
| Modify/add features to existing SDK app | Use codebase context + SDK references to implement |
| Add SDK to existing app code | [Integrate SDK](references/existing-project-integration.md) |
| Use Azure/own model | Step 3 (BYOM config) |

## Step 2A: Scaffold New (Greenfield)

`azd init --template azure-samples/copilot-sdk-service`

Template includes API (Express/TS) + Web UI (React/Vite) + infra (Bicep) + Dockerfiles + token scripts โ€” do NOT recreate. See [SDK ref](references/copilot-sdk.md).

## Step 2B: Add SDK Service to Existing Repo

User has existing code and wants a new Copilot SDK service alongside it. Scaffold template to a temp dir, copy the API service + infra into the user's repo, adapt `azure.yaml` to include both existing and new services. See [deploy existing ref](references/deploy-existing.md).

## Step 2C: Deploy Existing SDK App

User already has a working Copilot SDK app and needs Azure infra. See [deploy existing ref](references/deploy-existing.md).

## Step 3: Model Configuration

Three model paths (layers on top of 2A/2B):

| Path | Config |
|------|--------|
| **GitHub default** | No `model` param โ€” SDK picks default |
| **GitHub specific** | `model: "<name>"` โ€” use `listModels()` to discover |
| **Azure BYOM** | `model` + `provider` with `bearerToken` via `DefaultAzureCredential` |

> โš ๏ธ **BYOM Auth โ€” MANDATORY**: Azure BYOM configurations MUST use `DefaultAzureCredential` (local dev) or `ManagedIdentityCredential` (production) to obtain a `bearerToken`. **NEVER** use static API keys (`apiKey`), `AZURE_OPENAI_API_KEY`, or `AZURE_OPENAI_KEY` environment variables in BYOM provider configuration. See [auth-best-practices.md](references/auth-best-practices.md) for the credential pattern and [model config ref](references/azure-model-config.md) for the full BYOM code example.

See [model config ref](references/azure-model-config.md).

## Step 4: Deploy

Invoke **azure-prepare** (skip its Step 0 routing โ€” scaffolding is done) โ†’ **azure-validate** โ†’ **azure-deploy** in order.

## Rules

- Read `AGENTS.md` in user's repo before changes
- Docker required (`docker info`)
- BYOM auth: always `bearerToken` via `DefaultAzureCredential` or `ManagedIdentityCredential` โ€” never `apiKey` or `AZURE_OPENAI_API_KEY`/`AZURE_OPENAI_KEY`
references/
auth-best-practices.md 6.0 KB
# Azure Authentication Best Practices

> Source: [Microsoft โ€” Passwordless connections for Azure services](https://learn.microsoft.com/azure/developer/intro/passwordless-overview) and [Azure Identity client libraries](https://learn.microsoft.com/dotnet/azure/sdk/authentication/).

## Golden Rule

Use **managed identities** and **Azure RBAC** in production. Reserve `DefaultAzureCredential` for **local development only**.

## Authentication by Environment

| Environment | Recommended Credential | Why |
|---|---|---|
| **Production (Azure-hosted)** | `ManagedIdentityCredential` (system- or user-assigned) | No secrets to manage; auto-rotated by Azure |
| **Production (on-premises)** | `ClientCertificateCredential` or `WorkloadIdentityCredential` | Deterministic; no fallback chain overhead |
| **CI/CD pipelines** | `AzurePipelinesCredential` / `WorkloadIdentityCredential` | Scoped to pipeline identity |
| **Local development** | `DefaultAzureCredential` | Chains CLI, PowerShell, and VS Code credentials for convenience |

## Why Not `DefaultAzureCredential` in Production?

1. **Unpredictable fallback chain** โ€” walks through multiple credential types, adding latency and making failures harder to diagnose.
2. **Broad surface area** โ€” checks environment variables, CLI tokens, and other sources that should not exist in production.
3. **Non-deterministic** โ€” which credential actually authenticates depends on the environment, making behavior inconsistent across deployments.
4. **Performance** โ€” each failed credential attempt adds network round-trips before falling back to the next.

## Production Patterns

### .NET

```csharp
using Azure.Identity;

var credential = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development"
    ? new DefaultAzureCredential()                          // local dev โ€” uses CLI/VS credentials
    : new ManagedIdentityCredential();                      // production โ€” deterministic, no fallback chain
// For user-assigned identity: new ManagedIdentityCredential("<client-id>")
```

### TypeScript / JavaScript

```typescript
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";

const credential = process.env.NODE_ENV === "development"
  ? new DefaultAzureCredential()                          // local dev โ€” uses CLI/VS credentials
  : new ManagedIdentityCredential();                      // production โ€” deterministic, no fallback chain
// For user-assigned identity: new ManagedIdentityCredential("<client-id>")
```

### Python

```python
import os
from azure.identity import DefaultAzureCredential, ManagedIdentityCredential

credential = (
    DefaultAzureCredential()                              # local dev โ€” uses CLI/VS credentials
    if os.getenv("AZURE_FUNCTIONS_ENVIRONMENT") == "Development"
    else ManagedIdentityCredential()                      # production โ€” deterministic, no fallback chain
)
# For user-assigned identity: ManagedIdentityCredential(client_id="<client-id>")
```

### Java

```java
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.identity.ManagedIdentityCredentialBuilder;

var credential = "Development".equals(System.getenv("AZURE_FUNCTIONS_ENVIRONMENT"))
    ? new DefaultAzureCredentialBuilder().build()          // local dev โ€” uses CLI/VS credentials
    : new ManagedIdentityCredentialBuilder().build();      // production โ€” deterministic, no fallback chain
// For user-assigned identity: new ManagedIdentityCredentialBuilder().clientId("<client-id>").build()
```

## Local Development Setup

`DefaultAzureCredential` is ideal for local dev because it automatically picks up credentials from developer tools:

1. **Azure CLI** โ€” `az login`
2. **Azure Developer CLI** โ€” `azd auth login`
3. **Azure PowerShell** โ€” `Connect-AzAccount`
4. **Visual Studio / VS Code** โ€” sign in via Azure extension

```typescript
import { DefaultAzureCredential } from "@azure/identity";

// Local development only โ€” uses CLI/PowerShell/VS Code credentials
const credential = new DefaultAzureCredential();
```

## Environment-Aware Pattern

Detect the runtime environment and select the appropriate credential. The key principle: use `DefaultAzureCredential` only when running locally, and a specific credential in production.

> **Tip:** Azure Functions sets `AZURE_FUNCTIONS_ENVIRONMENT` to `"Development"` when running locally. For App Service or containers, use any environment variable you control (e.g. `NODE_ENV`, `ASPNETCORE_ENVIRONMENT`).

```typescript
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";

function getCredential() {
  if (process.env.NODE_ENV === "development") {
    return new DefaultAzureCredential();          // picks up az login / VS Code creds
  }
  return process.env.AZURE_CLIENT_ID
    ? new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID)  // user-assigned
    : new ManagedIdentityCredential();                            // system-assigned
}
```

## Security Checklist

- [ ] Use managed identity for all Azure-hosted apps
- [ ] Never hardcode credentials, connection strings, or keys
- [ ] Apply least-privilege RBAC roles at the narrowest scope
- [ ] Use `ManagedIdentityCredential` (not `DefaultAzureCredential`) in production
- [ ] Store any required secrets in Azure Key Vault
- [ ] Rotate secrets and certificates on a schedule
- [ ] Enable Microsoft Defender for Cloud on production resources

## Further Reading

- [Passwordless connections overview](https://learn.microsoft.com/azure/developer/intro/passwordless-overview)
- [Managed identities overview](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview)
- [Azure RBAC overview](https://learn.microsoft.com/azure/role-based-access-control/overview)
- [.NET authentication guide](https://learn.microsoft.com/dotnet/azure/sdk/authentication/)
- [Python identity library](https://learn.microsoft.com/python/api/overview/azure/identity-readme)
- [JavaScript identity library](https://learn.microsoft.com/javascript/api/overview/azure/identity-readme)
- [Java identity library](https://learn.microsoft.com/java/api/overview/azure/identity-readme)
azure-model-config.md 4.5 KB
# Model Configuration

Three model paths for the Copilot SDK. Each session needs different configuration.

## Path 1: GitHub Default

No configuration needed. SDK uses the default model.

```typescript
const session = await client.createSession({});
// or simply: client.createSession()
```

Best for: quick prototyping, no model preference.

## Path 2: GitHub Specific Model

Specify a model name. Discover available models with `listModels()`.

```typescript
const models = await client.listModels();
// Pick from available models
const session = await client.createSession({
  model: "o4-mini",
});
```

## Path 3: Azure BYOM (Bring Your Own Model)

Use your own Azure AI deployment. For local development use `DefaultAzureCredential`; for production use `ManagedIdentityCredential` โ€” see [auth-best-practices.md](auth-best-practices.md).

> โš ๏ธ **Warning:** The Copilot SDK encrypts prompt content. Only models that support decrypting encrypted content work with BYOM. Using unsupported models returns "400 Encrypted content is not supported" or silently times out.

### Supported Models

| Family | Models |
|--------|--------|
| **o-series** | o3, o3-mini, o4-mini (cheapest) |
| **gpt-5 family** | gpt-5, gpt-5-mini, gpt-5.1, gpt-5.1-mini, gpt-5.1-nano, gpt-5.2-codex, codex-mini |
| โŒ **NOT supported** | gpt-4o, gpt-4.1, gpt-4.1-nano, and other non-o/non-gpt-5 models |

### Required API Settings

| Setting | Value | Notes |
|---------|-------|-------|
| `wireApi` | `"completions"` | โš ๏ธ Do NOT use `"responses"` โ€” breaks multi-turn tool calls (`store: false` causes 400) |
| `azure.apiVersion` | `"2025-04-01-preview"` or later | โš ๏ธ Must be nested under `azure:`, NOT top-level |

### Provider Config

| Endpoint type | `type` | `baseUrl` pattern |
|---|---|---|
| Azure OpenAI | `azure` | `https://<resource>.openai.azure.com` |
| Azure AI Foundry | `openai` | `https://<resource>.services.ai.azure.com/api/projects/<project>/openai/v1/` |

### Code Pattern

> **Auth:** `DefaultAzureCredential` is for local development. See [auth-best-practices.md](auth-best-practices.md) for production patterns.

```typescript
import { DefaultAzureCredential } from "@azure/identity";

const credential = new DefaultAzureCredential();
const { token } = await credential.getToken("https://cognitiveservices.azure.com/.default");

const session = await client.createSession({
    model: process.env.MODEL_NAME || "o4-mini",
    provider: {
        type: "azure",
        baseUrl: process.env.AZURE_OPENAI_ENDPOINT,
        bearerToken: token,
        wireApi: "completions",
        azure: { apiVersion: "2025-04-01-preview" },
    },
});
```

### Environment Variables

| Variable | Value | Required |
|----------|-------|----------|
| `AZURE_OPENAI_ENDPOINT` | `https://<resource>.openai.azure.com` | Yes |
| `MODEL_NAME` | Model deployment name | Yes |
| `AZURE_CLIENT_ID` | Managed identity client ID | Yes (Container Apps with user-assigned MI) |

### Token Refresh

> โš ๏ธ **Warning:** `bearerToken` is static โ€” no auto-refresh.

- Tokens valid ~1 hour
- **Production**: get fresh token per request
- Long-running sessions fail after expiry

### Discovering Azure Deployments

`listModels()` returns GitHub models only. For Azure deployments:

```bash
az cognitiveservices account deployment list --name <resource> --resource-group <rg>
```

## Template Environment Variables

The template uses env vars for model path selection:

| Variable | Values | Effect |
|----------|--------|--------|
| `MODEL_PROVIDER` | unset or `azure` | Selects GitHub or Azure BYOM |
| `MODEL_NAME` | model/deployment name | Selects specific model |
| `AZURE_OPENAI_ENDPOINT` | Azure endpoint URL | Required for BYOM |

## Errors

| Error | Cause | Fix |
|-------|-------|-----|
| `400 Encrypted content is not supported` | Model doesn't support SDK encryption | Use o-series or gpt-5 family only |
| `400` with tool calls / multi-turn | `wireApi: "responses"` with `store: false` | Use `wireApi: "completions"` instead |
| `CAPIError: 404 Resource not found` | `apiVersion` at top level instead of nested | Use `azure: { apiVersion: "..." }` |
| Silent timeout | Unsupported model (e.g., gpt-4o, gpt-4.1) | Switch to o4-mini or gpt-5 family |
| `model is required` | Missing `model` in BYOM config | Set `MODEL_NAME` env var |
| `401 Unauthorized` | Token expired or wrong scope | Refresh via `DefaultAzureCredential` |
| `404 Not Found` | Wrong endpoint or deployment name | Verify URL and deployment exists |
| `500` in Container Apps | Missing `AZURE_CLIENT_ID` env var | Set to managed identity client ID in Bicep |
copilot-sdk.md 3.5 KB
# Copilot SDK Reference

## Template

```bash
azd init --template azure-samples/copilot-sdk-service
```

**Architecture:** API (Express/TS) + Web UI (React/Vite), deployed as 2 Container Apps. Chat endpoint streams token-level deltas via `assistant.message_delta` SSE events. Azure BYOM gets a fresh bearer token per-request (no expiry issues).

## Documentation

| Resource | URL |
|----------|-----|
| Overview & Getting Started | https://github.com/github/copilot-sdk |
| Getting Started Guide | https://github.com/github/copilot-sdk/blob/main/docs/getting-started.md |
| Node.js SDK | https://github.com/github/copilot-sdk/tree/main/nodejs |
| Python SDK | https://github.com/github/copilot-sdk/tree/main/python |
| Go SDK | https://github.com/github/copilot-sdk/tree/main/go |
| .NET SDK | https://github.com/github/copilot-sdk/tree/main/dotnet |
| Debugging | https://github.com/github/copilot-sdk/blob/main/docs/debugging.md |
| Compatibility | https://github.com/github/copilot-sdk/blob/main/docs/compatibility.md |

## Getting Current Examples

Use **context7** MCP tools as the PRIMARY way to get SDK documentation and code examples:

1. Call `context7-resolve-library-id` with `libraryName: "copilot-sdk"` to find the library ID
2. Call `context7-query-docs` with the resolved ID and a query matching the user's goal
3. Select the most relevant snippets for the user's scenario

> ๐Ÿ’ก **Tip:** Fall back to `github-mcp-server-get_file_contents` with `owner: "github"`, `repo: "copilot-sdk"` to read files directly from the repo.

## Three Model Paths

| Path | Config | Auth |
|------|--------|------|
| GitHub default | No `model` param | `GITHUB_TOKEN` |
| GitHub specific | `model: "<name>"` | `GITHUB_TOKEN` |
| Azure BYOM | `model` + `provider` with `bearerToken` | `DefaultAzureCredential` (local dev) / `ManagedIdentityCredential` (production) |

**Model discovery:**
- GitHub models: call `listModels()` on the SDK client
- Azure deployments: `az cognitiveservices account deployment list`

Full BYOM config details: [Azure Model Configuration](azure-model-config.md).

> โš ๏ธ **Warning:** `model` is **required** when using a provider โ€” SDK throws if missing.

## Template Customization

Read `AGENTS.md` FIRST โ€” it lists every source file with its purpose. Then:

1. Adapt routes โ€” update endpoints, system message, and tool definitions
2. Customize the UI โ€” the template UI is just an example
3. Keep template infra โ€” do NOT regenerate Dockerfile, Bicep, or `azure.yaml`
4. If using multi-tool AI sessions, increase `proxy_read_timeout` in `nginx.conf.template` to 300s (default 60s causes 504)

**Existing project:** See [Existing Project Integration](existing-project-integration.md) for adding Copilot SDK to your codebase.

## Testing

| Check | Command |
|-------|---------|
| Run locally | `azd app run` โ€” starts API + UI |
| Health check | `curl -s http://localhost:3000/health` |
| Test endpoint | `curl -s -X POST http://localhost:3000/api/<endpoint> -H "Content-Type: application/json" -d '{"input":"test"}'` |

## Errors

| Error | Fix |
|-------|-----|
| `docker info` fails | Install Docker Desktop and start it |
| `gh auth token` fails | Run `gh auth login` then `gh auth refresh --scopes copilot` |
| `ECONNREFUSED` on JSON-RPC | Set autoStart or start CLI manually |
| `Model not available` | Check model name; for BYOM verify provider config |
| Session hangs | Set a max turns limit or add a hook to break |
| `504 Gateway Timeout` | Increase `proxy_read_timeout` in `nginx.conf.template` to 300s |
deploy-existing.md 2.9 KB
# Deploy Existing Copilot SDK App

Adapt a user's existing Copilot SDK app for Azure by scaffolding the template to a temp directory, then copying infra into the user's project.

> โš ๏ธ **Warning:** Do NOT run `azd init` inside the user's project โ€” it overwrites existing files. Always scaffold to a temp dir.

## 1. Scaffold Template to Temp Dir

```bash
azd init --template azure-samples/copilot-sdk-service --cwd <temp-dir>
```

This gives you the working infra, scripts, and Dockerfiles without touching the user's project.

## 2. Copy Infra Into User's Project

From the temp scaffold, copy these into the user's project root:

| Source | Purpose |
|--------|---------|
| `infra/` | Bicep modules (AVM-based), main.bicep, resources.bicep |
| `scripts/get-github-token.mjs` | azd hook โ€” gets `GITHUB_TOKEN` via `gh auth token` |
| `azure.yaml` | Deployment manifest (will need editing) |

> โš ๏ธ Copy `scripts/get-github-token.mjs` exactly โ€” do NOT rewrite it.

## 3. Adapt azure.yaml

Edit the copied `azure.yaml` to point at the user's app:

- Set `services.api.project` to the user's API directory
- Set `services.api.language` to the detected language
- Set `services.api.ports` to the user's port
- Add a `web` service if the app has a frontend
- Keep the `hooks` section unchanged (preprovision + prerun call the token script)

## 4. GitHub Token Flow

The template's token flow (already in the copied files):

1. **`scripts/get-github-token.mjs`** โ€” runs `gh auth token`, verifies `copilot` scope, stores via `azd env set`
2. **`azure.yaml` hooks** โ€” `preprovision` and `prerun` call the token script
3. **Bicep `@secure() param githubToken`** โ€” azd auto-maps the env var
4. **Key Vault** โ€” stores secret; Managed Identity gets `Key Vault Secrets User` role

## 5. Dockerfile

If the user has no Dockerfile, copy the template's Dockerfile for the detected language from the temp dir and adapt entry point, build steps, and port.

## 6. BYOM Infrastructure (Azure Model)

If the user wants Azure BYOM, add to the copied Bicep:

| Resource | Purpose |
|----------|---------|
| Azure OpenAI / AI Services account | Hosts model deployments |
| Role assignment | `Cognitive Services OpenAI User` for Managed Identity |

Add env vars to Container App: `AZURE_OPENAI_ENDPOINT`, `MODEL_PROVIDER=azure`, `MODEL_NAME=<deployment>`, `AZURE_CLIENT_ID=<managed-identity-client-id>`.

> โš ๏ธ **Warning:** The template's `main.parameters.json` defaults to `gpt-4o` which does NOT support BYOM (encrypted content). Change the default to an o-series or gpt-5 family model.

See [Azure Model Configuration](azure-model-config.md) for provider config and auth pattern.

## 7. Errors

| Error | Fix |
|-------|-----|
| `gh` not installed | User must install GitHub CLI |
| Missing `copilot` scope | Run `gh auth refresh --scopes copilot` |
| Key Vault soft-delete conflict | Use a unique vault name or purge the old one |
| Token not injected | Verify `azure.yaml` hooks and `scripts/get-github-token.mjs` exist |
existing-project-integration.md 2.4 KB
# Integrating Copilot SDK into Existing Projects

Add Copilot SDK AI features to an existing application.

## Project Analysis

Detect the project type by scanning for indicator files:

| Indicator | Language | Framework hints |
|-----------|----------|-----------------|
| `package.json` | Node.js | Express, Fastify, Next.js |
| `requirements.txt` / `pyproject.toml` | Python | Flask, FastAPI, Django |
| `go.mod` | Go | Gin, Echo, net/http |
| `*.csproj` / `*.sln` | .NET | ASP.NET, Minimal API |

## Study Template Patterns

Read the template via MCP for reference implementation:

`github-mcp-server-get_file_contents` with `owner: "azure-samples"`, `repo: "copilot-sdk-service"`. Read `AGENTS.md` first.

Use context7 tools (`context7-resolve-library-id` โ†’ `context7-query-docs`) for current SDK API examples.

## Integration Steps

### 1. Add SDK dependency

| Language | Package |
|----------|---------|
| Node.js | `@github/copilot-sdk` |
| Python | `github-copilot-sdk` |
| Go / .NET | See SDK repo |

### 2. Create Copilot endpoint

Add a route (e.g., `/api/chat`) that creates a `CopilotClient`, starts a session, and returns the response. Use `sendAndWait` for one-shot or SSE streaming for chat.

Adapt to the app's existing routing pattern (Express router, FastAPI route, etc.).

### 3. Configure authentication

Use `gh auth token` for local dev; for production, use Key Vault.

### 4. Wire into existing app

Register the new route with the existing server/app instance. Do NOT create a separate server.

> โš ๏ธ **Warning:** Do not duplicate server startup logic.

## BYOM Support

If the user wants Azure BYOM, add on top of standard integration:

1. Add `@azure/identity` dependency
2. Get fresh token per request: `credential.getToken("https://cognitiveservices.azure.com/.default")`
3. Pass `provider` config with `bearerToken` to `createSession`
4. Set `model` to Azure deployment name (required)
5. Set env vars: `AZURE_OPENAI_ENDPOINT`, `MODEL_NAME`

See [Azure Model Configuration](azure-model-config.md).

## Testing

```bash
curl -s -X POST http://localhost:<port>/api/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"test"}'
```

## Errors

| Error | Fix |
|-------|-----|
| SDK not found | Verify dependency installed and import path |
| Auth fails locally | Run `gh auth login` then `gh auth refresh --scopes copilot` |
| Route conflicts | Ensure endpoint path doesn't collide |

License (MIT)

View full license text
MIT License

Copyright 2025 (c) Microsoft Corporation.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.