Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C#] feat: add adapter for telemetry headers #1174

Merged
merged 10 commits into from
Jan 17, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void Test_ApplicationBuilder_CustomSetup()
bool startTypingTimer = false;
string botAppId = "testBot";
IStorage storage = new MemoryStorage();
BotAdapter adapter = new SimpleAdapter();
SimpleAdapter adapter = new();
TestLoggerFactory loggerFactory = new();
Func<TurnState> turnStateFactory = () => new TurnState();
AdaptiveCardsOptions adaptiveCards = new()
Expand Down Expand Up @@ -91,7 +91,7 @@ public void Test_ApplicationBuilder_CustomSetup()
public void Test_ApplicationBuilder_LongRunningMessages_ExceptionThrown()
{
// Arrange
BotAdapter adapter = new SimpleAdapter();
SimpleAdapter adapter = new();

// Act
var func = () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void Test_Application_CustomSetup()
bool longRunningMessages = true;
string botAppId = "testBot";
IStorage storage = new MemoryStorage();
BotAdapter adapter = new SimpleAdapter();
SimpleAdapter adapter = new();
TestLoggerFactory loggerFactory = new();
Func<TurnState> turnStateFactory = () => new TurnState();
AdaptiveCardsOptions adaptiveCardOptions = new()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Net.Http.Headers;
using System.Reflection;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Teams.AI.Tests.Application
{
public class TeamsAdapterTests
{
[Fact]
public void Test_TeamsAdapter_HasDefaultHeaders()
{
string version = Assembly.GetAssembly(typeof(TeamsAdapter))?.GetName().Version?.ToString() ?? "";
ProductInfoHeaderValue productInfo = new("teamsai-dotnet", version);
TeamsAdapter adapter = new();
Assert.NotNull(adapter.HttpClientFactory);

HttpClient client = adapter.HttpClientFactory.CreateClient();
Assert.NotNull(client);
Assert.True(client.DefaultRequestHeaders.UserAgent.Contains(productInfo));
}

[Fact]
public void Test_TeamsAdapter_NoDuplicateDefaultHeaders()
{
string version = Assembly.GetAssembly(typeof(TeamsAdapter))?.GetName().Version?.ToString() ?? "";
ProductInfoHeaderValue productInfo = new("teamsai-dotnet", version);
ConfigurationBuilder config = new();
TeamsAdapter adapter = new(config.Build(), new TeamsHttpClientFactory());
Assert.NotNull(adapter.HttpClientFactory);

HttpClient client = adapter.HttpClientFactory.CreateClient();
Assert.NotNull(client);
Assert.True(client.DefaultRequestHeaders.UserAgent.Contains(productInfo));
Assert.True(client.DefaultRequestHeaders.UserAgent.Count == 1);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,28 @@

namespace Microsoft.Teams.AI.Tests.TestUtils
{
public class SimpleAdapter : BotAdapter
public class SimpleAdapter : TeamsAdapter
{
private readonly Action<Activity[]>? _callOnSend;
private readonly Action<Activity>? _callOnUpdate;
private readonly Action<ConversationReference>? _callOnDelete;

public SimpleAdapter()
public SimpleAdapter() : base()
{

}

public SimpleAdapter(Action<Activity[]> callOnSend)
public SimpleAdapter(Action<Activity[]> callOnSend) : base()
{
_callOnSend = callOnSend;
}

public SimpleAdapter(Action<Activity> callOnUpdate)
public SimpleAdapter(Action<Activity> callOnUpdate) : base()
{
_callOnUpdate = callOnUpdate;
}

public SimpleAdapter(Action<ConversationReference> callOnDelete)
public SimpleAdapter(Action<ConversationReference> callOnDelete) : base()
{
_callOnDelete = callOnDelete;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class ApplicationBuilder<TState>
/// <param name="adapter">The adapter to use for routing incoming requests.</param>
/// <param name="botAppId">The Microsoft App ID for the bot.</param>
/// <returns>The ApplicationBuilder instance.</returns>
public ApplicationBuilder<TState> WithLongRunningMessages(BotAdapter adapter, string botAppId)
public ApplicationBuilder<TState> WithLongRunningMessages(TeamsAdapter adapter, string botAppId)
{
if (string.IsNullOrEmpty(botAppId))
{
Expand Down Expand Up @@ -134,7 +134,7 @@ public ApplicationBuilder<TState> SetStartTypingTimer(bool startTypingTimer)
/// <param name="adapter">The bot adapter.</param>
/// <param name="authenticationOptions">The options for authentication.</param>
/// <returns>The ApplicationBuilder instance.</returns>
public ApplicationBuilder<TState> WithAuthentication(BotAdapter adapter, AuthenticationOptions<TState> authenticationOptions)
public ApplicationBuilder<TState> WithAuthentication(TeamsAdapter adapter, AuthenticationOptions<TState> authenticationOptions)
{
Options.Adapter = adapter;
Options.Authentication = authenticationOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ public class ApplicationOptions<TState>
where TState : TurnState, new()
{
/// <summary>
/// Optional. Bot adapter being used.
/// Optional. Teams Bot adapter being used.
/// </summary>
/// <remarks>
/// If using the <see cref="ApplicationOptions{TState}.LongRunningMessages"/> option, calling the <see cref="CloudAdapterBase.ContinueConversationAsync(string, Bot.Schema.Activity, BotCallbackHandler, CancellationToken)"/> method, or configuring user authentication, this property is required.
/// </remarks>
public BotAdapter? Adapter { get; set; }
public TeamsAdapter? Adapter { get; set; }

/// <summary>
/// Optional. Application ID of the bot.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Net.Http.Headers;
using System.Reflection;

// Note: this class should never modify the way `CloudAdapter` is intended to work.

namespace Microsoft.Teams.AI
{
/// <summary>
/// An adapter that implements the Bot Framework Protocol and can be hosted in different cloud environments both public and private.
/// </summary>
public class TeamsAdapter : CloudAdapter
{
/// <summary>
/// The Http Client Factory
/// </summary>
public IHttpClientFactory HttpClientFactory { get; }

/// <summary>
/// Initializes a new instance of the <see cref="TeamsAdapter"/> class. (Public cloud. No auth. For testing.)
/// </summary>
public TeamsAdapter() : base()
{
HttpClientFactory = new TeamsHttpClientFactory();
}

/// <summary>
/// Initializes a new instance of the <see cref="TeamsAdapter"/> class.
/// </summary>
/// <param name="configuration">The <see cref="IConfiguration"/> instance.</param>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> this adapter should use.</param>
/// <param name="logger">The <see cref="ILogger"/> implementation this adapter should use.</param>
public TeamsAdapter(
IConfiguration configuration,
IHttpClientFactory? httpClientFactory = null,
ILogger? logger = null) : base(
configuration,
new TeamsHttpClientFactory(httpClientFactory),
logger)
{
HttpClientFactory = new TeamsHttpClientFactory(httpClientFactory);
}
}

internal class TeamsHttpClientFactory : IHttpClientFactory
{
private readonly IHttpClientFactory? _parent;

public TeamsHttpClientFactory(IHttpClientFactory? parent = null)
{
_parent = parent;
}

public HttpClient CreateClient(string name)
{
HttpClient client = _parent != null ? _parent.CreateClient(name) : new();
string version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
ProductInfoHeaderValue productInfo = new("teamsai-dotnet", version);

if (!client.DefaultRequestHeaders.UserAgent.Contains(productInfo))
{
client.DefaultRequestHeaders.UserAgent.Add(productInfo);
}

return client;
}
}
}
30 changes: 12 additions & 18 deletions js/packages/teams-ai/src/Application.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ import {
FileConsentCardResponse
} from 'botbuilder';

import { PasswordServiceClientCredentialFactory } from 'botframework-connector';

import { Application, ConversationUpdateEvents, MessageReactionEvents, TeamsMessageEvents } from './Application';
import { AdaptiveCardsOptions } from './AdaptiveCards';
import { AIOptions } from './AI';
import { TaskModulesOptions } from './TaskModules';
import { TurnState } from './TurnState';
import { createTestConversationUpdate, createTestInvoke } from './internals';
import { TestPlanner } from './planners/TestPlanner';
import { TeamsBotFrameworkAuthentication } from './TeamsBotFrameworkAuthentication';
import { TeamsAdapter } from './TeamsAdapter';

class MockUserTokenClient {
/**
Expand Down Expand Up @@ -154,15 +152,19 @@ describe('Application', () => {
)
);
});

it('should have a configured adapter', () => {
const app = new Application({
adapter: new TeamsAdapter({}, undefined, undefined, {})
});

assert.doesNotThrow(() => app.adapter);
});
});

describe('botAuthentication', () => {
const app = new Application({
adapter: {
authentication: new TeamsBotFrameworkAuthentication({
credentialsFactory: new PasswordServiceClientCredentialFactory('', '')
})
}
adapter: new TeamsAdapter()
});

it('should initialize `CloudAdapter`', () => {
Expand Down Expand Up @@ -244,11 +246,7 @@ describe('Application', () => {

beforeEach(() => {
app = new Application({
adapter: {
authentication: new TeamsBotFrameworkAuthentication({
credentialsFactory: new PasswordServiceClientCredentialFactory('', '')
})
},
adapter: new TeamsAdapter(),
authentication: authenticationSettings
});

Expand All @@ -272,11 +270,7 @@ describe('Application', () => {
it('should start signin flow', async () => {
const authSettings = { ...authenticationSettings, autoSignIn: true };
const app = new Application({
adapter: {
authentication: new TeamsBotFrameworkAuthentication({
credentialsFactory: new PasswordServiceClientCredentialFactory('', '')
})
},
adapter: new TeamsAdapter(),
authentication: authSettings
});

Expand Down
13 changes: 3 additions & 10 deletions js/packages/teams-ai/src/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
Activity,
ActivityTypes,
BotAdapter,
CloudAdapter,
ConversationReference,
FileConsentCardResponse,
O365ConnectorCardActionQuery,
Expand All @@ -36,7 +35,7 @@ import {
setUserInSignInFlow,
userInSignInFlow
} from './authentication/BotAuthenticationBase';
import { BotAdapterOptions } from './BotAdapterOptions';
import { TeamsAdapter } from './TeamsAdapter';

/**
* @private
Expand Down Expand Up @@ -72,7 +71,7 @@ export interface ApplicationOptions<TState extends TurnState> {
/**
* Optional. Options used to initialize your `BotAdapter`
*/
adapter?: BotAdapter | BotAdapterOptions;
adapter?: TeamsAdapter;

/**
* Optional. OAuth prompt settings to use for authentication.
Expand Down Expand Up @@ -247,13 +246,7 @@ export class Application<TState extends TurnState = TurnState> {

// Create Adapter
if (this._options.adapter) {
if ('authentication' in this._options.adapter) {
if (this._options.adapter?.authentication) {
this._adapter = new CloudAdapter(this._options.adapter.authentication);
}
} else {
this._adapter = this._options.adapter as BotAdapter;
}
this._adapter = this._options.adapter;
}

// Create AI component if configured with a planner
Expand Down
11 changes: 2 additions & 9 deletions js/packages/teams-ai/src/ApplicationBuilder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import { strict as assert } from 'assert';
import { MemoryStorage } from 'botbuilder';
import { PasswordServiceClientCredentialFactory } from 'botframework-connector';

import { ApplicationBuilder } from './ApplicationBuilder';
import { AdaptiveCardsOptions } from './AdaptiveCards';
import { AIOptions } from './AI';
import { TurnState } from './TurnState';
import { TestPlanner } from './planners';
import { TaskModulesOptions } from './TaskModules';
import { BotAdapterOptions } from './BotAdapterOptions';
import { TeamsBotFrameworkAuthentication } from './TeamsBotFrameworkAuthentication';
import { TeamsAdapter } from './TeamsAdapter';

describe('ApplicationBuilder', () => {
const botAppId = 'testBot';
const adapter: BotAdapterOptions = {
authentication: new TeamsBotFrameworkAuthentication({
credentialsFactory: new PasswordServiceClientCredentialFactory('', '')
})
};

const adapter = new TeamsAdapter();
const adaptiveCards: AdaptiveCardsOptions = { actionSubmitFilter: 'cardFilter' };
const ai: AIOptions<TurnState> = { planner: new TestPlanner() };
const longRunningMessages = true;
Expand Down
15 changes: 6 additions & 9 deletions js/packages/teams-ai/src/ApplicationBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Storage, BotAdapter } from 'botbuilder';
import { Storage } from 'botbuilder';

import { Application, ApplicationOptions } from './Application';
import { BotAdapterOptions } from './BotAdapterOptions';
import { TeamsAdapter } from './TeamsAdapter';
import { AIOptions } from './AI';
import { TurnState } from './TurnState';
import { AdaptiveCardsOptions } from './AdaptiveCards';
Expand All @@ -18,11 +18,11 @@ export class ApplicationBuilder<TState extends TurnState = TurnState> {
/**
* Configures the application to use long running messages.
* Default state for longRunningMessages is false
* @param {BotAdapter | BotAdapterOptions} adapter The adapter to use for routing incoming requests.
* @param {TeamsAdapter} adapter The adapter to use for routing incoming requests.
* @param {string} botAppId The Microsoft App ID for the bot.
* @returns {this} The ApplicationBuilder instance.
*/
public withLongRunningMessages(adapter: BotAdapter | BotAdapterOptions, botAppId: string): this {
public withLongRunningMessages(adapter: TeamsAdapter, botAppId: string): this {
if (!botAppId) {
throw new Error(
`The Application.longRunningMessages property is unavailable because botAppId cannot be null or undefined.`
Expand Down Expand Up @@ -77,14 +77,11 @@ export class ApplicationBuilder<TState extends TurnState = TurnState> {

/**
* Configures user authentication settings.
* @param {BotAdapter | BotAdapterOptions} adapter The adapter to use for user authentication.
* @param {TeamsAdapter} adapter The adapter to use for user authentication.
* @param {AuthenticationOptions} authenticationOptions The options to configure the authentication manager.
* @returns {this} The ApplicationBuilder instance.
*/
public withAuthentication(
adapter: BotAdapter | BotAdapterOptions,
authenticationOptions: AuthenticationOptions
): this {
public withAuthentication(adapter: TeamsAdapter, authenticationOptions: AuthenticationOptions): this {
this._options.adapter = adapter;
this._options.authentication = authenticationOptions;
return this;
Expand Down
Loading
Loading