Skip to content

Conversation

@PierrickVoulet
Copy link
Contributor

No description provided.

@gemini-code-assist
Copy link

Summary of Changes

Hello @PierrickVoulet, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request adds a new sample application that showcases the integration of a Google Chat bot with an ADK AI agent hosted on Vertex AI Agent Engine. The sample provides a functional framework for processing user messages, interacting with the AI agent, and presenting the agent's responses, including grounded information and markdown, directly within Google Chat. It serves as a quickstart guide for developers looking to build AI-powered Chat applications on Google Workspace.

Highlights

  • New ADK AI Chat Agent Sample: Introduces a complete sample application for a Google Chat bot powered by an ADK AI agent.
  • Vertex AI Integration: The sample demonstrates how to integrate a Google Chat app with a Vertex AI Agent Engine for AI-driven responses.
  • Google Apps Script Implementation: The entire sample is built using Google Apps Script, showcasing how to handle Chat events, manage credentials, and render rich UI responses.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@PierrickVoulet PierrickVoulet merged commit e3165f7 into googleworkspace:main Dec 13, 2025
5 of 7 checks passed
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a sample ADK AI Chat agent. The code is well-structured, but there are a few critical areas that need improvement for robustness and security. My review includes feedback on:

  • Lack of error handling for API calls, which could lead to unhandled exceptions.
  • A potential Cross-Site Scripting (XSS) vulnerability due to unsanitized HTML rendering.
  • Fragile logic for parsing environment variables which could cause runtime errors.
    I've provided suggestions to address these high-severity issues to make the sample more secure and reliable.

function createMarkdownWidgets(markdown) {
if (!markdown) return [];
const textParagraph = CardService.newTextParagraph();
textParagraph.setText(new showdown.Converter().makeHtml(markdown));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The makeHtml function from the showdown library is used to convert Markdown to HTML. However, the output is not sanitized. Since the markdown content comes from an AI agent, it should be treated as untrusted input. This could potentially lead to a Cross-Site Scripting (XSS) vulnerability if the AI returns malicious HTML or JavaScript within the markdown. While Google Chat performs its own sanitization on card content, it is a security best practice to sanitize untrusted HTML content within your application. Consider using an HTML sanitization library or configuring showdown to sanitize its output if the version you are using supports it.

Comment on lines +26 to +27
// Request AI agent to answer the message
requestAgent(chatEvent.user.name, chatEvent.messagePayload.message)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The onMessage function, which is the entry point for chat messages, calls requestAgent directly. Any unhandled exceptions within requestAgent or the functions it calls (like network errors or parsing errors) will cause the script to terminate. This will result in a generic error message for the user in Google Chat, such as "App is not responding", which is not a good user experience. Wrapping the call to requestAgent in a try...catch block will handle potential errors gracefully and can provide a more informative error message to the user.

  // Request AI agent to answer the message
  try {
    requestAgent(chatEvent.user.name, chatEvent.messagePayload.message);
  } catch (e) {
    console.error('Failed to process agent request: %s', e);
    // Let the user know something went wrong.
    createMessage({ text: "Sorry, I encountered an error and couldn't process your request." });
  }

Comment on lines +59 to +69
function downloadChatAttachment(attachmentName) {
const response = UrlFetchApp.fetch(
`https://chat.googleapis.com/v1/media/${attachmentName}?alt=media`,
{
method: 'get',
headers: { 'Authorization': `Bearer ${getCredentials().getAccessToken()}` },
muteHttpExceptions: true
}
);
return Utilities.base64Encode(response.getContent());
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The UrlFetchApp.fetch call has muteHttpExceptions: true, but there's no subsequent check of the HTTP response code. If downloading the attachment fails (e.g., due to permissions or a transient network issue), response.getContent() might return an empty or error-like content. Calling Utilities.base64Encode on this will not produce the expected result and could lead to downstream errors when the AI agent processes the malformed attachment data. You should check the response code after the fetch call and handle failures appropriately, for example by throwing an error that can be caught by the caller.

Suggested change
function downloadChatAttachment(attachmentName) {
const response = UrlFetchApp.fetch(
`https://chat.googleapis.com/v1/media/${attachmentName}?alt=media`,
{
method: 'get',
headers: { 'Authorization': `Bearer ${getCredentials().getAccessToken()}` },
muteHttpExceptions: true
}
);
return Utilities.base64Encode(response.getContent());
}
function downloadChatAttachment(attachmentName) {
const response = UrlFetchApp.fetch(
`https://chat.googleapis.com/v1/media/${attachmentName}?alt=media`,
{
method: 'get',
headers: { 'Authorization': `Bearer ${getCredentials().getAccessToken()}` },
muteHttpExceptions: true
}
);
if (response.getResponseCode() !== 200) {
const errorText = response.getContentText();
console.error(`Failed to download attachment ${attachmentName}. Status: ${response.getResponseCode()}. Response: ${errorText}`);
throw new Error(`Failed to download attachment: ${attachmentName}`);
}
return Utilities.base64Encode(response.getContent());
}

Comment on lines +24 to +31
const LOCATION = PropertiesService.getScriptProperties().getProperty('LOCATION');

// Get reasoning engine location
function getLocation() {
const parts = REASONING_ENGINE_RESOURCE_NAME.split('/');
const locationIndex = parts.indexOf('locations') + 1;
return parts[locationIndex];
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The getLocation() function is fragile. It assumes REASONING_ENGINE_RESOURCE_NAME is always set and has a specific format. If the property is missing or has an unexpected format, this function will throw an error or return an incorrect value. Additionally, there is a LOCATION constant read from script properties on line 24 which is never used, while getLocation() re-parses the location from another property. This is redundant and confusing. You should consistently use one method to get the location. Using the LOCATION script property directly is simpler and more robust.

Suggested change
const LOCATION = PropertiesService.getScriptProperties().getProperty('LOCATION');
// Get reasoning engine location
function getLocation() {
const parts = REASONING_ENGINE_RESOURCE_NAME.split('/');
const locationIndex = parts.indexOf('locations') + 1;
return parts[locationIndex];
}
const LOCATION = PropertiesService.getScriptProperties().getProperty('LOCATION');
// Get reasoning engine location
function getLocation() {
if (!LOCATION) {
throw new Error('LOCATION script property must be set.');
}
return LOCATION;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant