Skip to content

feat: add GE quickstart sample#302

Merged
PierrickVoulet merged 2 commits intogoogleworkspace:mainfrom
PierrickVoulet:ge-quickstart
Feb 13, 2026
Merged

feat: add GE quickstart sample#302
PierrickVoulet merged 2 commits intogoogleworkspace:mainfrom
PierrickVoulet:ge-quickstart

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 establishes a foundational Google Apps Script project that enables a Gemini Enterprise AI agent to interact with users via Google Chat. It provides the necessary components for message processing, AI agent communication, authentication, and environment configuration, allowing for a quick setup of an AI-powered chat bot.

Highlights

  • New Google Apps Script Project: Introduced a new Google Apps Script project for integrating a Gemini Enterprise AI agent with Google Chat, including all necessary configuration files and source code.
  • AI Agent Integration: Implemented core logic in AgentHandler.gs to send user queries to a Gemini Enterprise AI agent, process its Server-Sent Events (SSE) responses, and format them for display in Google Chat.
  • Google Chat Message Handling: Added functionality in Chat.gs to handle incoming Google Chat messages, manage chat space configurations, and send formatted responses back to users.
  • Authentication and Environment Configuration: Set up service account-based OAuth2 authentication for Google APIs in Credentials.gs and centralized environment variable management for agent ID, reasoning engine, and debug mode in Env.gs.
Changelog
  • apps-script/chat/ge-ai-agent/.clasp.json
    • Added the Google Apps Script project configuration file, specifying the script ID.
  • apps-script/chat/ge-ai-agent/AgentHandler.gs
    • Implemented the requestAgent function to interact with the Gemini Enterprise AI agent API.
    • Added logic to parse and process Server-Sent Events (SSE) responses from the AI agent.
    • Included utility functions for formatting AI agent replies into Google Chat messages, converting snake_case strings, and creating markdown widgets.
  • apps-script/chat/ge-ai-agent/Chat.gs
    • Defined the onMessage function to handle incoming Google Chat events.
    • Added functions to set and retrieve Google Chat space configurations using user properties.
    • Implemented createMessage to send structured messages back to Google Chat.
  • apps-script/chat/ge-ai-agent/Credentials.gs
    • Created getCredentials to manage service account authentication for Google APIs, including scopes for Discovery Engine and Google Chat.
  • apps-script/chat/ge-ai-agent/Env.gs
    • Introduced functions to retrieve environment variables such as REASONING_ENGINE_RESOURCE_NAME, LOCATION, DEBUG status, and AGENT_ID from script properties.
  • apps-script/chat/ge-ai-agent/README.md
    • Added a README file providing a brief description of the project and a link to a tutorial.
  • apps-script/chat/ge-ai-agent/appsscript.json
    • Configured the Google Apps Script manifest, including dependencies for OAuth2 and advanced services for Google Chat.
    • Defined add-on metadata such as name and logo URL, and specified necessary OAuth scopes.
Activity
  • No specific activity or comments have been recorded for this pull request yet.
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 changed the title Ge quickstart feat: add GE quickstart sample Feb 13, 2026
@PierrickVoulet PierrickVoulet merged commit c9af0ed into googleworkspace:main Feb 13, 2026
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 introduces a Google Chat bot that integrates with a Gemini Enterprise AI agent. The code is well-structured, separating concerns like credentials, environment configuration, and agent interaction logic. However, I've identified several critical and high-severity issues related to error handling, robustness, and potential performance bottlenecks. Key areas for improvement include adding checks for HTTP responses, handling potential parsing errors, validating required configurations, and addressing a synchronous operation in the webhook that could lead to timeouts. Resolving these issues will significantly enhance the bot's reliability and stability.

Comment on lines +22 to +39
const responseContentText = UrlFetchApp.fetch(
`https://${getLocation()}-discoveryengine.googleapis.com/v1/${getReasoningEngine()}/assistants/default_assistant:streamAssist?alt=sse`,
{
method: 'post',
headers: { 'Authorization': `Bearer ${getCredentials().getAccessToken()}` },
contentType: 'application/json',
payload: JSON.stringify({
// Always use a new session
"session" : null,
// Only use the message text
"query": { "text": input.text },
"agentsSpec": { "agentSpecs": [{
"agentId": getAgentId()
}]}
}),
muteHttpExceptions: true
}
).getContentText();

Choose a reason for hiding this comment

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

critical

The UrlFetchApp.fetch() call is immediately chained with .getContentText(), which prevents you from checking the HTTP response status code. If the API request fails (e.g., due to an authentication error, invalid input, or server-side issue), the script will attempt to parse the error response body as a successful SSE stream. This will lead to subsequent failures or unpredictable behavior. It's critical to check the response code and handle any non-successful statuses gracefully.

  const response = UrlFetchApp.fetch(
    `https://${getLocation()}-discoveryengine.googleapis.com/v1/${getReasoningEngine()}/assistants/default_assistant:streamAssist?alt=sse`,
    {
      method: 'post',
      headers: { 'Authorization': `Bearer ${getCredentials().getAccessToken()}` },
      contentType: 'application/json',
      payload: JSON.stringify({
        // Always use a new session
        "session" : null,
        // Only use the message text
        "query": { "text": input.text },
        "agentsSpec": { "agentSpecs": [{
          "agentId": getAgentId()
        }]}
      }),
      muteHttpExceptions: true
    }
  );

  if (response.getResponseCode() < 200 || response.getResponseCode() >= 300) {
    console.error(`Agent request failed with status ${response.getResponseCode()}: ${response.getContentText()}`);
    answer(getAgentId(), "Something went wrong, check the Apps Script logs for more info.", false);
    return;
  }
  const responseContentText = response.getContentText();


// Get reasoning engine location
function getLocation() {
const parts = REASONING_ENGINE_RESOURCE_NAME.split('/');

Choose a reason for hiding this comment

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

critical

The REASONING_ENGINE_RESOURCE_NAME constant is read from script properties. If this property is not set, its value will be null. This line then attempts to call .split('/') on null, which will throw a TypeError and crash the script. It's critical to validate that this required property exists before attempting to use it.

  if (!REASONING_ENGINE_RESOURCE_NAME) {
    throw new Error('Script property "REASONING_ENGINE_RESOURCE_NAME" must be set.');
  }
  const parts = REASONING_ENGINE_RESOURCE_NAME.split('/'); 

Comment on lines +29 to +30
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.

critical

The logic to extract the location assumes that the substring 'locations' is always present in the resource name. If parts.indexOf('locations') returns -1, locationIndex will become 0, and the function will incorrectly return the first part of the resource name (e.g., 'projects') as the location. You should add validation to ensure 'locations' is found and handle the case where it's not.

  const locationIndex = parts.indexOf('locations');
  if (locationIndex === -1 || locationIndex + 1 >= parts.length) {
    throw new Error(`Could not determine location from resource name: ${REASONING_ENGINE_RESOURCE_NAME}`);
  }
  return parts[locationIndex + 1];

@@ -0,0 +1 @@
{"scriptId": "1YeDvZtq5c5TU2DXFVCNGjA6RCVkUn5x0ip5QVksmSwgsy7gwpI7izPLt"} No newline at end of file

Choose a reason for hiding this comment

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

high

Committing the .clasp.json file with a hardcoded scriptId is not a recommended practice for collaborative projects. If another developer clones this repository and pushes changes, they could accidentally overwrite the shared Apps Script project. To prevent this, this file should be added to .gitignore, and a template file (e.g., .clasp.json.example) should be provided instead, with instructions for developers to create their own local .clasp.json.

if (isInDebugMode()) {
console.log("Event: " + eventJson);
}
const event = JSON.parse(eventJson);

Choose a reason for hiding this comment

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

high

The call to JSON.parse(eventJson) is not wrapped in a try...catch block. Since the eventJson string comes from an external API response, it might not always be valid JSON. A malformed event would cause JSON.parse to throw an unhandled exception, terminating the script execution abruptly. To make the agent more robust, you should handle potential parsing errors.

    let event;
    try {
      event = JSON.parse(eventJson);
    } catch (e) {
      console.error(`Failed to parse agent event: ${eventJson}`, e);
      continue; // Skip malformed event and proceed to the next one.
    }

Comment on lines +27 to +29
requestAgent(chatEvent.messagePayload.message)
// Respond with an empty response to the Google Chat platform to acknowledge execution
return null;

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 serves as the Chat webhook handler, calls requestAgent synchronously. Since requestAgent performs a network request to an AI service, it can be a long-running operation. Google Chat webhooks have a 30-second timeout. If the agent's response time exceeds this limit, the webhook will time out, and the user will see an error. The comment on line 28 suggests an asynchronous acknowledgement was intended, but the implementation is blocking. For a production-ready bot, consider an asynchronous architecture to handle long-running tasks without blocking the initial response to Google Chat.

if (!credentials) {
throw new Error("SERVICE_ACCOUNT_KEY script property must be set.");
}
const parsedCredentials = JSON.parse(credentials);

Choose a reason for hiding this comment

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

high

The call to JSON.parse(credentials) assumes the SERVICE_ACCOUNT_KEY script property is always valid JSON. If the property is missing, empty, or malformed, this will throw an unhandled exception and crash the script. To improve robustness against configuration errors, you should wrap this call in a try...catch block and provide a more informative error message.

  let parsedCredentials;
  try {
    parsedCredentials = JSON.parse(credentials);
  } catch (e) {
    throw new Error('Failed to parse SERVICE_ACCOUNT_KEY. Please ensure it is valid JSON.');
  }

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