Skip to content

Commit

Permalink
Making API properties on the FE optional + addressing comments
Browse files Browse the repository at this point in the history
  • Loading branch information
teresaqhoang committed May 3, 2023
1 parent d19a54a commit 3277db1
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 90 deletions.
2 changes: 1 addition & 1 deletion samples/apps/copilot-chat-app/webapi/Skills/ChatSkill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ private bool TryExtractJsonFromOpenApiPlanResult(string openApiSkillResponse, ou
/// </summary>
private string OptimizeOpenApiSkillJson(string jsonContent, int tokenLimit, Plan plan)
{
int jsonTokenLimit = (int)(tokenLimit * 0.8);
int jsonTokenLimit = (int)(tokenLimit * this._promptSettings.RelatedInformationContextWeight);

// Remove all new line characters + leading and trailing white space
jsonContent = Regex.Replace(jsonContent.Trim(), @"[\n\r]", string.Empty);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace SemanticKernel.Service.Skills.OpenApiSkills;

/// <summary>
/// Represents a label.
/// Represents a pull request label.
/// </summary>
public class Label
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,85 @@ namespace SemanticKernel.Service.Skills.OpenApiSkills;
/// </summary>
public class PullRequest
{
// The URL of the pull request
/// <summary>
/// Gets or sets the URL of the pull request
/// </summary>
[JsonPropertyName("url")]
public System.Uri Url { get; set; }

// The unique identifier of the pull request
/// <summary>
/// Gets or sets the unique identifier of the pull request
/// </summary>
[JsonPropertyName("id")]
public int Id { get; set; }

// The number of the pull request
/// <summary>
/// Gets or sets the number of the pull request
/// </summary>
[JsonPropertyName("number")]
public int Number { get; set; }

// The state of the pull request
/// <summary>
/// Gets or sets the state of the pull request
/// </summary>
[JsonPropertyName("state")]
public string State { get; set; }

// Whether the pull request is locked
/// </summary>

Check warning on line 35 in samples/apps/copilot-chat-app/webapi/Skills/OpenApiSkills/GitHubSkill/Model/PullRequest.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, Release)

XML comment has badly formed XML -- 'End tag was not expected at this location.'

Check warning on line 35 in samples/apps/copilot-chat-app/webapi/Skills/OpenApiSkills/GitHubSkill/Model/PullRequest.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, Release)

XML comment has badly formed XML -- 'End tag was not expected at this location.'

Check warning on line 35 in samples/apps/copilot-chat-app/webapi/Skills/OpenApiSkills/GitHubSkill/Model/PullRequest.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, Debug)

XML comment has badly formed XML -- 'End tag was not expected at this location.'

Check warning on line 35 in samples/apps/copilot-chat-app/webapi/Skills/OpenApiSkills/GitHubSkill/Model/PullRequest.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, Debug)

XML comment has badly formed XML -- 'End tag was not expected at this location.'
[JsonPropertyName("locked")]
public bool Locked { get; set; }

// The title of the pull request
/// <summary>
/// Gets or sets the title of the pull request
/// </summary>
[JsonPropertyName("title")]
public string Title { get; set; }

// The user who created the pull request
/// <summary>
/// Gets or sets the user who created the pull request
/// </summary>
[JsonPropertyName("user")]
public GitHubUser User { get; set; }

// The labels associated with the pull request
/// <summary>
/// Gets or sets the labels associated with the pull request
/// </summary>
[JsonPropertyName("labels")]
public List<Label> Labels { get; set; }

// The date and time when the pull request was last updated
/// <summary>
/// Gets or sets the date and time when the pull request was last updated
/// </summary>
[JsonPropertyName("updated_at")]
public DateTime UpdatedAt { get; set; }

// The date and time when the pull request was closed
/// <summary>
/// Gets or sets the date and time when the pull request was closed
/// </summary>
[JsonPropertyName("closed_at")]
public DateTime? ClosedAt { get; set; }

// The date and time when the pull request was merged
/// <summary>
/// Gets or sets the date and time when the pull request was merged
/// </summary>
[JsonPropertyName("merged_at")]
public DateTime? MergedAt { get; set; }

// Constructor
/// <summary>
/// Initializes a new instance of the <see cref="PullRequest"/> class, representing a pull request on GitHub.
/// </summary>
/// <param name="url">The URL of the pull request.</param>
/// <param name="id">The unique identifier of the pull request.</param>
/// <param name="number">The number of the pull request within the repository.</param>
/// <param name="state">The state of the pull request, such as "open", "closed", or "merged".</param>
/// <param name="locked">A value indicating whether the pull request is locked for comments or changes.</param>
/// <param name="title">The title of the pull request.</param>
/// <param name="user">The user who created the pull request.</param>
/// <param name="labels">A list of labels assigned to the pull request.</param>
/// <param name="updatedAt">The date and time when the pull request was last updated.</param>
/// <param name="closedAt">The date and time when the pull request was closed, or null if it is not closed.</param>
/// <param name="mergedAt">The date and time when the pull request was merged, or null if it is not merged.</param>
public PullRequest(
System.Uri url,
int id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@ namespace SemanticKernel.Service.Skills.OpenApiSkills;
/// </summary>
public class Repo
{
// The name of the repo
/// <summary>
/// Gets or sets the name of the repo
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; }

// The full name of the repo
/// <summary>
/// Gets or sets the full name of the repo
/// </summary>
[JsonPropertyName("full_name")]
public string FullName { get; set; }


/// <summary>
/// Initializes a new instance of the <see cref="Repo"/>.
/// </summary>
/// <param name="name">The name of the repository, e.g. "dotnet/runtime".</param>
/// <param name="fullName">The full name of the repository, e.g. "Microsoft/dotnet/runtime".</param>
public Repo(string name, string fullName)
{
this.Name = name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ interface PluginCardProps {
}

export const PluginCard: React.FC<PluginCardProps> = ({ plugin }) => {
const { name, publisher, enabled, authRequirements, apiRequirements, icon, description } = plugin;
const { name, publisher, enabled, authRequirements, apiProperties, icon, description } = plugin;

const styles = useStyles();
const dispatch = useAppDispatch();
Expand All @@ -68,7 +68,7 @@ export const PluginCard: React.FC<PluginCardProps> = ({ plugin }) => {
icon={icon}
publisher={publisher}
authRequirements={authRequirements}
apiRequirements={apiRequirements}
apiProperties={apiProperties}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Dismiss20Regular } from '@fluentui/react-icons';
import { FormEvent, useState } from 'react';
import { TokenHelper } from '../../libs/auth/TokenHelper';
import { useAppDispatch } from '../../redux/app/hooks';
import { AdditionalApiRequirements, PluginAuthRequirements, Plugins } from '../../redux/features/plugins/PluginsState';
import { AdditionalApiProperties, PluginAuthRequirements, Plugins } from '../../redux/features/plugins/PluginsState';
import { connectPlugin } from '../../redux/features/plugins/pluginsSlice';

const useClasses = makeStyles({
Expand Down Expand Up @@ -53,15 +53,15 @@ interface PluginConnectorProps {
icon: string;
publisher: string;
authRequirements: PluginAuthRequirements;
apiRequirements?: AdditionalApiRequirements;
apiProperties?: AdditionalApiProperties;
}

export const PluginConnector: React.FC<PluginConnectorProps> = ({
name,
icon,
publisher,
authRequirements,
apiRequirements,
apiProperties,
}) => {
const classes = useClasses();

Expand All @@ -74,7 +74,7 @@ export const PluginConnector: React.FC<PluginConnectorProps> = ({
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [accessToken, setAccessToken] = useState('');
const [apiRequirementsInput, setApiRequirmentsInput] = useState(apiRequirements);
const [apiPropertiesInput, setApiRequirmentsInput] = useState(apiProperties);

const [open, setOpen] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | undefined>();
Expand All @@ -91,7 +91,7 @@ export const PluginConnector: React.FC<PluginConnectorProps> = ({
connectPlugin({
plugin: name,
accessToken: token,
apiRequirements: apiRequirementsInput,
apiProperties: apiPropertiesInput,
}),
);
} else if (oauthRequired) {
Expand All @@ -104,7 +104,7 @@ export const PluginConnector: React.FC<PluginConnectorProps> = ({
username: username,
password: password,
accessToken: accessToken,
apiRequirements: apiRequirementsInput,
apiProperties: apiPropertiesInput,
}),
);
}
Expand Down Expand Up @@ -223,37 +223,35 @@ export const PluginConnector: React.FC<PluginConnectorProps> = ({
</Body1>
</>
)}
{apiRequirements && (
{apiProperties && (
<>
<Body1Strong> Configuration </Body1Strong>
<Body1>Some additional information is required to enable {name}'s REST APIs.</Body1>
{Object.keys(apiRequirements).map((requirement) => {
const requirementDetails = apiRequirementsInput![requirement];
{Object.keys(apiProperties).map((property) => {
const propertyDetails = apiPropertiesInput![property];
return (
<div className={classes.section} key={requirement}>
<div className={classes.section} key={property}>
<Input
key={requirement}
required
key={property}
required={propertyDetails.required}
type="text"
id={'plugin-additional-info' + requirement}
id={'plugin-additional-info' + property}
onChange={(_e, input) => {
setApiRequirmentsInput({
...apiRequirementsInput,
[requirement]: {
...requirementDetails,
...apiPropertiesInput,
[property]: {
...propertyDetails,
value: input.value,
},
});
}}
placeholder={`Enter the ${
requirementDetails.description ?? requirement
}`}
placeholder={`Enter the ${propertyDetails.description ?? property}`}
/>
{requirementDetails.helpLink && (
{propertyDetails.helpLink && (
<Body1>
For more details on obtaining this information,{' '}
<a
href={requirementDetails.helpLink}
href={propertyDetails.helpLink}
target="_blank"
rel="noreferrer noopener"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,43 +50,13 @@ export const useConnectors = () => {
);
};

/**
* Helper function to invoke SK skills
* with GitHub token.
*/
const invokeSkillWithGitHubToken = async () => {
// This is an example of invoking a GitHub skill (imported as an Open API Skill),
// where PullsList comes from the API operation Id defined in the swagger
// and variables are parameter requirements of the API being called.
const listPullRequestsAsk = {
input: 'input',
variables: [
{ key: 'owner', value: 'microsoft' },
{ key: 'repo', value: 'semantic-kernel' },
],
};

return await sk.invokeAsync(
listPullRequestsAsk,
'GitHubSkill',
'PullsList',
await AuthHelper.getSKaaSAccessToken(instance),
[
{
headerTag: plugins.GitHub.headerTag,
authData: plugins.GitHub.authData!,
},
],
);
};

/*
* Once enabled, each plugin will have a custom dedicated header in every SK request
* containing respective auth information (i.e., token, encoded client info, etc.)
* that the server can use to authenticate to the downstream APIs
*/
const getEnabledPlugins = () => {
const enabledPlugins: { headerTag: AuthHeaderTags; authData: string; apiRequirements?: any }[] = [];
const enabledPlugins: { headerTag: AuthHeaderTags; authData: string; apiProperties?: any }[] = [];

Object.entries(plugins).map((entry) => {
const plugin = entry[1];
Expand All @@ -95,7 +65,7 @@ export const useConnectors = () => {
enabledPlugins.push({
headerTag: plugin.headerTag,
authData: plugin.authData!,
apiRequirements: plugin.apiRequirements,
apiProperties: plugin.apiProperties,
});
}

Expand All @@ -108,7 +78,6 @@ export const useConnectors = () => {
return {
invokeSkillWithMsalToken,
invokeSkillWithGraphToken,
invokeSkillWithGitHubToken,
getEnabledPlugins,
};
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.

import { AdditionalApiRequirements, AuthHeaderTags } from '../../redux/features/plugins/PluginsState';
import { AdditionalApiProperties, AuthHeaderTags } from '../../redux/features/plugins/PluginsState';
import { BaseService } from '../services/BaseService';
import { IAsk, IAskVariables } from './model/Ask';
import { IAskResult } from './model/AskResult';
Expand All @@ -14,23 +14,32 @@ export class SemanticKernel extends BaseService {
enabledPlugins?: {
headerTag: AuthHeaderTags;
authData: string;
apiRequirements?: AdditionalApiRequirements;
apiProperties?: AdditionalApiProperties;
}[],
): Promise<IAskResult> => {
// If skill requires any additional api requirements, append to context
// If skill requires any additional api properties, append to context
if (enabledPlugins && enabledPlugins.length > 0) {
const openApiSkillVariables: IAskVariables[] = [];

for (var idx in enabledPlugins) {
var plugin = enabledPlugins[idx];

if (plugin.apiRequirements) {
const apiRequirments = plugin.apiRequirements;
for (var property in apiRequirments) {
openApiSkillVariables.push({
key: property,
value: apiRequirments[property].value!,
});
if (plugin.apiProperties) {
const apiProperties = plugin.apiProperties;

for (var property in apiProperties) {
const propertyDetails = apiProperties[property];

if (propertyDetails.required && !propertyDetails.value) {
throw new Error(`Missing required property ${property} for ${plugin} skill.`);
}

if (propertyDetails.value) {
openApiSkillVariables.push({
key: property,
value: apiProperties[property].value!,
});
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AdditionalApiRequirements, AuthHeaderTags } from '../../redux/features/plugins/PluginsState';
import { AdditionalApiProperties, AuthHeaderTags } from '../../redux/features/plugins/PluginsState';

interface ServiceRequest {
commandPath: string;
Expand All @@ -17,7 +17,7 @@ export class BaseService {
enabledPlugins?: {
headerTag: AuthHeaderTags;
authData: string;
apiRequirements?: AdditionalApiRequirements;
apiProperties?: AdditionalApiProperties;
}[],
): Promise<T> => {
const { commandPath, method, body } = request;
Expand Down

0 comments on commit 3277db1

Please sign in to comment.