diff --git a/.github/workflows/aca-deploy.yaml b/.github/workflows/aca-deploy.yaml new file mode 100644 index 0000000..b4599c7 --- /dev/null +++ b/.github/workflows/aca-deploy.yaml @@ -0,0 +1,37 @@ +name: Reusable Azure Container Apps deploy +on: + workflow_call: + inputs: + env-name: + required: true + type: string + acr-name: + required: true + type: string + image-name: + required: true + type: string + container-app-name: + required: true + type: string + resource-group: + required: true + type: string + + +jobs: + build: + runs-on: ubuntu-latest + environment: ${{inputs.env-name}} + steps: + - name: Log in to Azure with sevice principal + uses: azure/login@v2 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + - name: Build and deploy Container App + uses: azure/container-apps-deploy-action@v1 + with: + acrName: ${{inputs.acr-name}} + containerAppName: ${{inputs.container-app-name}} + resourceGroup: ${{inputs.resource-group}} + imageToDeploy: ${{inputs.acr-name}}.azurecr.io/${{inputs.image-name}}:${{github.sha}} \ No newline at end of file diff --git a/.github/workflows/acr-build-push.yaml b/.github/workflows/acr-build-push.yaml new file mode 100644 index 0000000..6c14c23 --- /dev/null +++ b/.github/workflows/acr-build-push.yaml @@ -0,0 +1,40 @@ +name: Reusable ACR Build and Push workflow +on: + workflow_call: + inputs: + env-name: + required: true + type: string + acr-name: + required: true + type: string + image-name: + required: true + type: string + app-folder-path: + required: true + type: string + + +jobs: + build: + runs-on: ubuntu-latest + environment: ${{inputs.env-name}} + steps: + - name: Log in to Azure with sevice principal + uses: azure/login@v2 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + - name: Log in Azure Container Registry + uses: azure/cli@v2 + with: + azcliversion: latest + inlineScript: | + az acr login --name ${{inputs.acr-name}} + - name: Build and Push to ACR + run: | + echo "Building image [${{ inputs.image-name }}] and environment [${{ inputs.env-name }}]" + cd ${{ inputs.app-folder-path }} + docker build . -t ${{inputs.acr-name}}.azurecr.io/${{inputs.image-name}}:${{github.sha}} + docker push ${{inputs.acr-name}}.azurecr.io/${{inputs.image-name}}:${{github.sha}} + echo "image successfully pushed: ${{inputs.acr-name}}.azurecr.io/${{inputs.image-name}}:${{github.sha}} \ No newline at end of file diff --git a/.github/workflows/app-ci.yaml b/.github/workflows/app-ci.yaml deleted file mode 100644 index d72b8d5..0000000 --- a/.github/workflows/app-ci.yaml +++ /dev/null @@ -1,95 +0,0 @@ -name: APP CI/CD Pipeline - -on: - push: - branches: - - main - paths: - - "app/**" - tags: - - v*.*.* - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - outputs: - env-name: ${{steps.set-deploy-env.outputs.DEPLOY_ENVIRONMENT}} - steps: - - uses: actions/checkout@v2 - - - name: Set up Java version - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: '17' - cache: 'maven' - - - name: Set environment for branch - id: set-deploy-env - run: | - echo "checking branch name [${{github.ref_name}}]" - if [[ ${{github.ref_name}} == 'main' ]]; then - echo "main branch detected. Set Development environment" - echo "DEPLOY_ENVIRONMENT=Development" >> "$GITHUB_OUTPUT" - elif [[ ${{github.ref_name}} == *'release'* ]]; then - echo "release branch detected. Set Test environment" - echo "DEPLOY_ENVIRONMENT=Test" >> "$GITHUB_OUTPUT" - elif [[ ${{github.ref_name}} == *'v'* ]]; then - echo "tag detected. Set Production environment" - echo "DEPLOY_ENVIRONMENT=Production" >> "$GITHUB_OUTPUT" - else - echo "branch not detected. Set Development environment as default" - echo "DEPLOY_ENVIRONMENT=Development" >> "$GITHUB_OUTPUT" - fi - - name: Build React Frontend - run: | - echo "Building frontend and merge into spring boot static folder. Environment [${{ steps.set-deploy-env.outputs.DEPLOY_ENVIRONMENT }}]" - cd ./app/frontend - npm install - npm run build - mkdir -p ../backend/src/main/resources/static - cp -r ./build/* ../backend/src/main/resources/static - - - - name: Build Spring Boot App - run: | - echo "Building spring boot app. Environment [${{ steps.set-deploy-env.outputs.DEPLOY_ENVIRONMENT }}]" - cd ./app/backend - mvn package - artifactid=$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout) - jarversion=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) - originaljarname="$artifactid-$jarversion.jar" - echo "Renaming $originaljarname to app.jar" - # Renaming jar so it is auto detected by app service - mv ./target/$originaljarname ./target/app.jar - - - name: Upload artifacts for backend deployment jobs - uses: actions/upload-artifact@v2 - with: - name: spring-boot-app - path: | - ./app/backend/target/app.jar - - - deploy: - runs-on: ubuntu-latest - needs: build - environment: - name: ${{ needs.build.outputs.env-name}} - url: ${{ steps.deploy-app.outputs.webapp-url }} - - steps: - - name: Download backend artifact from build job - uses: actions/download-artifact@v2 - with: - name: spring-boot-app - path: ./backend - - - name: 'Deploy backend to Azure Web App' - uses: azure/webapps-deploy@v2 - id: deploy-app - with: - app-name: ${{ vars.AZUREAPPSERVICE_APP_NAME }} - package: ./backend - publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE}} diff --git a/.github/workflows/apps-ci.yaml b/.github/workflows/apps-ci.yaml new file mode 100644 index 0000000..bd7c02d --- /dev/null +++ b/.github/workflows/apps-ci.yaml @@ -0,0 +1,68 @@ +name: APP CI/CD Pipeline + +on: + push: + branches: + - main + paths: + - "app/**" + tags: + - v*.*.* + workflow_dispatch: + +jobs: + changes-detection: + runs-on: ubuntu-latest + outputs: + env-name: ${{steps.set-deploy-env.outputs.DEPLOY_ENVIRONMENT}} + build-copilot: ${{ steps.changes.outputs.copilot }} + build-frontend: ${{ steps.changes.outputs.frontend }} + build-account-api: ${{ steps.changes.outputs.account-api }} + build-payment-api: ${{ steps.changes.outputs.payment-api }} + build-transactions-api: ${{ steps.changes.outputs.transactions-api }} + steps: + - uses: actions/checkout@v2 + - name: Filter Changes + uses: dorny/paths-filter@v2 + id: changes + with: + filters: | + copilot: + - 'app/copilot/**' + frontend: + - 'app/frontend/**' + account-api: + - 'app/business-api/account/**' + payment-api: + - 'app/business-api/payment/**' + transactions-api: + - 'app/business-api/transactions-history/**' + + - name: Set environment for branch + id: set-deploy-env + run: | + echo "checking branch name [${{github.ref_name}}]" + if [[ ${{github.ref_name}} == 'main' ]]; then + echo "main branch detected. Set Development environment" + echo "DEPLOY_ENVIRONMENT=Development" >> "$GITHUB_OUTPUT" + elif [[ ${{github.ref_name}} == *'release'* ]]; then + echo "release branch detected. Set Test environment" + echo "DEPLOY_ENVIRONMENT=Test" >> "$GITHUB_OUTPUT" + elif [[ ${{github.ref_name}} == *'v'* ]]; then + echo "tag detected. Set Production environment" + echo "DEPLOY_ENVIRONMENT=Production" >> "$GITHUB_OUTPUT" + else + echo "branch not detected. Set Development environment as default" + echo "DEPLOY_ENVIRONMENT=Development" >> "$GITHUB_OUTPUT" + fi + + build-frontend-app: + needs: changes-detection + if : ${{ needs.changes-detection.outputs.build-frontend == 'true' }} + uses: ./.github/workflows/acr-build-push.yaml + with: + env-name: ${{ needs.changes-detection.outputs.env-name}} + acr-name: ${{ vars.AZUREACR_NAME }} + image-name: personal-finance-assistance-java/web + app-folder-path: app/frontend + \ No newline at end of file diff --git a/.github/workflows/nightly-jobs.yaml b/.github/workflows/nightly-jobs.yaml deleted file mode 100644 index 72345d5..0000000 --- a/.github/workflows/nightly-jobs.yaml +++ /dev/null @@ -1,98 +0,0 @@ -name: Nightly and CICD Jobs - -on: - pull_request: - branches: [ main ] - schedule: - - cron: '0 0 * * *' # Run at midnight every day - workflow_dispatch: - -permissions: - id-token: write - contents: read - security-events: write - -jobs: - validate-bicep: - name: "Infra Biceps Validation" - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Filter Changes - uses: dorny/paths-filter@v2 - id: changes - with: - filters: | - app-service: - - 'deploy/app-service/**' - aks: - - 'deploy/aks/**' - aca: - - 'deploy/aca/**' - - name: Build App Service Bicep for linting - if: steps.changes.outputs.app-service == 'true' - uses: azure/CLI@v1 - with: - inlineScript: az config set bicep.use_binary_from_path=false && az bicep build -f deploy/app-service/infra/main.bicep --stdout - - - name: Build AKS Bicep for linting - if: steps.changes.outputs.aks == 'true' - uses: azure/CLI@v1 - with: - inlineScript: az config set bicep.use_binary_from_path=false && az bicep build -f deploy/aks/infra/main.bicep --stdout - - - name: Build ACA Bicep for linting - if: steps.changes.outputs.aca == 'true' - uses: azure/CLI@v1 - with: - inlineScript: az config set bicep.use_binary_from_path=false && az bicep build -f deploy/aca/infra/main.bicep --stdout - - - name: Run Microsoft Security DevOps Analysis - uses: microsoft/security-devops-action@v1 - id: msdo - continue-on-error: true - with: - tools: templateanalyzer - - - name: Upload alerts to Security tab - uses: github/codeql-action/upload-sarif@v2 - if: github.repository == 'Azure-Samples/azure-search-openai-demo-java' - with: - sarif_file: ${{ steps.msdo.outputs.sarifFile }} - - frontend: - name: "Front-end validation" - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Build React Frontend - run: | - echo "Building front-end and merge into Spring Boot static folder." - cd ./app/frontend - npm install - npm run build - mkdir -p ../backend/src/main/resources/static - cp -r ./build/* ../backend/src/main/resources/static - - backend: - name: "Backend validation" - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Java version - uses: actions/setup-java@v2 - with: - distribution: 'microsoft' - java-version: '17' - cache: 'maven' - - - name: Build Spring Boot App - run: | - echo "Building Spring Boot app." - cd ./app/backend - mvn verify diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml deleted file mode 100644 index 44dc9ab..0000000 --- a/.github/workflows/stale-bot.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: 'Close stale issues and PRs' -on: - schedule: - - cron: '30 1 * * *' - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v8 - with: - stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this issue will be closed.' - stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed.' - close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' - close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.' - days-before-issue-stale: 60 - days-before-pr-stale: 60 - days-before-issue-close: -1 - days-before-pr-close: -1 diff --git a/app/backend/.mvn/wrapper/maven-wrapper.jar b/app/backend/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index 968b23b..0000000 Binary files a/app/backend/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/app/backend/.mvn/wrapper/maven-wrapper.properties b/app/backend/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 0b3b65e..0000000 --- a/app/backend/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.tar.gz diff --git a/app/backend/applicationinsights.json b/app/backend/applicationinsights.json deleted file mode 100644 index 6611add..0000000 --- a/app/backend/applicationinsights.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "role": { - "name": "api" - } - } \ No newline at end of file diff --git a/app/backend/mvnw b/app/backend/mvnw deleted file mode 100644 index eb65ff2..0000000 --- a/app/backend/mvnw +++ /dev/null @@ -1,305 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# M2_HOME - location of maven2's installed home dir -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "`uname`" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" - else - export JAVA_HOME="/Library/Java/Home" - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` - fi -fi - -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then - if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" - else - javaExecutable="`readlink -f \"$javaExecutable\"`" - fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="`which java`" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` - fi - # end of workaround - done - echo "${basedir}" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" - fi -} - -BASE_DIR=`find_maven_basedir "$(pwd)"` -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi -else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi - if [ "$MVNW_REPOURL" = true]; then - jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" - else - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" - fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; - esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" - if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` - fi - - if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" - else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f - else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f - fi - - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` - fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") - fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR -fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` - [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` - [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` -fi - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -exec "$JAVACMD" \ - $MAVEN_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/app/backend/mvnw.cmd b/app/backend/mvnw.cmd deleted file mode 100644 index 4f5150a..0000000 --- a/app/backend/mvnw.cmd +++ /dev/null @@ -1,172 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" -if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" - -FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - echo Found %WRAPPER_JAR% -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.2/maven-wrapper-0.5.2.jar" - ) - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ - "}" - echo Finished downloading %WRAPPER_JAR% -) -@REM End of extension - -%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" -if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%" == "on" pause - -if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% - -exit /B %ERROR_CODE% diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/HistoryReportingAgent.java b/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/HistoryReportingAgent.java deleted file mode 100644 index 2c682e4..0000000 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/HistoryReportingAgent.java +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -package com.microsoft.openai.samples.assistant.agent; - -import com.azure.ai.openai.OpenAIAsyncClient; -import com.azure.ai.openai.OpenAIClientBuilder; -import com.azure.core.credential.AzureKeyCredential; -import com.azure.core.http.policy.HttpLogDetailLevel; -import com.azure.core.http.policy.HttpLogOptions; -import com.microsoft.openai.samples.assistant.plugin.TransactionHistoryPlugin; -import com.microsoft.semantickernel.Kernel; -import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; -import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatMessageContent; -import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIFunctionToolCall; -import com.microsoft.semantickernel.contextvariables.CaseInsensitiveMap; -import com.microsoft.semantickernel.contextvariables.ContextVariable; -import com.microsoft.semantickernel.orchestration.FunctionResult; -import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; -import com.microsoft.semantickernel.orchestration.InvocationContext; -import com.microsoft.semantickernel.orchestration.ToolCallBehavior; -import com.microsoft.semantickernel.plugin.KernelPluginFactory; -import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; -import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; -import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.stream.Collectors; - -public class HistoryReportingAgent { - private static final Logger LOGGER = LoggerFactory.getLogger(HistoryReportingAgent.class); - private OpenAIAsyncClient client; - - private Kernel kernel; - - private ChatCompletionService chat; - - private String HISTORY_AGENT_SYSTEM_MESSAGE = """ - you are a personal financial advisor who help the user with their recurrent bill payments. To search about the payments history you need to know the payee name. - If the user doesn't provide the payee name, search the last 10 transactions order by date. - If the user want to search last transactions for a specific payee ask to provide the payee name. - """; - - public HistoryReportingAgent(OpenAIAsyncClient client, String modelId){ - this.client = client; - this.chat = OpenAIChatCompletion.builder() - .withModelId(modelId) - .withOpenAIAsyncClient(client) - .build(); - - var plugin = KernelPluginFactory.createFromObject(new TransactionHistoryPlugin(), "TransactionHistoryPlugin"); - - - kernel = Kernel.builder() - .withAIService(ChatCompletionService.class, chat) - .withPlugin(plugin) - .build(); - } - public HistoryReportingAgent(String azureClientKey, String clientEndpoint, String modelId){ - this.client = new OpenAIClientBuilder() - .credential(new AzureKeyCredential(azureClientKey)) - .endpoint(clientEndpoint) - .httpLogOptions(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS)) - .buildAsyncClient(); - - this.chat = OpenAIChatCompletion.builder() - .withModelId(modelId) - .withOpenAIAsyncClient(client) - .build(); - - var plugin = KernelPluginFactory.createFromObject(new TransactionHistoryPlugin(), "TransactionHistoryPlugin"); - - - kernel = Kernel.builder() - .withAIService(ChatCompletionService.class, chat) - .withPlugin(plugin) - .build(); - } - - public AgentContext run (ChatHistory userChatHistory, AgentContext agentContext){ - LOGGER.info("======== HistoryAndTransaction Agent: Starting ========"); - - var agentChatHistory = new ChatHistory(HISTORY_AGENT_SYSTEM_MESSAGE); - userChatHistory.forEach( chatMessageContent -> { - if(chatMessageContent.getAuthorRole() != AuthorRole.SYSTEM) - agentChatHistory.addMessage(chatMessageContent); - }); - - - while (true) { - var messages = this.chat.getChatMessageContentsAsync( - agentChatHistory, - kernel, - InvocationContext.builder() - .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true)).build()) - .block(); - - var message = messages.get(0); - agentContext.setResult(message.getContent()); - - - List toolCalls = messages.stream() - .filter(it -> it instanceof OpenAIChatMessageContent) - .map(it -> (OpenAIChatMessageContent) it) - .map(OpenAIChatMessageContent::getToolCall) - .flatMap(List::stream) - .collect(Collectors.toList()); - - if (toolCalls.isEmpty()) { - break; - } - - messages.stream() - .forEach(it -> agentChatHistory.addMessage(it)); - - for (var toolCall : toolCalls) { - - String content = null; - try { - // getFunction will throw an exception if the function is not found - var fn = kernel.getFunction(toolCall.getPluginName(), - toolCall.getFunctionName()); - FunctionResult fnResult = fn - .invokeAsync(kernel, toolCall.getArguments(), null, null).block(); - content = (String) fnResult.getResult(); - } catch (IllegalArgumentException e) { - content = "Unable to find function. Please try again!"; - } - - agentChatHistory.addMessage( - AuthorRole.TOOL, - content, - StandardCharsets.UTF_8, - new FunctionResultMetadata(new CaseInsensitiveMap<>() { - { - put(FunctionResultMetadata.ID, ContextVariable.of(toolCall.getId())); - } - })); - } - } - return agentContext; - } - - - - public static void main(String[] args) throws NoSuchMethodException { - - String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); - String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); - String MODEL_ID = System.getenv() - .getOrDefault("MODEL_ID", "gpt-3.5-turbo-1106"); - - HistoryReportingAgent agent = new HistoryReportingAgent(AZURE_CLIENT_KEY, CLIENT_ENDPOINT, MODEL_ID); - - agent.run(new ChatHistory(), new AgentContext()); - - - - } - } - - diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/PaymentAgent.java b/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/PaymentAgent.java deleted file mode 100644 index b5d694d..0000000 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/PaymentAgent.java +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -package com.microsoft.openai.samples.assistant.agent; - -import com.azure.ai.documentintelligence.DocumentIntelligenceClient; -import com.azure.ai.openai.OpenAIAsyncClient; -import com.azure.ai.openai.OpenAIClientBuilder; -import com.azure.core.credential.AzureKeyCredential; -import com.azure.core.http.policy.HttpLogDetailLevel; -import com.azure.core.http.policy.HttpLogOptions; -import com.microsoft.openai.samples.assistant.controller.ChatController; -import com.microsoft.openai.samples.assistant.invoice.DocumentIntelligenceInvoiceScanHelper; -import com.microsoft.openai.samples.assistant.plugin.InvoiceScanPlugin; -import com.microsoft.openai.samples.assistant.plugin.PaymentPlugin; -import com.microsoft.openai.samples.assistant.plugin.TransactionHistoryPlugin; -import com.microsoft.openai.samples.assistant.proxy.BlobStorageProxy; -import com.microsoft.semantickernel.Kernel; -import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; -import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatMessageContent; -import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIFunctionToolCall; -import com.microsoft.semantickernel.contextvariables.CaseInsensitiveMap; -import com.microsoft.semantickernel.contextvariables.ContextVariable; -import com.microsoft.semantickernel.orchestration.FunctionResult; -import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; -import com.microsoft.semantickernel.orchestration.InvocationContext; -import com.microsoft.semantickernel.orchestration.ToolCallBehavior; -import com.microsoft.semantickernel.plugin.KernelPluginFactory; -import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; -import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; -import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.stream.Collectors; - -public class PaymentAgent { - private static final Logger LOGGER = LoggerFactory.getLogger(PaymentAgent.class); - private OpenAIAsyncClient client; - - private Kernel kernel; - - private ChatCompletionService chat; - - private String PAYMENT_AGENT_SYSTEM_MESSAGE = """ - you are a personal financial advisor who help the user with their recurrent bill payments. The user may want to pay the bill uploading a photo of the bill, or it may start the payment checking transactions history for a specific payee. - For the bill payment you need to know the: bill id or invoice number, payee name, the total amount and the bill expiration date. - if you don't have enough information to pay the bill ask the user to provide the missing information. - - Always check if the bill has been paid already based on payment history before asking to execute the bill payment. - Always ask for the payment method to use: direct debit, credit card, or bank transfer - Before executing the payBill function provide the user with the bill details and ask for confirmation. - If the payment succeeds provide the user with the payment confirmation. If not provide the user with the error message. - - """; - - public PaymentAgent(OpenAIAsyncClient client, String modelId, DocumentIntelligenceClient documentIntelligenceClient, BlobStorageProxy blobStorageProxy) { - this.client = client; - this.chat = OpenAIChatCompletion.builder() - .withModelId(modelId) - .withOpenAIAsyncClient(client) - .build(); - - kernel = Kernel.builder() - .withAIService(ChatCompletionService.class, chat) - .build(); - - var paymentPlugin = KernelPluginFactory.createFromObject(new PaymentPlugin(), "PaymentPlugin"); - var historyPlugin = KernelPluginFactory.createFromObject(new TransactionHistoryPlugin(), "TransactionHistoryPlugin"); - var invoiceScanPlugin = KernelPluginFactory.createFromObject(new InvoiceScanPlugin(new DocumentIntelligenceInvoiceScanHelper(documentIntelligenceClient,blobStorageProxy)), "InvoiceScanPlugin"); - - - kernel = Kernel.builder() - .withAIService(ChatCompletionService.class, chat) - .withPlugin(paymentPlugin) - .withPlugin(historyPlugin) - .withPlugin(invoiceScanPlugin) - .build(); - } - - - public void run (ChatHistory userChatHistory, AgentContext agentContext){ - LOGGER.info("======== Payment Agent: Starting ========"); - - var agentChatHistory = new ChatHistory(PAYMENT_AGENT_SYSTEM_MESSAGE); - - userChatHistory.forEach( chatMessageContent -> { - if(chatMessageContent.getAuthorRole() != AuthorRole.SYSTEM) - agentChatHistory.addMessage(chatMessageContent); - - }); - - - while (true) { - var messages = this.chat.getChatMessageContentsAsync( - agentChatHistory, - kernel, - InvocationContext.builder() - .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true)).build()) - .block(); - - var message = messages.get(0); - - agentContext.setResult(message.getContent()); - - - List toolCalls = messages.stream() - .filter(it -> it instanceof OpenAIChatMessageContent) - .map(it -> (OpenAIChatMessageContent) it) - .map(OpenAIChatMessageContent::getToolCall) - .flatMap(List::stream) - .collect(Collectors.toList()); - - if (toolCalls.isEmpty()) { - break; - } - - messages.stream() - .forEach(it -> agentChatHistory.addMessage(it)); - - for (var toolCall : toolCalls) { - - String content = null; - try { - // getFunction will throw an exception if the function is not found - var fn = kernel.getFunction(toolCall.getPluginName(), - toolCall.getFunctionName()); - FunctionResult fnResult = fn - .invokeAsync(kernel, toolCall.getArguments(), null, null).block(); - content = (String) fnResult.getResult(); - } catch (IllegalArgumentException e) { - content = "Unable to find function. Please try again!"; - } - - agentChatHistory.addMessage( - AuthorRole.TOOL, - content, - StandardCharsets.UTF_8, - new FunctionResultMetadata(new CaseInsensitiveMap<>() { - { - put(FunctionResultMetadata.ID, ContextVariable.of(toolCall.getId())); - } - })); - } - } - } - - - } - - diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/PaymentTransaction.java b/app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/PaymentTransaction.java deleted file mode 100644 index 9121ca0..0000000 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/PaymentTransaction.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.microsoft.openai.samples.assistant.plugin; - -public record PaymentTransaction(String transactionId, String documentId, String recipientName, String amount, String transactionDatetime) {} - diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/TransactionService.java b/app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/TransactionService.java deleted file mode 100644 index e138b90..0000000 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/TransactionService.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.microsoft.openai.samples.assistant.plugin; - -import java.util.ArrayList; -import java.util.List; - -public class TransactionService { - - private List plumberTransactions = new ArrayList<>(); - - private List electricianTransactions = new ArrayList<>(); - - private List lastTransactions = new ArrayList<>(); - - public TransactionService(){ - - plumberTransactions.add(new PaymentTransaction("11", "0001", "Bill ThePlumber", "100.00", "2024-4-01T12:00:00Z")); - plumberTransactions.add(new PaymentTransaction("21", "0002", "Bill ThePlumber", "200.00", "2024-1-02T12:00:00Z")); - plumberTransactions.add(new PaymentTransaction("31", "0003", "Bill ThePlumber", "300.00", "2023-10-03T12:00:00Z")); - plumberTransactions.add(new PaymentTransaction("41", "0004", "Bill ThePlumber", "400.00", "2023-8-04T12:00:00Z")); - plumberTransactions.add(new PaymentTransaction("51", "0005", "Bill ThePlumber", "500.00", "2023-4-05T12:00:00Z")); - - - electricianTransactions.add(new PaymentTransaction("12", "0001", "Jane TheElectrician", "100.00", "2024-3-01T12:00:00Z")); - electricianTransactions.add(new PaymentTransaction("22", "0002", "Jane TheElectrician", "200.00", "2023-1-02T12:00:00Z")); - electricianTransactions.add(new PaymentTransaction("32", "0003", "Jane TheElectrician", "300.00", "2022-10-03T12:00:00Z")); - electricianTransactions.add(new PaymentTransaction("42", "0004", "Jane TheElectrician", "400.00", "2022-8-04T12:00:00Z")); - electricianTransactions.add(new PaymentTransaction("52", "0005", "Jane TheElectrician", "500.00", "2020-4-05T12:00:00Z")); - - - lastTransactions.add(new PaymentTransaction("11", "0001", "Bill ThePlumber", "100.00", "2024-4-01T12:00:00Z")); - lastTransactions.add(new PaymentTransaction("22", "0002", "Jane TheElectrician", "200.00", "2024-3-02T12:00:00Z")); - lastTransactions.add(new PaymentTransaction("33", "0003", "Bob TheCarpenter", "300.00", "2023-10-03T12:00:00Z")); - lastTransactions.add(new PaymentTransaction("43", "0004", "Alice ThePainter", "400.00", "2023-8-04T12:00:00Z")); - lastTransactions.add(new PaymentTransaction("53", "0005", "Charlie TheMechanic", "500.00", "2023-4-05T12:00:00Z")); - } - public List getTransactionsByRecipientName(String name) { - switch (name){ - case "Bill ThePlumber": - return plumberTransactions; - case "Jane TheElectrician": - return electricianTransactions; - default: - return lastTransactions; - } - } - - public List getlastTransactions() { - return lastTransactions; - } - -} diff --git a/app/business-api/account/.mvn/wrapper/maven-wrapper.properties b/app/business-api/account/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..23c7e59 --- /dev/null +++ b/app/business-api/account/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/app/backend/Dockerfile b/app/business-api/account/Dockerfile similarity index 56% rename from app/backend/Dockerfile rename to app/business-api/account/Dockerfile index 45171c1..095425c 100644 --- a/app/backend/Dockerfile +++ b/app/business-api/account/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/openjdk/jdk:17-mariner AS build +FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build WORKDIR /workspace/app EXPOSE 3100 @@ -14,7 +14,7 @@ RUN sed -i 's/\r$//' ./mvnw RUN ./mvnw package -DskipTests RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) -FROM mcr.microsoft.com/openjdk/jdk:17-mariner +FROM mcr.microsoft.com/openjdk/jdk:21-mariner ARG DEPENDENCY=/workspace/app/target/dependency COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib @@ -22,9 +22,9 @@ COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app -RUN curl -LJ -o /app/applicationinsights-agent-3.4.19.jar https://github.com/microsoft/ApplicationInsights-Java/releases/download/3.4.19/applicationinsights-agent-3.4.19.jar +RUN curl -LJ -o /app/applicationinsights-agent-3.5.2.jar https://github.com/microsoft/ApplicationInsights-Java/releases/download/3.5.2/applicationinsights-agent-3.5.2.jar COPY applicationinsights.json /app EXPOSE 8080 -ENTRYPOINT ["java","-javaagent:/app/applicationinsights-agent-3.4.19.jar","-noverify", "-XX:MaxRAMPercentage=70", "-XX:+UseParallelGC", "-XX:ActiveProcessorCount=2", "-cp","app:app/lib/*","com.microsoft.openai.samples.assistant.Application"] \ No newline at end of file +ENTRYPOINT ["java","-javaagent:/app/applicationinsights-agent-3.5.2.jar","-noverify", "-XX:MaxRAMPercentage=70", "-XX:+UseParallelGC", "-XX:ActiveProcessorCount=2", "-cp","app:app/lib/*","com.microsoft.openai.samples.assistant.business.AccountApplication"] \ No newline at end of file diff --git a/app/business-api/account/applicationinsights.json b/app/business-api/account/applicationinsights.json new file mode 100644 index 0000000..547fc9f --- /dev/null +++ b/app/business-api/account/applicationinsights.json @@ -0,0 +1,5 @@ +{ + "role": { + "name": "accounts-api" + } + } \ No newline at end of file diff --git a/app/business-api/account/mvnw b/app/business-api/account/mvnw new file mode 100644 index 0000000..19529dd --- /dev/null +++ b/app/business-api/account/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/app/business-api/account/mvnw.cmd b/app/business-api/account/mvnw.cmd new file mode 100644 index 0000000..b150b91 --- /dev/null +++ b/app/business-api/account/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/app/business-api/account/pom.xml b/app/business-api/account/pom.xml new file mode 100644 index 0000000..a933201 --- /dev/null +++ b/app/business-api/account/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.13 + + + com.microsoft.openai.samples.assistant.business + account + 1.0.0-SNAPSHOT + + + + 17 + 17 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/AccountApplication.java b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/AccountApplication.java new file mode 100644 index 0000000..fc90184 --- /dev/null +++ b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/AccountApplication.java @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.openai.samples.assistant.business; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; + +@SpringBootApplication +public class AccountApplication { + + private static final Logger LOG = LoggerFactory.getLogger(AccountApplication.class); + + public static void main(String[] args) { + LOG.info( + "Application profile from system property is [{}]", + System.getProperty("spring.profiles.active")); + new SpringApplication(AccountApplication.class).run(args); + } +} diff --git a/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/controller/AccountController.java b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/controller/AccountController.java new file mode 100644 index 0000000..9375434 --- /dev/null +++ b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/controller/AccountController.java @@ -0,0 +1,44 @@ +package com.microsoft.openai.samples.assistant.business.controller; + +import com.microsoft.openai.samples.assistant.business.models.Account; +import com.microsoft.openai.samples.assistant.business.models.PaymentMethod; +import com.microsoft.openai.samples.assistant.business.models.Beneficiary; +import com.microsoft.openai.samples.assistant.business.service.AccountService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/accounts") +public class AccountController { + + private final AccountService accountService; + private static final Logger logger = LoggerFactory.getLogger(AccountController.class); + + public AccountController(AccountService accountService) { + this.accountService = accountService; + } + + @GetMapping("/{accountId}") + public Account getAccountDetails(@PathVariable String accountId) { + logger.info("Received request to get account details for account id: {}", accountId); + return accountService.getAccountDetails(accountId); + } + + @GetMapping("/{accountId}/paymentmethods/{methodId}") + public PaymentMethod getPaymentMethodDetails(@PathVariable String accountId, @PathVariable String methodId) { + logger.info("Received request to get payment method details for account id: {} and method id: {}", accountId, methodId); + return accountService.getPaymentMethodDetails(methodId); + } + + @GetMapping("/{accountId}/registeredBeneficiaries") + public List getBeneficiaryDetails(@PathVariable String accountId) { + logger.info("Received request to get beneficiary details for account id: {}", accountId); + return accountService.getRegisteredBeneficiary(accountId); + } +} \ No newline at end of file diff --git a/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/controller/UserController.java b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/controller/UserController.java new file mode 100644 index 0000000..81d8472 --- /dev/null +++ b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/controller/UserController.java @@ -0,0 +1,33 @@ +package com.microsoft.openai.samples.assistant.business.controller; + +import com.microsoft.openai.samples.assistant.business.models.Account; +import com.microsoft.openai.samples.assistant.business.service.UserService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/users") +public class UserController { + + private final UserService userService; + private static final Logger logger = LoggerFactory.getLogger(UserController.class); + + + @Autowired + public UserController(UserService userService) { + this.userService = userService; + } + @GetMapping("/{userName}/accounts") + public List getAccountsByUserName(@PathVariable String userName) { + // Implement the logic to get the list of all accounts for a specific user + logger.info("Received request to get accounts for user: {}", userName); + return userService.getAccountsByUserName(userName); + } +} diff --git a/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/Account.java b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/Account.java new file mode 100644 index 0000000..a41b03f --- /dev/null +++ b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/Account.java @@ -0,0 +1,18 @@ +package com.microsoft.openai.samples.assistant.business.models; + + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record Account( + String id, + String userName, + String accountHolderFullName, + String currency, + String activationDate, + String balance, + List paymentMethods +) {} + diff --git a/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/Beneficiary.java b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/Beneficiary.java new file mode 100644 index 0000000..7e9f8e0 --- /dev/null +++ b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/Beneficiary.java @@ -0,0 +1,12 @@ +package com.microsoft.openai.samples.assistant.business.models; + + +import java.util.List; + +public record Beneficiary( + String id, + String fullName, + String bankCode, + String bankName +) {} + diff --git a/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/PaymentMethod.java b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/PaymentMethod.java new file mode 100644 index 0000000..fc18597 --- /dev/null +++ b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/PaymentMethod.java @@ -0,0 +1,16 @@ +package com.microsoft.openai.samples.assistant.business.models; + + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record PaymentMethod( + String id, + String type, + String activationDate, + String expirationDate, + String availableBalance, + // card number is valued only for credit card type + String cardNumber +) {} + diff --git a/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/PaymentMethodSummary.java b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/PaymentMethodSummary.java new file mode 100644 index 0000000..c2c1a35 --- /dev/null +++ b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/PaymentMethodSummary.java @@ -0,0 +1,10 @@ +package com.microsoft.openai.samples.assistant.business.models; + + +public record PaymentMethodSummary( + String id, + String type, + String activationDate, + String expirationDate +) {} + diff --git a/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/service/AccountService.java b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/service/AccountService.java new file mode 100644 index 0000000..9ff1aea --- /dev/null +++ b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/service/AccountService.java @@ -0,0 +1,96 @@ +package com.microsoft.openai.samples.assistant.business.service; + +import com.microsoft.openai.samples.assistant.business.models.Account; +import com.microsoft.openai.samples.assistant.business.models.PaymentMethod; +import com.microsoft.openai.samples.assistant.business.models.PaymentMethodSummary; +import com.microsoft.openai.samples.assistant.business.models.Beneficiary; + +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class AccountService { + + private final Map accounts; + private final Map paymentMethods; + + public AccountService() { + this.accounts = new HashMap<>(); + this.paymentMethods = new HashMap<>(); + // Fill the map with dummy data + this.accounts.put("1000", new Account( + "1000", + "alice.user@microsoft.com", + "Alice User", + "USD", + "2022-01-01", + "1000.00", + Arrays.asList(new PaymentMethodSummary("12345", "Visa", "2022-01-01", "2025-01-01"), + new PaymentMethodSummary("23456", "BankTransfer", "2022-01-01", "9999-01-01")))); + this.accounts.put("1010", new Account( + "1010", + "bob.user@microsoft.com", + "Bob User", + "EUR", + "2022-01-01", + "2000,40", + Arrays.asList(new PaymentMethodSummary("345678", "BankTransfer", "2022-01-01", "9999-01-01"), + new PaymentMethodSummary("55555", "Visa", "2022-01-01", "2025-01-01")))); + this.accounts.put("1020", new Account( + "1020", + "charlie.user@microsoft.com", + "Charlie User", + "EUR", + "2022-01-01", + "3000,20", + Arrays.asList(new PaymentMethodSummary("46748576", "DirectDebit", "2022-02-01", "9999-02-01")))); + + this.paymentMethods.put("12345", new PaymentMethod("12345", "Visa", "2022-01-01", "2025-01-01", "500.00", "1234567812345678")); + this.paymentMethods.put("55555", new PaymentMethod("55555", "Visa", "2024-01-01", "2028-01-01", "350.00", "637362551913266")); + this.paymentMethods.put("23456", new PaymentMethod("23456", "BankTransfer", "2022-01-01", "9999-01-01", "5000.00", null)); + this.paymentMethods.put("345678", new PaymentMethod("345678", "BankTransfer", "2022-01-01", "9999-01-01", "10000.00", null)); + } + + public Account getAccountDetails(String accountId) { + if (accountId == null || accountId.isEmpty()) + throw new IllegalArgumentException("AccountId is empty or null"); + try { + Integer.parseInt(accountId); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("AccountId is not a valid number"); + } + // Return account data from the map + return this.accounts.get(accountId); + } + + public PaymentMethod getPaymentMethodDetails(String paymentMethodId) { + if (paymentMethodId == null || paymentMethodId.isEmpty()) + throw new IllegalArgumentException("AccountId is empty or null"); + try { + Integer.parseInt(paymentMethodId); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("AccountId is not a valid number"); + } + // Return account data from the map + return this.paymentMethods.get(paymentMethodId); + } + + public List getRegisteredBeneficiary(String accountId) { + if (accountId == null || accountId.isEmpty()) + throw new IllegalArgumentException("AccountId is empty or null"); + try { + Integer.parseInt(accountId); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("AccountId is not a valid number"); + } + // Return dummy list of beneficiaries + return Arrays.asList( + new Beneficiary("1", "Mike ThePlumber", "123456789", "Intesa Sanpaolo"), + new Beneficiary("2", "Jane TheElectrician", "987654321", "UBS") + ); + } +} \ No newline at end of file diff --git a/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/service/UserService.java b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/service/UserService.java new file mode 100644 index 0000000..a9eb06a --- /dev/null +++ b/app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/service/UserService.java @@ -0,0 +1,59 @@ +package com.microsoft.openai.samples.assistant.business.service; + +import com.microsoft.openai.samples.assistant.business.models.Account; +import org.springframework.stereotype.Service; + +import java.util.*; + + +@Service +public class UserService { + + + private Map accounts = new HashMap<>(); + + public UserService() { + accounts.put( + "alice.user@microsoft.com", + new Account( + "1000", + "alice.user@microsoft.com", + "Alice User", + "USD", + "2022-01-01", + "1000.00", + null + ) + ); + accounts.put( + "bob.user@microsoft.com", + new Account( + "1010", + "bob.user@microsoft.com", + "Bob User", + "EUR", + "2022-01-01", + "2000,40", + null + ) + ); + accounts.put( + "charlie.user@microsoft.com", + new Account( + "1020", + "charlie.user@microsoft.com", + "Charlie User", + "EUR", + "2022-01-01", + "3000,20", + null + ) + ); + + } + public List getAccountsByUserName(String userName) { + return Arrays.asList(accounts.get(userName)); + } + + +} diff --git a/app/business-api/account/src/main/resources/account.yaml b/app/business-api/account/src/main/resources/account.yaml new file mode 100644 index 0000000..022bf7d --- /dev/null +++ b/app/business-api/account/src/main/resources/account.yaml @@ -0,0 +1,174 @@ +openapi: 3.0.3 +info: + title: Account API + version: 1.0.0 +paths: + /users/{user_name}/accounts: + get: + summary: Get the list of all accounts for a specific user + description: Get the list of all accounts for a specific user + operationId: getAccountsByUserId + parameters: + - name: user_name + description: userName once the user has logged. + in: path + required: true + schema: + type: string + responses: + '200': + description: A list of accounts + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Account' + /accounts/{accountid}: + get: + summary: Get account details and available payment methods + description: Get account details + operationId: getAccountDetails + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: string + responses: + '200': + description: Account details + content: + application/json: + schema: + type: object + items: + $ref: '#/components/schemas/Account' + /accounts/{accountid}/paymentmethods/{methodid}: + get: + summary: Get payment method detail with available balance + description: Get payment method detail with available balance + operationId: getPaymentMethodDetails + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: string + - name: methodId + description: id of specific payment method available for the account id. + in: path + required: true + schema: + type: string + responses: + '200': + description: Payment method details + content: + application/json: + schema: + type: object + items: + $ref: '#/components/schemas/PaymentMethod' + /accounts/{accountid}/registeredBeneficiaries: + get: + summary: Get list of registered beneficiaries for a specific account + description: Get list of registered beneficiaries for a specific account + operationId: getBeneficiaryMethodDetails + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: string + responses: + '200': + description: Payment method details + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Beneficiary' +components: + schemas: + Account: + type: object + properties: + id: + type: string + description: The unique identifier for the account + userName: + type: string + description: The unique identifier for the user + accountHolderFullName: + type: string + description: The full name of the account holder + currency: + type: string + description: The currency of the account + activationDate: + type: string + description: The date when the account was activated + balance: + type: string + description: The current balance of the account + paymentMethods: + type: array + items: + $ref: '#/components/schemas/PaymentMethod' + description: The list of payment methods associated with the account + PaymentMethodSummary: + type: object + properties: + id: + type: string + description: The unique identifier for the payment method + type: + type: string + description: The type of the payment method + activationDate: + type: string + description: The date when the payment method was activated + expirationDate: + type: string + description: The date when the payment method will expire + PaymentMethod: + type: object + properties: + id: + type: string + description: The unique identifier for the payment method + type: + type: string + description: The type of the payment method + activationDate: + type: string + description: The date when the payment method was activated + expirationDate: + type: string + description: The date when the payment method will expire + availableBalance: + type: string + description: The available balance of the payment method + cardNumber: + type: string + description: The card number of the payment method + Beneficiary: + type: object + properties: + id: + type: string + description: The unique identifier for the beneficiary + fullName: + type: string + description: The full name of the beneficiary + bankCode: + type: string + description: The bank code of the beneficiary's bank + bankName: + type: string + description: The name of the beneficiary's bank \ No newline at end of file diff --git a/app/business-api/account/src/main/resources/application-dev.properties b/app/business-api/account/src/main/resources/application-dev.properties new file mode 100644 index 0000000..49b37f1 --- /dev/null +++ b/app/business-api/account/src/main/resources/application-dev.properties @@ -0,0 +1 @@ +server.port=8070 diff --git a/app/business-api/payment/.mvn/wrapper/maven-wrapper.properties b/app/business-api/payment/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..23c7e59 --- /dev/null +++ b/app/business-api/payment/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/app/business-api/payment/Dockerfile b/app/business-api/payment/Dockerfile new file mode 100644 index 0000000..f4d22c0 --- /dev/null +++ b/app/business-api/payment/Dockerfile @@ -0,0 +1,30 @@ +FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build + +WORKDIR /workspace/app +EXPOSE 3100 + +COPY mvnw . +COPY .mvn .mvn +COPY pom.xml . +COPY src src + +RUN chmod +x ./mvnw +# Convert CRLF to LF +RUN sed -i 's/\r$//' ./mvnw +RUN ./mvnw package -DskipTests +RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) + +FROM mcr.microsoft.com/openjdk/jdk:21-mariner + +ARG DEPENDENCY=/workspace/app/target/dependency +COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib +COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF +COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app + + +RUN curl -LJ -o /app/applicationinsights-agent-3.5.2.jar https://github.com/microsoft/ApplicationInsights-Java/releases/download/3.5.2/applicationinsights-agent-3.5.2.jar +COPY applicationinsights.json /app + +EXPOSE 8080 + +ENTRYPOINT ["java","-javaagent:/app/applicationinsights-agent-3.5.2.jar","-noverify", "-XX:MaxRAMPercentage=70", "-XX:+UseParallelGC", "-XX:ActiveProcessorCount=2", "-cp","app:app/lib/*","com.microsoft.openai.samples.assistant.business.PaymentApplication"] \ No newline at end of file diff --git a/app/business-api/payment/applicationinsights.json b/app/business-api/payment/applicationinsights.json new file mode 100644 index 0000000..0c10530 --- /dev/null +++ b/app/business-api/payment/applicationinsights.json @@ -0,0 +1,5 @@ +{ + "role": { + "name": "payments-api" + } + } \ No newline at end of file diff --git a/app/business-api/payment/mvnw b/app/business-api/payment/mvnw new file mode 100644 index 0000000..19529dd --- /dev/null +++ b/app/business-api/payment/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/app/business-api/payment/mvnw.cmd b/app/business-api/payment/mvnw.cmd new file mode 100644 index 0000000..b150b91 --- /dev/null +++ b/app/business-api/payment/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/app/business-api/payment/pom.xml b/app/business-api/payment/pom.xml new file mode 100644 index 0000000..a0f93ef --- /dev/null +++ b/app/business-api/payment/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.13 + + + com.microsoft.openai.samples.assistant.business + payment + 1.0.0-SNAPSHOT + + + + 17 + 17 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-webflux + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/Application.java b/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/PaymentApplication.java similarity index 64% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/Application.java rename to app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/PaymentApplication.java index 30b5ec7..6b78d12 100644 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/Application.java +++ b/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/PaymentApplication.java @@ -1,5 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -package com.microsoft.openai.samples.assistant; +package com.microsoft.openai.samples.assistant.business; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -7,14 +7,14 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class Application { +public class PaymentApplication { - private static final Logger LOG = LoggerFactory.getLogger(Application.class); + private static final Logger LOG = LoggerFactory.getLogger(PaymentApplication.class); public static void main(String[] args) { LOG.info( "Application profile from system property is [{}]", System.getProperty("spring.profiles.active")); - new SpringApplication(Application.class).run(args); + new SpringApplication(PaymentApplication.class).run(args); } } diff --git a/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/controller/PaymentsController.java b/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/controller/PaymentsController.java new file mode 100644 index 0000000..a9a87a2 --- /dev/null +++ b/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/controller/PaymentsController.java @@ -0,0 +1,28 @@ +package com.microsoft.openai.samples.assistant.business.controller; + +import com.microsoft.openai.samples.assistant.business.models.Payment; +import com.microsoft.openai.samples.assistant.business.service.PaymentService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PaymentsController { + + private static final Logger logger = LoggerFactory.getLogger(PaymentsController.class); + + private final PaymentService paymentService; + + public PaymentsController(PaymentService paymentService) { + this.paymentService = paymentService; + } + + @PostMapping("/payments") + public void submitPayment(@RequestBody Payment payment) { + logger.info("Received payment request: {}", payment); + paymentService.processPayment(payment); + } +} \ No newline at end of file diff --git a/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/models/Payment.java b/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/models/Payment.java new file mode 100644 index 0000000..38727cd --- /dev/null +++ b/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/models/Payment.java @@ -0,0 +1,13 @@ +package com.microsoft.openai.samples.assistant.business.models; + + +public record Payment( + String description, + String recipientName, + String recipientBankCode, + String accountId, + String paymentMethodId, + String paymentType, + String amount, + String timestamp +) {} \ No newline at end of file diff --git a/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/models/Transaction.java b/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/models/Transaction.java new file mode 100644 index 0000000..33cfa33 --- /dev/null +++ b/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/models/Transaction.java @@ -0,0 +1,16 @@ +package com.microsoft.openai.samples.assistant.business.models; + + +public record Transaction( + String id, + String description, + //income/outcome + String type, + + String recipientName, + String recipientBankReference, + String accountId, + String paymentType, + String amount, + String timestamp +) {} \ No newline at end of file diff --git a/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/service/PaymentService.java b/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/service/PaymentService.java new file mode 100644 index 0000000..8f35be7 --- /dev/null +++ b/app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/service/PaymentService.java @@ -0,0 +1,82 @@ +package com.microsoft.openai.samples.assistant.business.service; + +import com.microsoft.openai.samples.assistant.business.models.Payment; +import com.microsoft.openai.samples.assistant.business.models.Transaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; + +import java.util.UUID; + +@Service +public class PaymentService { + + private static final Logger logger = LoggerFactory.getLogger(PaymentService.class); + + private final WebClient.Builder webClientBuilder; + private final String transactionAPIUrl; + + public PaymentService(WebClient.Builder webClientBuilder, @Value("${transactions.api.url}") String transactionAPIUrl) { + this.webClientBuilder = webClientBuilder; + this.transactionAPIUrl = transactionAPIUrl; + } + + public void processPayment(Payment payment) { + + if (payment.accountId() == null || payment.accountId().isEmpty()) + throw new IllegalArgumentException("AccountId is empty or null"); + try { + Integer.parseInt(payment.accountId()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("AccountId is not a valid number"); + } + + if (payment.paymentMethodId() == null || payment.paymentMethodId().isEmpty()) + throw new IllegalArgumentException("paymentMethodId is empty or null"); + + try { + Integer.parseInt(payment.paymentMethodId()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("paymentMethodId is not a valid number"); + } + + + // Log the payment details + logger.info("Payment successful for: {}", payment.toString()); + + // Convert the Payment object into a Transaction object + Transaction transaction = convertPaymentToTransaction(payment); + + /** + * Make the POST request. The transaction is sent to the transaction API. In a real scenario this would be an event published to a hub and consumed by the transaction API. + */ + + logger.info("Notifying payment [{}] for account[{}]..", payment.description() , transaction.accountId()); + webClientBuilder.build() + .post() + .uri(transactionAPIUrl + "/transactions/{accountId}", payment.accountId()) + .contentType(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(transaction)) + .retrieve() + .bodyToMono(String.class) + .subscribe(response -> logger.info("Transaction notified for: {}", transaction.toString())); + } + + private Transaction convertPaymentToTransaction(Payment payment) { + return new Transaction( + UUID.randomUUID().toString(), + payment.description(), + "outcome", + payment.recipientName(), + payment.recipientBankCode(), + payment.accountId(), + payment.paymentType(), + payment.amount(), + payment.timestamp() + ); + } +} \ No newline at end of file diff --git a/app/business-api/payment/src/main/resources/application-dev.properties b/app/business-api/payment/src/main/resources/application-dev.properties new file mode 100644 index 0000000..8e6402c --- /dev/null +++ b/app/business-api/payment/src/main/resources/application-dev.properties @@ -0,0 +1,3 @@ +server.port=8060 + +logging.level.org.springframework.web=DEBUG \ No newline at end of file diff --git a/app/business-api/payment/src/main/resources/application.properties b/app/business-api/payment/src/main/resources/application.properties new file mode 100644 index 0000000..74181ab --- /dev/null +++ b/app/business-api/payment/src/main/resources/application.properties @@ -0,0 +1 @@ +transactions.api.url=${TRANSACTIONS_API_SERVER_URL} \ No newline at end of file diff --git a/app/business-api/payment/src/main/resources/payments.yaml b/app/business-api/payment/src/main/resources/payments.yaml new file mode 100644 index 0000000..7859580 --- /dev/null +++ b/app/business-api/payment/src/main/resources/payments.yaml @@ -0,0 +1,59 @@ +openapi: 3.0.3 +info: + title: Payment API + version: 1.0.0 +paths: + /payments: + post: + operationId: submitPayment + summary: Submit a payment request + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Payment' + responses: + '201': + description: Payment request submitted successfully + '400': + description: Invalid request body + '500': + description: Internal server error +components: + schemas: + Payment: + type: object + properties: + description: + type: string + description: Description of the payment + recipientName: + type: string + description: Name of the recipient + recipientId: + type: string + description: ID of the recipient + recipientBankCode: + type: string + description: Bank code of the recipient + accountId: + type: string + description: ID of the account + paymentMethodId: + type: string + description: ID of the payment method + amount: + type: string + description: Amount of the payment + timestamp: + type: string + description: Timestamp of the payment + required: + - description + - recipientName + - recipientBankCode + - accountId + - paymentMethodId + - amount + - timestamp \ No newline at end of file diff --git a/app/business-api/payment/src/main/resources/transaction-history.yaml b/app/business-api/payment/src/main/resources/transaction-history.yaml new file mode 100644 index 0000000..a6aef1a --- /dev/null +++ b/app/business-api/payment/src/main/resources/transaction-history.yaml @@ -0,0 +1,91 @@ +openapi: 3.0.3 +info: + title: Transaction History and Reporting API + version: 1.0.0 +paths: + /transactions/{accountid}: + get: + summary: Get transactions list. + description: Gets the transactions lists. They can be filtered based on recipient name + operationId: getTransactionsByRecipientName + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: string + - name: recipient_name + description: Name of the payee, recipient + in: query + required: false + schema: + type: string + + responses: + '200': + description: A list of transactions for a specific recipient + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PaymentTransaction' + post: + operationId: notifyTransaction + summary: Notify the banking transaction so that it's being stored in the history + description: Notify the banking transaction so that it's being stored in the history + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: string + requestBody: + required: true + description: transaction to notify + content: + application/json: + schema: + $ref: '#/components/schemas/Payment' + responses: + '200': + description: Payment request submitted successfully + '400': + description: Invalid request body + '500': + description: Internal server error +components: + schemas: + Transaction: + type: object + properties: + id: + type: string + description: 'The unique identifier for the transaction' + description: + type: string + description: 'The description of the transaction which contains reason for the payment and other details' + type: + type: string + description: 'The transaction type expressed as income or outcome transaction' + recipientName: + type: string + description: 'The name of the recipient' + recipientBankReference: + type: string + description: 'The bank reference of the recipient' + accountId: + type: string + description: 'The account ID associated with the transaction' + paymentType: + type: string + description: 'The type of payment creditcard, banktransfer, directdebit' + amount: + type: string + description: 'The amount of the transaction' + timestamp: + type: string + format: date-time + description: 'The timestamp of the transaction' \ No newline at end of file diff --git a/app/business-api/transactions-history/.mvn/wrapper/maven-wrapper.properties b/app/business-api/transactions-history/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..23c7e59 --- /dev/null +++ b/app/business-api/transactions-history/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/app/business-api/transactions-history/Dockerfile b/app/business-api/transactions-history/Dockerfile new file mode 100644 index 0000000..3cf6521 --- /dev/null +++ b/app/business-api/transactions-history/Dockerfile @@ -0,0 +1,30 @@ +FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build + +WORKDIR /workspace/app +EXPOSE 3100 + +COPY mvnw . +COPY .mvn .mvn +COPY pom.xml . +COPY src src + +RUN chmod +x ./mvnw +# Convert CRLF to LF +RUN sed -i 's/\r$//' ./mvnw +RUN ./mvnw package -DskipTests +RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) + +FROM mcr.microsoft.com/openjdk/jdk:21-mariner + +ARG DEPENDENCY=/workspace/app/target/dependency +COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib +COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF +COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app + + +RUN curl -LJ -o /app/applicationinsights-agent-3.5.2.jar https://github.com/microsoft/ApplicationInsights-Java/releases/download/3.5.2/applicationinsights-agent-3.5.2.jar +COPY applicationinsights.json /app + +EXPOSE 8080 + +ENTRYPOINT ["java","-javaagent:/app/applicationinsights-agent-3.5.2.jar","-noverify", "-XX:MaxRAMPercentage=70", "-XX:+UseParallelGC", "-XX:ActiveProcessorCount=2", "-cp","app:app/lib/*","com.microsoft.openai.samples.assistant.business.TransactionsHistoryApplication"] \ No newline at end of file diff --git a/app/business-api/transactions-history/applicationinsights.json b/app/business-api/transactions-history/applicationinsights.json new file mode 100644 index 0000000..db7aa10 --- /dev/null +++ b/app/business-api/transactions-history/applicationinsights.json @@ -0,0 +1,5 @@ +{ + "role": { + "name": "transactions-api" + } + } \ No newline at end of file diff --git a/app/business-api/transactions-history/mvnw b/app/business-api/transactions-history/mvnw new file mode 100644 index 0000000..19529dd --- /dev/null +++ b/app/business-api/transactions-history/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/app/business-api/transactions-history/mvnw.cmd b/app/business-api/transactions-history/mvnw.cmd new file mode 100644 index 0000000..b150b91 --- /dev/null +++ b/app/business-api/transactions-history/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/app/business-api/transactions-history/pom.xml b/app/business-api/transactions-history/pom.xml new file mode 100644 index 0000000..31c32d9 --- /dev/null +++ b/app/business-api/transactions-history/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.13 + + + com.microsoft.openai.samples.assistant.business + transactions-history + 1.0.0-SNAPSHOT + + + + 17 + 17 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/Transaction.java b/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/Transaction.java new file mode 100644 index 0000000..93d5246 --- /dev/null +++ b/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/Transaction.java @@ -0,0 +1,16 @@ +package com.microsoft.openai.samples.assistant.business; + + +public record Transaction( + String id, + String description, + //income/outcome + String type, + + String recipientName, + String recipientBankReference, + String accountId, + String paymentType, + String amount, + String timestamp +) {} \ No newline at end of file diff --git a/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/TransactionController.java b/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/TransactionController.java new file mode 100644 index 0000000..2bc8162 --- /dev/null +++ b/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/TransactionController.java @@ -0,0 +1,36 @@ +package com.microsoft.openai.samples.assistant.business; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/transactions") +public class TransactionController { + + private final TransactionService transactionService; + private static final Logger logger = LoggerFactory.getLogger(TransactionController.class); + public TransactionController(TransactionService transactionService) { + this.transactionService = transactionService; + } + + @GetMapping("/{accountId}") + public List getTransactions(@PathVariable String accountId, @RequestParam(name = "recipient_name", required = false) String recipientName){ + logger.info("Received request to get transactions for accountid[{}]. Recipient filter is[{}]",accountId,recipientName); + if(recipientName != null && !recipientName.isEmpty()){ + return transactionService.getTransactionsByRecipientName(accountId, recipientName); + } + else + return transactionService.getlastTransactions(accountId); + } + + @PostMapping("/{accountId}") + public void notifyTransaction(@PathVariable String accountId, @RequestBody Transaction transaction){ + logger.info("Received request to notify transaction for accountid[{}]. {}", accountId,transaction); + transactionService.notifyTransaction(accountId, transaction); + } + + +} \ No newline at end of file diff --git a/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/TransactionService.java b/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/TransactionService.java new file mode 100644 index 0000000..b46a187 --- /dev/null +++ b/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/TransactionService.java @@ -0,0 +1,96 @@ +package com.microsoft.openai.samples.assistant.business; + +import com.microsoft.openai.samples.assistant.business.Transaction; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class TransactionService { + + private Map> lastTransactions= new HashMap<>(); + private Map> allTransactions= new HashMap<>(); + + public TransactionService(){ + + lastTransactions.put("1010",new ArrayList<> (Arrays.asList( + new Transaction("11", "Payment of the bill 334398", "outcome","Mike ThePlumber", "0001", "1010", "BankTransfer", "100.00", "2024-4-01T12:00:00Z"), + new Transaction("22", "Payment of the bill 4613","outcome", "Jane TheElectrician", "0002", "1010", "CreditCard", "200.00", "2024-3-02T12:00:00Z"), + new Transaction("33", "Payment of the bill 724563","outcome", "Bob TheCarpenter", "0003", "1010", "BankTransfer", "300.00", "2023-10-03T12:00:00Z"), + new Transaction("43", "Payment of the bill 8898943","outcome", "Alice ThePainter", "0004", "1010", "DirectDebit", "400.00", "2023-8-04T12:00:00Z"), + new Transaction("53", "Payment of the bill 19dee","outcome", "Charlie TheMechanic", "0005", "1010", "BankTransfer", "500.00", "2023-4-05T12:00:00Z")) + )); + + + allTransactions.put("1010",new ArrayList<>(Arrays.asList( + new Transaction("11", "payment of bill id with 0001","outcome", "Mike ThePlumber", "A012TABTYT156!", "1010", "BankTransfer", "100.00", "2024-4-01T12:00:00Z"), + new Transaction("21", "Payment of the bill 4200","outcome", "Mike ThePlumber", "0002", "1010", "BankTransfer", "200.00", "2024-1-02T12:00:00Z"), + new Transaction("31", "Payment of the bill 3743","outcome", "Mike ThePlumber", "0003", "1010", "DirectDebit", "300.00", "2023-10-03T12:00:00Z"), + new Transaction("41", "Payment of the bill 8921","outcome", "Mike ThePlumber", "0004", "1010", "Transfer", "400.00", "2023-8-04T12:00:00Z"), + new Transaction("51", "Payment of the bill 7666","outcome", "Mike ThePlumber", "0005", "1010", "CreditCard", "500.00", "2023-4-05T12:00:00Z"), + + new Transaction("12", "Payment of the bill 5517","outcome", "Jane TheElectrician", "0001", "1010", "CreditCard", "100.00", "2024-3-01T12:00:00Z"), + new Transaction("22", "Payment of the bill 682222","outcome", "Jane TheElectrician", "0002", "1010", "CreditCard", "200.00", "2023-1-02T12:00:00Z"), + new Transaction("32", "Payment of the bill 94112","outcome", "Jane TheElectrician", "0003", "1010", "Transfer", "300.00", "2022-10-03T12:00:00Z"), + new Transaction("42", "Payment of the bill 23122","outcome", "Jane TheElectrician", "0004", "1010", "Transfer", "400.00", "2022-8-04T12:00:00Z"), + new Transaction("52", "Payment of the bill 171443","outcome", "Jane TheElectrician", "0005", "1010", "Transfer", "500.00", "2020-4-05T12:00:00Z") + ))); + + + + } + public List getTransactionsByRecipientName(String accountId, String name) { + + if (accountId == null || accountId.isEmpty()) + throw new IllegalArgumentException("AccountId is empty or null"); + try { + Integer.parseInt(accountId); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("AccountId is not a valid number"); + } + + if ( allTransactions.get(accountId) == null) return new ArrayList<>(); + else + return allTransactions.get(accountId).stream() + .filter(transaction -> transaction.recipientName().toLowerCase().contains(name.toLowerCase())) + .collect(Collectors.toList()); + + } + + public List getlastTransactions(String accountId) { + if (accountId == null || accountId.isEmpty()) + throw new IllegalArgumentException("AccountId is empty or null"); + try { + Integer.parseInt(accountId); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("AccountId is not a valid number"); + } + + if ( lastTransactions.get(accountId) == null) return new ArrayList<>(); + else + return lastTransactions.get(accountId); + } + + public void notifyTransaction(String accountId,Transaction transaction){ + if (accountId == null || accountId.isEmpty()) + throw new IllegalArgumentException("AccountId is empty or null"); + try { + Integer.parseInt(accountId); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("AccountId is not a valid number"); + } + + var transactionsList = allTransactions.get(accountId); + if ( transactionsList == null) + throw new RuntimeException("Cannot find all transactions for account id: "+accountId); + transactionsList.add(transaction); + + var lastTransactionsList = lastTransactions.get(accountId); + if ( lastTransactionsList == null) + throw new RuntimeException("Cannot find last transactions for account id: "+accountId); + lastTransactionsList.add(transaction); + + + } +} diff --git a/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/TransactionsHistoryApplication.java b/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/TransactionsHistoryApplication.java new file mode 100644 index 0000000..5d1c4c9 --- /dev/null +++ b/app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/TransactionsHistoryApplication.java @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.openai.samples.assistant.business; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TransactionsHistoryApplication { + + private static final Logger LOG = LoggerFactory.getLogger(TransactionsHistoryApplication.class); + + public static void main(String[] args) { + LOG.info( + "Application profile from system property is [{}]", + System.getProperty("spring.profiles.active")); + new SpringApplication(TransactionsHistoryApplication.class).run(args); + } +} diff --git a/app/business-api/transactions-history/src/main/resources/application-dev.properties b/app/business-api/transactions-history/src/main/resources/application-dev.properties new file mode 100644 index 0000000..9aa5480 --- /dev/null +++ b/app/business-api/transactions-history/src/main/resources/application-dev.properties @@ -0,0 +1,3 @@ +server.port=8090 + +logging.level.org.springframework.web=DEBUG \ No newline at end of file diff --git a/app/business-api/transactions-history/src/main/resources/transaction-history.yaml b/app/business-api/transactions-history/src/main/resources/transaction-history.yaml new file mode 100644 index 0000000..a6aef1a --- /dev/null +++ b/app/business-api/transactions-history/src/main/resources/transaction-history.yaml @@ -0,0 +1,91 @@ +openapi: 3.0.3 +info: + title: Transaction History and Reporting API + version: 1.0.0 +paths: + /transactions/{accountid}: + get: + summary: Get transactions list. + description: Gets the transactions lists. They can be filtered based on recipient name + operationId: getTransactionsByRecipientName + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: string + - name: recipient_name + description: Name of the payee, recipient + in: query + required: false + schema: + type: string + + responses: + '200': + description: A list of transactions for a specific recipient + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PaymentTransaction' + post: + operationId: notifyTransaction + summary: Notify the banking transaction so that it's being stored in the history + description: Notify the banking transaction so that it's being stored in the history + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: string + requestBody: + required: true + description: transaction to notify + content: + application/json: + schema: + $ref: '#/components/schemas/Payment' + responses: + '200': + description: Payment request submitted successfully + '400': + description: Invalid request body + '500': + description: Internal server error +components: + schemas: + Transaction: + type: object + properties: + id: + type: string + description: 'The unique identifier for the transaction' + description: + type: string + description: 'The description of the transaction which contains reason for the payment and other details' + type: + type: string + description: 'The transaction type expressed as income or outcome transaction' + recipientName: + type: string + description: 'The name of the recipient' + recipientBankReference: + type: string + description: 'The bank reference of the recipient' + accountId: + type: string + description: 'The account ID associated with the transaction' + paymentType: + type: string + description: 'The type of payment creditcard, banktransfer, directdebit' + amount: + type: string + description: 'The amount of the transaction' + timestamp: + type: string + format: date-time + description: 'The timestamp of the transaction' \ No newline at end of file diff --git a/app/backend/.mvn/jvm.config b/app/copilot/.mvn/jvm.config similarity index 100% rename from app/backend/.mvn/jvm.config rename to app/copilot/.mvn/jvm.config diff --git a/app/copilot/.mvn/wrapper/maven-wrapper.properties b/app/copilot/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..23c7e59 --- /dev/null +++ b/app/copilot/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/app/copilot/Dockerfile b/app/copilot/Dockerfile new file mode 100644 index 0000000..598f6ec --- /dev/null +++ b/app/copilot/Dockerfile @@ -0,0 +1,31 @@ +FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build + +WORKDIR /workspace/app +EXPOSE 3100 + +COPY mvnw . +COPY .mvn .mvn +COPY pom.xml . +COPY copilot-backend copilot-backend +COPY semantickernel-openapi-plugin semantickernel-openapi-plugin + +RUN chmod +x ./mvnw +# Convert CRLF to LF +RUN sed -i 's/\r$//' ./mvnw +RUN ./mvnw package -DskipTests +RUN mkdir -p copilot-backend/target/dependency && (cd copilot-backend/target/dependency; jar -xf ../*.jar) + +FROM mcr.microsoft.com/openjdk/jdk:21-mariner + +ARG DEPENDENCY=/workspace/app/copilot-backend/target/dependency/ +COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib +COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF +COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app + + +RUN curl -LJ -o /app/applicationinsights-agent-3.5.2.jar https://github.com/microsoft/ApplicationInsights-Java/releases/download/3.5.2/applicationinsights-agent-3.5.2.jar +COPY applicationinsights.json /app + +EXPOSE 8080 + +ENTRYPOINT ["java","-javaagent:/app/applicationinsights-agent-3.5.2.jar","-noverify", "-XX:MaxRAMPercentage=70", "-XX:+UseParallelGC", "-XX:ActiveProcessorCount=2", "-cp","app:app/lib/*","com.microsoft.openai.samples.assistant.CopilotApplication"] \ No newline at end of file diff --git a/app/copilot/applicationinsights.json b/app/copilot/applicationinsights.json new file mode 100644 index 0000000..6d49c08 --- /dev/null +++ b/app/copilot/applicationinsights.json @@ -0,0 +1,5 @@ +{ + "role": { + "name": "copilot-api" + } + } \ No newline at end of file diff --git a/app/copilot/copilot-backend/manifests/azd-env-configmap.yml b/app/copilot/copilot-backend/manifests/azd-env-configmap.yml new file mode 100644 index 0000000..a46b28d --- /dev/null +++ b/app/copilot/copilot-backend/manifests/azd-env-configmap.yml @@ -0,0 +1,45 @@ +# Updated 2024-04-18 16:01:23 +apiVersion: v1 +kind: ConfigMap +metadata: + name: azd-env-configmap +data: + API_ALLOW_ORIGINS: "" + APPLICATIONINSIGHTS_CONNECTION_STRING: "InstrumentationKey=6fa478c2-3ea8-451e-8350-acafdaf6264a;IngestionEndpoint=https://francecentral-1.in.applicationinsights.azure.com/;LiveEndpoint=https://francecentral.livediagnostics.monitor.azure.com/;ApplicationId=cc2958e9-c1f3-4fc8-910f-49d81979c208" + AZURE_AKS_CLUSTER_NAME: "aks-yvh3rjtnvwvvm" + AZURE_CLIENT_ID: "19ccc9b6-37b0-47ce-b99e-efddf02bef26" + AZURE_CONTAINER_REGISTRY_ENDPOINT: "cryvh3rjtnvwvvm.azurecr.io" + AZURE_CONTAINER_REGISTRY_NAME: "cryvh3rjtnvwvvm" + AZURE_ENV_NAME: "java-chat-12-aks-test" + AZURE_FORMRECOGNIZER_RESOURCE_GROUP: "rg-java-chat-12-aks-test" + AZURE_FORMRECOGNIZER_SERVICE: "cog-fr-yvh3rjtnvwvvm" + AZURE_KEY_VAULT_ENDPOINT: "https://kv-yvh3rjtnvwvvm.vault.azure.net/" + AZURE_LOCATION: "francecentral" + AZURE_OPENAI_CHATGPT_DEPLOYMENT: "chat" + AZURE_OPENAI_CHATGPT_MODEL: "gpt-35-turbo" + AZURE_OPENAI_EMB_DEPLOYMENT: "embedding" + AZURE_OPENAI_EMB_MODEL_NAME: "text-embedding-ada-002" + AZURE_OPENAI_RESOURCE_GROUP: "rg-java-chat-12-aks-test" + AZURE_OPENAI_SERVICE: "cog-yvh3rjtnvwvvm" + AZURE_RESOURCE_GROUP: "rg-java-chat-12-aks-test" + AZURE_SEARCH_INDEX: "gptkbindex" + AZURE_SEARCH_SERVICE: "gptkb-yvh3rjtnvwvvm" + AZURE_SEARCH_SERVICE_RESOURCE_GROUP: "rg-java-chat-12-aks-test" + AZURE_SERVICEBUS_NAMESPACE: "sb-yvh3rjtnvwvvm" + AZURE_SERVICEBUS_SKU_NAME: "Standard" + AZURE_STORAGE_ACCOUNT: "styvh3rjtnvwvvm" + AZURE_STORAGE_CONTAINER: "content" + AZURE_STORAGE_RESOURCE_GROUP: "rg-java-chat-12-aks-test" + AZURE_SUBSCRIPTION_ID: "8b82fc4d-aabe-4658-88f2-5674bf49eec0" + AZURE_TENANT_ID: "fa3408a7-4997-49b5-bda2-62886815fa49" + OPENAI_API_KEY: "" + OPENAI_HOST: "azure" + OPENAI_ORGANIZATION: "" + REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING: "InstrumentationKey=6fa478c2-3ea8-451e-8350-acafdaf6264a;IngestionEndpoint=https://francecentral-1.in.applicationinsights.azure.com/;LiveEndpoint=https://francecentral.livediagnostics.monitor.azure.com/;ApplicationId=cc2958e9-c1f3-4fc8-910f-49d81979c208" + SERVICE_API_ENDPOINT_URL: "http://68.220.199.26" + SERVICE_API_IMAGE_NAME: "crnq6gqbtyxqno2.azurecr.io/azure-search-openai-demo/api-java-chat-12-aks-test:azd-deploy-1710941647" + SERVICE_FRONTEND_ENDPOINT_URL: "http://10.0.118.173:80" + SERVICE_FRONTEND_IMAGE_NAME: "crnq6gqbtyxqno2.azurecr.io/azure-search-openai-demo/frontend-java-chat-12-aks-test:azd-deploy-1710941650" + SERVICE_INDEXER_ENDPOINT_URL: "http://10.0.165.214:80" + SERVICE_INDEXER_IMAGE_NAME: "crnq6gqbtyxqno2.azurecr.io/azure-search-openai-demo/indexer-java-chat-12-aks-test:azd-deploy-1710941756" + diff --git a/app/backend/manifests/backend-deployment.tmpl.yml b/app/copilot/copilot-backend/manifests/backend-deployment.tmpl.yml similarity index 100% rename from app/backend/manifests/backend-deployment.tmpl.yml rename to app/copilot/copilot-backend/manifests/backend-deployment.tmpl.yml diff --git a/app/backend/manifests/backend-service.yml b/app/copilot/copilot-backend/manifests/backend-service.yml similarity index 100% rename from app/backend/manifests/backend-service.yml rename to app/copilot/copilot-backend/manifests/backend-service.yml diff --git a/app/backend/manifests/ingress.yml b/app/copilot/copilot-backend/manifests/ingress.yml similarity index 100% rename from app/backend/manifests/ingress.yml rename to app/copilot/copilot-backend/manifests/ingress.yml diff --git a/app/backend/pom.xml b/app/copilot/copilot-backend/pom.xml similarity index 93% rename from app/backend/pom.xml rename to app/copilot/copilot-backend/pom.xml index 30103ba..4e95a2c 100644 --- a/app/backend/pom.xml +++ b/app/copilot/copilot-backend/pom.xml @@ -9,16 +9,17 @@ com.microsoft.openai.samples.assistant - personal-finance-assistant-java + personal-finance-assistant-copilot 1.0.0-SNAPSHOT - personal-finance-assistant-java + personal-finance-assistant-copilot This sample demonstrate how to create a generative ai multi-agent solution for a banking personal assistant + 17 4.9.0 11.6.0-beta.8 - 1.0.2 + 1.1.5 4.5.1 3.11.0 @@ -60,6 +61,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-security + com.fasterxml.jackson.core @@ -133,7 +138,16 @@ com.microsoft.semantic-kernel semantickernel-aiservices-openai + + + + + com.microsoft.openai.samples.assistant + semantickernel-openapi-plugin + 1.1.5-SNAPSHOT + + diff --git a/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/CopilotApplication.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/CopilotApplication.java new file mode 100644 index 0000000..d807c2a --- /dev/null +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/CopilotApplication.java @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.openai.samples.assistant; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; + +@SpringBootApplication(exclude = { SecurityAutoConfiguration.class }) + +public class CopilotApplication { + + private static final Logger LOG = LoggerFactory.getLogger(CopilotApplication.class); + + public static void main(String[] args) { + LOG.info( + "Application profile from system property is [{}]", + System.getProperty("spring.profiles.active")); + new SpringApplication(CopilotApplication.class).run(args); + } +} diff --git a/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AccountAgent.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AccountAgent.java new file mode 100644 index 0000000..4d5a27e --- /dev/null +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AccountAgent.java @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.openai.samples.assistant.agent; + +import com.azure.ai.documentintelligence.DocumentIntelligenceClient; +import com.azure.ai.openai.OpenAIAsyncClient; +import com.microsoft.openai.samples.assistant.invoice.DocumentIntelligenceInvoiceScanHelper; +import com.microsoft.openai.samples.assistant.plugin.InvoiceScanPlugin; +import com.microsoft.openai.samples.assistant.plugin.LoggedUserPlugin; +import com.microsoft.openai.samples.assistant.proxy.BlobStorageProxy; +import com.microsoft.openai.samples.assistant.security.LoggedUserService; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; +import com.microsoft.semantickernel.implementation.EmbeddedResourceLoader; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.orchestration.InvocationReturnMode; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.orchestration.ToolCallBehavior; +import com.microsoft.semantickernel.plugin.KernelPlugin; +import com.microsoft.semantickernel.plugin.KernelPluginFactory; +import com.microsoft.semantickernel.samples.openapi.SemanticKernelOpenAPIImporter; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileNotFoundException; + +public class AccountAgent { + private static final Logger LOGGER = LoggerFactory.getLogger(AccountAgent.class); + private OpenAIAsyncClient client; + + private Kernel kernel; + + private ChatCompletionService chat; + + private LoggedUserService loggedUserService; + + private String ACCOUNT_AGENT_SYSTEM_MESSAGE = """ + you are a personal financial advisor who help the user to retrieve information about their bank accounts. + Use html list or table to display the account information. + Always use the below logged user details to retrieve account info: + %s + + """; + + public AccountAgent(OpenAIAsyncClient client, LoggedUserService loggedUserService, String modelId,String accountAPIUrl) { + this.client = client; + this.loggedUserService = loggedUserService; + this.chat = OpenAIChatCompletion.builder() + .withModelId(modelId) + .withOpenAIAsyncClient(client) + .build(); + + kernel = Kernel.builder() + .withAIService(ChatCompletionService.class, chat) + .build(); + + + String accountsAPIYaml = null; + try { + accountsAPIYaml = EmbeddedResourceLoader.readFile("account.yaml", + HistoryReportingAgent.class, + EmbeddedResourceLoader.ResourceLocation.CLASSPATH_ROOT); + } catch (FileNotFoundException e) { + throw new RuntimeException("Cannot find account-history.yaml file in the classpath",e); + } + //Used to retrieve account id. Transaction API requires account id to retrieve transactions + KernelPlugin openAPIImporterAccountPlugin = SemanticKernelOpenAPIImporter + .builder() + .withPluginName("AccountsPlugin") + .withSchema(accountsAPIYaml) + .withServer(accountAPIUrl) + .build(); + + + + kernel = Kernel.builder() + .withAIService(ChatCompletionService.class, chat) + .withPlugin(openAPIImporterAccountPlugin) + + .build(); + } + + + public void run (ChatHistory userChatHistory, AgentContext agentContext){ + LOGGER.info("======== Account Agent: Starting ========"); + + // Extend system prompt with logged user details + String extendedSystemMessage = ACCOUNT_AGENT_SYSTEM_MESSAGE.formatted(new LoggedUserPlugin(loggedUserService).getUserContext()); + var agentChatHistory = new ChatHistory(extendedSystemMessage); + + userChatHistory.forEach( chatMessageContent -> { + if(chatMessageContent.getAuthorRole() != AuthorRole.SYSTEM) + agentChatHistory.addMessage(chatMessageContent); + + }); + + var messages = this.chat.getChatMessageContentsAsync( + agentChatHistory, + kernel, + InvocationContext.builder().withToolCallBehavior( + ToolCallBehavior.allowAllKernelFunctions(true)) + .withReturnMode(InvocationReturnMode.NEW_MESSAGES_ONLY) + .withPromptExecutionSettings( + PromptExecutionSettings.builder() + .withTemperature(0.1) + .withTopP(1) + .withPresencePenalty(0) + .withFrequencyPenalty(0) + .build()) + .build()) + .block(); + + //get last message + var message = messages.get(messages.size()-1); + + agentContext.setResult(message.getContent()); + + } + } + + diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AgentContext.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AgentContext.java similarity index 86% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AgentContext.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AgentContext.java index 1afd566..4a068ee 100644 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AgentContext.java +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AgentContext.java @@ -1,3 +1,4 @@ +// Copyright (c) Microsoft. All rights reserved. package com.microsoft.openai.samples.assistant.agent; import java.util.HashMap; diff --git a/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AgentRouter.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AgentRouter.java new file mode 100644 index 0000000..63773d9 --- /dev/null +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/AgentRouter.java @@ -0,0 +1,61 @@ +package com.microsoft.openai.samples.assistant.agent; + +import com.azure.ai.documentintelligence.DocumentIntelligenceClient; +import com.azure.ai.openai.OpenAIAsyncClient; +import com.microsoft.openai.samples.assistant.proxy.BlobStorageProxy; +import com.microsoft.openai.samples.assistant.security.LoggedUserService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class AgentRouter { + private static final Logger LOGGER = LoggerFactory.getLogger(AgentRouter.class); + private final IntentAgent intentAgent; + private final PaymentAgent paymentAgent; + private final HistoryReportingAgent historyReportingAgent; + private final AccountAgent accountAgent; + + public AgentRouter(LoggedUserService loggedUserService, OpenAIAsyncClient openAIAsyncClient, DocumentIntelligenceClient documentIntelligenceClient, BlobStorageProxy blobStorageProxy, @Value("${openai.chatgpt.deployment}") String gptChatDeploymentModelId, @Value("${transactions.api.url}") String transactionsAPIUrl, @Value("${accounts.api.url}") String accountsAPIUrl, @Value("${payments.api.url}") String paymentsAPIUrl ){ + this.intentAgent = new IntentAgent(openAIAsyncClient,gptChatDeploymentModelId); + this.paymentAgent = new PaymentAgent(openAIAsyncClient,loggedUserService,gptChatDeploymentModelId,documentIntelligenceClient,blobStorageProxy,transactionsAPIUrl,accountsAPIUrl,paymentsAPIUrl); + this.historyReportingAgent = new HistoryReportingAgent(openAIAsyncClient,loggedUserService,gptChatDeploymentModelId,transactionsAPIUrl,accountsAPIUrl); + this.accountAgent = new AccountAgent(openAIAsyncClient,loggedUserService,gptChatDeploymentModelId,accountsAPIUrl); + + } + + public void run(ChatHistory chatHistory, AgentContext agentContext){ + IntentResponse intentResponse = intentAgent.run(chatHistory, agentContext); + + LOGGER.info("Intent Type for chat conversation: {}", intentResponse.getIntentType()); + + routeToAgent(intentResponse, chatHistory, agentContext); + } + private void routeToAgent(IntentResponse intentResponse, ChatHistory chatHistory, AgentContext agentContext) { + if(agentContext == null) + agentContext = new AgentContext(); + + switch (intentResponse.getIntentType()) { + case BillPayment: + case RepeatTransaction: + paymentAgent.run(chatHistory, agentContext); + chatHistory.addAssistantMessage(agentContext.getResult()); + break; + case TransactionHistory: + historyReportingAgent.run(chatHistory, agentContext); + chatHistory.addAssistantMessage(agentContext.getResult()); + break; + case AccountInfo: + accountAgent.run(chatHistory, agentContext); + chatHistory.addAssistantMessage(agentContext.getResult()); + break; + case None: + chatHistory.addAssistantMessage(agentContext.getResult()); + break; + default: + break; + } + } +} \ No newline at end of file diff --git a/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/HistoryReportingAgent.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/HistoryReportingAgent.java new file mode 100644 index 0000000..048acff --- /dev/null +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/HistoryReportingAgent.java @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.openai.samples.assistant.agent; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.http.policy.HttpLogDetailLevel; +import com.azure.core.http.policy.HttpLogOptions; +import com.microsoft.openai.samples.assistant.plugin.TransactionHistoryPlugin; +import com.microsoft.openai.samples.assistant.plugin.LoggedUserPlugin; +import com.microsoft.openai.samples.assistant.security.LoggedUserService; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; +import com.microsoft.semantickernel.implementation.EmbeddedResourceLoader; +import com.microsoft.semantickernel.orchestration.*; +import com.microsoft.semantickernel.plugin.KernelPlugin; +import com.microsoft.semantickernel.plugin.KernelPluginFactory; +import com.microsoft.semantickernel.samples.openapi.SemanticKernelOpenAPIImporter; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileNotFoundException; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class HistoryReportingAgent { + private static final Logger LOGGER = LoggerFactory.getLogger(HistoryReportingAgent.class); + private OpenAIAsyncClient client; + + private Kernel kernel; + + private ChatCompletionService chat; + + private LoggedUserService loggedUserService; + + private String HISTORY_AGENT_SYSTEM_MESSAGE = """ + you are a personal financial advisor who help the user with their recurrent bill payments. To search about the payments history you need to know the payee name. + If the user doesn't provide the payee name, search the last 10 transactions order by date. + If the user want to search last transactions for a specific payee, ask to provide the payee name. + Use html list or table to display the transaction information. + Always use the below logged user details to search the transactions: + %s + Current timestamp: %s + """; + + public HistoryReportingAgent(OpenAIAsyncClient client, LoggedUserService loggedUserService, String modelId, String transactionAPIUrl, String accountAPIUrl){ + this.client = client; + this.loggedUserService = loggedUserService; + this.chat = OpenAIChatCompletion.builder() + .withModelId(modelId) + .withOpenAIAsyncClient(client) + .build(); + + /** + * Using native function to create a plugin + */ + //var plugin = KernelPluginFactory.createFromObject(new TransactionHistoryPlugin(), "TransactionHistoryPlugin"); + + String transactionAPIYaml = null; + try { + transactionAPIYaml = EmbeddedResourceLoader.readFile("transaction-history.yaml", + HistoryReportingAgent.class, + EmbeddedResourceLoader.ResourceLocation.CLASSPATH_ROOT); + } catch (FileNotFoundException e) { + throw new RuntimeException("Cannot find transaction-history.yaml file in the classpath",e); + } + + //Used to retrieve transactions. + KernelPlugin openAPIImporterTransactionPlugin = SemanticKernelOpenAPIImporter + .builder() + .withPluginName("TransactionHistoryPlugin") + .withSchema(transactionAPIYaml) + .withServer(transactionAPIUrl) + .build(); + + + String accountAPIYaml = null; + try { + accountAPIYaml = EmbeddedResourceLoader.readFile("account.yaml", + HistoryReportingAgent.class, + EmbeddedResourceLoader.ResourceLocation.CLASSPATH_ROOT); + } catch (FileNotFoundException e) { + throw new RuntimeException("Cannot find account-history.yaml file in the classpath",e); + } + //Used to retrieve account id. Transaction API requires account id to retrieve transactions + KernelPlugin openAPIImporterAccountPlugin = SemanticKernelOpenAPIImporter + .builder() + .withPluginName("AccountPlugin") + .withSchema(accountAPIYaml) + .withServer(accountAPIUrl) + .build(); + + + + kernel = Kernel.builder() + .withAIService(ChatCompletionService.class, chat) + .withPlugin(openAPIImporterTransactionPlugin) + .withPlugin(openAPIImporterAccountPlugin) + .build(); + + } + + public void run (ChatHistory userChatHistory, AgentContext agentContext){ + LOGGER.info("======== TransactionsHistory Agent: Starting ========"); + + // Extend system prompt with logged user details and current timestamp + var datetimeIso8601 = ZonedDateTime.now(ZoneId.of("UTC")).toInstant().toString(); + String extendedSystemMessage = HISTORY_AGENT_SYSTEM_MESSAGE.formatted(new LoggedUserPlugin(loggedUserService).getUserContext(),datetimeIso8601); + + var agentChatHistory = new ChatHistory(extendedSystemMessage); + userChatHistory.forEach( chatMessageContent -> { + if(chatMessageContent.getAuthorRole() != AuthorRole.SYSTEM) + agentChatHistory.addMessage(chatMessageContent); + }); + + + var messages = this.chat.getChatMessageContentsAsync( + agentChatHistory, + kernel, + InvocationContext.builder().withToolCallBehavior( + ToolCallBehavior.allowAllKernelFunctions(true)) + .withReturnMode(InvocationReturnMode.NEW_MESSAGES_ONLY) + /* .withPromptExecutionSettings( + PromptExecutionSettings.builder() + .withTemperature(0.1) + .withTopP(1) + //.withPresencePenalty(0) + //.withFrequencyPenalty(0) + .build())*/ + .build()) + .block(); + + //get last message + var message = messages.get(messages.size()-1); + agentContext.setResult(message.getContent()); + } + + + } + + diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentAgent.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentAgent.java similarity index 75% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentAgent.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentAgent.java index 94fd1bc..0997c9f 100644 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentAgent.java +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentAgent.java @@ -23,12 +23,12 @@ public class IntentAgent { private ChatCompletionService chat; private String INTENT_SYSTEM_MESSAGE = """ -You are a personal financial advisor who help the user with their recurrent bill payments. -The user may want to pay the bill uploading a photo of the bill, or it may start the payment checking payments history for a specific payee. -In other cases it may want to just review the payments history. +You are a personal financial advisor who help bank customers manage their banking accounts and services. +The user may need help with his recurrent bill payments, it may start the payment checking payments history for a specific payee. +In other cases it may want to just review account details or transactions history. Based on the conversation you need to identify the user intent. The available intents are: -"BillPayment", "RepeatTransaction","TransactionHistory" +"BillPayment","RepeatTransaction","TransactionHistory","AccountInfo" If none of the intents are identified provide the user with the list of the available intents. If an intent is identified return the output as json format as below @@ -36,8 +36,7 @@ public class IntentAgent { "intent": "BillPayment" } -If you don't understand or if an intent is not identified be polite with the user, ask clarifying question also using the list the available intents. - +If you don't understand or if an intent is not identified be polite with the user, ask clarifying question also using the list of the available intents. Don't add any comments in the output or other characters, just the use a json format. """; @@ -70,7 +69,7 @@ public IntentAgent(String azureClientKey, String clientEndpoint, String modelId) .build(); } - public IntentResponse run(ChatHistory userChatHistory){ + public IntentResponse run(ChatHistory userChatHistory,AgentContext agentContext){ var agentChatHistory = new ChatHistory(INTENT_SYSTEM_MESSAGE); agentChatHistory.addAll(userChatHistory); @@ -91,7 +90,7 @@ public IntentResponse run(ChatHistory userChatHistory){ JSONObject jsonData = new JSONObject(); /** - * Try to see if the model answered with a formatted json. If not it is just trying to move the conversation forward to understand the user intent + * Try to see if the model answered with a formatted json. If not it is just trying to keep the conversation going to understand the user intent * but without answering with a formatted output. In this case the intent is None and the clarifying sentence is not used. */ try{ @@ -105,27 +104,12 @@ public IntentResponse run(ChatHistory userChatHistory){ try { clarifySentence = jsonData.get("clarify_sentence").toString(); } catch(Exception e){ - // this is the case where the intent has been identified and the clarifying sentence is not present in the json outpu + // this is the case where the intent has been identified and the clarifying sentence is not present in the json output } return new IntentResponse(intentType, clarifySentence != null ? clarifySentence.toString() : ""); } - public static void main(String[] args) throws NoSuchMethodException { - - String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); - String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); - String MODEL_ID = System.getenv() - .getOrDefault("MODEL_ID", "gpt-3.5-turbo-1106"); - - IntentAgent agent = new IntentAgent(AZURE_CLIENT_KEY, CLIENT_ENDPOINT, MODEL_ID); - var chatHistory = new ChatHistory(); - // agent.run("when did I pay my last electricity bill?"); - //chatHistory.addUserMessage("when did I pay my last electricity bill?"); - chatHistory.addUserMessage("I want to pay my electricity bill"); - agent.run( chatHistory); - - } - } +} diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentResponse.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentResponse.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentResponse.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentResponse.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentType.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentType.java similarity index 84% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentType.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentType.java index 9ecf129..693ca07 100644 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentType.java +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/IntentType.java @@ -5,6 +5,7 @@ public enum IntentType { BillPayment, RepeatTransaction, TransactionHistory, + AccountInfo, None; diff --git a/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/PaymentAgent.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/PaymentAgent.java new file mode 100644 index 0000000..15709c1 --- /dev/null +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/agent/PaymentAgent.java @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.openai.samples.assistant.agent; + +import com.azure.ai.documentintelligence.DocumentIntelligenceClient; +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.http.policy.HttpLogDetailLevel; +import com.azure.core.http.policy.HttpLogOptions; +import com.microsoft.openai.samples.assistant.controller.ChatController; +import com.microsoft.openai.samples.assistant.invoice.DocumentIntelligenceInvoiceScanHelper; +import com.microsoft.openai.samples.assistant.plugin.InvoiceScanPlugin; +import com.microsoft.openai.samples.assistant.plugin.LoggedUserPlugin; +import com.microsoft.openai.samples.assistant.plugin.PaymentPlugin; +import com.microsoft.openai.samples.assistant.plugin.TransactionHistoryPlugin; +import com.microsoft.openai.samples.assistant.proxy.BlobStorageProxy; +import com.microsoft.openai.samples.assistant.security.LoggedUserService; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatMessageContent; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIFunctionToolCall; +import com.microsoft.semantickernel.contextvariables.CaseInsensitiveMap; +import com.microsoft.semantickernel.contextvariables.ContextVariable; +import com.microsoft.semantickernel.implementation.EmbeddedResourceLoader; +import com.microsoft.semantickernel.orchestration.*; +import com.microsoft.semantickernel.plugin.KernelPlugin; +import com.microsoft.semantickernel.plugin.KernelPluginFactory; +import com.microsoft.semantickernel.samples.openapi.SemanticKernelOpenAPIImporter; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileNotFoundException; +import java.nio.charset.StandardCharsets; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.stream.Collectors; + +public class PaymentAgent { + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentAgent.class); + private OpenAIAsyncClient client; + + private Kernel kernel; + + private ChatCompletionService chat; + + private LoggedUserService loggedUserService; + + private String PAYMENT_AGENT_SYSTEM_MESSAGE = """ + you are a personal financial advisor who help the user with their recurrent bill payments. The user may want to pay the bill uploading a photo of the bill, or it may start the payment checking transactions history for a specific payee. + For the bill payment you need to know the: bill id or invoice number, payee name, the total amount. + If you don't have enough information to pay the bill ask the user to provide the missing information. + If the user submit a photo, always ask the user to confirm the extracted data from the photo. + Always check if the bill has been paid already based on payment history before asking to execute the bill payment. + Ask for the payment method to use based on the available methods on the user account. + if the user wants to pay using bank transfer, check if the payee is in account registered beneficiaries list. If not ask the user to provide the payee bank code. + Check if the payment method selected by the user has enough funds to pay the bill. Don't use the account balance to evaluate the funds. + Before submitting the payment to the system ask the user confirmation providing the payment details. + When submitting payment always use the available functions to retrieve accountId, paymentMethodId. + If the payment succeeds provide the user with the payment confirmation. If not provide the user with the error message. + Use HTML list or table to display bill extracted data, payments, account or transaction details. + Always use the below logged user details to retrieve account info: + %s + Current timestamp: %s + Don't try to guess accountId,paymentMethodId from the conversation.When submitting payment always use functions to retrieve accountId, paymentMethodId. + """; + + public PaymentAgent(OpenAIAsyncClient client, LoggedUserService loggedUserService, String modelId, DocumentIntelligenceClient documentIntelligenceClient, BlobStorageProxy blobStorageProxy, String transactionAPIUrl, String accountAPIUrl, String paymentsAPIUrl) { + this.client = client; + this.loggedUserService = loggedUserService; + this.chat = OpenAIChatCompletion.builder() + .withModelId(modelId) + .withOpenAIAsyncClient(client) + .build(); + + kernel = Kernel.builder() + .withAIService(ChatCompletionService.class, chat) + .build(); + + //var paymentPlugin = KernelPluginFactory.createFromObject(new PaymentPlugin(), "PaymentPlugin"); + //var historyPlugin = KernelPluginFactory.createFromObject(new TransactionHistoryPlugin(), "TransactionHistoryPlugin"); + var invoiceScanPlugin = KernelPluginFactory.createFromObject(new InvoiceScanPlugin(new DocumentIntelligenceInvoiceScanHelper(documentIntelligenceClient,blobStorageProxy)), "InvoiceScanPlugin"); + + String transactionsAPIYaml = null; + try { + transactionsAPIYaml = EmbeddedResourceLoader.readFile("transaction-history.yaml", + HistoryReportingAgent.class, + EmbeddedResourceLoader.ResourceLocation.CLASSPATH_ROOT); + } catch (FileNotFoundException e) { + throw new RuntimeException("Cannot find transaction-history.yaml file in the classpath",e); + } + + //Used to retrieve transactions. + KernelPlugin openAPIImporterTransactionPlugin = SemanticKernelOpenAPIImporter + .builder() + .withPluginName("TransactionHistoryPlugin") + .withSchema(transactionsAPIYaml) + .withServer(transactionAPIUrl) + .build(); + + + String accountsAPIYaml = null; + try { + accountsAPIYaml = EmbeddedResourceLoader.readFile("account.yaml", + HistoryReportingAgent.class, + EmbeddedResourceLoader.ResourceLocation.CLASSPATH_ROOT); + } catch (FileNotFoundException e) { + throw new RuntimeException("Cannot find account-history.yaml file in the classpath",e); + } + //Used to retrieve account id. Transaction API requires account id to retrieve transactions + KernelPlugin openAPIImporterAccountPlugin = SemanticKernelOpenAPIImporter + .builder() + .withPluginName("AccountsPlugin") + .withSchema(accountsAPIYaml) + .withServer(accountAPIUrl) + .build(); + + String paymentsAPIYaml = null; + try { + paymentsAPIYaml = EmbeddedResourceLoader.readFile("payments.yaml", + HistoryReportingAgent.class, + EmbeddedResourceLoader.ResourceLocation.CLASSPATH_ROOT); + } catch (FileNotFoundException e) { + throw new RuntimeException("Cannot find account-history.yaml file in the classpath",e); + } + //Used to submit payments + KernelPlugin openAPIImporterPaymentsPlugin = SemanticKernelOpenAPIImporter + .builder() + .withPluginName("PaymentsPlugin") + .withSchema(paymentsAPIYaml) + .withServer(paymentsAPIUrl) + .build(); + + kernel = Kernel.builder() + .withAIService(ChatCompletionService.class, chat) + .withPlugin(invoiceScanPlugin) + .withPlugin(openAPIImporterTransactionPlugin) + .withPlugin(openAPIImporterPaymentsPlugin) + .withPlugin(openAPIImporterAccountPlugin) + + .build(); + } + + + public void run (ChatHistory userChatHistory, AgentContext agentContext){ + LOGGER.info("======== Payment Agent: Starting ========"); + + // Extend system prompt with logged user details and current timestamp + var datetimeIso8601 = ZonedDateTime.now(ZoneId.of("UTC")).toInstant().toString(); + + String extendedSystemMessage = PAYMENT_AGENT_SYSTEM_MESSAGE.formatted(new LoggedUserPlugin(loggedUserService).getUserContext(),datetimeIso8601); + var agentChatHistory = new ChatHistory(extendedSystemMessage); + + userChatHistory.forEach( chatMessageContent -> { + if(chatMessageContent.getAuthorRole() != AuthorRole.SYSTEM) + agentChatHistory.addMessage(chatMessageContent); + + }); + + var messages = this.chat.getChatMessageContentsAsync( + agentChatHistory, + kernel, + InvocationContext.builder().withToolCallBehavior( + ToolCallBehavior.allowAllKernelFunctions(true)) + .withReturnMode(InvocationReturnMode.NEW_MESSAGES_ONLY) + .withPromptExecutionSettings( + PromptExecutionSettings.builder() + .withTemperature(0.1) + .withTopP(1) + .withPresencePenalty(-0) + .withFrequencyPenalty(-0) + .build()) + .build()) + .block(); + + //get last message + var message = messages.get(messages.size()-1); + + agentContext.setResult(message.getContent()); + + } + + + } + + + + diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTConversation.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTConversation.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTConversation.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTConversation.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTMessage.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTMessage.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTMessage.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTMessage.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTUtils.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTUtils.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTUtils.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTUtils.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/config/AzureAuthenticationConfiguration.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/AzureAuthenticationConfiguration.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/config/AzureAuthenticationConfiguration.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/AzureAuthenticationConfiguration.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/config/DocumentIntelligenceConfiguration.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/DocumentIntelligenceConfiguration.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/config/DocumentIntelligenceConfiguration.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/DocumentIntelligenceConfiguration.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/config/OpenAIConfiguration.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/OpenAIConfiguration.java similarity index 95% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/config/OpenAIConfiguration.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/OpenAIConfiguration.java index 9488d5a..3c8a618 100644 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/config/OpenAIConfiguration.java +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/OpenAIConfiguration.java @@ -34,7 +34,7 @@ public OpenAIClient openAItracingEnabledClient() { var httpLogOptions = new HttpLogOptions(); // httpLogOptions.setPrettyPrintBody(true); - httpLogOptions.setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS); + httpLogOptions.setLogLevel(HttpLogDetailLevel.BODY); return new OpenAIClientBuilder() .endpoint(endpoint) @@ -60,7 +60,7 @@ public OpenAIAsyncClient tracingEnabledAsyncClient() { var httpLogOptions = new HttpLogOptions(); httpLogOptions.setPrettyPrintBody(true); - httpLogOptions.setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS); + httpLogOptions.setLogLevel(HttpLogDetailLevel.BODY); return new OpenAIClientBuilder() .endpoint(endpoint) diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequest.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequest.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequest.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequest.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequestContext.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequestContext.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequestContext.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequestContext.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequestOverrides.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequestOverrides.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequestOverrides.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequestOverrides.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatController.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatController.java similarity index 60% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatController.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatController.java index 1367f45..1ee9bfa 100644 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatController.java +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatController.java @@ -7,6 +7,7 @@ import com.microsoft.openai.samples.assistant.proxy.BlobStorageProxy; +import com.microsoft.openai.samples.assistant.security.LoggedUserService; import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; @@ -31,16 +32,10 @@ public class ChatController { private static final Logger LOGGER = LoggerFactory.getLogger(ChatController.class); + private final AgentRouter agentRouter; - private final IntentAgent intentAgent; - private final PaymentAgent paymentAgent; - private final HistoryReportingAgent historyReportingAgent; - - public ChatController(OpenAIAsyncClient openAIAsyncClient, DocumentIntelligenceClient documentIntelligenceClient, BlobStorageProxy blobStorageProxy, @Value("${openai.chatgpt.deployment}") String gptChatDeploymentModelId){ - this.intentAgent = new IntentAgent(openAIAsyncClient,gptChatDeploymentModelId); - this.paymentAgent = new PaymentAgent(openAIAsyncClient,gptChatDeploymentModelId,documentIntelligenceClient,blobStorageProxy); - this.historyReportingAgent = new HistoryReportingAgent(openAIAsyncClient,gptChatDeploymentModelId); - + public ChatController(AgentRouter agentRouter){ + this.agentRouter = agentRouter; } @@ -56,58 +51,28 @@ public ResponseEntity openAIAsk(@RequestBody ChatAppRequest chatRe + " Please use a content-type of application/ndjson"); } - LOGGER.info("Received request for chat api with approach[{}]", chatRequest.approach()); - if (chatRequest.messages() == null || chatRequest.messages().isEmpty()) { LOGGER.warn("history cannot be null in Chat request"); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); } ChatHistory chatHistory = convertSKChatHistory(chatRequest); - LOGGER.info("Processing chat conversation..", chatHistory.getLastMessage().get().getContent()); - IntentResponse response = intentAgent.run(chatHistory); - - LOGGER.info("Intent Type for chat conversation: {}", response.getIntentType()); - if (response.getIntentType() == IntentType.None) { - chatHistory.addAssistantMessage(response.getMessage()); - } - var agentContext = new AgentContext(); agentContext.put("requestContext", chatRequest.context()); agentContext.put("attachments", chatRequest.attachments()); agentContext.put("approach", chatRequest.approach()); - if (response.getIntentType() == IntentType.BillPayment || response.getIntentType() == IntentType.RepeatTransaction) { - paymentAgent.run(chatHistory,agentContext); - chatHistory.addAssistantMessage(agentContext.getResult()); - } - - if (response.getIntentType() == IntentType.TransactionHistory) { - historyReportingAgent.run(chatHistory,agentContext); - chatHistory.addAssistantMessage(agentContext.getResult()); - } + agentRouter.run(chatHistory,agentContext); return ResponseEntity.ok( ChatResponse.buildChatResponse(chatHistory, agentContext)); } private ChatHistory convertSKChatHistory(ChatAppRequest chatAppRequest) { - ChatHistory chatHistory = new ChatHistory(false); - /* - ChatMessageContent lastUserMessage = new ChatMessageContent(AuthorRole.USER, - chatAppRequest.messages().remove(chatAppRequest.messages().size()-1).content()); - - if(chatAppRequest.attachments() != null && !chatAppRequest.attachments().isEmpty()) { - // prepare last user message including attachments. Append list of attachments to the last user message content - lastUserMessage = new ChatMessageContent(AuthorRole.USER, - chatAppRequest.messages().remove(chatAppRequest.messages().size()-1).content() + " " +chatAppRequest.attachments().toString()); - } - - */ - - chatAppRequest.messages().forEach( + ChatHistory chatHistory = new ChatHistory(); + chatAppRequest.messages().forEach( historyChat -> { if("user".equals(historyChat.role())) { if(historyChat.attachments() == null || historyChat.attachments().isEmpty()) diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatResponse.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatResponse.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatResponse.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatResponse.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseChoice.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseChoice.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseChoice.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseChoice.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseContext.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseContext.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseContext.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseContext.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseMessage.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseMessage.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseMessage.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseMessage.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/auth/AuthSetup.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/auth/AuthSetup.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/auth/AuthSetup.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/auth/AuthSetup.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/content/ContentController.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/content/ContentController.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/content/ContentController.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/content/ContentController.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/invoice/DocumentIntelligenceInvoiceScanHelper.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/invoice/DocumentIntelligenceInvoiceScanHelper.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/invoice/DocumentIntelligenceInvoiceScanHelper.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/invoice/DocumentIntelligenceInvoiceScanHelper.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/InvoiceScanPlugin.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/InvoiceScanPlugin.java similarity index 94% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/InvoiceScanPlugin.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/InvoiceScanPlugin.java index aded68f..3f5cd4c 100644 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/InvoiceScanPlugin.java +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/InvoiceScanPlugin.java @@ -66,7 +66,7 @@ public InvoiceScanPlugin(DocumentIntelligenceInvoiceScanHelper documentIntellige } @DefineKernelFunction(name = "scanInvoice", description = "Extract the invoice or bill data scanning a photo or image") public String scanInvoice( - @KernelFunctionParameter(name = "filaPath", description = "the path to the file containing the image or photo") String filePath) { + @KernelFunctionParameter(name = "filePath", description = "the path to the file containing the image or photo") String filePath) { Map scanData = null; diff --git a/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/LoggedUserPlugin.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/LoggedUserPlugin.java new file mode 100644 index 0000000..cac3b61 --- /dev/null +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/LoggedUserPlugin.java @@ -0,0 +1,23 @@ +package com.microsoft.openai.samples.assistant.plugin; + +import com.microsoft.openai.samples.assistant.security.LoggedUser; +import com.microsoft.openai.samples.assistant.security.LoggedUserService; +import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; + +public class LoggedUserPlugin { + + private final LoggedUserService loggedUserService; + public LoggedUserPlugin(LoggedUserService loggedUserService) + { + this.loggedUserService = loggedUserService; + } + @DefineKernelFunction(name = "UserContext", description = "Gets the user details after login") + public String getUserContext() { + LoggedUser loggedUser = loggedUserService.getLoggedUser(); + return loggedUser.toString(); + + + } + +} + diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/PaymentPlugin.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/PaymentPlugin.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/PaymentPlugin.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/PaymentPlugin.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/TransactionHistoryPlugin.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/TransactionHistoryPlugin.java similarity index 73% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/TransactionHistoryPlugin.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/TransactionHistoryPlugin.java index d836ad3..10d6ae2 100644 --- a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/TransactionHistoryPlugin.java +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/TransactionHistoryPlugin.java @@ -1,13 +1,11 @@ package com.microsoft.openai.samples.assistant.plugin; +import com.microsoft.openai.samples.assistant.plugin.mock.TransactionService; import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; - public class TransactionHistoryPlugin { private static final Logger LOGGER = LoggerFactory.getLogger(TransactionHistoryPlugin.class); private final TransactionService transactionService; @@ -17,8 +15,9 @@ public TransactionHistoryPlugin(){ @DefineKernelFunction(name = "getTransactionsByRecepient", description = "Gets the last payment transactions based on the payee, recipient name") public String getTransactionsByRecepient( + @KernelFunctionParameter(name = "accountId", description = "The banking account id of the user") String accountId, @KernelFunctionParameter(name = "recipientName", description = "Name of the payee, recipient") String recipientName) { - String transactionsByRecipient = transactionService.getTransactionsByRecipientName(recipientName).toString(); + String transactionsByRecipient = transactionService.getTransactionsByRecipientName(accountId,recipientName).toString(); LOGGER.info("Transactions for [{}]:{} ",recipientName,transactionsByRecipient); return transactionsByRecipient; @@ -27,8 +26,10 @@ public String getTransactionsByRecepient( @DefineKernelFunction(name = "getTransactions", description = "Gets the last payment transactions") - public String getTransactions() { - String lastTransactions = transactionService.getlastTransactions().toString(); + public String getTransactions( + @KernelFunctionParameter(name = "accountId", description = "The banking account id of the user") String accountId + ) { + String lastTransactions = transactionService.getlastTransactions(accountId).toString(); LOGGER.info("Last transactions:{} ",lastTransactions); return lastTransactions; diff --git a/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/mock/PaymentTransaction.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/mock/PaymentTransaction.java new file mode 100644 index 0000000..ba15680 --- /dev/null +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/mock/PaymentTransaction.java @@ -0,0 +1,13 @@ +package com.microsoft.openai.samples.assistant.plugin.mock; + +public record PaymentTransaction( + String id, + String description, + String recipientName, + String recipientBankReference, + String accountId, + String paymentType, + String amount, + String timestamp +) {} + diff --git a/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/mock/TransactionService.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/mock/TransactionService.java new file mode 100644 index 0000000..34c92c2 --- /dev/null +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/mock/TransactionService.java @@ -0,0 +1,56 @@ +package com.microsoft.openai.samples.assistant.plugin.mock; + +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class TransactionService { + + private Map> lastTransactions= new HashMap<>(); + private Map> allTransactions= new HashMap<>(); + + public TransactionService(){ + + lastTransactions.put("1010", Arrays.asList( + new PaymentTransaction("11", "Payment of the bill 334398", "Mike ThePlumber", "0001", "1010", "BankTransfer", "100.00", "2024-4-01T12:00:00Z"), + new PaymentTransaction("22", "Payment of the bill 4613", "Jane TheElectrician", "0002", "1010", "CreditCard", "200.00", "2024-3-02T12:00:00Z"), + new PaymentTransaction("33", "Payment of the bill 724563", "Bob TheCarpenter", "0003", "1010", "BankTransfer", "300.00", "2023-10-03T12:00:00Z"), + new PaymentTransaction("43", "Payment of the bill 8898943", "Alice ThePainter", "0004", "1010", "DirectDebit", "400.00", "2023-8-04T12:00:00Z"), + new PaymentTransaction("53", "Payment of the bill 19dee", "Charlie TheMechanic", "0005", "1010", "BankTransfer", "500.00", "2023-4-05T12:00:00Z")) + ); + + + allTransactions.put("1010",Arrays.asList( + new PaymentTransaction("11", "payment of bill id with 0001", "Mike ThePlumber", "A012TABTYT156!", "1010", "BankTransfer", "100.00", "2024-4-01T12:00:00Z"), + new PaymentTransaction("21", "Payment of the bill 4200", "Mike ThePlumber", "0002", "1010", "BankTransfer", "200.00", "2024-1-02T12:00:00Z"), + new PaymentTransaction("31", "Payment of the bill 3743", "Mike ThePlumber", "0003", "1010", "DirectDebit", "300.00", "2023-10-03T12:00:00Z"), + new PaymentTransaction("41", "Payment of the bill 8921", "Mike ThePlumber", "0004", "1010", "Transfer", "400.00", "2023-8-04T12:00:00Z"), + new PaymentTransaction("51", "Payment of the bill 7666", "Mike ThePlumber", "0005", "1010", "CreditCard", "500.00", "2023-4-05T12:00:00Z"), + + new PaymentTransaction("12", "Payment of the bill 5517", "Jane TheElectrician", "0001", "1010", "CreditCard", "100.00", "2024-3-01T12:00:00Z"), + new PaymentTransaction("22", "Payment of the bill 682222", "Jane TheElectrician", "0002", "1010", "CreditCard", "200.00", "2023-1-02T12:00:00Z"), + new PaymentTransaction("32", "Payment of the bill 94112", "Jane TheElectrician", "0003", "1010", "Transfer", "300.00", "2022-10-03T12:00:00Z"), + new PaymentTransaction("42", "Payment of the bill 23122", "Jane TheElectrician", "0004", "1010", "Transfer", "400.00", "2022-8-04T12:00:00Z"), + new PaymentTransaction("52", "Payment of the bill 171443", "Jane TheElectrician", "0005", "1010", "Transfer", "500.00", "2020-4-05T12:00:00Z") + )); + + + + } + public List getTransactionsByRecipientName(String accountId, String name) { + + return allTransactions.get(accountId) + .stream() + .filter(transaction -> transaction.recipientName().contains(name)) + .collect(Collectors.toList()); + + } + + public List getlastTransactions(String accountId) { + + return lastTransactions.get(accountId); + } + +} diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/proxy/BlobStorageProxy.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/proxy/BlobStorageProxy.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/proxy/BlobStorageProxy.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/proxy/BlobStorageProxy.java diff --git a/app/backend/src/main/java/com/microsoft/openai/samples/assistant/proxy/OpenAIProxy.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/proxy/OpenAIProxy.java similarity index 100% rename from app/backend/src/main/java/com/microsoft/openai/samples/assistant/proxy/OpenAIProxy.java rename to app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/proxy/OpenAIProxy.java diff --git a/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/security/LoggedUser.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/security/LoggedUser.java new file mode 100644 index 0000000..666d0b6 --- /dev/null +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/security/LoggedUser.java @@ -0,0 +1,5 @@ +package com.microsoft.openai.samples.assistant.security; + +public record LoggedUser(String username, String mail, String role, String displayName) { + +} diff --git a/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/security/LoggedUserService.java b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/security/LoggedUserService.java new file mode 100644 index 0000000..bb7f667 --- /dev/null +++ b/app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/security/LoggedUserService.java @@ -0,0 +1,32 @@ +package com.microsoft.openai.samples.assistant.security; + +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Component +public class LoggedUserService { + + public LoggedUser getLoggedUser(){ + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + //this is always true in the PoC code + if(authentication == null) { + return getDefaultUser(); + } + //this code is never executed in the PoC. It's a hook for future improvements requiring integration with authentication providers. + if (!(authentication instanceof AnonymousAuthenticationToken)) { + String currentUserName = authentication.getName(); + + Object details = authentication.getDetails(); + //object should be cast to specific type based on the authentication provider + return new LoggedUser(currentUserName, "changeme@microsoft.com", "changeme", "changeme"); + } + return getDefaultUser(); + } + + private LoggedUser getDefaultUser() { + return new LoggedUser("bob.user@microsoft.com", "bob.user@microsoft.com", "generic", "Bob The User"); + } +} diff --git a/app/copilot/copilot-backend/src/main/resources/account.yaml b/app/copilot/copilot-backend/src/main/resources/account.yaml new file mode 100644 index 0000000..0708a9c --- /dev/null +++ b/app/copilot/copilot-backend/src/main/resources/account.yaml @@ -0,0 +1,178 @@ +openapi: 3.0.3 +info: + title: Account API + version: 1.0.0 +paths: + /users/{user_name}/accounts: + get: + summary: Get the list of all accounts for a specific user + description: Get the list of all accounts for a specific user + operationId: getAccountsByUserName + parameters: + - name: user_name + description: userName once the user has logged. + in: path + required: true + schema: + type: string + responses: + '200': + description: A list of accounts + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Account' + /accounts/{accountid}: + get: + summary: Get account details and available payment methods + description: Get account details and available payment methods + operationId: getAccountDetails + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: integer + example: 123456 + responses: + '200': + description: Account details + content: + application/json: + schema: + type: object + items: + $ref: '#/components/schemas/Account' + /accounts/{accountid}/paymentmethods/{methodid}: + get: + summary: Get payment method detail with available balance + description: Get payment method detail with available balance + operationId: getPaymentMethodDetails + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: integer + example: 123456 + - name: methodid + description: id of specific payment method available for the account id. + in: path + required: true + schema: + type: integer + example: 74839113 + responses: + '200': + description: Payment method details + content: + application/json: + schema: + type: object + items: + $ref: '#/components/schemas/PaymentMethod' + /accounts/{accountid}/registeredBeneficiaries: + get: + summary: Get list of registered beneficiaries for a specific account + description: Get list of registered beneficiaries for a specific account + operationId: getBeneficiaryMethodDetails + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: integer + example: 123456 + responses: + '200': + description: Payment method details + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Beneficiary' +components: + schemas: + Account: + type: object + properties: + id: + type: string + description: The unique identifier for the account + userName: + type: string + description: The unique identifier for the user + accountHolderFullName: + type: string + description: The full name of the account holder + currency: + type: string + description: The currency of the account + activationDate: + type: string + description: The date when the account was activated + balance: + type: string + description: The current balance of the account + paymentMethods: + type: array + items: + $ref: '#/components/schemas/PaymentMethod' + description: The list of payment methods associated with the account + PaymentMethodSummary: + type: object + properties: + id: + type: string + description: The unique identifier for the payment method + type: + type: string + description: The type of the payment method + activationDate: + type: string + description: The date when the payment method was activated + expirationDate: + type: string + description: The date when the payment method will expire + PaymentMethod: + type: object + properties: + id: + type: string + description: The unique identifier for the payment method + type: + type: string + description: The type of the payment method + activationDate: + type: string + description: The date when the payment method was activated + expirationDate: + type: string + description: The date when the payment method will expire + availableBalance: + type: string + description: The available balance of the payment method + cardNumber: + type: string + description: The card number of the payment method + Beneficiary: + type: object + properties: + id: + type: string + description: The unique identifier for the beneficiary + fullName: + type: string + description: The full name of the beneficiary + bankCode: + type: string + description: The bank code of the beneficiary's bank + bankName: + type: string + description: The name of the beneficiary's bank \ No newline at end of file diff --git a/app/backend/src/main/resources/application.properties b/app/copilot/copilot-backend/src/main/resources/application.properties similarity index 78% rename from app/backend/src/main/resources/application.properties rename to app/copilot/copilot-backend/src/main/resources/application.properties index 2133743..765adaa 100644 --- a/app/backend/src/main/resources/application.properties +++ b/app/copilot/copilot-backend/src/main/resources/application.properties @@ -15,4 +15,9 @@ logging.level.com.microsoft.openai.samples.rag.ask.approaches.semantickernel=DEB server.error.include-message=always # Support for User Assigned Managed identity -azure.identity.client-id=${AZURE_CLIENT_ID:system-managed-identity} \ No newline at end of file +azure.identity.client-id=${AZURE_CLIENT_ID:system-managed-identity} + +# Business api endpoints +transactions.api.url=${TRANSACTIONS_API_SERVER_URL} +accounts.api.url=${ACCOUNTS_API_SERVER_URL} +payments.api.url=${PAYMENTS_API_SERVER_URL} \ No newline at end of file diff --git a/app/copilot/copilot-backend/src/main/resources/payments.yaml b/app/copilot/copilot-backend/src/main/resources/payments.yaml new file mode 100644 index 0000000..49ab21b --- /dev/null +++ b/app/copilot/copilot-backend/src/main/resources/payments.yaml @@ -0,0 +1,60 @@ +openapi: 3.0.3 +info: + title: Payment API + version: 1.0.0 +paths: + /payments: + post: + operationId: submitPayment + summary: Submit a payment request + description: Submit a payment request + requestBody: + required: true + description: Payment to submit + content: + application/json: + schema: + $ref: '#/components/schemas/Payment' + responses: + '200': + description: Payment request submitted successfully + '400': + description: Invalid request body + '500': + description: Internal server error +components: + schemas: + Payment: + type: object + properties: + description: + type: string + description: Description of the payment + recipientName: + type: string + description: Name of the recipient + recipientBankCode: + type: string + description: Bank code of the recipient + accountId: + type: string + description: ID of the account + paymentMethodId: + type: string + description: ID of the payment method + paymentType: + type: string + description: 'The type of payment: creditcard, banktransfer, directdebit, visa, mastercard, paypal, etc.' + amount: + type: string + description: Amount of the payment + timestamp: + type: string + description: Timestamp of the payment + requestBodies: + Payment: + content: + application/json: + schema: + $ref: '#/components/schemas/Payment' + description: Payment object to submit \ No newline at end of file diff --git a/app/copilot/copilot-backend/src/main/resources/transaction-history.yaml b/app/copilot/copilot-backend/src/main/resources/transaction-history.yaml new file mode 100644 index 0000000..200771f --- /dev/null +++ b/app/copilot/copilot-backend/src/main/resources/transaction-history.yaml @@ -0,0 +1,94 @@ +openapi: 3.0.3 +info: + title: Transaction History and Reporting API + version: 1.0.0 +paths: + /transactions/{accountid}: + get: + summary: Get transactions list. + description: Gets the transactions lists. They can be filtered based on recipient name + operationId: getTransactionsByRecipientName + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: integer + example: 123456 + - name: recipient_name + description: Name of the payee, recipient + in: query + required: false + schema: + type: string + example: contoso + + responses: + '200': + description: A list of transactions for a specific recipient + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Transaction' + post: + operationId: notifyTransaction + summary: Notify the banking transaction so that it's being stored in the history + description: Notify the banking transaction so that it's being stored in the history + parameters: + - name: accountid + description: id of specific account. + in: path + required: true + schema: + type: integer + example: 123456 + requestBody: + required: true + description: transaction to notify + content: + application/json: + schema: + $ref: '#/components/schemas/Transaction' + responses: + '200': + description: Payment request submitted successfully + '400': + description: Invalid request body + '500': + description: Internal server error +components: + schemas: + Transaction: + type: object + properties: + id: + type: string + description: 'The unique identifier for the transaction' + description: + type: string + description: 'The description of the transaction which contains reason for the payment and other details' + type: + type: string + description: 'The transaction type expressed as income or outcome transaction' + recipientName: + type: string + description: 'The name of the recipient' + recipientBankReference: + type: string + description: 'The bank reference of the recipient' + accountId: + type: string + description: 'The account ID associated with the transaction' + paymentType: + type: string + description: 'The type of payment: creditcard, banktransfer, directdebit, visa, mastercard, paypal, etc.' + amount: + type: string + description: 'The amount of the transaction' + timestamp: + type: string + format: date-time + description: 'The timestamp of the transaction' \ No newline at end of file diff --git a/app/copilot/mvnw b/app/copilot/mvnw new file mode 100644 index 0000000..19529dd --- /dev/null +++ b/app/copilot/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/app/copilot/mvnw.cmd b/app/copilot/mvnw.cmd new file mode 100644 index 0000000..b150b91 --- /dev/null +++ b/app/copilot/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/app/copilot/pom.xml b/app/copilot/pom.xml new file mode 100644 index 0000000..006453c --- /dev/null +++ b/app/copilot/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + com.microsoft.openai.samples.assistant + copilot-parent + 1.0.0-SNAPSHOT + pom + + + copilot-backend + semantickernel-openapi-plugin + + + + + + + \ No newline at end of file diff --git a/app/copilot/semantickernel-openapi-plugin/pom.xml b/app/copilot/semantickernel-openapi-plugin/pom.xml new file mode 100644 index 0000000..da5faa5 --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/pom.xml @@ -0,0 +1,152 @@ + + + 4.0.0 + + com.microsoft.openai.samples.assistant + copilot-parent + 1.0.0-SNAPSHOT + ../pom.xml + + + semantickernel-openapi-plugin + semantickernel-openapi-plugin + 1.1.5-SNAPSHOT + jar + + + 17 + 1.1.5 + + + + + + com.microsoft.semantic-kernel + semantickernel-bom + ${semantic-kernel.version} + pom + import + + + com.microsoft.semantic-kernel + semantickernel-parent + ${semantic-kernel.version} + pom + import + + + + + + + com.microsoft.semantic-kernel + semantickernel-api + + + + org.apache.logging.log4j + log4j-api + runtime + + + org.apache.logging.log4j + log4j-core + runtime + + + org.apache.logging.log4j + log4j-slf4j2-impl + runtime + + + com.fasterxml.jackson.core + jackson-databind + compile + + + com.fasterxml.jackson.core + jackson-core + compile + + + com.azure + azure-identity + + + io.swagger.parser.v3 + swagger-parser + 2.1.22 + + + com.microsoft.semantic-kernel + semantickernel-aiservices-openai + + + org.apache.logging.log4j + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j2-impl + test + + + + + + bug-check + + + false + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + + true + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + + org.codehaus.mojo + exec-maven-plugin + + + run-sample + + java + + + + + com.microsoft.semantickernel.samples.syntaxexamples.${sample} + + + + + diff --git a/app/copilot/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/OpenAPIHttpRequestPlugin.java b/app/copilot/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/OpenAPIHttpRequestPlugin.java new file mode 100644 index 0000000..e37b089 --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/OpenAPIHttpRequestPlugin.java @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.openapi; + +import com.azure.core.http.ContentType; +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.semantickernel.contextvariables.ContextVariable; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.parameters.PathParameter; +import io.swagger.v3.oas.models.parameters.QueryParameter; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; + +/** + * Plugin for making HTTP requests specifically to endpoints discovered via OpenAPI. + */ +public class OpenAPIHttpRequestPlugin { + + private static final Logger LOGGER = LoggerFactory.getLogger(OpenAPIHttpRequestPlugin.class); + + private final String serverUrl; + private final String path; + private final PathItem pathItem; + private final HttpClient client; + private final HttpMethod method; + private final Operation operation; + private final HttpHeaders httpHeaders; + + public OpenAPIHttpRequestPlugin( + HttpMethod method, + String serverUrl, + String path, + PathItem pathItem, + HttpClient client, + HttpHeaders httpHeaders, + Operation operation) { + this.method = method; + this.serverUrl = serverUrl; + this.path = path; + this.pathItem = pathItem; + this.client = client; + this.httpHeaders = httpHeaders; + this.operation = operation; + } + + /** + * Executes the HTTP request and return the body of the response. + * + * @param arguments The arguments to the http request. + * @return The body of the response. + */ + public Mono execute(KernelFunctionArguments arguments) { + String body = getBody(arguments); + String query = buildQueryString(arguments); + String path = buildQueryPath(arguments); + + String url; + if (!query.isEmpty()) { + url = serverUrl + path + "?" + query; + } else { + url = serverUrl + path; + } + + HttpRequest request = new HttpRequest(method, url); + + HttpHeaders headers = new HttpHeaders(); + if (httpHeaders != null) { + headers = headers.setAllHttpHeaders(httpHeaders); + } + + if (body != null) { + headers = headers.add(HttpHeaderName.CONTENT_TYPE, ContentType.APPLICATION_JSON); + request = request.setBody(body); + } + + if (headers.getSize() > 0) { + request.setHeaders(headers); + } + + LOGGER.debug("Executing {} {}", method.name(), url); + if (body != null) { + LOGGER.debug("Body: {}", body); + } + + return client + .send(request) + .flatMap(response -> { + if (response.getStatusCode() >= 400) { + return Mono.error(new RuntimeException( + "Request failed with status code: " + response.getStatusCode())); + } else { + return Mono.just(response); + } + }) + .flatMap(HttpResponse::getBodyAsString) + .doOnNext(response -> LOGGER.debug("Request response: {}", response)); + } + + private static @Nullable String getBody(KernelFunctionArguments arguments) { + String body = null; + if (arguments.containsKey("requestbody")) { + ContextVariable requestBody = arguments.get("requestbody"); + if (requestBody != null) { + try { + JsonNode tree = new ObjectMapper().readTree(requestBody.getValue(String.class)); + body = tree.toPrettyString(); + } catch (JsonProcessingException e) { + body = requestBody.getValue(String.class); + } + } + arguments.remove("requestbody"); + } + return body; + } + + private String buildQueryPath(KernelFunctionArguments arguments) { + return getParameterStreamOfArguments(arguments) + .filter(p -> p instanceof PathParameter) + .reduce(path, (path, parameter) -> { + String name = parameter.getName(); + String rendered = getRenderedParameter(arguments, name); + + return path.replaceAll("\\{" + name + "}", rendered); + }, (a, b) -> a + b); + } + + private static String getRenderedParameter( + KernelFunctionArguments arguments, String name) { + ContextVariable value = arguments.get(name); + + if (value == null) { + throw new IllegalArgumentException("Missing value for path parameter: " + name); + } + String rendered = value.getValue(String.class); + + if (rendered == null) { + throw new IllegalArgumentException("Path parameter value is null: " + name); + } + return URLEncoder.encode(rendered, StandardCharsets.US_ASCII); + } + + private String buildQueryString(KernelFunctionArguments arguments) { + return getParameterStreamOfArguments(arguments) + .filter(p -> p instanceof QueryParameter) + .map(parameter -> { + String name = parameter.getName(); + String rendered = getRenderedParameter(arguments, name); + return name + "=" + rendered; + }) + .collect(Collectors.joining("&")); + } + + private Stream getParameterStreamOfArguments( + KernelFunctionArguments arguments) { + if (operation.getParameters() == null) { + return Stream.empty(); + } + return arguments + .keySet() + .stream() + .map(contextVariable -> pathItem + .getGet() + .getParameters() + .stream() + .filter(param -> param.getName().equalsIgnoreCase(contextVariable)).findFirst()) + .filter(Optional::isPresent) + .map(Optional::get); + } +} \ No newline at end of file diff --git a/app/copilot/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/SemanticKernelOpenAPIImporter.java b/app/copilot/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/SemanticKernelOpenAPIImporter.java new file mode 100644 index 0000000..c1be538 --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/src/main/java/com/microsoft/semantickernel/samples/openapi/SemanticKernelOpenAPIImporter.java @@ -0,0 +1,618 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.samples.openapi; + +import com.azure.core.http.ContentType; +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpMethod; +import com.azure.core.util.Header; +import com.azure.core.util.HttpClientOptions; +import com.fasterxml.jackson.core.JsonParser.Feature; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.semantickernel.exceptions.SKException; +import com.microsoft.semantickernel.plugin.KernelPlugin; +import com.microsoft.semantickernel.semanticfunctions.InputVariable; +import com.microsoft.semantickernel.semanticfunctions.KernelFunction; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionArguments; +import com.microsoft.semantickernel.semanticfunctions.OutputVariable; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.BooleanSchema; +import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.MapSchema; +import io.swagger.v3.oas.models.media.MediaType; +import io.swagger.v3.oas.models.media.NumberSchema; +import io.swagger.v3.oas.models.media.ObjectSchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.parameters.Parameter; +import io.swagger.v3.oas.models.parameters.RequestBody; +import io.swagger.v3.oas.models.servers.Server; +import io.swagger.v3.parser.OpenAPIV3Parser; +import io.swagger.v3.parser.core.models.ParseOptions; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.apache.commons.text.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SemanticKernelOpenAPIImporter { + + private static final Logger LOGGER = LoggerFactory.getLogger( + SemanticKernelOpenAPIImporter.class); + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String pluginName; + private String schema; + private HttpHeaders httpHeaders; + private HttpClient client; + private Function, String> serverSelector; + + public Builder withPluginName(String pluginName) { + this.pluginName = pluginName; + return this; + } + + public Builder withSchema(String schema) { + this.schema = schema; + return this; + } + + public Builder withHttpHeaders(HttpHeaders httpHeaders) { + this.httpHeaders = httpHeaders; + return this; + } + + public Builder withClient(HttpClient client) { + this.client = client; + return this; + } + + public Builder withServerSelector(Function, String> serverSelector) { + this.serverSelector = serverSelector; + return this; + } + + public Builder withServer(String serverUrl) { + this.serverSelector = (ignore) -> serverUrl; + return this; + } + + public KernelPlugin build() { + return SemanticKernelOpenAPIImporter.fromSchema(pluginName, schema, httpHeaders, client, + serverSelector); + } + } + + public static KernelPlugin fromSchema( + String pluginName, + String schema, + @Nullable HttpHeaders httpHeaders, + @Nullable HttpClient client, + @Nullable Function, String> serverSelector) { + ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolve(true); + parseOptions.setResolveFully(true); + + OpenAPI openAPI = new OpenAPIV3Parser().readContents(schema, null, parseOptions) + .getOpenAPI(); + + client = getHttpClient(httpHeaders, client); + + Paths paths = openAPI.getPaths(); + + String serverUrl; + + if (serverSelector != null) { + serverUrl = serverSelector.apply(openAPI.getServers()); + } else { + serverUrl = openAPI.getServers().get(0).getUrl(); + if (openAPI.getServers().size() > 1) { + LOGGER.warn("Multiple servers found, using the first one: {}", serverUrl); + } + } + + Map> functions = getFunctions( + client, + pluginName, + paths, + serverUrl, + httpHeaders); + + return new KernelPlugin( + pluginName, + openAPI.getInfo().getDescription(), + functions); + } + + private static HttpClient getHttpClient( + @Nullable HttpHeaders httpHeaders, + @Nullable HttpClient client) { + + // Currently this does not apply as the Netty client does not obey the headers provided in the + // HttpClientOptions, however they may do one day + if (httpHeaders != null && client != null) { + throw new IllegalArgumentException( + "Both httpHeaders and client cannot be provided at the same time"); + } + + HttpClientOptions options = new HttpClientOptions(); + if (httpHeaders != null) { + options.setHeaders(httpHeaders.stream() + .map(header -> new Header(header.getName(), header.getValuesList())) + .toList()); + } + + if (client == null) { + client = HttpClient.createDefault(options); + } + return client; + } + + private static Map> getFunctions( + HttpClient client, + String pluginName, + Paths paths, + String serverUrl, + HttpHeaders headers) { + return paths + .entrySet() + .stream() + .flatMap((entry) -> { + return formOperation( + client, + pluginName, + serverUrl, + entry.getKey(), + entry.getValue(), + headers) + .stream(); + }) + .collect(Collectors.toMap(KernelFunction::getName, (func) -> func)); + } + + private static List> formOperation( + HttpClient client, + String pluginName, + String serverUrl, + String path, + PathItem pathItem, + HttpHeaders headers) { + + List> plugins = new ArrayList<>(); + + if (pathItem.getGet() != null) { + var function = getKernelFunctionFromRequest( + client, + pluginName, + serverUrl, + path, + pathItem, + pathItem.getGet(), + headers, + HttpMethod.GET); + + plugins.add(function); + } + if (pathItem.getDelete() != null) { + var function = getKernelFunctionFromRequest( + client, + pluginName, + serverUrl, + path, + pathItem, + pathItem.getDelete(), + headers, + HttpMethod.DELETE); + + plugins.add(function); + } + if (pathItem.getPost() != null) { + var function = getKernelFunctionFromRequest( + client, + pluginName, + serverUrl, + path, + pathItem, + pathItem.getPost(), + headers, + HttpMethod.POST); + + plugins.add(function); + } + if (pathItem.getPut() != null) { + var function = getKernelFunctionFromRequest( + client, + pluginName, + serverUrl, + path, + pathItem, + pathItem.getPut(), + headers, + HttpMethod.PUT); + + plugins.add(function); + } + if (pathItem.getPatch() != null) { + var function = getKernelFunctionFromRequest( + client, + pluginName, + serverUrl, + path, + pathItem, + pathItem.getPatch(), + headers, + HttpMethod.PATCH); + + plugins.add(function); + } + if (pathItem.getOptions() != null) { + var function = getKernelFunctionFromRequest( + client, + pluginName, + serverUrl, + path, + pathItem, + pathItem.getOptions(), + headers, + HttpMethod.OPTIONS); + + plugins.add(function); + } + if (pathItem.getHead() != null) { + var function = getKernelFunctionFromRequest( + client, + pluginName, + serverUrl, + path, + pathItem, + pathItem.getHead(), + headers, + HttpMethod.HEAD); + + plugins.add(function); + } + if (pathItem.getTrace() != null) { + var function = getKernelFunctionFromRequest( + client, + pluginName, + serverUrl, + path, + pathItem, + pathItem.getTrace(), + headers, + HttpMethod.TRACE); + + plugins.add(function); + } + + return plugins; + } + + private static @Nullable KernelFunction getKernelFunctionFromRequest( + HttpClient client, + String pluginName, + String serverUrl, + String path, + PathItem pathItem, + Operation operation, + HttpHeaders headers, + HttpMethod method) { + + List variableList = getInputVariables(operation); + OutputVariable ov = getOutputVariable(operation); + String description = getDescription(operation.getDescription()); + + OpenAPIHttpRequestPlugin plugin = new OpenAPIHttpRequestPlugin( + method, + serverUrl, + path, + pathItem, + client, + headers, + operation); + + RequestBody requestBody = operation.getRequestBody(); + + if (requestBody != null) { + if (requestBody.getContent().get(ContentType.APPLICATION_JSON) != null) { + MediaType mediaType = requestBody.getContent().get(ContentType.APPLICATION_JSON); + Schema schema = mediaType.getSchema(); + + String example = renderJsonExample(schema); + + try { + JsonNode parsed = new ObjectMapper() + .enable(Feature.ALLOW_TRAILING_COMMA) + .readTree(example); + example = parsed.get("root").toPrettyString(); + } catch (JsonProcessingException e) { + throw new RuntimeException("Failed to parse example JSON", e); + } + + List enums = getEnumValues(schema); + + variableList = new ArrayList<>(variableList); + variableList.add(InputVariable.build( + "requestBody", + String.class, + "Example request body:\n" + example, + example, + enums, + true)); + + } else { + LOGGER.warn("No content type found for operation {}", operation.getOperationId()); + } + } + + return buildKernelFunction(pluginName, plugin, operation, description, variableList, ov); + } + + private static String renderJsonExample(Schema schema) { + StringBuilder stringBuilder = new StringBuilder(); + renderJsonExample(stringBuilder, "root", schema); + return "{" + stringBuilder + "}"; + } + + private static void renderJsonExample( + StringBuilder stringBuilder, + String valueKey, + Schema schema) { + + if (schema.getExample() != null) { + if (valueKey != null) { + stringBuilder.append("\"").append(valueKey).append("\": "); + } + + if (schema instanceof StringSchema) { + stringBuilder.append("\"").append(schema.getExample().toString()).append("\","); + } else { + stringBuilder.append(schema.getExample().toString()).append(","); + } + return; + } + + if (schema instanceof IntegerSchema || + schema instanceof StringSchema || + schema instanceof NumberSchema || + schema instanceof BooleanSchema) { + + String value; + + if (schema instanceof IntegerSchema) { + value = "1"; + } else if (schema instanceof StringSchema) { + if (schema.getEnum() != null && !schema.getEnum().isEmpty()) { + value = "\"" + schema.getEnum().get(0).toString() + "\""; + } else { + value = "\"string\""; + } + } else if (schema instanceof NumberSchema) { + value = "1.0"; + } else { + value = "true"; + } + + if (valueKey != null) { + stringBuilder.append("\"").append(valueKey).append("\": "); + } + stringBuilder.append(value).append(","); + + } else if (schema instanceof ObjectSchema objectSchema) { + + if (valueKey != null) { + stringBuilder.append("\"").append(valueKey).append("\": "); + } + + stringBuilder.append("{"); + + objectSchema.getProperties().forEach((key, value) -> { + renderJsonExample(stringBuilder, key, value); + }); + stringBuilder.append("},"); + } else if (schema instanceof ArraySchema arraySchema) { + + if (valueKey != null) { + stringBuilder.append("\"").append(valueKey).append("\": "); + } + + stringBuilder.append("["); + renderJsonExample(stringBuilder, null, + arraySchema.getItems()); + stringBuilder.append("],"); + + } else if (schema instanceof MapSchema) { + throw new SKException("Not yet supported"); + } else { + LOGGER.warn("Unsupported schema type {}", schema.getClass().getName()); + } + } + + private static String renderXmlExample(Schema schema) { + StringBuilder stringBuilder = new StringBuilder(); + renderXmlExample(stringBuilder, null, schema); + return stringBuilder.toString(); + } + + private static void renderXmlExample(StringBuilder stringBuilder, String valueKey, + Schema schema) { + + if (schema.getExample() != null) { + stringBuilder.append("<").append(valueKey).append(">"); + stringBuilder.append(schema.getExample().toString()); + stringBuilder.append(""); + return; + } + + if (schema instanceof IntegerSchema || + schema instanceof StringSchema || + schema instanceof NumberSchema || + schema instanceof BooleanSchema) { + + String value; + + if (schema instanceof IntegerSchema) { + value = "1"; + } else if (schema instanceof StringSchema) { + if (schema.getEnum() != null && !schema.getEnum().isEmpty()) { + value = schema.getEnum().get(0).toString(); + } else { + value = "string"; + } + } else if (schema instanceof NumberSchema) { + value = "1.0"; + } else { + value = "true"; + } + + stringBuilder.append("<").append(valueKey).append(">"); + stringBuilder.append(value); + stringBuilder.append(""); + } else if (schema instanceof ObjectSchema objectSchema) { + + String nameTag = "<" + schema.getXml().getName() + ">"; + String closingNameTag = ""; + + stringBuilder.append(nameTag); + objectSchema.getProperties().forEach((key, value) -> { + renderXmlExample(stringBuilder, key, value); + }); + stringBuilder.append(closingNameTag); + } else if (schema instanceof ArraySchema arraySchema) { + String nameTag = "<" + valueKey + ">"; + String closingNameTag = ""; + stringBuilder.append(nameTag); + renderXmlExample(stringBuilder, arraySchema.getItems().getXml().getName(), + arraySchema.getItems()); + stringBuilder.append(closingNameTag); + } else if (schema instanceof MapSchema) { + throw new SKException("Not yet supported"); + } else { + LOGGER.warn("Unsupported schema type {}", schema.getClass().getName()); + } + } + + private static KernelFunction buildKernelFunction( + String pluginName, + OpenAPIHttpRequestPlugin plugin, + Operation operation, + String description, + List variableList, + OutputVariable ov) { + + try { + Method method = OpenAPIHttpRequestPlugin.class.getMethod("execute", + KernelFunctionArguments.class); + + return KernelFunction + .createFromMethod(method, plugin) + .withFunctionName(operation.getOperationId()) + .withPluginName(pluginName) + .withDescription(description) + .withParameters(variableList) + .withReturnParameter(ov) + .build(); + } catch (NoSuchMethodException e) { + return null; + } + } + + private static OutputVariable getOutputVariable(Operation get) { + return new OutputVariable<>( + get.getDescription(), + String.class); + } + + private static @Nullable String getDescription(String get) { + String description = get; + if (description != null) { + description = StringEscapeUtils.escapeXml11(description); + description = description.replaceAll("\\n", ""); + } + return description; + } + + private static List getInputVariables(Operation get) { + List variableList = List.of(); + if (get.getParameters() != null) { + variableList = get + .getParameters() + .stream() + .map(parameter -> { + Class type = getType(parameter); + + Object def = parameter.getSchema().getDefault(); + + String description = getDescription(parameter.getDescription()); + + List enums = getEnumValues(parameter.getSchema()); + + return InputVariable.build( + parameter.getName(), + type, + description, + def != null ? def.toString() : null, + enums, + parameter.getRequired()); + }) + .collect(Collectors.toList()); + } + return variableList; + } + + private static List getEnumValues(Schema parameter) { + if (parameter.getEnum() == null) { + return null; + } + return parameter.getEnum() + .stream() + .map(Object::toString) + .toList(); + } + + private static Class getType(Parameter parameter) { + Class type = String.class; + String t = parameter.getSchema().getType(); + if (t != null) { + switch (t) { + case "integer": + type = Integer.class; + break; + case "number": + type = Double.class; + break; + case "boolean": + type = Boolean.class; + break; + case "array": + type = List.class; + break; + + // Not sure if we can support this + case "null": + break; + case "object": + break; + default: + break; + } + } + return type; + } +} diff --git a/app/copilot/semantickernel-openapi-plugin/src/test/java/ExampleAdoptiumImporter.java b/app/copilot/semantickernel-openapi-plugin/src/test/java/ExampleAdoptiumImporter.java new file mode 100644 index 0000000..b1c77c3 --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/src/test/java/ExampleAdoptiumImporter.java @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All rights reserved. +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.implementation.EmbeddedResourceLoader; +import com.microsoft.semantickernel.orchestration.FunctionResult; +import com.microsoft.semantickernel.orchestration.ToolCallBehavior; +import com.microsoft.semantickernel.plugin.KernelPlugin; +import com.microsoft.semantickernel.samples.openapi.SemanticKernelOpenAPIImporter; +import com.microsoft.semantickernel.semanticfunctions.KernelFunction; +import java.io.FileNotFoundException; + +public class ExampleAdoptiumImporter { + + public static void main(String[] args) throws FileNotFoundException { + runJson(); + runYaml(); + } + + private static void runJson() throws FileNotFoundException { + String json = EmbeddedResourceLoader.readFile("adoptium.json", + ExampleAdoptiumImporter.class); + makeRequests(json); + } + + private static void runYaml() throws FileNotFoundException { + String yaml = EmbeddedResourceLoader.readFile("adoptium.yaml", + ExampleAdoptiumImporter.class); + makeRequests(yaml); + } + + private static void makeRequests(String schema) { + KernelPlugin plugin = SemanticKernelOpenAPIImporter + .builder() + .withPluginName("adoptium") + .withSchema(schema) + .build(); + + Kernel kernel = ExampleOpenAPIParent.kernelBuilder() + .withPlugin(plugin) + .build(); + + performRequest(kernel, + """ + Parse the java version 11.0.22+9? + """); + + performRequest(kernel, + """ + What are the names of the GA versions of Java available between 8 and 9 inclusive? + """); + + performRequest(kernel, + """ + What is the version of the latest GA release of Java between 11 and 12? + """); + } + + private static void performRequest(Kernel kernel, String request) { + KernelFunction function = KernelFunction.createFromPrompt(request) + .build(); + + FunctionResult result = kernel.invokeAsync(function) + .withResultType(String.class) + .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true)) + .block(); + + System.out.println(result.getResult()); + } + +} diff --git a/app/copilot/semantickernel-openapi-plugin/src/test/java/ExampleAuthenticatedRequestImporter.java b/app/copilot/semantickernel-openapi-plugin/src/test/java/ExampleAuthenticatedRequestImporter.java new file mode 100644 index 0000000..3be277c --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/src/test/java/ExampleAuthenticatedRequestImporter.java @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft. All rights reserved. +import com.azure.core.http.HttpHeader; +import com.azure.core.http.HttpHeaders; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.implementation.EmbeddedResourceLoader; +import com.microsoft.semantickernel.orchestration.FunctionResult; +import com.microsoft.semantickernel.orchestration.ToolCallBehavior; +import com.microsoft.semantickernel.plugin.KernelPlugin; +import com.microsoft.semantickernel.samples.openapi.SemanticKernelOpenAPIImporter; +import com.microsoft.semantickernel.semanticfunctions.KernelFunction; +import com.sun.net.httpserver.HttpServer; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.List; +import org.junit.jupiter.api.Assertions; + +public class ExampleAuthenticatedRequestImporter { + + public static void main(String[] args) throws IOException { + + String yaml = EmbeddedResourceLoader.readFile("authenticatedRequest.yaml", + ExampleAuthenticatedRequestImporter.class); + + KernelPlugin plugin = SemanticKernelOpenAPIImporter + .builder() + .withPluginName("authenticatedRequest") + .withSchema(yaml) + .withServer("http://127.0.0.1:8890") + .withHttpHeaders(new HttpHeaders(List.of( + new HttpHeader("Authorization", "Bearer 1234")))) + .build(); + + Kernel kernel = ExampleOpenAPIParent.kernelBuilder() + .withPlugin(plugin) + .build(); + HttpServer httpServer = null; + try { + httpServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 8890), 0); + httpServer.createContext("/state", exchange -> { + + Assertions.assertTrue( + exchange.getRequestHeaders().containsKey("Authorization"), + "Authorization header not found"); + + exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0); + exchange.getResponseBody().write( + """ + { + "state": "running" + } + """ + .stripIndent() + .getBytes(StandardCharsets.UTF_8)); + exchange.close(); + }); + httpServer.start(); + + performRequest(kernel, + """ + What is the current state of the system? + """.stripIndent()); + + } finally { + httpServer.stop(0); + } + + } + + private static void performRequest(Kernel kernel, String request) { + KernelFunction function = KernelFunction.createFromPrompt(request) + .build(); + + FunctionResult result = kernel.invokeAsync(function) + .withResultType(String.class) + .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true)) + .block(); + + System.out.println(result.getResult()); + } + +} diff --git a/app/copilot/semantickernel-openapi-plugin/src/test/java/ExampleOpenAPIParent.java b/app/copilot/semantickernel-openapi-plugin/src/test/java/ExampleOpenAPIParent.java new file mode 100644 index 0000000..9cfc77a --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/src/test/java/ExampleOpenAPIParent.java @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.credential.KeyCredential; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.Kernel.Builder; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; + +public class ExampleOpenAPIParent { + + private static final String PLUGIN_DIR = System.getenv("PLUGIN_DIR") == null ? "." + : System.getenv("PLUGIN_DIR"); + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); + + // Only required if AZURE_CLIENT_KEY is set + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + private static final String MODEL_ID = System.getenv() + .getOrDefault("MODEL_ID", "gpt-35-turbo-2"); + + public static Builder kernelBuilder() { + + OpenAIAsyncClient client; + + if (AZURE_CLIENT_KEY != null) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(AZURE_CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + ChatCompletionService openAIChatCompletion = ChatCompletionService.builder() + .withOpenAIAsyncClient(client) + .withModelId(MODEL_ID) + .build(); + + return Kernel.builder() + .withAIService(ChatCompletionService.class, openAIChatCompletion); + } + +} diff --git a/app/copilot/semantickernel-openapi-plugin/src/test/java/ExamplePetstoreImporter.java b/app/copilot/semantickernel-openapi-plugin/src/test/java/ExamplePetstoreImporter.java new file mode 100644 index 0000000..4996707 --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/src/test/java/ExamplePetstoreImporter.java @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.implementation.EmbeddedResourceLoader; +import com.microsoft.semantickernel.orchestration.FunctionResult; +import com.microsoft.semantickernel.orchestration.ToolCallBehavior; +import com.microsoft.semantickernel.plugin.KernelPlugin; +import com.microsoft.semantickernel.samples.openapi.SemanticKernelOpenAPIImporter; +import com.microsoft.semantickernel.semanticfunctions.KernelFunction; +import java.io.FileNotFoundException; + +public class ExamplePetstoreImporter { + + public static void main(String[] args) throws FileNotFoundException { + + String yaml = EmbeddedResourceLoader.readFile("petstore.yaml", + ExamplePetstoreImporter.class); + + KernelPlugin plugin = SemanticKernelOpenAPIImporter + .builder() + .withPluginName("petstore") + .withSchema(yaml) + .withServer("http://localhost:8090/api/v3") + .build(); + + Kernel kernel = ExampleOpenAPIParent.kernelBuilder() + .withPlugin(plugin) + .build(); + + performRequest(kernel, + """ + Create a user with the following details: + username: john_doe + first name: John + last name: Doe + email: john_doe@example.com + password: password123 + phone: 1234567890 + user status: 1"""); + performRequest(kernel, "Add a new cat called Sandy to the store."); + performRequest(kernel, "List all available pets."); + + } + + private static void performRequest(Kernel kernel, String request) { + KernelFunction function = KernelFunction.createFromPrompt(request) + .build(); + + FunctionResult result = kernel.invokeAsync(function) + .withResultType(String.class) + .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true)) + .block(); + + System.out.println(result.getResult()); + } + +} diff --git a/app/copilot/semantickernel-openapi-plugin/src/test/resources/adoptium.json b/app/copilot/semantickernel-openapi-plugin/src/test/resources/adoptium.json new file mode 100644 index 0000000..9b439f4 --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/src/test/resources/adoptium.json @@ -0,0 +1,1857 @@ +{ + "openapi" : "3.0.3", + "info" : { + "title" : "v3", + "description" : "", + "version" : "3.0.0" + }, + "servers" : [ { + "url" : "https://api.adoptium.net" + }, { + "url" : "https://staging-api.adoptium.net" + } ], + "tags" : [ { + "name" : "Assets" + }, { + "name" : "Binary" + }, { + "name" : "Checksum" + }, { + "name" : "Installer" + }, { + "name" : "Release Info" + }, { + "name" : "Signature" + }, { + "name" : "Version" + } ], + "paths" : { + "/v3/assets/feature_releases/{feature_version}/{release_type}" : { + "get" : { + "tags" : [ "Assets" ], + "summary" : "Returns release information", + "description" : "List of information about builds that match the current query", + "operationId" : "searchReleases", + "parameters" : [ { + "name" : "feature_version", + "in" : "path", + "description" : "\n

\n Feature release version you wish to download. Feature versions are whole numbers e.g. 8,11,16,17,18.\n

\n

\n Available Feature versions can be obtained from \n https://api.adoptium.net/v3/info/available_releases\n

\n", + "required" : true, + "schema" : { + "default" : 8, + "type" : "integer", + "nullable" : true + } + }, { + "name" : "release_type", + "in" : "path", + "description" : "\n

Type of release. Either a release version, known as General Availability(ga) or an Early Access(ea)

\n", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ReleaseType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "architecture", + "in" : "query", + "description" : "Architecture", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Architecture" + }, { + "nullable" : true + } ] + } + }, { + "name" : "before", + "in" : "query", + "description" : "

Return binaries whose updated_at is before the given date/time. When a date is given the match is inclusive of the given day.

  • 2020-01-21
  • 2020-01-21T10:15:30
  • 20200121
  • 2020-12-21T10:15:30Z
  • 2020-12-21+01:00

", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/DateTime" + }, { + "nullable" : true + } ] + } + }, { + "name" : "c_lib", + "in" : "query", + "description" : "C Lib type, typically would imply image_type has been set to staticlibs", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/CLib" + }, { + "nullable" : true + } ] + } + }, { + "name" : "heap_size", + "in" : "query", + "description" : "Heap Size", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/HeapSize" + }, { + "nullable" : true + } ] + } + }, { + "name" : "image_type", + "in" : "query", + "description" : "Image Type", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ImageType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "jvm_impl", + "in" : "query", + "description" : "JVM Implementation", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/JvmImpl" + }, { + "nullable" : true + } ] + } + }, { + "name" : "os", + "in" : "query", + "description" : "Operating System", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/OperatingSystem" + }, { + "nullable" : true + } ] + } + }, { + "name" : "page", + "in" : "query", + "description" : "Pagination page number", + "required" : false, + "schema" : { + "default" : 0, + "type" : "integer", + "nullable" : true + } + }, { + "name" : "page_size", + "in" : "query", + "description" : "Pagination page size", + "required" : false, + "schema" : { + "default" : 10, + "maximum" : 20, + "type" : "integer", + "nullable" : true + } + }, { + "name" : "project", + "in" : "query", + "description" : "Project", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/project" + }, { + "nullable" : true + } ] + } + }, { + "name" : "sort_method", + "in" : "query", + "description" : "Result sort method", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SortMethod" + }, { + "nullable" : true + } ] + } + }, { + "name" : "sort_order", + "in" : "query", + "description" : "Result sort order", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SortOrder" + }, { + "nullable" : true + } ] + } + }, { + "name" : "vendor", + "in" : "query", + "description" : "

Vendor of the binary. This is the organisation that produced the binary package.

", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Vendor" + }, { + "nullable" : true + } ] + } + } ], + "responses" : { + "200" : { + "description" : "search results matching criteria", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Release" + } + } + } + } + }, + "400" : { + "description" : "bad input parameter" + } + } + } + }, + "/v3/assets/latest/{feature_version}/{jvm_impl}" : { + "get" : { + "tags" : [ "Assets" ], + "summary" : "Returns list of latest assets for the given feature version and jvm impl", + "operationId" : "getLatestAssets", + "parameters" : [ { + "name" : "feature_version", + "in" : "path", + "description" : "\n

\n Feature release version you wish to download. Feature versions are whole numbers e.g. 8,11,16,17,18.\n

\n

\n Available Feature versions can be obtained from \n https://api.adoptium.net/v3/info/available_releases\n

\n", + "required" : true, + "schema" : { + "default" : 8, + "type" : "integer" + } + }, { + "name" : "jvm_impl", + "in" : "path", + "description" : "JVM Implementation", + "required" : true, + "schema" : { + "$ref" : "#/components/schemas/JvmImpl" + } + }, { + "name" : "architecture", + "in" : "query", + "description" : "Architecture", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Architecture" + }, { + "nullable" : true + } ] + } + }, { + "name" : "image_type", + "in" : "query", + "description" : "Image Type", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ImageType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "os", + "in" : "query", + "description" : "Operating System", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/OperatingSystem" + }, { + "nullable" : true + } ] + } + }, { + "name" : "vendor", + "in" : "query", + "description" : "

Vendor of the binary. This is the organisation that produced the binary package.

", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Vendor" + }, { + "nullable" : true + } ] + } + } ], + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/BinaryAssetView" + } + } + } + } + } + } + } + }, + "/v3/assets/release_name/{vendor}/{release_name}" : { + "get" : { + "tags" : [ "Assets" ], + "summary" : "Returns release information", + "description" : "List of releases with the given release name", + "operationId" : "getReleaseInfo", + "parameters" : [ { + "name" : "release_name", + "in" : "path", + "description" : "Name of the release i.e ", + "required" : true, + "schema" : { + "type" : "string", + "nullable" : true + } + }, { + "name" : "vendor", + "in" : "path", + "description" : "

Vendor of the binary. This is the organisation that produced the binary package.

", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Vendor" + }, { + "nullable" : true + } ] + } + }, { + "name" : "architecture", + "in" : "query", + "description" : "Architecture", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Architecture" + }, { + "nullable" : true + } ] + } + }, { + "name" : "c_lib", + "in" : "query", + "description" : "C Lib type, typically would imply image_type has been set to staticlibs", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/CLib" + }, { + "nullable" : true + } ] + } + }, { + "name" : "heap_size", + "in" : "query", + "description" : "Heap Size", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/HeapSize" + }, { + "nullable" : true + } ] + } + }, { + "name" : "image_type", + "in" : "query", + "description" : "Image Type", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ImageType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "jvm_impl", + "in" : "query", + "description" : "JVM Implementation", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/JvmImpl" + }, { + "nullable" : true + } ] + } + }, { + "name" : "os", + "in" : "query", + "description" : "Operating System", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/OperatingSystem" + }, { + "nullable" : true + } ] + } + }, { + "name" : "project", + "in" : "query", + "description" : "Project", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/project" + }, { + "nullable" : true + } ] + } + } ], + "responses" : { + "200" : { + "description" : "Release with the given vendor and name", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Release" + } + } + } + }, + "400" : { + "description" : "bad input parameter" + }, + "404" : { + "description" : "no releases match the request" + }, + "500" : { + "description" : "multiple releases match the request" + } + } + } + }, + "/v3/assets/version/{version}" : { + "get" : { + "tags" : [ "Assets" ], + "summary" : "Returns release information about the specified version.", + "description" : "List of information about builds that match the current query ", + "operationId" : "searchReleasesByVersion", + "parameters" : [ { + "name" : "version", + "in" : "path", + "description" : "\nJava version range (maven style) of versions to include.\n\ne.g:\n* `11.0.4.1+11.1`\n* `[1.0,2.0)`\n* `(,1.0]`\n\nDetails of maven version ranges can be found at\n \n", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "architecture", + "in" : "query", + "description" : "Architecture", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Architecture" + }, { + "nullable" : true + } ] + } + }, { + "name" : "c_lib", + "in" : "query", + "description" : "C Lib type, typically would imply image_type has been set to staticlibs", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/CLib" + }, { + "nullable" : true + } ] + } + }, { + "name" : "heap_size", + "in" : "query", + "description" : "Heap Size", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/HeapSize" + }, { + "nullable" : true + } ] + } + }, { + "name" : "image_type", + "in" : "query", + "description" : "Image Type", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ImageType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "jvm_impl", + "in" : "query", + "description" : "JVM Implementation", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/JvmImpl" + }, { + "nullable" : true + } ] + } + }, { + "name" : "lts", + "in" : "query", + "description" : "Include only LTS releases", + "required" : false, + "schema" : { + "type" : "boolean", + "nullable" : true + } + }, { + "name" : "os", + "in" : "query", + "description" : "Operating System", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/OperatingSystem" + }, { + "nullable" : true + } ] + } + }, { + "name" : "page", + "in" : "query", + "description" : "Pagination page number", + "required" : false, + "schema" : { + "default" : 0, + "type" : "integer", + "nullable" : true + } + }, { + "name" : "page_size", + "in" : "query", + "description" : "Pagination page size", + "required" : false, + "schema" : { + "default" : 10, + "maximum" : 20, + "type" : "integer", + "nullable" : true + } + }, { + "name" : "project", + "in" : "query", + "description" : "Project", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/project" + }, { + "nullable" : true + } ] + } + }, { + "name" : "release_type", + "in" : "query", + "description" : "\n

Type of release. Either a release version, known as General Availability(ga) or an Early Access(ea)

\n", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ReleaseType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "semver", + "in" : "query", + "description" : "Indicates that any version arguments provided in this request were Adoptium semantic versions", + "required" : false, + "schema" : { + "default" : false, + "type" : "boolean", + "nullable" : true + } + }, { + "name" : "sort_method", + "in" : "query", + "description" : "Result sort method", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SortMethod" + }, { + "nullable" : true + } ] + } + }, { + "name" : "sort_order", + "in" : "query", + "description" : "Result sort order", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SortOrder" + }, { + "nullable" : true + } ] + } + }, { + "name" : "vendor", + "in" : "query", + "description" : "

Vendor of the binary. This is the organisation that produced the binary package.

", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Vendor" + }, { + "nullable" : true + } ] + } + } ], + "responses" : { + "200" : { + "description" : "search results matching criteria", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Release" + } + } + } + } + }, + "400" : { + "description" : "bad input parameter" + } + } + } + }, + "/v3/binary/latest/{feature_version}/{release_type}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}" : { + "get" : { + "tags" : [ "Binary" ], + "summary" : "Redirects to the binary that matches your current query", + "description" : "Redirects to the binary that matches your current query", + "operationId" : "getBinary", + "parameters" : [ { + "name" : "arch", + "in" : "path", + "description" : "Architecture", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Architecture" + }, { + "nullable" : true + } ] + } + }, { + "name" : "feature_version", + "in" : "path", + "description" : "\n

\n Feature release version you wish to download. Feature versions are whole numbers e.g. 8,11,16,17,18.\n

\n

\n Available Feature versions can be obtained from \n https://api.adoptium.net/v3/info/available_releases\n

\n", + "required" : true, + "schema" : { + "default" : 8, + "type" : "integer", + "nullable" : true + } + }, { + "name" : "heap_size", + "in" : "path", + "description" : "Heap Size", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/HeapSize" + }, { + "nullable" : true + } ] + } + }, { + "name" : "image_type", + "in" : "path", + "description" : "Image Type", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ImageType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "jvm_impl", + "in" : "path", + "description" : "JVM Implementation", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/JvmImpl" + }, { + "nullable" : true + } ] + } + }, { + "name" : "os", + "in" : "path", + "description" : "Operating System", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/OperatingSystem" + }, { + "nullable" : true + } ] + } + }, { + "name" : "release_type", + "in" : "path", + "description" : "\n

Type of release. Either a release version, known as General Availability(ga) or an Early Access(ea)

\n", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ReleaseType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "vendor", + "in" : "path", + "description" : "

Vendor of the binary. This is the organisation that produced the binary package.

", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Vendor" + }, { + "nullable" : true + } ] + } + }, { + "name" : "c_lib", + "in" : "query", + "description" : "C Lib type, typically would imply image_type has been set to staticlibs", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/CLib" + }, { + "nullable" : true + } ] + } + }, { + "name" : "project", + "in" : "query", + "description" : "Project", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/project" + }, { + "nullable" : true + } ] + } + } ], + "responses" : { + "307" : { + "description" : "link to binary that matches your current query" + }, + "400" : { + "description" : "bad input parameter" + }, + "404" : { + "description" : "No matching binary found" + } + } + } + }, + "/v3/binary/version/{release_name}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}" : { + "get" : { + "tags" : [ "Binary" ], + "summary" : "Redirects to the binary that matches your current query", + "description" : "Redirects to the binary that matches your current query", + "operationId" : "getBinaryByVersion", + "parameters" : [ { + "name" : "arch", + "in" : "path", + "description" : "Architecture", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Architecture" + }, { + "nullable" : true + } ] + } + }, { + "name" : "heap_size", + "in" : "path", + "description" : "Heap Size", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/HeapSize" + }, { + "nullable" : true + } ] + } + }, { + "name" : "image_type", + "in" : "path", + "description" : "Image Type", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ImageType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "jvm_impl", + "in" : "path", + "description" : "JVM Implementation", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/JvmImpl" + }, { + "nullable" : true + } ] + } + }, { + "name" : "os", + "in" : "path", + "description" : "Operating System", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/OperatingSystem" + }, { + "nullable" : true + } ] + } + }, { + "name" : "release_name", + "in" : "path", + "description" : "\n

\n Name of the release as displayed in github or https://adoptopenjdk.net/ e.g\n jdk-11.0.4+11, jdk8u172-b00-201807161800.\n

\n

\n A list of release names can be obtained from \n https://api.adoptium.net/v3/info/release_names\n

\n", + "required" : true, + "schema" : { + "default" : "jdk-11.0.6+10", + "type" : "string", + "nullable" : true + } + }, { + "name" : "vendor", + "in" : "path", + "description" : "

Vendor of the binary. This is the organisation that produced the binary package.

", + "required" : true, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Vendor" + }, { + "nullable" : true + } ] + } + }, { + "name" : "c_lib", + "in" : "query", + "description" : "C Lib type, typically would imply image_type has been set to staticlibs", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/CLib" + }, { + "nullable" : true + } ] + } + }, { + "name" : "project", + "in" : "query", + "description" : "Project", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/project" + }, { + "nullable" : true + } ] + } + } ], + "responses" : { + "307" : { + "description" : "link to binary that matches your current query" + }, + "400" : { + "description" : "bad input parameter" + }, + "404" : { + "description" : "No matching binary found" + } + } + } + }, + "/v3/info/available_releases" : { + "get" : { + "tags" : [ "Release Info" ], + "summary" : "Returns information about available releases", + "operationId" : "getAvailableReleases", + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ReleaseInfo" + } + } + } + } + } + } + }, + "/v3/info/release_names" : { + "get" : { + "tags" : [ "Release Info" ], + "summary" : "Returns a list of all release names", + "operationId" : "getReleaseNames", + "parameters" : [ { + "name" : "architecture", + "in" : "query", + "description" : "Architecture", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Architecture" + }, { + "nullable" : true + } ] + } + }, { + "name" : "c_lib", + "in" : "query", + "description" : "C Lib type, typically would imply image_type has been set to staticlibs", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/CLib" + }, { + "nullable" : true + } ] + } + }, { + "name" : "heap_size", + "in" : "query", + "description" : "Heap Size", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/HeapSize" + }, { + "nullable" : true + } ] + } + }, { + "name" : "image_type", + "in" : "query", + "description" : "Image Type", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ImageType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "jvm_impl", + "in" : "query", + "description" : "JVM Implementation", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/JvmImpl" + }, { + "nullable" : true + } ] + } + }, { + "name" : "lts", + "in" : "query", + "description" : "Include only LTS releases", + "required" : false, + "schema" : { + "type" : "boolean", + "nullable" : true + } + }, { + "name" : "os", + "in" : "query", + "description" : "Operating System", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/OperatingSystem" + }, { + "nullable" : true + } ] + } + }, { + "name" : "page", + "in" : "query", + "description" : "Pagination page number", + "required" : false, + "schema" : { + "default" : 0, + "type" : "integer", + "nullable" : true + } + }, { + "name" : "page_size", + "in" : "query", + "description" : "Pagination page size", + "required" : false, + "schema" : { + "default" : 10, + "maximum" : 20, + "type" : "integer", + "nullable" : true + } + }, { + "name" : "project", + "in" : "query", + "description" : "Project", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/project" + }, { + "nullable" : true + } ] + } + }, { + "name" : "release_type", + "in" : "query", + "description" : "\n

Type of release. Either a release version, known as General Availability(ga) or an Early Access(ea)

\n", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ReleaseType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "semver", + "in" : "query", + "description" : "Indicates that any version arguments provided in this request were Adoptium semantic versions", + "required" : false, + "schema" : { + "default" : false, + "type" : "boolean", + "nullable" : true + } + }, { + "name" : "sort_method", + "in" : "query", + "description" : "Result sort method", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SortMethod" + }, { + "nullable" : true + } ] + } + }, { + "name" : "sort_order", + "in" : "query", + "description" : "Result sort order", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SortOrder" + }, { + "nullable" : true + } ] + } + }, { + "name" : "vendor", + "in" : "query", + "description" : "

Vendor of the binary. This is the organisation that produced the binary package.

", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Vendor" + }, { + "nullable" : true + } ] + } + }, { + "name" : "version", + "in" : "query", + "description" : "\nJava version range (maven style) of versions to include.\n\ne.g:\n* `11.0.4.1+11.1`\n* `[1.0,2.0)`\n* `(,1.0]`\n\nDetails of maven version ranges can be found at\n \n", + "required" : false, + "schema" : { + "type" : "string", + "nullable" : true + } + } ], + "responses" : { + "200" : { + "description" : "A list of all release names", + "content" : { + "application/json" : { + "schema" : { + "required" : [ "releases" ], + "type" : "object", + "properties" : { + "releases" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } + } + } + } + } + } + } + }, + "/v3/info/release_versions" : { + "get" : { + "tags" : [ "Release Info" ], + "summary" : "Returns a list of all release versions", + "operationId" : "getReleaseVersions", + "parameters" : [ { + "name" : "architecture", + "in" : "query", + "description" : "Architecture", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Architecture" + }, { + "nullable" : true + } ] + } + }, { + "name" : "c_lib", + "in" : "query", + "description" : "C Lib type, typically would imply image_type has been set to staticlibs", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/CLib" + }, { + "nullable" : true + } ] + } + }, { + "name" : "heap_size", + "in" : "query", + "description" : "Heap Size", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/HeapSize" + }, { + "nullable" : true + } ] + } + }, { + "name" : "image_type", + "in" : "query", + "description" : "Image Type", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ImageType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "jvm_impl", + "in" : "query", + "description" : "JVM Implementation", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/JvmImpl" + }, { + "nullable" : true + } ] + } + }, { + "name" : "lts", + "in" : "query", + "description" : "Include only LTS releases", + "required" : false, + "schema" : { + "type" : "boolean", + "nullable" : true + } + }, { + "name" : "os", + "in" : "query", + "description" : "Operating System", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/OperatingSystem" + }, { + "nullable" : true + } ] + } + }, { + "name" : "page", + "in" : "query", + "description" : "Pagination page number", + "required" : false, + "schema" : { + "default" : 0, + "type" : "integer", + "nullable" : true + } + }, { + "name" : "page_size", + "in" : "query", + "description" : "Pagination page size", + "required" : false, + "schema" : { + "default" : 10, + "maximum" : 50, + "type" : "integer", + "nullable" : true + } + }, { + "name" : "project", + "in" : "query", + "description" : "Project", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/project" + }, { + "nullable" : true + } ] + } + }, { + "name" : "release_type", + "in" : "query", + "description" : "\n

Type of release. Either a release version, known as General Availability(ga) or an Early Access(ea)

\n", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/ReleaseType" + }, { + "nullable" : true + } ] + } + }, { + "name" : "semver", + "in" : "query", + "description" : "Indicates that any version arguments provided in this request were Adoptium semantic versions", + "required" : false, + "schema" : { + "default" : false, + "type" : "boolean", + "nullable" : true + } + }, { + "name" : "sort_method", + "in" : "query", + "description" : "Result sort method", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SortMethod" + }, { + "nullable" : true + } ] + } + }, { + "name" : "sort_order", + "in" : "query", + "description" : "Result sort order", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/SortOrder" + }, { + "nullable" : true + } ] + } + }, { + "name" : "vendor", + "in" : "query", + "description" : "

Vendor of the binary. This is the organisation that produced the binary package.

", + "required" : false, + "schema" : { + "allOf" : [ { + "$ref" : "#/components/schemas/Vendor" + }, { + "nullable" : true + } ] + } + }, { + "name" : "version", + "in" : "query", + "description" : "\nJava version range (maven style) of versions to include.\n\ne.g:\n* `11.0.4.1+11.1`\n* `[1.0,2.0)`\n* `(,1.0]`\n\nDetails of maven version ranges can be found at\n \n", + "required" : false, + "schema" : { + "type" : "string", + "nullable" : true + } + } ], + "responses" : { + "200" : { + "description" : "A list of all release versions", + "content" : { + "application/json" : { + "schema" : { + "required" : [ "versions" ], + "type" : "object", + "properties" : { + "versions" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/VersionData" + } + } + } + } + } + } + } + } + } + }, + "/v3/version/{version}" : { + "get" : { + "tags" : [ "Version" ], + "summary" : "Parses a java version string", + "description" : "Parses a java version string and returns that data in a structured format", + "operationId" : "parseVersion", + "parameters" : [ { + "name" : "version", + "in" : "path", + "description" : "Version", + "required" : true, + "schema" : { + "type" : "string", + "nullable" : true + } + } ], + "responses" : { + "400" : { + "description" : "bad input parameter" + } + } + } + } + }, + "components" : { + "schemas" : { + "AdoptiumJvmImpl" : { + "enum" : [ "hotspot" ], + "type" : "string" + }, + "AdoptiumVendor" : { + "default" : "eclipse", + "enum" : [ "eclipse" ], + "type" : "string", + "example" : "eclipse" + }, + "Architecture" : { + "enum" : [ "x64", "x86", "x32", "ppc64", "ppc64le", "s390x", "aarch64", "arm", "sparcv9", "riscv64" ], + "type" : "string" + }, + "Binary" : { + "required" : [ "os", "architecture", "image_type", "jvm_impl", "heap_size", "updated_at", "project" ], + "type" : "object", + "properties" : { + "os" : { + "$ref" : "#/components/schemas/OperatingSystem" + }, + "architecture" : { + "$ref" : "#/components/schemas/Architecture" + }, + "image_type" : { + "$ref" : "#/components/schemas/ImageType" + }, + "c_lib" : { + "type" : "string", + "allOf" : [ { + "$ref" : "#/components/schemas/CLib" + } ], + "nullable" : true + }, + "jvm_impl" : { + "$ref" : "#/components/schemas/JvmImpl" + }, + "package" : { + "$ref" : "#/components/schemas/Package" + }, + "installer" : { + "$ref" : "#/components/schemas/Installer" + }, + "heap_size" : { + "$ref" : "#/components/schemas/HeapSize" + }, + "download_count" : { + "format" : "int64", + "type" : "integer", + "example" : 3899 + }, + "updated_at" : { + "$ref" : "#/components/schemas/DateTime" + }, + "scm_ref" : { + "type" : "string", + "example" : "dd28d6d2cde2b931caf94ac2422a2ad082ea62f0beee3bf7057317c53093de93", + "nullable" : true + }, + "project" : { + "$ref" : "#/components/schemas/project" + } + } + }, + "BinaryAssetView" : { + "required" : [ "release_name", "release_link" ], + "type" : "object", + "properties" : { + "binary" : { + "$ref" : "#/components/schemas/Binary" + }, + "release_name" : { + "type" : "string", + "example" : "jdk8u162-b12_openj9-0.8.0" + }, + "release_link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/tag/jdk8u162-b12_openj9-0.8.0" + }, + "vendor" : { + "$ref" : "#/components/schemas/Vendor" + }, + "version" : { + "$ref" : "#/components/schemas/VersionData" + } + } + }, + "CLib" : { + "enum" : [ "musl", "glibc" ], + "type" : "string" + }, + "DateTime" : { + "description" : "

Date/time. When only a date is given the time is set to the end of the given day.

  • 2020-01-21
  • 2020-01-21T10:15:30
  • 20200121
  • 2020-12-21T10:15:30Z
  • 2020-12-21+01:00

", + "type" : "string" + }, + "HeapSize" : { + "enum" : [ "normal", "large" ], + "type" : "string", + "example" : "normal" + }, + "ImageType" : { + "enum" : [ "jdk", "jre", "testimage", "debugimage", "staticlibs", "sources", "sbom" ], + "type" : "string", + "example" : "jdk" + }, + "Installer" : { + "required" : [ "name", "link" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "example" : "OpenJDK8U-jre_x86-32_windows_hotspot_8u212b04.msi" + }, + "link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk8-binaries/ga/download/jdk8u212-b04/OpenJDK8U-jre_x86-32_windows_hotspot_8u212b04.msi" + }, + "size" : { + "format" : "int64", + "type" : "integer", + "example" : 82573385 + }, + "checksum" : { + "type" : "string", + "example" : "dd28d6d2cde2b931caf94ac2422a2ad082ea62f0beee3bf7057317c53093de93", + "nullable" : true + }, + "checksum_link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/download/jdk8u162-b12_openj9-0.8.0/OpenJDK8-OPENJ9_x64_Linux_jdk8u162-b12_openj9-0.8.0.tar.gz.sha256.txt", + "nullable" : true + }, + "signature_link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk11-upstream-binaries/releases/download/jdk-11.0.5%2B10/OpenJDK11U-jdk_x64_linux_11.0.5_10.tar.gz.sign", + "nullable" : true + }, + "download_count" : { + "format" : "int64", + "type" : "integer", + "example" : 2 + }, + "metadata_link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/download/jdk8u162-b12_openj9-0.8.0/OpenJDK8-OPENJ9_x64_Linux_jdk8u162-b12_openj9-0.8.0.tar.gz.json", + "nullable" : true + } + } + }, + "JvmImpl" : { + "$ref" : "#/components/schemas/AdoptiumJvmImpl" + }, + "OperatingSystem" : { + "enum" : [ "linux", "windows", "mac", "solaris", "aix", "alpine-linux" ], + "type" : "string" + }, + "Package" : { + "required" : [ "name", "link" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "example" : "OpenJDK8U-jre_x86-32_windows_hotspot_8u212b04.msi" + }, + "link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk8-binaries/ga/download/jdk8u212-b04/OpenJDK8U-jre_x86-32_windows_hotspot_8u212b04.msi" + }, + "size" : { + "format" : "int64", + "type" : "integer", + "example" : 82573385 + }, + "checksum" : { + "type" : "string", + "example" : "dd28d6d2cde2b931caf94ac2422a2ad082ea62f0beee3bf7057317c53093de93", + "nullable" : true + }, + "checksum_link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/download/jdk8u162-b12_openj9-0.8.0/OpenJDK8-OPENJ9_x64_Linux_jdk8u162-b12_openj9-0.8.0.tar.gz.sha256.txt", + "nullable" : true + }, + "signature_link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk11-upstream-binaries/releases/download/jdk-11.0.5%2B10/OpenJDK11U-jdk_x64_linux_11.0.5_10.tar.gz.sign", + "nullable" : true + }, + "download_count" : { + "format" : "int64", + "type" : "integer", + "example" : 2 + }, + "metadata_link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/download/jdk8u162-b12_openj9-0.8.0/OpenJDK8-OPENJ9_x64_Linux_jdk8u162-b12_openj9-0.8.0.tar.gz.json", + "nullable" : true + } + } + }, + "Release" : { + "required" : [ "id", "release_link", "release_name", "timestamp", "updated_at", "binaries", "release_type", "vendor", "version_data" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "example" : "VXNlci0xMA==" + }, + "release_link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/tag/jdk8u162-b12_openj9-0.8.0" + }, + "release_name" : { + "type" : "string", + "example" : "jdk8u162-b12_openj9-0.8.0" + }, + "timestamp" : { + "$ref" : "#/components/schemas/DateTime" + }, + "updated_at" : { + "$ref" : "#/components/schemas/DateTime" + }, + "binaries" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Binary" + } + }, + "download_count" : { + "format" : "int64", + "type" : "integer", + "example" : 7128 + }, + "release_type" : { + "$ref" : "#/components/schemas/ReleaseType" + }, + "vendor" : { + "$ref" : "#/components/schemas/Vendor" + }, + "version_data" : { + "$ref" : "#/components/schemas/VersionData" + }, + "source" : { + "type" : "object", + "allOf" : [ { + "$ref" : "#/components/schemas/SourcePackage" + } ], + "nullable" : true + }, + "release_notes" : { + "type" : "object", + "allOf" : [ { + "$ref" : "#/components/schemas/ReleaseNotesPackage" + } ], + "nullable" : true + }, + "aqavit_results_link" : { + "type" : "string", + "example" : "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6%2B10/AQAvitTapFiles.tar.gz", + "nullable" : true + } + } + }, + "ReleaseInfo" : { + "required" : [ "available_releases", "available_lts_releases" ], + "type" : "object", + "properties" : { + "available_releases" : { + "description" : "The versions for which adopt have produced a ga release", + "type" : "array", + "items" : { + "format" : "int32", + "type" : "integer" + }, + "example" : [ 8, 9, 10, 11, 12, 13, 14 ] + }, + "available_lts_releases" : { + "description" : "The LTS versions for which adopt have produced a ga release", + "type" : "array", + "items" : { + "format" : "int32", + "type" : "integer" + }, + "example" : [ 8, 11 ] + }, + "most_recent_lts" : { + "format" : "int32", + "description" : "The highest LTS version for which adopt have produced a ga release", + "type" : "integer", + "example" : 11 + }, + "most_recent_feature_release" : { + "format" : "int32", + "description" : "The highest version (LTS or not) for which adopt have produced a ga release", + "type" : "integer", + "example" : 13 + }, + "most_recent_feature_version" : { + "format" : "int32", + "description" : "The highest version (LTS or not) for which we have produced a build, this may be a version that has not yet produced a ga release", + "type" : "integer", + "example" : 15 + }, + "tip_version" : { + "format" : "int32", + "description" : "The version that is currently in development at openjdk", + "type" : "integer", + "example" : 15 + } + } + }, + "ReleaseNote" : { + "required" : [ "id" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "title" : { + "type" : "string", + "nullable" : true + }, + "priority" : { + "type" : "string", + "nullable" : true + }, + "component" : { + "type" : "string", + "nullable" : true + }, + "subcomponent" : { + "type" : "string", + "nullable" : true + }, + "link" : { + "type" : "string", + "nullable" : true + }, + "type" : { + "type" : "string", + "nullable" : true + }, + "backportOf" : { + "type" : "string", + "nullable" : true + } + } + }, + "ReleaseNotesPackage" : { + "required" : [ "name", "link" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "example" : "OpenJDK8U-sources_8u232b09.tar.gz" + }, + "link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk8-upstream-binaries/releases/download/jdk8u232-b09/OpenJDK8U-sources_8u232b09.tar.gz" + }, + "size" : { + "format" : "int64", + "type" : "integer", + "example" : 82573385 + } + } + }, + "ReleaseType" : { + "default" : "ga", + "enum" : [ "ga", "ea" ], + "type" : "string" + }, + "SortMethod" : { + "description" : "DEFAULT sort order is by: version, then date, then name, then id. DATE sorts by date, then version, then name, then id.", + "default" : "DEFAULT", + "enum" : [ "DEFAULT", "DATE" ], + "type" : "string" + }, + "SortOrder" : { + "default" : "DESC", + "enum" : [ "ASC", "DESC" ], + "type" : "string" + }, + "SourcePackage" : { + "required" : [ "name", "link" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "example" : "OpenJDK8U-sources_8u232b09.tar.gz" + }, + "link" : { + "type" : "string", + "example" : "https://github.com/AdoptOpenJDK/openjdk8-upstream-binaries/releases/download/jdk8u232-b09/OpenJDK8U-sources_8u232b09.tar.gz" + }, + "size" : { + "format" : "int64", + "type" : "integer", + "example" : 82573385 + } + } + }, + "StatsSource" : { + "default" : "all", + "enum" : [ "github", "dockerhub", "all" ], + "type" : "string", + "example" : "all" + }, + "Vendor" : { + "$ref" : "#/components/schemas/AdoptiumVendor" + }, + "VersionData" : { + "required" : [ "semver", "openjdk_version" ], + "type" : "object", + "properties" : { + "major" : { + "format" : "int32", + "type" : "integer" + }, + "minor" : { + "format" : "int32", + "type" : "integer" + }, + "security" : { + "format" : "int32", + "type" : "integer" + }, + "patch" : { + "format" : "int32", + "type" : "integer", + "nullable" : true + }, + "pre" : { + "type" : "string", + "nullable" : true + }, + "adopt_build_number" : { + "format" : "int32", + "type" : "integer", + "nullable" : true + }, + "semver" : { + "type" : "string", + "example" : "11.0.0+28" + }, + "openjdk_version" : { + "type" : "string", + "example" : "11.0.4+10-201907081820" + }, + "build" : { + "format" : "int32", + "type" : "integer" + }, + "optional" : { + "type" : "string", + "nullable" : true + } + } + }, + "project" : { + "description" : "Project", + "default" : "jdk", + "enum" : [ "jdk", "valhalla", "metropolis", "jfr", "shenandoah" ], + "type" : "string" + } + } + } +} \ No newline at end of file diff --git a/app/copilot/semantickernel-openapi-plugin/src/test/resources/adoptium.yaml b/app/copilot/semantickernel-openapi-plugin/src/test/resources/adoptium.yaml new file mode 100644 index 0000000..a19b7d2 --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/src/test/resources/adoptium.yaml @@ -0,0 +1,1381 @@ +--- +openapi: 3.0.3 +info: + title: v3 + description: "" + version: 3.0.0 +servers: + - url: https://api.adoptium.net + - url: https://staging-api.adoptium.net +tags: + - name: Assets + - name: Binary + - name: Checksum + - name: Installer + - name: Release Info + - name: Signature + - name: Version +paths: + /v3/assets/feature_releases/{feature_version}/{release_type}: + get: + tags: + - Assets + summary: Returns release information + description: List of information about builds that match the current query + operationId: searchReleases + parameters: + - name: feature_version + in: path + description: "\n

\n Feature release version you wish to download. Feature\ + \ versions are whole numbers e.g. 8,11,16,17,18.\n

\n

\n\ + \ Available Feature versions can be obtained from \n https://api.adoptium.net/v3/info/available_releases\n

\n" + required: true + schema: + default: 8 + type: integer + nullable: true + - name: release_type + in: path + description: |2 + +

Type of release. Either a release version, known as General Availability(ga) or an Early Access(ea)

+ required: true + schema: + allOf: + - $ref: '#/components/schemas/ReleaseType' + - nullable: true + - name: architecture + in: query + description: Architecture + required: false + schema: + allOf: + - $ref: '#/components/schemas/Architecture' + - nullable: true + - name: before + in: query + description: '

Return binaries whose updated_at is before the given date/time. + When a date is given the match is inclusive of the given day.

  • 2020-01-21
  • +
  • 2020-01-21T10:15:30
  • 20200121
  • 2020-12-21T10:15:30Z
  • +
  • 2020-12-21+01:00

' + required: false + schema: + allOf: + - $ref: '#/components/schemas/DateTime' + - nullable: true + - name: c_lib + in: query + description: "C Lib type, typically would imply image_type has been set to\ + \ staticlibs" + required: false + schema: + allOf: + - $ref: '#/components/schemas/CLib' + - nullable: true + - name: heap_size + in: query + description: Heap Size + required: false + schema: + allOf: + - $ref: '#/components/schemas/HeapSize' + - nullable: true + - name: image_type + in: query + description: Image Type + required: false + schema: + allOf: + - $ref: '#/components/schemas/ImageType' + - nullable: true + - name: jvm_impl + in: query + description: JVM Implementation + required: false + schema: + allOf: + - $ref: '#/components/schemas/JvmImpl' + - nullable: true + - name: os + in: query + description: Operating System + required: false + schema: + allOf: + - $ref: '#/components/schemas/OperatingSystem' + - nullable: true + - name: page + in: query + description: Pagination page number + required: false + schema: + default: 0 + type: integer + nullable: true + - name: page_size + in: query + description: Pagination page size + required: false + schema: + default: 10 + maximum: 20 + type: integer + nullable: true + - name: project + in: query + description: Project + required: false + schema: + allOf: + - $ref: '#/components/schemas/project' + - nullable: true + - name: sort_method + in: query + description: Result sort method + required: false + schema: + allOf: + - $ref: '#/components/schemas/SortMethod' + - nullable: true + - name: sort_order + in: query + description: Result sort order + required: false + schema: + allOf: + - $ref: '#/components/schemas/SortOrder' + - nullable: true + - name: vendor + in: query + description:

Vendor of the binary. This is the organisation that produced + the binary package.

+ required: false + schema: + allOf: + - $ref: '#/components/schemas/Vendor' + - nullable: true + responses: + "200": + description: search results matching criteria + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Release' + "400": + description: bad input parameter + /v3/assets/latest/{feature_version}/{jvm_impl}: + get: + tags: + - Assets + summary: Returns list of latest assets for the given feature version and jvm + impl + operationId: getLatestAssets + parameters: + - name: feature_version + in: path + description: "\n

\n Feature release version you wish to download. Feature\ + \ versions are whole numbers e.g. 8,11,16,17,18.\n

\n

\n\ + \ Available Feature versions can be obtained from \n https://api.adoptium.net/v3/info/available_releases\n

\n" + required: true + schema: + default: 8 + type: integer + - name: jvm_impl + in: path + description: JVM Implementation + required: true + schema: + $ref: '#/components/schemas/JvmImpl' + - name: architecture + in: query + description: Architecture + required: false + schema: + allOf: + - $ref: '#/components/schemas/Architecture' + - nullable: true + - name: image_type + in: query + description: Image Type + required: false + schema: + allOf: + - $ref: '#/components/schemas/ImageType' + - nullable: true + - name: os + in: query + description: Operating System + required: false + schema: + allOf: + - $ref: '#/components/schemas/OperatingSystem' + - nullable: true + - name: vendor + in: query + description:

Vendor of the binary. This is the organisation that produced + the binary package.

+ required: false + schema: + allOf: + - $ref: '#/components/schemas/Vendor' + - nullable: true + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/BinaryAssetView' + /v3/assets/release_name/{vendor}/{release_name}: + get: + tags: + - Assets + summary: Returns release information + description: List of releases with the given release name + operationId: getReleaseInfo + parameters: + - name: release_name + in: path + description: 'Name of the release i.e ' + required: true + schema: + type: string + nullable: true + - name: vendor + in: path + description:

Vendor of the binary. This is the organisation that produced + the binary package.

+ required: true + schema: + allOf: + - $ref: '#/components/schemas/Vendor' + - nullable: true + - name: architecture + in: query + description: Architecture + required: false + schema: + allOf: + - $ref: '#/components/schemas/Architecture' + - nullable: true + - name: c_lib + in: query + description: "C Lib type, typically would imply image_type has been set to\ + \ staticlibs" + required: false + schema: + allOf: + - $ref: '#/components/schemas/CLib' + - nullable: true + - name: heap_size + in: query + description: Heap Size + required: false + schema: + allOf: + - $ref: '#/components/schemas/HeapSize' + - nullable: true + - name: image_type + in: query + description: Image Type + required: false + schema: + allOf: + - $ref: '#/components/schemas/ImageType' + - nullable: true + - name: jvm_impl + in: query + description: JVM Implementation + required: false + schema: + allOf: + - $ref: '#/components/schemas/JvmImpl' + - nullable: true + - name: os + in: query + description: Operating System + required: false + schema: + allOf: + - $ref: '#/components/schemas/OperatingSystem' + - nullable: true + - name: project + in: query + description: Project + required: false + schema: + allOf: + - $ref: '#/components/schemas/project' + - nullable: true + responses: + "200": + description: Release with the given vendor and name + content: + application/json: + schema: + $ref: '#/components/schemas/Release' + "400": + description: bad input parameter + "404": + description: no releases match the request + "500": + description: multiple releases match the request + /v3/assets/version/{version}: + get: + tags: + - Assets + summary: Returns release information about the specified version. + description: 'List of information about builds that match the current query ' + operationId: searchReleasesByVersion + parameters: + - name: version + in: path + description: |2 + + Java version range (maven style) of versions to include. + + e.g: + * `11.0.4.1+11.1` + * `[1.0,2.0)` + * `(,1.0]` + + Details of maven version ranges can be found at + + required: true + schema: + type: string + - name: architecture + in: query + description: Architecture + required: false + schema: + allOf: + - $ref: '#/components/schemas/Architecture' + - nullable: true + - name: c_lib + in: query + description: "C Lib type, typically would imply image_type has been set to\ + \ staticlibs" + required: false + schema: + allOf: + - $ref: '#/components/schemas/CLib' + - nullable: true + - name: heap_size + in: query + description: Heap Size + required: false + schema: + allOf: + - $ref: '#/components/schemas/HeapSize' + - nullable: true + - name: image_type + in: query + description: Image Type + required: false + schema: + allOf: + - $ref: '#/components/schemas/ImageType' + - nullable: true + - name: jvm_impl + in: query + description: JVM Implementation + required: false + schema: + allOf: + - $ref: '#/components/schemas/JvmImpl' + - nullable: true + - name: lts + in: query + description: Include only LTS releases + required: false + schema: + type: boolean + nullable: true + - name: os + in: query + description: Operating System + required: false + schema: + allOf: + - $ref: '#/components/schemas/OperatingSystem' + - nullable: true + - name: page + in: query + description: Pagination page number + required: false + schema: + default: 0 + type: integer + nullable: true + - name: page_size + in: query + description: Pagination page size + required: false + schema: + default: 10 + maximum: 20 + type: integer + nullable: true + - name: project + in: query + description: Project + required: false + schema: + allOf: + - $ref: '#/components/schemas/project' + - nullable: true + - name: release_type + in: query + description: |2 + +

Type of release. Either a release version, known as General Availability(ga) or an Early Access(ea)

+ required: false + schema: + allOf: + - $ref: '#/components/schemas/ReleaseType' + - nullable: true + - name: semver + in: query + description: Indicates that any version arguments provided in this request + were Adoptium semantic versions + required: false + schema: + default: false + type: boolean + nullable: true + - name: sort_method + in: query + description: Result sort method + required: false + schema: + allOf: + - $ref: '#/components/schemas/SortMethod' + - nullable: true + - name: sort_order + in: query + description: Result sort order + required: false + schema: + allOf: + - $ref: '#/components/schemas/SortOrder' + - nullable: true + - name: vendor + in: query + description:

Vendor of the binary. This is the organisation that produced + the binary package.

+ required: false + schema: + allOf: + - $ref: '#/components/schemas/Vendor' + - nullable: true + responses: + "200": + description: search results matching criteria + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Release' + "400": + description: bad input parameter + /v3/checksum/version/{release_name}/{os}/{arch}/{image_type}/{jvm_impl}/{heap_size}/{vendor}: + get: + tags: + - Checksum + summary: Redirects to the checksum of the release that matches your current + query + description: Redirects to the checksum of the release that matches your current + query + operationId: getChecksumByVersion + parameters: + - name: arch + in: path + description: Architecture + required: true + schema: + allOf: + - $ref: '#/components/schemas/Architecture' + - nullable: true + - name: heap_size + in: path + description: Heap Size + required: true + schema: + allOf: + - $ref: '#/components/schemas/HeapSize' + - nullable: true + - name: image_type + in: path + description: Image Type + required: true + schema: + allOf: + - $ref: '#/components/schemas/ImageType' + - nullable: true + - name: jvm_impl + in: path + description: JVM Implementation + required: true + schema: + allOf: + - $ref: '#/components/schemas/JvmImpl' + - nullable: true + - name: os + in: path + description: Operating System + required: true + schema: + allOf: + - $ref: '#/components/schemas/OperatingSystem' + - nullable: true + - name: release_name + in: path + description: "\n

\n Name of the release as displayed in github or https://adoptopenjdk.net/ e.g\n\ + \ jdk-11.0.4+11, jdk8u172-b00-201807161800.\n

\n

\n\ + \ A list of release names can be obtained from \n https://api.adoptium.net/v3/info/release_names\n

\n" + required: true + schema: + default: jdk-11.0.6+10 + type: string + nullable: true + - name: vendor + in: path + description:

Vendor of the binary. This is the organisation that produced + the binary package.

+ required: true + schema: + allOf: + - $ref: '#/components/schemas/Vendor' + - nullable: true + - name: c_lib + in: query + description: "C Lib type, typically would imply image_type has been set to\ + \ staticlibs" + required: false + schema: + allOf: + - $ref: '#/components/schemas/CLib' + - nullable: true + - name: project + in: query + description: Project + required: false + schema: + allOf: + - $ref: '#/components/schemas/project' + - nullable: true + responses: + "307": + description: link to checksum of the release that matches your current query + "400": + description: bad input parameter + "404": + description: No matching signature found + /v3/info/available_releases: + get: + tags: + - Release Info + summary: Returns information about available releases + operationId: getAvailableReleases + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ReleaseInfo' + /v3/info/release_names: + get: + tags: + - Release Info + summary: Returns a list of all release names + operationId: getReleaseNames + parameters: + - name: architecture + in: query + description: Architecture + required: false + schema: + allOf: + - $ref: '#/components/schemas/Architecture' + - nullable: true + - name: c_lib + in: query + description: "C Lib type, typically would imply image_type has been set to\ + \ staticlibs" + required: false + schema: + allOf: + - $ref: '#/components/schemas/CLib' + - nullable: true + - name: heap_size + in: query + description: Heap Size + required: false + schema: + allOf: + - $ref: '#/components/schemas/HeapSize' + - nullable: true + - name: image_type + in: query + description: Image Type + required: false + schema: + allOf: + - $ref: '#/components/schemas/ImageType' + - nullable: true + - name: jvm_impl + in: query + description: JVM Implementation + required: false + schema: + allOf: + - $ref: '#/components/schemas/JvmImpl' + - nullable: true + - name: lts + in: query + description: Include only LTS releases + required: false + schema: + type: boolean + nullable: true + - name: os + in: query + description: Operating System + required: false + schema: + allOf: + - $ref: '#/components/schemas/OperatingSystem' + - nullable: true + - name: page + in: query + description: Pagination page number + required: false + schema: + default: 0 + type: integer + nullable: true + - name: page_size + in: query + description: Pagination page size + required: false + schema: + default: 10 + maximum: 20 + type: integer + nullable: true + - name: project + in: query + description: Project + required: false + schema: + allOf: + - $ref: '#/components/schemas/project' + - nullable: true + - name: release_type + in: query + description: |2 + +

Type of release. Either a release version, known as General Availability(ga) or an Early Access(ea)

+ required: false + schema: + allOf: + - $ref: '#/components/schemas/ReleaseType' + - nullable: true + - name: semver + in: query + description: Indicates that any version arguments provided in this request + were Adoptium semantic versions + required: false + schema: + default: false + type: boolean + nullable: true + - name: sort_method + in: query + description: Result sort method + required: false + schema: + allOf: + - $ref: '#/components/schemas/SortMethod' + - nullable: true + - name: sort_order + in: query + description: Result sort order + required: false + schema: + allOf: + - $ref: '#/components/schemas/SortOrder' + - nullable: true + - name: vendor + in: query + description:

Vendor of the binary. This is the organisation that produced + the binary package.

+ required: false + schema: + allOf: + - $ref: '#/components/schemas/Vendor' + - nullable: true + - name: version + in: query + description: |2 + + Java version range (maven style) of versions to include. + + e.g: + * `11.0.4.1+11.1` + * `[1.0,2.0)` + * `(,1.0]` + + Details of maven version ranges can be found at + + required: false + schema: + type: string + nullable: true + responses: + "200": + description: A list of all release names + content: + application/json: + schema: + required: + - releases + type: object + properties: + releases: + type: array + items: + type: string + /v3/info/release_versions: + get: + tags: + - Release Info + summary: Returns a list of all release versions + operationId: getReleaseVersions + parameters: + - name: architecture + in: query + description: Architecture + required: false + schema: + allOf: + - $ref: '#/components/schemas/Architecture' + - nullable: true + - name: c_lib + in: query + description: "C Lib type, typically would imply image_type has been set to\ + \ staticlibs" + required: false + schema: + allOf: + - $ref: '#/components/schemas/CLib' + - nullable: true + - name: heap_size + in: query + description: Heap Size + required: false + schema: + allOf: + - $ref: '#/components/schemas/HeapSize' + - nullable: true + - name: image_type + in: query + description: Image Type + required: false + schema: + allOf: + - $ref: '#/components/schemas/ImageType' + - nullable: true + - name: jvm_impl + in: query + description: JVM Implementation + required: false + schema: + allOf: + - $ref: '#/components/schemas/JvmImpl' + - nullable: true + - name: lts + in: query + description: Include only LTS releases + required: false + schema: + type: boolean + nullable: true + - name: os + in: query + description: Operating System + required: false + schema: + allOf: + - $ref: '#/components/schemas/OperatingSystem' + - nullable: true + - name: page + in: query + description: Pagination page number + required: false + schema: + default: 0 + type: integer + nullable: true + - name: page_size + in: query + description: Pagination page size + required: false + schema: + default: 10 + maximum: 50 + type: integer + nullable: true + - name: project + in: query + description: Project + required: false + schema: + allOf: + - $ref: '#/components/schemas/project' + - nullable: true + - name: release_type + in: query + description: |2 + +

Type of release. Either a release version, known as General Availability(ga) or an Early Access(ea)

+ required: false + schema: + allOf: + - $ref: '#/components/schemas/ReleaseType' + - nullable: true + - name: semver + in: query + description: Indicates that any version arguments provided in this request + were Adoptium semantic versions + required: false + schema: + default: false + type: boolean + nullable: true + - name: sort_method + in: query + description: Result sort method + required: false + schema: + allOf: + - $ref: '#/components/schemas/SortMethod' + - nullable: true + - name: sort_order + in: query + description: Result sort order + required: false + schema: + allOf: + - $ref: '#/components/schemas/SortOrder' + - nullable: true + - name: vendor + in: query + description:

Vendor of the binary. This is the organisation that produced + the binary package.

+ required: false + schema: + allOf: + - $ref: '#/components/schemas/Vendor' + - nullable: true + - name: version + in: query + description: |2 + + Java version range (maven style) of versions to include. + + e.g: + * `11.0.4.1+11.1` + * `[1.0,2.0)` + * `(,1.0]` + + Details of maven version ranges can be found at + + required: false + schema: + type: string + nullable: true + responses: + "200": + description: A list of all release versions + content: + application/json: + schema: + required: + - versions + type: object + properties: + versions: + type: array + items: + $ref: '#/components/schemas/VersionData' + /v3/version/{version}: + get: + tags: + - Version + summary: Parses a java version string + description: Parses a java version string and returns that data in a structured + format + operationId: parseVersion + parameters: + - name: version + in: path + description: Version + required: true + schema: + type: string + nullable: true + responses: + "400": + description: bad input parameter +components: + schemas: + AdoptiumJvmImpl: + enum: + - hotspot + type: string + AdoptiumVendor: + default: eclipse + enum: + - eclipse + type: string + example: eclipse + Architecture: + enum: + - x64 + - x86 + - x32 + - ppc64 + - ppc64le + - s390x + - aarch64 + - arm + - sparcv9 + - riscv64 + type: string + Binary: + required: + - os + - architecture + - image_type + - jvm_impl + - heap_size + - updated_at + - project + type: object + properties: + os: + $ref: '#/components/schemas/OperatingSystem' + architecture: + $ref: '#/components/schemas/Architecture' + image_type: + $ref: '#/components/schemas/ImageType' + c_lib: + type: string + allOf: + - $ref: '#/components/schemas/CLib' + nullable: true + jvm_impl: + $ref: '#/components/schemas/JvmImpl' + package: + $ref: '#/components/schemas/Package' + installer: + $ref: '#/components/schemas/Installer' + heap_size: + $ref: '#/components/schemas/HeapSize' + download_count: + format: int64 + type: integer + example: 3899 + updated_at: + $ref: '#/components/schemas/DateTime' + scm_ref: + type: string + example: dd28d6d2cde2b931caf94ac2422a2ad082ea62f0beee3bf7057317c53093de93 + nullable: true + project: + $ref: '#/components/schemas/project' + BinaryAssetView: + required: + - release_name + - release_link + type: object + properties: + binary: + $ref: '#/components/schemas/Binary' + release_name: + type: string + example: jdk8u162-b12_openj9-0.8.0 + release_link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/tag/jdk8u162-b12_openj9-0.8.0 + vendor: + $ref: '#/components/schemas/Vendor' + version: + $ref: '#/components/schemas/VersionData' + CLib: + enum: + - musl + - glibc + type: string + DateTime: + description:

Date/time. When only a date is given the time is set to the + end of the given day.

  • 2020-01-21
  • 2020-01-21T10:15:30
  • +
  • 20200121
  • 2020-12-21T10:15:30Z
  • 2020-12-21+01:00
  • +

+ type: string + HeapSize: + enum: + - normal + - large + type: string + example: normal + ImageType: + enum: + - jdk + - jre + - testimage + - debugimage + - staticlibs + - sources + - sbom + type: string + example: jdk + Installer: + required: + - name + - link + type: object + properties: + name: + type: string + example: OpenJDK8U-jre_x86-32_windows_hotspot_8u212b04.msi + link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk8-binaries/ga/download/jdk8u212-b04/OpenJDK8U-jre_x86-32_windows_hotspot_8u212b04.msi + size: + format: int64 + type: integer + example: 82573385 + checksum: + type: string + example: dd28d6d2cde2b931caf94ac2422a2ad082ea62f0beee3bf7057317c53093de93 + nullable: true + checksum_link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/download/jdk8u162-b12_openj9-0.8.0/OpenJDK8-OPENJ9_x64_Linux_jdk8u162-b12_openj9-0.8.0.tar.gz.sha256.txt + nullable: true + signature_link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk11-upstream-binaries/releases/download/jdk-11.0.5%2B10/OpenJDK11U-jdk_x64_linux_11.0.5_10.tar.gz.sign + nullable: true + download_count: + format: int64 + type: integer + example: 2 + metadata_link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/download/jdk8u162-b12_openj9-0.8.0/OpenJDK8-OPENJ9_x64_Linux_jdk8u162-b12_openj9-0.8.0.tar.gz.json + nullable: true + JvmImpl: + $ref: '#/components/schemas/AdoptiumJvmImpl' + OperatingSystem: + enum: + - linux + - windows + - mac + - solaris + - aix + - alpine-linux + type: string + Package: + required: + - name + - link + type: object + properties: + name: + type: string + example: OpenJDK8U-jre_x86-32_windows_hotspot_8u212b04.msi + link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk8-binaries/ga/download/jdk8u212-b04/OpenJDK8U-jre_x86-32_windows_hotspot_8u212b04.msi + size: + format: int64 + type: integer + example: 82573385 + checksum: + type: string + example: dd28d6d2cde2b931caf94ac2422a2ad082ea62f0beee3bf7057317c53093de93 + nullable: true + checksum_link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/download/jdk8u162-b12_openj9-0.8.0/OpenJDK8-OPENJ9_x64_Linux_jdk8u162-b12_openj9-0.8.0.tar.gz.sha256.txt + nullable: true + signature_link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk11-upstream-binaries/releases/download/jdk-11.0.5%2B10/OpenJDK11U-jdk_x64_linux_11.0.5_10.tar.gz.sign + nullable: true + download_count: + format: int64 + type: integer + example: 2 + metadata_link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/download/jdk8u162-b12_openj9-0.8.0/OpenJDK8-OPENJ9_x64_Linux_jdk8u162-b12_openj9-0.8.0.tar.gz.json + nullable: true + Release: + required: + - id + - release_link + - release_name + - timestamp + - updated_at + - binaries + - release_type + - vendor + - version_data + type: object + properties: + id: + type: string + example: VXNlci0xMA== + release_link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk8-openj9-releases/ga/tag/jdk8u162-b12_openj9-0.8.0 + release_name: + type: string + example: jdk8u162-b12_openj9-0.8.0 + timestamp: + $ref: '#/components/schemas/DateTime' + updated_at: + $ref: '#/components/schemas/DateTime' + binaries: + type: array + items: + $ref: '#/components/schemas/Binary' + download_count: + format: int64 + type: integer + example: 7128 + release_type: + $ref: '#/components/schemas/ReleaseType' + vendor: + $ref: '#/components/schemas/Vendor' + version_data: + $ref: '#/components/schemas/VersionData' + source: + type: object + allOf: + - $ref: '#/components/schemas/SourcePackage' + nullable: true + release_notes: + type: object + allOf: + - $ref: '#/components/schemas/ReleaseNotesPackage' + nullable: true + aqavit_results_link: + type: string + example: https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.6%2B10/AQAvitTapFiles.tar.gz + nullable: true + ReleaseInfo: + required: + - available_releases + - available_lts_releases + type: object + properties: + available_releases: + description: The versions for which adopt have produced a ga release + type: array + items: + format: int32 + type: integer + example: + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 + available_lts_releases: + description: The LTS versions for which adopt have produced a ga release + type: array + items: + format: int32 + type: integer + example: + - 8 + - 11 + most_recent_lts: + format: int32 + description: The highest LTS version for which adopt have produced a ga + release + type: integer + example: 11 + most_recent_feature_release: + format: int32 + description: The highest version (LTS or not) for which adopt have produced + a ga release + type: integer + example: 13 + most_recent_feature_version: + format: int32 + description: "The highest version (LTS or not) for which we have produced\ + \ a build, this may be a version that has not yet produced a ga release" + type: integer + example: 15 + tip_version: + format: int32 + description: The version that is currently in development at openjdk + type: integer + example: 15 + ReleaseNote: + required: + - id + type: object + properties: + id: + type: string + title: + type: string + nullable: true + priority: + type: string + nullable: true + component: + type: string + nullable: true + subcomponent: + type: string + nullable: true + link: + type: string + nullable: true + type: + type: string + nullable: true + backportOf: + type: string + nullable: true + ReleaseNotesPackage: + required: + - name + - link + type: object + properties: + name: + type: string + example: OpenJDK8U-sources_8u232b09.tar.gz + link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk8-upstream-binaries/releases/download/jdk8u232-b09/OpenJDK8U-sources_8u232b09.tar.gz + size: + format: int64 + type: integer + example: 82573385 + ReleaseType: + default: ga + enum: + - ga + - ea + type: string + SortMethod: + description: "DEFAULT sort order is by: version, then date, then name, then\ + \ id. DATE sorts by date, then version, then name, then id." + default: DEFAULT + enum: + - DEFAULT + - DATE + type: string + SortOrder: + default: DESC + enum: + - ASC + - DESC + type: string + SourcePackage: + required: + - name + - link + type: object + properties: + name: + type: string + example: OpenJDK8U-sources_8u232b09.tar.gz + link: + type: string + example: https://github.com/AdoptOpenJDK/openjdk8-upstream-binaries/releases/download/jdk8u232-b09/OpenJDK8U-sources_8u232b09.tar.gz + size: + format: int64 + type: integer + example: 82573385 + StatsSource: + default: all + enum: + - github + - dockerhub + - all + type: string + example: all + Vendor: + $ref: '#/components/schemas/AdoptiumVendor' + VersionData: + required: + - semver + - openjdk_version + type: object + properties: + major: + format: int32 + type: integer + minor: + format: int32 + type: integer + security: + format: int32 + type: integer + patch: + format: int32 + type: integer + nullable: true + pre: + type: string + nullable: true + adopt_build_number: + format: int32 + type: integer + nullable: true + semver: + type: string + example: 11.0.0+28 + openjdk_version: + type: string + example: 11.0.4+10-201907081820 + build: + format: int32 + type: integer + optional: + type: string + nullable: true + project: + description: Project + default: jdk + enum: + - jdk + - valhalla + - metropolis + - jfr + - shenandoah + type: string diff --git a/app/copilot/semantickernel-openapi-plugin/src/test/resources/authenticatedRequest.yaml b/app/copilot/semantickernel-openapi-plugin/src/test/resources/authenticatedRequest.yaml new file mode 100644 index 0000000..b38ea8d --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/src/test/resources/authenticatedRequest.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.2 +servers: + - url: / +info: + description: |- + Example of an authenticated api + version: 1.0.1-SNAPSHOT + title: Auth Example +paths: + /state: + get: + summary: Get the latest state of the system + description: The current state the system + operationId: getState + responses: + '200': + description: The current state of the system + security: + - BearerAuth: + - 'read' +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer diff --git a/app/copilot/semantickernel-openapi-plugin/src/test/resources/log4j2.xml b/app/copilot/semantickernel-openapi-plugin/src/test/resources/log4j2.xml new file mode 100644 index 0000000..50a638f --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/copilot/semantickernel-openapi-plugin/src/test/resources/petstore.yaml b/app/copilot/semantickernel-openapi-plugin/src/test/resources/petstore.yaml new file mode 100644 index 0000000..29a3432 --- /dev/null +++ b/app/copilot/semantickernel-openapi-plugin/src/test/resources/petstore.yaml @@ -0,0 +1,822 @@ +# Sample from: Swagger - https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml +# Under Apache 2.0 License +--- +openapi: 3.0.2 +servers: + - url: /v3 +info: + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + version: 1.0.20-SNAPSHOT + title: Swagger Petstore - OpenAPI 3.0 + termsOfService: 'http://swagger.io/terms/' + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: 'http://swagger.io' + - name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: 'http://swagger.io' + - name: user + description: Operations about user +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + responses: + '200': + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + description: Create a new pet in the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + responses: + '200': + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + description: Update an existent pet in the store + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + - petstore_auth: + - 'write:pets' + - 'read:pets' + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + description: '' + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + x-swagger-router-controller: OrderController + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: Place a new order in the store + operationId: placeOrder + x-swagger-router-controller: OrderController + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + '405': + description: Invalid input + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + x-swagger-router-controller: OrderController + description: >- + For valid response try integer IDs with value <= 5 or > 10. Other values + will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + x-swagger-router-controller: OrderController + description: >- + For valid response try integer IDs with value < 1000. Anything above + 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + description: Created user object + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: 'Creates list of users with given input array' + x-swagger-router-controller: UserController + operationId: createUsersWithListInput + responses: + '200': + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + default: + description: successful operation + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + '200': + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + parameters: [] + responses: + default: + description: successful operation + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Update user + x-swagger-router-controller: UserController + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that needs to be updated + required: true + schema: + type: string + responses: + default: + description: successful operation + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + schemas: + Order: + x-swagger-router-model: io.swagger.petstore.model.Order + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + example: approved + complete: + type: boolean + xml: + name: order + type: object + Customer: + properties: + id: + type: integer + format: int64 + example: 100000 + username: + type: string + example: fehguy + address: + type: array + items: + $ref: '#/components/schemas/Address' + xml: + wrapped: true + name: addresses + xml: + name: customer + type: object + Address: + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: 94301 + xml: + name: address + type: object + Category: + x-swagger-router-model: io.swagger.petstore.model.Category + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + type: object + User: + x-swagger-router-model: io.swagger.petstore.model.User + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: 12345 + phone: + type: string + example: 12345 + userStatus: + type: integer + format: int32 + example: 1 + description: User Status + xml: + name: user + type: object + Tag: + x-swagger-router-model: io.swagger.petstore.model.Tag + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + type: object + Pet: + x-swagger-router-model: io.swagger.petstore.model.Pet + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + xml: + name: tag + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + type: object + ApiResponse: + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + type: object + requestBodies: + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: 'http://localhost:8123/oauth/authorize' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/app/frontend/src/components/Example/ExampleList.tsx b/app/frontend/src/components/Example/ExampleList.tsx index c592d1d..b9e1177 100644 --- a/app/frontend/src/components/Example/ExampleList.tsx +++ b/app/frontend/src/components/Example/ExampleList.tsx @@ -9,8 +9,8 @@ export type ExampleModel = { const EXAMPLES: ExampleModel[] = [ { text: "I want to pay a bill", value: "I want to pay a bill"}, - { text: "When did I last pay my plumber?", value: "When did I last pay my plumber" }, - { text: "Pay Bill the plumber as last time", value: "Pay Bill the plumber as last time" } + { text: "what are this year payments ?", value: "what are this year payments ?" }, + { text: "What is the limit on my visa ?", value: "What is the limit on my visa ?" } ]; interface Props { diff --git a/app/frontend/src/components/QuestionInput/QuestionInput.tsx b/app/frontend/src/components/QuestionInput/QuestionInput.tsx index 3954a6d..3005c04 100644 --- a/app/frontend/src/components/QuestionInput/QuestionInput.tsx +++ b/app/frontend/src/components/QuestionInput/QuestionInput.tsx @@ -1,6 +1,6 @@ import { useState, useRef } from "react"; import { Stack, TextField } from "@fluentui/react"; -import { Button, Tooltip, Field, Textarea } from "@fluentui/react-components"; +import { Button, Tooltip,Textarea } from "@fluentui/react-components"; import { Send28Filled, Attach24Filled, Delete16Filled } from "@fluentui/react-icons"; import { QuestionContextType } from "./QuestionContext"; import { uploadAttachment } from "../../api"; @@ -19,6 +19,24 @@ export const QuestionInput = ({ onSend, disabled, placeholder, clearOnSend }: P const inputFile = useRef(null); const [attachmentRef, setAttachmentRef] = useState(null); const [previewImage, setPreviewImage] = useState(null); + const [isUploading, setIsUploading] = useState(false); + + + const internalSendQuestion = async() => { + const questionContext = { + question: question, + attachments: attachmentRef != null ? [attachmentRef.name] : [] + }; + + onSend(questionContext); + + if (clearOnSend) { + setQuestion(""); + } + + setAttachmentRef(null); + setPreviewImage(null); + } const sendQuestion = async() => { if (disabled || !question.trim()) { @@ -27,23 +45,25 @@ export const QuestionInput = ({ onSend, disabled, placeholder, clearOnSend }: P if( attachmentRef != null){ - console.log("Uploading file... "+ attachmentRef.name); - await uploadAttachment(attachmentRef); + setIsUploading(true); + console.log("Uploading file... "+ attachmentRef.name); + //await uploadAttachment(attachmentRef); + + uploadAttachment(attachmentRef) + .then((response) => { + console.log("File uploaded."); + setIsUploading(false); + internalSendQuestion() + }) + .catch((error) => { + console.error(error); + setIsUploading(false); + }); + } else { + internalSendQuestion() } - const questionContext = { - question: question, - attachments: attachmentRef != null ? [attachmentRef.name] : [] - }; - - onSend(questionContext); - - if (clearOnSend) { - setQuestion(""); - } - - setAttachmentRef(null); - setPreviewImage(null); + }; const onEnterPress = (ev: React.KeyboardEvent) => { @@ -80,12 +100,14 @@ export const QuestionInput = ({ onSend, disabled, placeholder, clearOnSend }: P const sendQuestionDisabled = disabled || !question.trim(); return ( +
{previewImage && (
)}
+ +
); }; diff --git a/app/frontend/src/pages/chat/Chat.tsx b/app/frontend/src/pages/chat/Chat.tsx index 2fe8d08..e4abe90 100644 --- a/app/frontend/src/pages/chat/Chat.tsx +++ b/app/frontend/src/pages/chat/Chat.tsx @@ -281,7 +281,7 @@ const Chat = () => {
) : ( @@ -347,7 +347,7 @@ const Chat = () => {
makeApiRequest(question)} /> diff --git a/app/frontend/src/pages/layout/Layout.tsx b/app/frontend/src/pages/layout/Layout.tsx index 415d890..90de2c4 100644 --- a/app/frontend/src/pages/layout/Layout.tsx +++ b/app/frontend/src/pages/layout/Layout.tsx @@ -38,7 +38,7 @@ const Layout = () => { -

Azure OpenAI + Cognitive Search

+

Personal Banking Assistance Copilot

{useLogin && }
diff --git a/app/start.ps1 b/app/start.ps1 deleted file mode 100644 index f578ea1..0000000 --- a/app/start.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -Write-Host "" -Write-Host "Loading azd .env file from current environment" -Write-Host "" - -foreach ($line in (& azd env get-values)) { - if ($line -match "([^=]+)=(.*)") { - $key = $matches[1] - $value = $matches[2] -replace '^"|"$' - Set-Item -Path "env:\$key" -Value $value - } -} - -if ($LASTEXITCODE -ne 0) { - Write-Host "Failed to load environment variables from azd environment" - exit $LASTEXITCODE -} - - -Write-Host "" -Write-Host "Restoring frontend npm packages" -Write-Host "" -Set-Location ./frontend -npm install -if ($LASTEXITCODE -ne 0) { - Write-Host "Failed to restore frontend npm packages" - exit $LASTEXITCODE -} - -Write-Host "" -Write-Host "Building frontend" -Write-Host "" -npm run build -if ($LASTEXITCODE -ne 0) { - Write-Host "Failed to build frontend" - exit $LASTEXITCODE -} - -Write-Host "" -Write-Host "Starting spring boot api backend and react spa from backend/public static content" -Write-Host "" -Set-Location ../backend -Start-Process http://localhost:8080 - -Start-Process -FilePath "./mvnw.cmd" -ArgumentList "spring-boot:run -Dspring-boot.run.profiles=dev" -Wait -NoNewWindow - -if ($LASTEXITCODE -ne 0) { - Write-Host "Failed to start backend" - exit $LASTEXITCODE -} diff --git a/app/start.sh b/app/start.sh deleted file mode 100644 index 26e69eb..0000000 --- a/app/start.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/sh - -echo "" -echo "Loading azd .env file from current environment" -echo "" - -while IFS='=' read -r key value; do - value=$(echo "$value" | sed 's/^"//' | sed 's/"$//') - export "$key=$value" - echo "export $key=$value" -done < + + + + + + + + + + + + + + { + "associatedIndex": 4 +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1713948773648 + + + + + + + + file://$PROJECT_DIR$/app/backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatController.java + 65 + + + + + \ No newline at end of file