在 Cloud Run 上使用 Node.js 建構自訂 Google Workspace Studio 步驟

1. 簡介

Google Workspace Studio 可讓您自動執行 Google Workspace 應用程式 (例如 Gmail、Google Chat、Google 雲端硬碟和 Google 試算表) 的業務程序。如果工作流程需要從外部網頁應用程式啟動,或在您自己的雲端環境中執行自訂邏輯,您可以在 Cloud Run 上,透過 Node.js 中的 HTTP Google Workspace 外掛程式擴充 Google Workspace Studio:

  1. 自訂啟動步驟 (workflowTrigger):當發生外部事件 (例如客戶在內部網路入口網站提交支援單) 時,呼叫 Google Workspace Studio triggers.fire REST API,啟動 Google Workspace Studio 流程。
  2. 自訂動作步驟 (workflowAction):接收工作流程中先前步驟的變數,在 Node.js 服務中執行自訂邏輯或 AI 推論,並將輸出變數傳回 Google Workspace Studio。

在本程式碼實驗室中,您將在 Cloud Run 上建構及部署 Google Workspace Studio 的完整 Node.js HTTP 擴充功能,使用 Cloud Firestore 持續保存容器修訂版本中的有效流程觸發條件訂閱項目和已提交的支援單,並在自訂動作步驟中使用 Gemini 3.8 Flash (gemini-3.8-flash) Gemini Enterprise Agent Platform 上的模型,搭配彈性關鍵字比對備援機制,將支援單分類為緊急。

建構項目

  • 以 Node.js、Express 和 Firestore 為基礎,在 Cloud Run 上建構的支援快速入口網頁應用程式和 HTTP Google Workspace 外掛程式。
  • 「New Support Ticket」(新支援單) 自訂起始步驟 (supportTicketTrigger):在 studio_triggers Firestore 集合中保留有效流程訂閱項目,在 support_tickets Firestore 集合中記錄提交的支援單,並在網頁入口提交支援單時觸發,將 ticketTitle 和 ticketDescription 傳遞至 Google Workspace Studio 流程。
  • 「偵測緊急程度」自訂動作步驟 (urgencyDetectorStep),使用 Gemini 3.8 Flash (gemini-3.8-flash) 在 Gemini Enterprise Agent Platform (aiplatform.googleapis.com) 上分類工單緊急程度,並自動改用關鍵字比對 (urgent、blocker、critical)。

課程內容

  • 如何使用 workflowTrigger 和 workflowAction 元素,設定 HTTP Google Workspace 外掛程式部署資訊清單 (deployment.json)。
  • 如何將 Google Workspace Studio 觸發條件訂閱項目 (triggerId、notifyUri 和 userOAuthToken) 保存在 studio_triggers Firestore 集合中,並使用 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

在這個步驟中,您將設定專案的 OAuth 同意畫面、在 Cloud Shell 中啟用必要的 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:實作「New Support Ticket」(新增支援單) 自訂起始步驟 Webhook (/onConfigTrigger 和 /onManageTrigger)。
  • action.js:使用 Gemini 3.8 Flash (gemini-3.8-flash) 實作「偵測緊急程度」自訂動作步驟 Webhook (/onConfigUrgency 和 /onExecuteUrgency),並採用關鍵字比對後備機制。
  • portal.js:提供「支援快速入口」網頁 UI (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 流程訂閱項目 (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 - 自訂動作步驟 (workflowAction) with Gemini 3.8 Flash

建立 action.js,實作 Google Workspace Studio 自訂動作步驟所需的兩個 Webhook 端點:

  • onConfigUrgency (POST /onConfigUrgency):傳回含有 textInput 小工具的設定資訊卡,該小工具會設定 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 - 支援 Quick Portal UI 和 triggers.fire 調度員

建立 portal.js,用於提供網路入口網站,並將提交的服務單轉送至 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. 將 Cloud Run 服務的 roles/run.invoker 授予 Google Workspace 外掛程式服務帳戶,讓 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. 將 roles/datastore.user (用於讀取及寫入 studio_triggers 和 support_tickets Firestore 集合) 和 roles/aiplatform.user (用於在 Gemini Enterprise Agent Platform 上呼叫 Gemini) 授予 Cloud Run 執行階段服務帳戶:
    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. 按一下「新增步驟」 (+),然後新增「Chat」 >「在 Chat 中通知我」步驟,並在訊息中加入「票證標題」、「緊急程度」和「票證說明」變數。
  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 上,使用 Gemini 3.8 Flash (gemini-3.8-flash) 結構化輸出內容,建構可分類支援單的 workflowAction 步驟,並提供彈性的關鍵字比對備援機制。

後續步驟