Skip to content
Draft
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -491,3 +491,7 @@ $RECYCLE.BIN/

# Claude local settings
.claude/settings.local.json

# Claude session and private files
session-context.md
*-private.md
178 changes: 178 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Startup

Before responding to the user's first message, complete these steps:

### 1. Read knowledge files
- Read `Claude-KB.md` in this directory (domain knowledge, lessons learned). Create it if it doesn't exist with a `## Lessons Learned` heading.
- **Don't read md files from the parent directory unless the user requests it** — this slows down session start.
- Look for a `*-private.md` file matching the user's name (e.g., `Rajan-private.md`). If one exists, read it — it contains personal TODOs, preferences, and reminders. These files are gitignored and never committed.
- The private file may reference a durable location (e.g., a private git repo). If it does, also read and update that location for persistent notes and TODOs.

### 2. Read session context
- Read `session-context.md` if it exists. It contains ephemeral state from the previous session: what was in flight, what to pick up, any "don't forget" items. This file is gitignored and overwritten each save.
- Surface relevant items in the greeting (e.g., "Last session you were working on PR 1234").

### 3. Greet the user and surface
- Any open TODOs or reminders from private notes
- Common scenarios / quick-start commands:
- **Build the solution** — `dotnet build`
- **Run all tests** — `dotnet test`
- **Run a specific test** — `dotnet test --filter "FullyQualifiedName~TestName"`
- **Run a sample app** — `dotnet run --project Samples/Samples.Echo`
- **Format code** — `dotnet format`
- **Create NuGet packages** — `dotnet pack`

## Build Commands

```bash
dotnet build # Build solution
dotnet test # Run all tests
dotnet test -v d # Run tests with detailed verbosity
dotnet format # Format code (EditorConfig enforced)
dotnet pack # Create NuGet packages
```

Run a specific test project:
```bash
dotnet test Tests/Microsoft.Teams.Apps.Tests
```

Run a single test by name:
```bash
dotnet test --filter "FullyQualifiedName~TestMethodName"
```

Run tests with coverage:
```bash
dotnet test --collect:"XPlat Code Coverage"
```

Run a specific sample:
```bash
dotnet run --project Samples/Samples.Echo
dotnet run --project Samples/Samples.Lights
```

## Development Workflow

- Cannot push directly to main - all changes require a pull request
- Create a feature branch, make changes, then open a PR
- CI runs build, test, and lint checks on PRs

## Architecture Overview

This is the Microsoft Teams SDK for .NET (`Microsoft.Teams.sln`) - a suite of packages for building Teams bots and apps.

### Core Libraries (Libraries/)

- **Microsoft.Teams.Apps** - Core bot functionality: activity handling, message processing, routing, context management
- **Microsoft.Teams.AI** - AI/LLM integration: chat plugins, function definitions, prompt templates
- **Microsoft.Teams.AI.Models.OpenAI** - OpenAI-specific model implementation
- **Microsoft.Teams.Api** - Teams API client for bot-to-Teams communication
- **Microsoft.Teams.Cards** - Adaptive Cards support
- **Microsoft.Teams.Common** - Shared utilities, JSON helpers, HTTP, logging, storage patterns

### Extensions (Libraries/Microsoft.Teams.Extensions/)

- **Configuration** - Configuration helpers
- **Hosting** - ASP.NET Core DI integration
- **Logging** - Microsoft.Extensions.Logging integration
- **Graph** - Microsoft Graph integration

### Plugins (Libraries/Microsoft.Teams.Plugins/)

- **AspNetCore** - Core middleware for ASP.NET Core
- **AspNetCore.DevTools** - Development tools
- **AspNetCore.BotBuilder** - Bot Builder SDK adapter
- **External.Mcp** / **External.McpClient** - Model Context Protocol integration

## Code Patterns

### Basic App Setup

```csharp
var builder = WebApplication.CreateBuilder(args);
builder.AddTeams(); // Register Teams services
var app = builder.Build();
var teams = app.UseTeams(); // Get Teams middleware

teams.OnMessage(async context => { // Handle messages
await context.Send("Hello!");
});

app.Run();
```

### AI Plugin

```csharp
[Prompt]
[Prompt.Description("description")]
[Prompt.Instructions("system instructions")]
public class MyPrompt(IContext.Accessor accessor)
{
[Function]
[Function.Description("what this function does")]
public string MyFunction([Param("param description")] string input)
{
return "result";
}
}
```

## Code Style

EditorConfig is strictly enforced. Key conventions:

- **Namespaces**: File-scoped (`namespace Foo;`)
- **Fields**: `_camelCase` for private, `s_camelCase` for private static
- **Nullable**: Enabled throughout
- **Async**: All async methods, CancellationToken support

All files require Microsoft copyright header:
```csharp
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
```

## Testing

- xUnit with Moq for mocking, implicit `using Xunit` in test projects
- Test projects target net9.0 (libraries target net8.0)
- Test naming: `{LibraryName}.Tests` in `Tests/` directory
- Use `Microsoft.Teams.Apps.Testing` for test utilities

## Lessons Learned

This workspace is a **learning system**. Claude-KB.md contains a `## Lessons Learned` section that persists knowledge across sessions.

### When to add an entry

Proactively add a lesson whenever you encounter:

- **Unexpected behavior** — an API, tool, or workflow didn't work as expected and you found the cause
- **Workarounds** — a problem required a non-obvious solution that future sessions should know about
- **User preferences** — the user corrects your approach or states a preference
- **Process discoveries** — you learn how something actually works vs. how it's documented
- **Pitfalls** — something that wasted time and could be avoided next time

### How to add an entry

Append to the `## Lessons Learned` section in `Claude-KB.md` using this format:

```markdown
### YYYY-MM-DD: Short descriptive title
Description of what happened and what to do differently. Keep it concise and actionable.
```

### Guidelines

- Write for your future self — assume no prior context from this session
- Be specific: include tool names, flag names, error messages, or exact steps
- Don't duplicate existing entries — read the section first
- One entry per distinct lesson; don't bundle unrelated things
- Ask the user before adding if you're unsure whether something qualifies
3 changes: 3 additions & 0 deletions Claude-KB.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Claude Knowledge Base — Microsoft Teams SDK for .NET

## Lessons Learned
5 changes: 3 additions & 2 deletions Libraries/Microsoft.Teams.Api/Auth/ClientCredentials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class ClientCredentials : IHttpCredentials
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string? TenantId { get; set; }
public CloudEnvironment Cloud { get; set; } = CloudEnvironment.Public;

public ClientCredentials(string clientId, string clientSecret)
{
Expand All @@ -26,9 +27,9 @@ public ClientCredentials(string clientId, string clientSecret, string? tenantId)

public async Task<ITokenResponse> Resolve(IHttpClient client, string[] scopes, CancellationToken cancellationToken = default)
{
var tenantId = TenantId ?? "botframework.com";
var tenantId = TenantId ?? Cloud.LoginTenant;
var request = HttpRequest.Post(
$"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token"
$"{Cloud.LoginEndpoint}/{tenantId}/oauth2/v2.0/token"
);

request.Headers.Add("Content-Type", ["application/x-www-form-urlencoded"]);
Expand Down
175 changes: 175 additions & 0 deletions Libraries/Microsoft.Teams.Api/Auth/CloudEnvironment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Teams.Api.Auth;

/// <summary>
/// Bundles all cloud-specific service endpoints for a given Azure environment.
/// Use predefined instances (<see cref="Public"/>, <see cref="USGov"/>, <see cref="USGovDoD"/>, <see cref="China"/>)
/// or construct a custom one.
/// </summary>
public class CloudEnvironment
{
/// <summary>
/// The Azure AD login endpoint (e.g. "https://login.microsoftonline.com").
/// </summary>
public string LoginEndpoint { get; }

/// <summary>
/// The default multi-tenant login tenant (e.g. "botframework.com").
/// </summary>
public string LoginTenant { get; }

/// <summary>
/// The Bot Framework OAuth scope (e.g. "https://api.botframework.com/.default").
/// </summary>
public string BotScope { get; }

/// <summary>
/// The Bot Framework token service base URL (e.g. "https://token.botframework.com").
/// </summary>
public string TokenServiceUrl { get; }

/// <summary>
/// The OpenID metadata URL for token validation (e.g. "https://login.botframework.com/v1/.well-known/openidconfiguration").
/// </summary>
public string OpenIdMetadataUrl { get; }

/// <summary>
/// The token issuer for Bot Framework tokens (e.g. "https://api.botframework.com").
/// </summary>
public string TokenIssuer { get; }

/// <summary>
/// The channel service URL. Empty for public cloud; set for sovereign clouds
/// (e.g. "https://botframework.azure.us").
/// </summary>
public string ChannelService { get; }

/// <summary>
/// The OAuth redirect URL (e.g. "https://token.botframework.com/.auth/web/redirect").
/// </summary>
public string OAuthRedirectUrl { get; }

public CloudEnvironment(
string loginEndpoint,
string loginTenant,
string botScope,
string tokenServiceUrl,
string openIdMetadataUrl,
string tokenIssuer,
string channelService,
string oauthRedirectUrl)
{
LoginEndpoint = loginEndpoint;
LoginTenant = loginTenant;
BotScope = botScope;
TokenServiceUrl = tokenServiceUrl;
OpenIdMetadataUrl = openIdMetadataUrl;
TokenIssuer = tokenIssuer;
ChannelService = channelService;
OAuthRedirectUrl = oauthRedirectUrl;
}

/// <summary>
/// Microsoft public (commercial) cloud.
/// </summary>
public static readonly CloudEnvironment Public = new(
loginEndpoint: "https://login.microsoftonline.com",
loginTenant: "botframework.com",
botScope: "https://api.botframework.com/.default",
tokenServiceUrl: "https://token.botframework.com",
openIdMetadataUrl: "https://login.botframework.com/v1/.well-known/openidconfiguration",
tokenIssuer: "https://api.botframework.com",
channelService: "",
oauthRedirectUrl: "https://token.botframework.com/.auth/web/redirect"
);

/// <summary>
/// US Government Community Cloud High (GCCH).
/// </summary>
public static readonly CloudEnvironment USGov = new(
loginEndpoint: "https://login.microsoftonline.us",
loginTenant: "MicrosoftServices.onmicrosoft.us",
botScope: "https://api.botframework.us/.default",
tokenServiceUrl: "https://tokengcch.botframework.azure.us",
openIdMetadataUrl: "https://login.botframework.azure.us/v1/.well-known/openidconfiguration",
tokenIssuer: "https://api.botframework.us",
channelService: "https://botframework.azure.us",
oauthRedirectUrl: "https://tokengcch.botframework.azure.us/.auth/web/redirect"
);

/// <summary>
/// US Government Department of Defense (DoD).
/// </summary>
public static readonly CloudEnvironment USGovDoD = new(
loginEndpoint: "https://login.microsoftonline.us",
loginTenant: "MicrosoftServices.onmicrosoft.us",
botScope: "https://api.botframework.us/.default",
tokenServiceUrl: "https://apiDoD.botframework.azure.us",
openIdMetadataUrl: "https://login.botframework.azure.us/v1/.well-known/openidconfiguration",
tokenIssuer: "https://api.botframework.us",
channelService: "https://botframework.azure.us",
oauthRedirectUrl: "https://apiDoD.botframework.azure.us/.auth/web/redirect"
);

/// <summary>
/// China cloud (21Vianet).
/// </summary>
public static readonly CloudEnvironment China = new(
loginEndpoint: "https://login.partner.microsoftonline.cn",
loginTenant: "microsoftservices.partner.onmschina.cn",
botScope: "https://api.botframework.azure.cn/.default",
tokenServiceUrl: "https://token.botframework.azure.cn",
openIdMetadataUrl: "https://login.botframework.azure.cn/v1/.well-known/openidconfiguration",
tokenIssuer: "https://api.botframework.azure.cn",
channelService: "https://botframework.azure.cn",
oauthRedirectUrl: "https://token.botframework.azure.cn/.auth/web/redirect"
);

/// <summary>
/// Creates a new <see cref="CloudEnvironment"/> by applying non-null overrides on top of this instance.
/// Returns the same instance if all overrides are null (no allocation).
/// </summary>
public CloudEnvironment WithOverrides(
string? loginEndpoint = null,
string? loginTenant = null,
string? botScope = null,
string? tokenServiceUrl = null,
string? openIdMetadataUrl = null,
string? tokenIssuer = null,
string? channelService = null,
string? oauthRedirectUrl = null)
{
if (loginEndpoint is null && loginTenant is null && botScope is null &&
tokenServiceUrl is null && openIdMetadataUrl is null && tokenIssuer is null &&
channelService is null && oauthRedirectUrl is null)
{
return this;
}

return new CloudEnvironment(
loginEndpoint ?? LoginEndpoint,
loginTenant ?? LoginTenant,
botScope ?? BotScope,
tokenServiceUrl ?? TokenServiceUrl,
openIdMetadataUrl ?? OpenIdMetadataUrl,
tokenIssuer ?? TokenIssuer,
channelService ?? ChannelService,
oauthRedirectUrl ?? OAuthRedirectUrl
);
}

/// <summary>
/// Resolves a cloud environment name (case-insensitive) to its corresponding instance.
/// Valid names: "Public", "USGov", "USGovDoD", "China".
/// </summary>
public static CloudEnvironment FromName(string name) => name.ToLowerInvariant() switch
{
"public" => Public,
"usgov" => USGov,
"usgovdod" => USGovDoD,
"china" => China,
_ => throw new ArgumentException($"Unknown cloud environment: '{name}'. Valid values are: Public, USGov, USGovDoD, China.", nameof(name))
};
}
Loading
Loading