feat: add GE quickstart sample#302
Conversation
Summary of ChangesHello @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
Changelog
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
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.
| 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(); |
There was a problem hiding this comment.
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('/'); |
There was a problem hiding this comment.
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('/'); | const locationIndex = parts.indexOf('locations') + 1; | ||
| return parts[locationIndex]; |
There was a problem hiding this comment.
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 | |||
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
}| requestAgent(chatEvent.messagePayload.message) | ||
| // Respond with an empty response to the Google Chat platform to acknowledge execution | ||
| return null; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.');
}
No description provided.