在 Cloud Run 上使用 Node.js 构建自定义 Google Workspace Studio 步骤

1. 简介

借助 Google Workspace Studio,您可以自动执行 Gmail、Google Chat、Google 云端硬盘和 Google 表格等 Google Workspace 应用中的业务流程。如果您的工作流程需要从外部 Web 应用启动,或者需要在您自己的云环境中运行自定义逻辑,您可以使用 Cloud Run 上的 Node.js 中的 HTTP Google Workspace 插件来扩展 Google Workspace Studio:

  1. 自定义初始步骤 (workflowTrigger):通过调用 Google Workspace Studio triggers.fire REST API,在发生外部事件(例如客户在内部 Web 门户中提交工单)时启动 Google Workspace Studio 流。
  2. 自定义操作步骤 (workflowAction):从流程中之前的步骤接收变量,在 Node.js 服务中运行自定义逻辑或 AI 推理,并将输出变量返回给 Google Workspace Studio。

在此 Codelab 中,您将在 Cloud Run 上为 Google Workspace Studio 构建并部署完整的 Node.js HTTP 扩展程序,使用 Cloud Firestore 在容器修订版本之间持久保留有效流程触发订阅和已提交的支持请求,并使用 Gemini Enterprise Agent Platform 上的 Gemini 3.8 Flash (gemini-3.8-flash) 在自定义操作步骤中对支持请求紧急程度进行分类,并提供可恢复的关键字匹配回退。

构建内容

  • 一个使用 Node.js、Express 和 Firestore 在 Cloud Run 上构建的支持快速门户 Web 应用和 HTTP Google Workspace 插件。
  • 一个新的支持服务工单自定义起始步骤 (supportTicketTrigger),可在 studio_triggers Firestore 集合中保留有效流程订阅,在 support_tickets Firestore 集合中记录提交的工单,并在每次通过 Web 门户提交工单时触发,从而将 ticketTitle 和 ticketDescription 传递到 Google Workspace Studio 流程中。
  • 一个检测紧急程度自定义操作步骤 (urgencyDetectorStep),用于在 Gemini Enterprise Agent Platform (aiplatform.googleapis.com) 上使用 Gemini 3.8 Flash (gemini-3.8-flash) 对工单紧急程度进行分类,并自动回退到关键字匹配 (urgent、blocker、critical)。

学习内容

  • 如何使用 workflowTrigger 和 workflowAction 元素配置 HTTP Google Workspace 附加组件部署清单 (deployment.json)。
  • 如何在 studio_triggers Firestore 集合中持久保存 Google Workspace Studio 触发器订阅(triggerId、notifyUri 和 userOAuthToken)并使用 triggers.fire REST API 触发流。
  • 如何使用 Gemini Enterprise Agent Platform 上的 Gemini 3.8 Flash (gemini-3.8-flash) 结构化输出在 Node.js 中构建 workflowAction 自定义步骤,并提供关键字回退。

前提条件

  • 拥有 Google Workspace Studio 访问权限的 Google Workspace 账号。
  • 已启用结算功能的 Google Cloud 项目,该项目与您的用户账号在同一 Google Workspace 组织中创建,以便您安装未发布的插件部署进行测试。

2. 设置 Google Cloud 云项目和 Firestore

在此步骤中,您将在 Cloud Shell 中配置项目的 OAuth 权限请求页面、启用所需的 Google Cloud 和 Google Workspace API、预配 (default) Firestore 数据库,并初始化插件授权服务账号。

  1. 打开 Google Cloud 控制台,选择您的 Google Cloud 云项目,然后点击顶部导航栏中的激活 Cloud Shell 图标。
  2. 配置 OAuth 权限请求页面,以便 Google Workspace 可以为您的插件预配 OAuth 客户端:
    1. 打开 Google Auth Platform 品牌推广页面。
    2. 如果系统提示,请点击开始。(如果您的权限请求页面已配置完毕,请跳至第 3 步。)
    3. 在应用信息下,为应用名称输入 Support Extension,选择用户支持电子邮件地址,然后点击下一步。
    4. 在受众群体下,选择内部,然后点击下一步。
    5. 在联系信息下方,输入您的电子邮件地址,然后点击下一步。
    6. 在完成部分,同意 Google API 服务:用户数据政策,点击继续,然后点击创建。
  3. 在 Cloud Shell 中,设置您的有效项目(将 YOUR_PROJECT_ID 替换为您的 Google Cloud 项目 ID),并启用所需的 API:
    gcloud config set project YOUR_PROJECT_ID
    gcloud services enable \
      aiplatform.googleapis.com \
      firestore.googleapis.com \
      run.googleapis.com \
      cloudbuild.googleapis.com \
      artifactregistry.googleapis.com \
      appsmarket-component.googleapis.com \
      gsuiteaddons.googleapis.com \
      workspacestudio.googleapis.com
    
  4. 在 us-central1 中预配 (default) Firestore 数据库(如果尚不存在),并初始化您的 Google Workspace 插件服务账号:
    gcloud firestore databases describe --database="(default)" >/dev/null 2>&1 || \
      gcloud firestore databases create \
        --database="(default)" \
        --location=us-central1 \
        --type=firestore-native \
        --quiet
    gcloud workspace-add-ons get-authorization
    

3. 使用 Firestore 和 Gemini 3.8 Flash 构建 Node.js 应用

在此步骤中,您将在 ~/studio-support-addon 中构建 Node.js 应用。为了保持代码的模块化和可读性,您将项目整理为五个重点文件:

  • firestore.js:初始化 Cloud Firestore 客户端(studio_triggers 和 support_tickets 集合),并刷新缓存的 OAuth 令牌。
  • starter.js:实现了新支持服务工单自定义启动器步骤 webhook(/onConfigTrigger 和 /onManageTrigger)。
  • action.js:使用 Gemini 3.8 Flash (gemini-3.8-flash) 实现 Detect Urgency 自定义操作步骤 webhook(/onConfigUrgency 和 /onExecuteUrgency),并提供关键字匹配回退。
  • portal.js:提供支持快速门户 Web 界面 (GET /) 并触发有效的 Google Workspace Studio 触发器 (POST /api/submit-ticket)。
  • index.js:将所有 Express 路由连接在一起并启动 HTTP 服务器。

初始化项目和 package.json

  1. 创建项目目录并进入该目录:
    mkdir -p ~/studio-support-addon && cd ~/studio-support-addon
    
  2. 使用 express、@google-cloud/firestore 和 @google/genai 创建 package.json:
    cat << 'EOF' > package.json
    {
      "name": "studio-support-addon",
      "version": "1.0.0",
      "type": "module",
      "main": "index.js",
      "scripts": {
        "start": "node index.js"
      },
      "dependencies": {
        "@google-cloud/firestore": "^7.11.0",
        "@google/genai": "^1.0.0",
        "express": "^4.21.2"
      }
    }
    EOF
    

firestore.js - 共享 Firestore 状态和 OAuth 令牌刷新

创建 firestore.js 以管理您的两个 Firestore 集合并保持缓存的用户 OAuth 令牌处于最新状态:

  • studio_triggers:存储有效的 Google Workspace Studio flow 订阅(triggerId、notifyUri 和 userOAuthToken),以便在 Cloud Run 容器重启和缩减至零事件后仍能正常运行。
  • support_tickets:存储通过门户提交的每个支持服务工单的记录。
  • refreshTokenInFirestore(req):每当 Google Workspace Studio 调用您的任何 Webhook 端点时,都会包含一个短效 authorizationEventObject.userOAuthToken。此辅助函数会使用最新令牌更新有效触发器文档,以便后台 triggers.fire 调用保持授权状态。
cat << 'EOF' > firestore.js
import { Firestore } from '@google-cloud/firestore';

export const db = new Firestore({
  projectId: process.env.GOOGLE_CLOUD_PROJECT || undefined,
  databaseId: '(default)',
  ignoreUndefinedProperties: true,
});

export const triggersCollection = db.collection(
  process.env.FIRESTORE_TRIGGERS_COLLECTION || 'studio_triggers'
);

export const ticketsCollection = db.collection(
  process.env.FIRESTORE_TICKETS_COLLECTION || 'support_tickets'
);

// Keep cached userOAuthToken fresh across active trigger documents
export async function refreshTokenInFirestore(req) {
  const freshToken = req.body?.authorizationEventObject?.userOAuthToken;
  if (!freshToken) return;
  try {
    const snapshot = await triggersCollection.get();
    if (snapshot.empty) return;
    const batch = db.batch();
    snapshot.docs.forEach((doc) => {
      batch.set(
        doc.ref,
        { userOAuthToken: freshToken, updatedAt: new Date().toISOString() },
        { merge: true }
      );
    });
    await batch.commit();
  } catch (err) {
    console.error('Error refreshing OAuth token in Firestore:', err.message);
  }
}
EOF

starter.js - 自定义初始步骤 (workflowTrigger)

创建 starter.js 以实现 Google Workspace Studio 自定义初始步骤所需的两个 webhook 端点:

  • onConfigTrigger (POST /onConfigTrigger):当用户在 Google Workspace Studio 中选择新建支持服务工单作为流程的启动器时调用。返回描述启动器的 pushCard 导航操作。
  • onManageTrigger (POST /onManageTrigger):当用户开启 (req.body.workflow.triggerCreation) 或关闭 / 删除 (req.body.workflow.triggerDeletion) 流程时调用。在 studio_triggers Firestore 集合中保存或删除订阅文档。
cat << 'EOF' > starter.js
import { triggersCollection, refreshTokenInFirestore } from './firestore.js';

// Custom Starter Configuration Card
export async function onConfigTrigger(req, res) {
  await refreshTokenInFirestore(req);
  res.status(200).json({
    action: {
      navigations: [
        {
          pushCard: {
            sections: [
              {
                header: 'Support Ticket Listener',
                widgets: [
                  {
                    textParagraph: {
                      text: 'This starter listens for new tickets submitted through the Support Quick Portal and stores subscriptions in Firestore.',
                    },
                  },
                ],
              },
            ],
          },
        },
      ],
    },
  });
}

// Custom Starter Subscription Lifecycle (Firestore studio_triggers collection)
export async function onManageTrigger(req, res) {
  try {
    const triggerCreation = req.body?.workflow?.triggerCreation;
    const triggerDeletion = req.body?.workflow?.triggerDeletion;
    const userOAuthToken =
      req.body?.authorizationEventObject?.userOAuthToken || null;

    if (triggerCreation?.triggerId) {
      const triggerId = String(triggerCreation.triggerId);
      const docId = encodeURIComponent(triggerId);
      await triggersCollection.doc(docId).set(
        {
          triggerId,
          notifyUri: triggerCreation.notifyUri || null,
          ...(userOAuthToken ? { userOAuthToken } : {}),
          updatedAt: new Date().toISOString(),
        },
        { merge: true }
      );
    }

    if (triggerDeletion?.triggerId) {
      const triggerId = String(triggerDeletion.triggerId);
      const docId = encodeURIComponent(triggerId);
      await triggersCollection.doc(docId).delete();
    }

    if (!triggerCreation && !triggerDeletion && userOAuthToken) {
      await refreshTokenInFirestore(req);
    }
  } catch (err) {
    console.error('Error in /onManageTrigger:', err.message);
  }
  res.status(200).json({});
}
EOF

action.js - 使用 Gemini 3.8 Flash 的自定义操作步骤 (workflowAction)

创建 action.js 以实现 Google Workspace Studio 自定义操作步骤所需的两个 Webhook 端点:

  • onConfigUrgency (POST /onConfigUrgency):返回一个包含 textInput widget 的配置卡,该 widget 用于设置 hostAppDataSource.workflowDataSource.includeVariables: true,以便用户选择初始步骤输出的 ticketDescription 变量。
  • onExecuteUrgency (POST /onExecuteUrgency):从 req.body.workflow.actionInvocation.inputs 读取 ticketDescription 输入,使用 Gemini 3.8 Flash (gemini-3.8-flash) 在 Gemini Enterprise Agent Platform 上对输入进行分类,确定其业务影响和紧急程度,并使用结构化输出(responseSchema 枚举 ["High", "Normal"]),如果模型调用失败,则回退到 detectKeywordUrgency()(urgent、blocker、critical),并使用 hostAppAction.workflowAction.returnOutputVariablesAction 返回 urgencyLevel 输出变量。
cat << 'EOF' > action.js
import { GoogleGenAI, Type } from '@google/genai';
import { refreshTokenInFirestore } from './firestore.js';

// Initialize Google GenAI SDK for the Gemini Enterprise Agent Platform
const ai = new GoogleGenAI({
  vertexai: true,
  project: process.env.GOOGLE_CLOUD_PROJECT,
  location: 'global',
});

// Fallback keyword classifier if the Gemini API call is unavailable
export function detectKeywordUrgency(description) {
  const lower = (description || '').toLowerCase();
  return lower.includes('urgent') ||
    lower.includes('blocker') ||
    lower.includes('critical')
    ? 'High'
    : 'Normal';
}

// Custom Action Step Configuration Card
export async function onConfigUrgency(req, res) {
  await refreshTokenInFirestore(req);
  res.status(200).json({
    action: {
      navigations: [
        {
          pushCard: {
            sections: [
              {
                header: 'Urgency Detector Step',
                widgets: [
                  {
                    textInput: {
                      name: 'ticketDescription',
                      label: 'Source Description',
                      hintText:
                        'Select the description variable outputted by the starter',
                      hostAppDataSource: {
                        workflowDataSource: {
                          includeVariables: true,
                        },
                      },
                    },
                  },
                ],
              },
            ],
          },
        },
      ],
    },
  });
}

// Custom Action Step Execution: Gemini 3.8 Flash with keyword fallback
export async function onExecuteUrgency(req, res) {
  await refreshTokenInFirestore(req);
  const inputs = req.body?.workflow?.actionInvocation?.inputs || {};
  const description = inputs.ticketDescription?.stringValues?.[0] || '';

  let urgencyLevel = detectKeywordUrgency(description);
  try {
    const response = await ai.models.generateContent({
      model: 'gemini-3.8-flash',
      contents: `Evaluate the business impact and time sensitivity of this support ticket description and classify its urgency:\n\n${description}`,
      config: {
        responseMimeType: 'text/x.enum',
        responseSchema: {
          type: Type.STRING,
          enum: ['High', 'Normal'],
        },
      },
    });
    const candidate = (response.text || '').trim();
    if (candidate === 'High' || candidate === 'Normal') {
      urgencyLevel = candidate;
    }
  } catch (err) {
    console.error('Gemini fallback to keyword matching:', err.message);
  }

  res.status(200).json({
    hostAppAction: {
      workflowAction: {
        returnOutputVariablesAction: {
          variables: {
            urgencyLevel: {
              stringValues: [urgencyLevel],
            },
          },
        },
      },
    },
  });
}
EOF

portal.js - 支持快速门户界面和 triggers.fire 调度程序

创建 portal.js 以提供 Web 门户服务,并将支持请求提交给 Google Workspace Studio:

  • renderPortal (GET /):查询 studio_triggers Firestore 集合以显示已注册的有效 Google Workspace Studio 触发器的数量,并提供 HTML 支持请求提交表单。
  • submitTicket (POST /api/submit-ticket):遍历 studio_triggers 中的所有有效触发文档,使用 ticketTitle 和 ticketDescription 输出变量以及唯一的 requestId 调用 Google Workspace Studio triggers.fire 端点 (https://workspacestudio.googleapis.com/v1/triggers/{triggerId}:fire),并将工单保存到 support_tickets Firestore 集合。
cat << 'EOF' > portal.js
import { triggersCollection, ticketsCollection } from './firestore.js';

// Support Quick Portal UI
export async function renderPortal(req, res) {
  let activeCount = 0;
  try {
    const snapshot = await triggersCollection.get();
    activeCount = snapshot.size;
  } catch (err) {
    console.error('Error reading studio_triggers collection:', err.message);
  }

  res.status(200).send(`<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Support Quick Portal</title>
    <style>
      body { font-family: Arial, sans-serif; padding: 24px; max-width: 540px; margin: auto; color: #202124; }
      .badge { background: #e8f0fe; color: #1967d2; padding: 8px 12px; border-radius: 6px; font-size: 14px; margin-bottom: 16px; }
      label { font-weight: bold; display: block; margin-top: 16px; }
      input, textarea { width: 100%; margin-top: 6px; padding: 10px; box-sizing: border-box; border: 1px solid #dadce0; border-radius: 4px; }
      button { background-color: #1a73e8; color: white; border: none; padding: 10px 18px; margin-top: 16px; font-weight: bold; cursor: pointer; border-radius: 4px; }
      button:hover { background-color: #1557b0; }
      #status { margin-top: 16px; font-weight: bold; }
    </style>
  </head>
  <body>
    <h2>Support Quick Portal</h2>
    <div class="badge">
      Active Google Workspace Studio triggers in Firestore (<code>studio_triggers</code>):
      <strong id="trigger-count">${activeCount}</strong>
    </div>
    <p>Submit a support ticket below to fire your Google Workspace Studio flow.</p>

    <label for="title">Ticket Title</label>
    <input type="text" id="title" placeholder="e.g. Database failure">

    <label for="description">Issue Description</label>
    <textarea id="description" rows="4" placeholder="Describe the issue..."></textarea>

    <button onclick="submitTicket()">Submit Ticket</button>
    <div id="status"></div>

    <script>
      async function submitTicket() {
        const title = document.getElementById('title').value.trim();
        const description = document.getElementById('description').value.trim();
        const statusEl = document.getElementById('status');

        if (!title || !description) {
          statusEl.style.color = '#d93025';
          statusEl.textContent = 'Please fill out both fields.';
          return;
        }

        statusEl.style.color = '#188038';
        statusEl.textContent = 'Submitting ticket...';

        try {
          const res = await fetch('/api/submit-ticket', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ title, description }),
          });
          const data = await res.json();
          if (data.ok) {
            statusEl.textContent =
              'Ticket saved to Firestore! Fired ' + data.triggersFired + ' Google Workspace Studio trigger(s).';
          } else {
            statusEl.style.color = '#d93025';
            statusEl.textContent = 'Error: ' + (data.error || 'Unknown error');
          }
        } catch (err) {
          statusEl.style.color = '#d93025';
          statusEl.textContent = 'Request failed: ' + err.message;
        }
      }
    </script>
  </body>
</html>`);
}

// Portal Form Submission -> Fire Active Google Workspace Studio Triggers
export async function submitTicket(req, res) {
  const title = req.body?.title || 'Untitled Ticket';
  const description = req.body?.description || '';
  const results = [];

  try {
    const snapshot = await triggersCollection.get();
    for (const doc of snapshot.docs) {
      const trigger = doc.data();
      if (!trigger.triggerId || !trigger.userOAuthToken) continue;

      const response = await fetch(
        `https://workspacestudio.googleapis.com/v1/triggers/${encodeURIComponent(trigger.triggerId)}:fire`,
        {
          method: 'POST',
          headers: {
            Authorization: `Bearer ${trigger.userOAuthToken}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            name: 'triggers/' + trigger.triggerId,
            outputs: {
              ticketTitle: { stringValues: [title] },
              ticketDescription: { stringValues: [description] },
            },
            requestId:
              'req_' + Date.now() + '_' + Math.floor(Math.random() * 1000),
          }),
        }
      );
      results.push({ triggerId: trigger.triggerId, status: response.status });
    }

    await ticketsCollection.add({
      title,
      description,
      triggersFired: results.length,
      createdAt: new Date().toISOString(),
    });

    res.status(200).json({ ok: true, triggersFired: results.length, results });
  } catch (err) {
    console.error('Error in /api/submit-ticket:', err.message);
    res.status(500).json({ ok: false, error: err.message });
  }
}
EOF

index.js - Express 服务器入口点

创建 index.js,将门户网站路由和 Google Workspace Studio Webhook 端点连接在一起,形成一个在 PORT 上监听的 Express HTTP 服务器(在 Cloud Run 上默认为 8080):

cat << 'EOF' > index.js
import express from 'express';
import { onConfigTrigger, onManageTrigger } from './starter.js';
import { onConfigUrgency, onExecuteUrgency } from './action.js';
import { renderPortal, submitTicket } from './portal.js';

const app = express();
app.use(express.json());

// Support Quick Portal routes
app.get('/', renderPortal);
app.post('/api/submit-ticket', submitTicket);

// Google Workspace Studio Custom Starter Step webhooks
app.post('/onConfigTrigger', onConfigTrigger);
app.post('/onManageTrigger', onManageTrigger);

// Google Workspace Studio Custom Action Step webhooks
app.post('/onConfigUrgency', onConfigUrgency);
app.post('/onExecuteUrgency', onExecuteUrgency);

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`Support Extension listening on port ${PORT}`);
});
EOF

4. 部署到 Cloud Run 并安装插件

在此步骤中,您将 Node.js 服务部署到 Cloud Run,为 Google Workspace 插件调用方和 Firestore 授予 IAM 权限,并授予 Agent Platform 运行时访问权限,生成 deployment.json 清单,然后安装插件部署。

  1. 使用项目和 Firestore 集合环境变量将 support-extension 服务部署到 Cloud Run:
    gcloud run deploy support-extension \
      --source . \
      --region us-central1 \
      --allow-unauthenticated \
      --set-env-vars="GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project),FIRESTORE_TRIGGERS_COLLECTION=studio_triggers,FIRESTORE_TICKETS_COLLECTION=support_tickets" \
      --quiet
    
  2. 向 Google Workspace 插件服务账号授予 Cloud Run 服务的 roles/run.invoker 权限,以便 Google Workspace Studio 可以调用您的 Webhook 端点:
    ADDON_SA=$(gcloud workspace-add-ons get-authorization --format="value(serviceAccountEmail)")
    gcloud run services add-iam-policy-binding support-extension \
      --region=us-central1 \
      --member="serviceAccount:${ADDON_SA}" \
      --role="roles/run.invoker" \
      --quiet
    
  3. 向 Cloud Run 运行时服务账号授予 roles/datastore.user(用于读取和写入 studio_triggers 和 support_tickets Firestore 集合)和 roles/aiplatform.user(用于在 Gemini Enterprise Agent Platform 上调用 Gemini):
    PROJECT_ID=$(gcloud config get-value project)
    PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format="value(projectNumber)")
    RUNTIME_SA=$(gcloud run services describe support-extension --region=us-central1 --format="value(spec.template.spec.serviceAccountName)")
    RUNTIME_SA=${RUNTIME_SA:-"${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"}
    
    gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
      --member="serviceAccount:${RUNTIME_SA}" \
      --role="roles/datastore.user" \
      --condition=None \
      --quiet
    
    gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
      --member="serviceAccount:${RUNTIME_SA}" \
      --role="roles/aiplatform.user" \
      --condition=None \
      --quiet
    
  4. 检索已部署的 Cloud Run 网址,并生成定义 supportTicketTrigger (workflowTrigger) 和 urgencyDetectorStep (workflowAction) 元素的 deployment.json 清单:
    SERVICE_URL=$(gcloud run services describe support-extension --region=us-central1 --format="value(status.url)")
    
    cat << EOF > deployment.json
    {
      "oauthScopes": [
        "https://www.googleapis.com/auth/workspace.studio.trigger",
        "https://www.googleapis.com/auth/userinfo.email"
      ],
      "addOns": {
        "common": {
          "name": "Support Extension",
          "logoUrl": "https://www.gstatic.com/images/branding/productlogos/buganizer/v1/192px.svg"
        },
        "studio": {
          "flows": {
            "workflowElements": [
              {
                "id": "supportTicketTrigger",
                "state": "ACTIVE",
                "name": "New Support Ticket",
                "description": "Fires when someone submits a ticket in the Support Quick Portal.",
                "workflowTrigger": {
                  "inputs": [],
                  "outputs": [
                    {
                      "id": "ticketTitle",
                      "description": "Ticket Title",
                      "cardinality": "SINGLE",
                      "dataType": { "basicType": "STRING" }
                    },
                    {
                      "id": "ticketDescription",
                      "description": "Ticket Description",
                      "cardinality": "SINGLE",
                      "dataType": { "basicType": "STRING" }
                    }
                  ],
                  "onConfigFunction": "${SERVICE_URL}/onConfigTrigger",
                  "onManageFunction": "${SERVICE_URL}/onManageTrigger"
                }
              },
              {
                "id": "urgencyDetectorStep",
                "state": "ACTIVE",
                "name": "Detect Urgency",
                "description": "Evaluates the ticket description to detect High or Normal urgency.",
                "workflowAction": {
                  "inputs": [
                    {
                      "id": "ticketDescription",
                      "description": "Description to scan",
                      "cardinality": "SINGLE",
                      "dataType": { "basicType": "STRING" }
                    }
                  ],
                  "outputs": [
                    {
                      "id": "urgencyLevel",
                      "description": "Detected Urgency (High / Normal)",
                      "cardinality": "SINGLE",
                      "dataType": { "basicType": "STRING" }
                    }
                  ],
                  "onConfigFunction": "${SERVICE_URL}/onConfigUrgency",
                  "onExecuteFunction": "${SERVICE_URL}/onExecuteUrgency"
                }
              }
            ]
          }
        },
        "httpOptions": {
          "authorizationHeader": "SYSTEM_ID_TOKEN"
        }
      }
    }
    EOF
    
  5. 为您的账号创建(或替换)并安装 Google Workspace 加购项部署,然后打印支持快速门户网址:
    gcloud workspace-add-ons deployments create support-extension --deployment-file=deployment.json || \
      gcloud workspace-add-ons deployments replace support-extension --deployment-file=deployment.json
    gcloud workspace-add-ons deployments install support-extension
    echo "Support Quick Portal URL: ${SERVICE_URL}"
    

5. 在 Google Workspace Studio 中测试自定义步骤

  1. 打开 Google Workspace Studio(如果浏览器标签页已打开,请刷新该标签页),然后点击新建流程。
  2. 依次点击选择初始内容 > 支持扩展程序 > 新建支持服务工单。
  3. 依次点击添加步骤 (+) > 支持扩展程序 > 检测紧急程度。在来源说明字段中,点击 + 变量,然后选择工单说明。
  4. 点击添加步骤 (+),然后添加一个聊天 > 在聊天中通知我步骤,其中包含消息中的工单标题、紧急程度和工单说明变量。
  5. 在右上角将流程切换为开启。Google Workspace Studio 会立即调用 Cloud Run 上的 /onManageTrigger 端点,该端点会将有效触发器订阅写入 Firestore 中的 studio_triggers 集合。
  6. 在新标签页中打开您的支持快速门户网址 (https://support-extension-...run.app),确认门户在 Firestore 中显示 1 有效触发器,然后提交两张工单来测试 Gemini 3.8 Flash (gemini-3.8-flash) 分类:
    • 高影响工单(工单标题:London office lockout,问题说明:All 500 employees in the London office are locked out of their workstations 20 minutes before the board presentation.):即使没有 urgent 或 blocker 等硬编码的关键字,gemini-3.8-flash 也会将此工单归类为High 紧急程度。
    • 常规支持请求(支持请求标题:Conference room adapters,问题说明:Could we order two extra HDMI adapters for the third-floor conference room next month?):gemini-3.8-flash 将此支持请求归类为Normal 紧急程度。
  7. 在 Google Chat 和 Google Workspace Studio 的活动标签页中验证您的流程是否已针对 Urgency Level: High 和 Urgency Level: Normal 这两个工单运行。

6. 清理

为避免系统向您的 Google Cloud 账号持续收取费用,请在 Cloud Shell 中移除已部署的资源:

gcloud workspace-add-ons deployments uninstall support-extension
gcloud workspace-add-ons deployments delete support-extension --quiet
gcloud run services delete support-extension --region=us-central1 --quiet
gcloud firestore databases delete --database="(default)" --quiet

7. 恭喜!

您已在 Gemini Enterprise Agent Platform 上使用 Firestore、Cloud Run 和 Gemini 3.8 Flash 为 Google Workspace Studio 构建、部署并测试了 Node.js HTTP 扩展程序!

所学内容

  • 使用 workflowTrigger 和 workflowAction 元素配置 Google Workspace Studio HTTP 附加组件清单 (deployment.json)。
  • 在 studio_triggers Firestore 集合中持久保留 Google Workspace Studio workflowTrigger 订阅,并在 support_tickets 中记录门户提交内容,以便触发器在 Cloud Run 扩缩和重新部署后仍能正常运行。
  • 在 Gemini Enterprise Agent Platform 上构建一个 workflowAction 步骤,该步骤使用 Gemini 3.8 Flash (gemini-3.8-flash) 结构化输出对支持服务工单进行分类,并具有弹性的关键字匹配回退功能。

后续步骤