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
91 changes: 91 additions & 0 deletions agent-plugins/agents-for-js/skills/agents-sdk-to-prod/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
name: agents-sdk-to-prod
description: Guide the user to create or adapt a Microsoft 365 Agents SDK sample into a production-ready sample with selectable features.
disable-model-invocation: true
---

This skill is designed to help and guide the user to create a Microsoft 365 Agents SDK sample or adapt an existing one into a production-ready sample with selectable features and best practices. It will ask the user to select which production features they want to implement, and then guide them through the process of implementing those features in their sample.

Use the supported feature references before making implementation decisions. Read only the references for the baseline and the selected or detected features.

## Supported Features
- `required-baseline`: Production baseline checklist, required auth shape, safe defaults, and a minimal hardened sample shape. Reference: [required-baseline.md](./references/required-baseline.md).
- `persistent-storage`: Replace volatile memory storage with Azure Blob Storage or Azure Cosmos DB. Reference: [persistent-storage.md](./references/persistent-storage.md).
- `health-readiness`: Add health and readiness endpoints for hosting platforms and dependency probes. Reference: [health-readiness.md](./references/health-readiness.md).
- `jwt-authentication`: Require JWT authentication for the messages endpoint and protected custom routes. Reference: [jwt-authentication.md](./references/jwt-authentication.md).
- `rate-limiting`: Add rate limiting to public endpoints. Reference: [rate-limiting.md](./references/rate-limiting.md).
- `downstream-auth`: Protect downstream API access with user authorization handlers and token exchange. Reference: [downstream-auth.md](./references/downstream-auth.md).
- `payload-limit`: Limit JSON request body size when owning the Express app or custom routes. Reference: [payload-limit.md](./references/payload-limit.md).
- `proactive-endpoints`: Secure proactive and custom endpoints, store conversation references, and require persistent storage for proactive flows. Reference: [proactive-endpoints.md](./references/proactive-endpoints.md).
- `environment-secrets`: Configure production environment settings and keep secrets out of source control. Reference: [environment-secrets.md](./references/environment-secrets.md).
- `safe-error-handling`: Add safe agent error handling with generic user messages and server-side diagnostics. Reference: [safe-error-handling.md](./references/safe-error-handling.md).
- `header-propagation`: Propagate only approved outbound headers such as trace and correlation IDs. Reference: [header-propagation.md](./references/header-propagation.md).
- `attachments`: Handle attachments defensively with downloader setup and validation. Reference: [attachments.md](./references/attachments.md).
- `observability`: Enable SDK diagnostics and OpenTelemetry tracing. Reference: [observability.md](./references/observability.md).
- `transcripts`: Store transcripts only when required and document retention, access, and disclosure expectations. Reference: [transcripts.md](./references/transcripts.md).

## Feature Rules
- Do not combine Blob turn-state storage and Cosmos turn-state storage; ask the user to choose one.
- Prefer `persistent-storage` with Blob Storage when a selected feature needs durable state and the user has not chosen a store.
- `proactive-endpoints` requires persistent storage.
- `transcripts` can use Blob transcript storage even when Cosmos DB is used for turn state.
- `payload-limit` usually means owning the Express setup instead of relying only on `startServer` defaults.

## Workflow
Ask the user if they want to create a new sample or adapt an existing one, unless the user already specified it.
If the user called the skill without specifying a task: either create or adapt/update an existing sample, ask the user which one they want to do.

Important: a user can ask questions and reason about the production features during any step of the workflow, the skill will answer and provide guidance to reach a common understanding of the production features and their implementation, but the skill should not lose track of the workflow and the selected features, and should not let the user get lost in the reasoning process.

Important: make sure to validate the sample builds and runs after implementing the selected features, and before reporting the final results to the user.

### Create a new sample
1. Ask the user to specify the name of the new sample, unless the user already specified it.
1. Alongside asking the name, show suggestions for a name based on the context provided by the user.
2. Ask the user to select which production features they want to implement in their new sample, unless the user already specified them.
1. If the selected features require or it is recommended to implement other features, suggest those features to the user.
3. Read [required-baseline.md](./references/required-baseline.md) and the references for the selected features.
4. Create a new sample with the required baseline plus the selected features.

Example of a required baseline for a new sample:
When creating a new sample, start with a minimal empty-agent shape and add only the features the user selects or mentions.
Usually related files are:
- `src/` folder with TypeScript code
- `index.ts` with the agent and message handler
- `agent.ts` with the agent and adapter setup
- `package.json` with dependencies and scripts
- `tsconfig.json` with TypeScript settings
- `.env` with environment variable settings
- `README.md` with setup, run, and production notes

#### Report
List the selected features in the new sample, and if the user wants to implement additional features, suggest which ones are missing or unclear and how to implement them.

Show the following numbered list:
```md
1. `{{ list item }}`: {{ file reference and line number if applicable }} {{ note about the implementation, recommendation, etc. , if applicable }}
```

### Adapt an existing sample
1. Inspect the existing sample to determine which production features are already implemented.
2. Ask the user which additional features they want to implement or modify.
1. If the selected features require or it is recommended to implement other features, suggest those features to the user.
3. Read [required-baseline.md](./references/required-baseline.md) and the references for detected or selected features.
4. Update the sample with the required baseline plus the selected features.

If more than one sample is detected in the repo, ask these kind of questions (adapt as needed):
- If user specified a sample, use that one. If another sample is detected, ask if the user wants to adapt that one as well.
- Adapt both samples or just one?
- Which sample?

#### Report
List the detected, missing, and unclear features in the existing sample. If the user wants to implement additional features, suggest which ones are missing or unclear and how to implement them.

Show the following numbered list:
```md
1. `{{ list item }}`: {{ file reference and line number if applicable }} {{ note about the implementation, recommendation, etc. , if applicable }}
```

## References
- SDK repo: https://github.com/microsoft/Agents/blob/main/README.md
- SDK samples: https://github.com/microsoft/Agents/blob/main/samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## Handle attachments defensively

When to use it: Add attachment downloaders only for samples that actually need to inspect or persist user files.

Why it matters: Attachments can contain malware, sensitive data, very large payloads, or URLs that should not be fetched by your service. Production agents should validate content type, size, source, scan policy, and retention before processing files.

Use the SDK downloaders for channel-aware downloads:

```ts
import {
AgentApplication,
InputFile,
M365AttachmentDownloader,
TurnContext,
TurnState,
} from '@microsoft/agents-hosting'

const agent = new AgentApplication<TurnState>({
fileDownloaders: [
new M365AttachmentDownloader('inputFiles'),
],
})
```

Then validate the downloaded files inside the handler that uses them. In this example, the downloader stores files in `state.inputFiles` before the message handler runs, and the handler rejects files that do not match the sample's allow-list:

```ts
agent.onActivity('message', async (context: TurnContext, state: TurnState) => {
const files = state.getValue<InputFile[]>('inputFiles') ?? []
const allowedTypes = new Set(['image/png', 'image/jpeg', 'application/pdf'])
const maxBytes = 5 * 1024 * 1024

for (const file of files) {
if (!allowedTypes.has(file.contentType) || file.content.length > maxBytes) {
await context.sendActivity('One or more attachments could not be processed.')
return
}
}

// Process the validated files here.
})
```

Do not download arbitrary user-provided URLs outside the channel attachment flow unless you have an allow-list, network egress controls, timeout limits, and malware scanning in place.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
## Protect downstream user access

When to use it: Use user authorization handlers when a sample calls Microsoft Graph, Copilot Studio, Power Platform, or any downstream API on behalf of the signed-in user.

Why it matters: Inbound JWT authentication proves the channel or caller can send the activity. It does not automatically prove that the user has consented to the downstream API scopes your sample wants to call.

Require an authorization handler on routes that need user tokens:

```ts
import { AgentApplication, TurnContext, TurnState } from '@microsoft/agents-hosting'

const agent = new AgentApplication<TurnState>({
authorization: {
graph: {
title: 'Sign in',
text: 'Sign in to continue.',
},
},
})

agent.onActivity('message', async (context: TurnContext, _state: TurnState) => {
const token = await agent.authorization.exchangeToken(context, 'graph', {
scopes: ['https://graph.microsoft.com/.default'],
})

if (!token.token) {
await context.sendActivity('Sign in is required before continuing.')
return
}

// Call the downstream API with token.token here.
}, ['graph'])
```

Configure the handler and OBO connection through environment settings:

```env
AgentApplication__UserAuthorization__Handlers__graph__Settings__azureBotOAuthConnectionName=GraphConnection
AgentApplication__UserAuthorization__Handlers__graph__Settings__title=Sign in
AgentApplication__UserAuthorization__Handlers__graph__Settings__text=Sign in to continue.
AgentApplication__UserAuthorization__Handlers__graph__Settings__oboConnectionName=graphOboConnection
AgentApplication__UserAuthorization__Handlers__graph__Settings__oboScopes=https://graph.microsoft.com/.default

connections__graphOboConnection__settings__clientId=<obo-app-id>
connections__graphOboConnection__settings__clientSecret=<secret-from-secret-store>
connections__graphOboConnection__settings__tenantId=<tenant-id>
connections__graphOboConnection__settings__authorityEndpoint=https://login.microsoftonline.com
```

Request the smallest set of downstream scopes needed for the sample, handle sign-out, and avoid logging access tokens or decoded token contents.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
## Configure secrets and environment

When to use it: Use environment configuration for every deployed sample. Put values in your host's secret/config system, not in committed `.env` files.

Why it matters: The current SDK auth loader supports multiple named connections and service URL mapping. Keeping this format consistent makes samples portable across Bot Framework, Teams, and additional authenticated callers.

Minimal production auth settings:

```env
NODE_ENV=production

connections__serviceConnection__settings__clientId=<app-id>
connections__serviceConnection__settings__clientSecret=<secret-from-secret-store>
connections__serviceConnection__settings__tenantId=<tenant-id>
connections__serviceConnection__settings__authorityEndpoint=https://login.microsoftonline.com

connectionsMap__0__connection=serviceConnection
connectionsMap__0__serviceUrl=*
```

For production, prefer service URL and audience-specific mappings when the target channels are known. The `serviceUrl` value is interpreted as a regular expression by the connection manager.

```env
connectionsMap__0__connection=teamsConnection
connectionsMap__0__serviceUrl=^https://smba\.trafficmanager\.net/teams/
connectionsMap__0__audience=<expected-token-audience>
```

Keep `connectionsMap__0__serviceUrl=*` for local development, broad multi-channel samples, or when your deployment intentionally accepts multiple channel service URLs. Use `CloudAdapterOptions__validateServiceUrl=true` either way so inbound activity service URLs are checked against the authenticated token claim.

Storage settings for the snippets above:

```env
BLOB_CONTAINER_ID=<container-name>
BLOB_STORAGE_CONNECTION_STRING=<secret-from-secret-store>

COSMOS_DATABASE_ID=<database-id>
COSMOS_CONTAINER_ID=<container-id>
COSMOS_ENDPOINT=<cosmos-endpoint>
COSMOS_KEY=<secret-from-secret-store>
```

Avoid logging raw environment values. The SDK redacts sensitive auth settings in its own logs, but application logs should follow the same rule.

Prefer secretless or rotatable credentials in production when your host supports them:

```env
# User-assigned managed identity
connections__serviceConnection__settings__authType=UserManagedIdentity
connections__serviceConnection__settings__clientId=<managed-identity-client-id>
connections__serviceConnection__settings__tenantId=<tenant-id>

# Workload identity
connections__serviceConnection__settings__authType=WorkloadIdentity
connections__serviceConnection__settings__clientId=<app-id>
connections__serviceConnection__settings__tenantId=<tenant-id>
connections__serviceConnection__settings__federatedTokenFile=<token-file-path>

# Certificate credential
connections__serviceConnection__settings__authType=Certificate
connections__serviceConnection__settings__clientId=<app-id>
connections__serviceConnection__settings__tenantId=<tenant-id>
connections__serviceConnection__settings__certPemFile=<certificate-pem-path>
connections__serviceConnection__settings__certKeyFile=<certificate-key-path>
```

Use `clientSecret` only when the host cannot use managed identity, workload identity, federated credentials, or certificates, and rotate it through your secret store.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## Propagate headers intentionally

When to use it: Configure header propagation when downstream connector calls or API clients need correlation IDs, trace context, or a product header.

Why it matters: Blindly forwarding inbound headers can leak `Authorization`, cookies, or tenant-specific data to services that should not receive them. The SDK lets you add or propagate only the headers you choose.

Use an allow-list:

```ts
import { AgentApplication, TurnState } from '@microsoft/agents-hosting'

const agent = new AgentApplication<TurnState>({
headerPropagation: (headers) => {
headers.propagate(['traceparent', 'tracestate', 'x-correlation-id'])
headers.add({ 'x-agent-component': 'orders-agent' })
},
})
```

Do not propagate `authorization`, `cookie`, API keys, or user-provided headers unless the downstream service explicitly requires them and is authorized to receive them.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## Add health and readiness endpoints

When to use it: Add health endpoints when the sample runs in a container, App Service, Azure Container Apps, Kubernetes, or any environment with load balancers and probes.

Why it matters: Health probes tell the platform whether the process is alive. Readiness checks tell the platform whether dependencies such as storage are reachable before traffic is routed to the instance.

Use `beforeListen` when `startServer` owns the Express app:

```ts
import { AgentApplication, TurnState } from '@microsoft/agents-hosting'
import { startServer } from '@microsoft/agents-hosting-express'
import { BlobsStorage } from '@microsoft/agents-hosting-storage-blob'

const storage = new BlobsStorage(
process.env.BLOB_CONTAINER_ID!,
process.env.BLOB_STORAGE_CONNECTION_STRING!
)
const agent = new AgentApplication<TurnState>({ storage })

startServer(agent, {
beforeListen: (app) => {
app.get('/health', (_req, res) => {
res.status(200).json({ status: 'ok' })
})

app.get('/ready', async (_req, res) => {
try {
await storage.read(['readiness'])
res.status(200).json({ status: 'ready' })
} catch {
res.status(503).json({ status: 'not ready' })
}
})
},
})
```

Keep health routes lightweight and avoid exposing secrets, tenant IDs, tokens, or detailed exception messages.
Loading
Loading