Cloud Run の Node.js でカスタム Google Workspace Studio ステップを構築する

1. はじめに

Google Workspace Studio を使用すると、Gmail、Google Chat、Google ドライブ、Google スプレッドシートなどの Google Workspace アプリケーションにまたがるビジネス プロセスを自動化できます。ワークフローを外部のウェブ アプリケーションから開始する必要がある場合や、独自のクラウド環境でカスタム ロジックを実行する必要がある場合は、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 に返します。

この Codelab では、Cloud Run で Google Workspace Studio 用の完全な Node.js HTTP 拡張機能を構築してデプロイし、Cloud Firestore を使用して、コンテナ リビジョン間でアクティブなフロー トリガー サブスクリプションと送信されたチケットを永続化します。また、Gemini Enterprise Agent Platform の Gemini 3.8 Flash(gemini-3.8-flash)と復元力のあるキーワード マッチング フォールバックを使用して、カスタム アクション ステップでチケットの緊急度を分類します。

作成するアプリの概要

  • Cloud Run 上の Node.js、Express、Firestore で構築された Support Quick Portal ウェブアプリと HTTP Google Workspace アドオン。
  • 新しいサポート チケット カスタム スターター ステップ(supportTicketTrigger)。アクティブなフロー サブスクリプションを studio_triggers Firestore コレクションに保持し、送信されたチケットを support_tickets Firestore コレクションに記録します。また、ウェブポータルでチケットが送信されるたびに起動し、ticketTitle と ticketDescription を Google Workspace Studio フローに渡します。
  • Gemini Enterprise Agent Platform(aiplatform.googleapis.com)の Gemini 3.8 Flash(gemini-3.8-flash)を使用してチケットの緊急度を分類する Detect Urgency カスタム アクション ステップ(urgencyDetectorStep)。キーワード マッチング(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 Workspace 組織内に作成され、課金が有効になっている Google Cloud プロジェクト。これにより、テスト用に未公開のアドオン デプロイをインストールできます。

2. Google Cloud プロジェクトと Firestore を設定する

このステップでは、プロジェクトの OAuth 同意画面を構成し、Cloud Shell で必要な Google Cloud と Google Workspace の API を有効にし、(default) Firestore データベースをプロビジョニングし、アドオン認証サービス アカウントを初期化します。

  1. Google Cloud コンソールを開き、Google Cloud プロジェクトを選択して、上部のナビゲーション バーにある [Cloud Shell をアクティブにする] アイコンをクリックします。
  2. Google Workspace がアドオンの OAuth クライアントをプロビジョニングできるように、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 アプリケーションをビルドします。コードをモジュール化して読みやすくするために、プロジェクトを次の 5 つのファイルに整理します。

  • 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: サポート クイック ポータルのウェブ 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 トークンの更新

2 つの Firestore コレクションを管理し、キャッシュに保存されたユーザー OAuth トークンを最新の状態に保つために、firestore.js を作成します。

  • 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)

Google Workspace Studio のカスタム スターター ステップに必要な 2 つの Webhook エンドポイントを実装する starter.js を作成します。

  • onConfigTrigger(POST /onConfigTrigger): ユーザーが Google Workspace Studio でフローのスターターとして [New Support Ticket] を選択したときに呼び出されます。開始条件を説明する 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)

Google Workspace Studio のカスタム アクション ステップに必要な 2 つの Webhook エンドポイントを実装する action.js を作成します。

  • onConfigUrgency(POST /onConfigUrgency): hostAppDataSource.workflowDataSource.includeVariables: true を設定する textInput ウィジェットを含む構成カードを返します。これにより、ユーザーはスターター ステップで出力された ticketDescription 変数を選択できます。
  • onExecuteUrgency(POST /onExecuteUrgency): req.body.workflow.actionInvocation.inputs から ticketDescription 入力を読み取り、Gemini Enterprise Agent Platform の Gemini 3.8 Flash(gemini-3.8-flash)を使用してビジネスへの影響と緊急度を分類し、構造化された出力(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 ディスパッチャーをサポート

ウェブポータルを提供し、チケットの送信を Google Workspace Studio にディスパッチする portal.js を作成します。

  • 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 および Agent Platform ランタイム アクセスに IAM 権限を付与し、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 Studio が Webhook エンドポイントを呼び出せるように、Cloud Run サービスに対する roles/run.invoker を Google Workspace アドオン サービス アカウントに付与します。
    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 URL を取得し、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 アドオンのデプロイを作成(または置き換え)してインストールし、サポート クイック ポータルの URL を印刷します。
    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. [Select a starter] > [Support Extension] > [New Support Ticket] をクリックします。
  3. [ステップを追加](+)> [サポート拡張機能] > [緊急度を検出] をクリックします。[ソースの説明] フィールドで [+ 変数] をクリックし、[チケットの説明] を選択します。
  4. [ステップを追加](+)をクリックし、メッセージに [チケットのタイトル]、[緊急度]、[チケットの説明] の各変数を含む [Chat] > [Chat で通知] ステップを追加します。
  5. 右上にあるフローを [オン] に切り替えます。Google Workspace Studio は、Cloud Run の /onManageTrigger エンドポイントを直ちに呼び出し、アクティブなトリガー サブスクリプションを Firestore の studio_triggers コレクションに書き込みます。
  6. 新しいタブでサポート クイック ポータル URL(https://support-extension-...run.app)を開き、ポータルに Firestore の 1 アクティブ トリガーが表示されていることを確認し、Gemini 3.8 Flash(gemini-3.8-flash)の分類をテストするために 2 つのチケットを送信します。
    • 影響度の高いチケット(チケットのタイトル: 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 ステップを構築します。キーワード マッチングのフォールバックも用意します。

次のステップ