Criar etapas personalizadas do Google Workspace Studio em Node.js no Cloud Run

1. Introdução

Com o Google Workspace Studio, você automatiza processos de negócios em aplicativos do Google Workspace, como Gmail, Google Chat, Google Drive e Google Planilhas. Quando seus fluxos de trabalho precisam começar em um aplicativo da Web externo ou executar uma lógica personalizada no seu próprio ambiente de nuvem, é possível estender o Google Workspace Studio com um complemento do Google Workspace HTTP em Node.js no Cloud Run:

  1. Etapa inicial personalizada (workflowTrigger): inicia um fluxo do Google Workspace Studio quando ocorre um evento externo, como um cliente enviando um tíquete em um portal da Web interno, chamando a API REST triggers.fire do Google Workspace Studio.
  2. Etapa de ação personalizada (workflowAction): recebe variáveis de etapas anteriores no fluxo, executa lógica personalizada ou inferência de IA no seu serviço Node.js e retorna variáveis de saída para o Google Workspace Studio.

Neste codelab, você vai criar e implantar uma extensão HTTP Node.js completa para o Google Workspace Studio no Cloud Run, usar o Cloud Firestore para manter as assinaturas de gatilho de fluxo ativas e os tíquetes enviados em todas as revisões de contêineres e classificar a urgência dos tíquetes na etapa de ação personalizada usando o Gemini 3.8 Flash (gemini-3.8-flash) na Gemini Enterprise Agent Platform com um fallback resiliente de correspondência de palavras-chave.

O que você vai criar

  • Um web app Support Quick Portal e um complemento HTTP do Google Workspace criados com Node.js, Express e Firestore no Cloud Run.
  • Uma etapa inicial personalizada Novo tíquete de suporte (supportTicketTrigger) que mantém as assinaturas de fluxo ativas em uma coleção do Firestore studio_triggers, registra os tíquetes enviados em uma coleção do Firestore support_tickets e é acionada sempre que um tíquete é enviado no portal da Web, transmitindo ticketTitle e ticketDescription para um fluxo do Google Workspace Studio.
  • Uma etapa de ação personalizada Detect Urgency (urgencyDetectorStep) que classifica a urgência do tíquete usando o Gemini 3.8 Flash (gemini-3.8-flash) na Gemini Enterprise Agent Platform (aiplatform.googleapis.com), com um fallback automático para correspondência de palavras-chave (urgent, blocker, critical).

O que você vai aprender

  • Como configurar um manifesto de implantação de complemento do Google Workspace HTTP (deployment.json) com elementos workflowTrigger e workflowAction.
  • Como persistir assinaturas de gatilhos do Google Workspace Studio (triggerId, notifyUri e userOAuthToken) em uma coleção studio_triggers do Firestore e disparar fluxos usando a API REST triggers.fire.
  • Como criar uma etapa personalizada workflowAction em Node.js usando a saída estruturada do Gemini 3.8 Flash (gemini-3.8-flash) na Gemini Enterprise Agent Platform com um fallback de palavra-chave.

Pré-requisitos

  • Uma conta do Google Workspace com acesso ao Google Workspace Studio.
  • Um projeto do Google Cloud com o faturamento ativado, criado na mesma organização do Google Workspace que sua conta de usuário para que você possa instalar implantações de complementos não publicadas para teste.

2. Configurar seu projeto na nuvem do Google Cloud e o Firestore

Nesta etapa, você vai configurar a tela de permissão do OAuth do projeto, ativar as APIs necessárias do Google Cloud e do Google Workspace no Cloud Shell, provisionar o banco de dados do Firestore (default) e inicializar a conta de serviço de autorização do complemento.

  1. Abra o console do Google Cloud, selecione seu projeto na nuvem e clique no ícone Ativar o Cloud Shell na barra de navegação superior.
  2. Configure a tela de permissão OAuth para que o Google Workspace possa provisionar um cliente OAuth para seu complemento:
    1. Abra a página de branding da Google Auth Platform.
    2. Se for solicitado, clique em Começar. Se a tela de permissão já estiver configurada, pule para a etapa 3.
    3. Em Informações do app, insira Support Extension no Nome do app, selecione seu E-mail de suporte ao usuário e clique em Próxima.
    4. Em Público-alvo, selecione Interno e clique em Próxima.
    5. Em Informações de contato, insira seu endereço de e-mail e clique em Próxima.
    6. Em Concluir, aceite a Política de dados do usuário dos serviços de API do Google, clique em Continuar e em Criar.
  3. No Cloud Shell, defina seu projeto ativo (substitua YOUR_PROJECT_ID pelo ID do projeto do Google Cloud) e ative as APIs necessárias:
    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. Provisione seu banco de dados Firestore (default) em us-central1 (se ele ainda não existir) e inicialize sua conta de serviço de complementos do 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. Criar o app Node.js com o Firestore e o Gemini 3.8 Flash

Nesta etapa, você vai criar o aplicativo Node.js em ~/studio-support-addon. Para manter o código modular e legível, organize o projeto em cinco arquivos focados:

  • firestore.js: inicializa o cliente do Cloud Firestore (coleções studio_triggers e support_tickets) e atualiza os tokens OAuth em cache.
  • starter.js: implementa os webhooks da etapa inicial personalizada Novo tíquete de suporte (/onConfigTrigger e /onManageTrigger).
  • action.js: implementa os webhooks da etapa de ação personalizada Detect Urgency (/onConfigUrgency e /onExecuteUrgency) usando o Gemini 3.8 Flash (gemini-3.8-flash) com um fallback de correspondência de palavras-chave.
  • portal.js: veicula a interface da Web do Portal rápido de suporte (GET /) e aciona gatilhos ativos do Google Workspace Studio (POST /api/submit-ticket).
  • index.js: conecta todas as rotas do Express e inicia o servidor HTTP.

Inicializar o projeto e package.json

  1. Crie um diretório de projeto e navegue até ele:
    mkdir -p ~/studio-support-addon && cd ~/studio-support-addon
    
  2. Crie package.json com express, @google-cloud/firestore e @google/genai:
    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 — Estado compartilhado do Firestore e atualização do token OAuth

Crie firestore.js para gerenciar as duas coleções do Firestore e manter atualizados os tokens OAuth do usuário em cache:

  • studio_triggers: armazena as assinaturas ativas do fluxo do Google Workspace Studio (triggerId, notifyUri e userOAuthToken) para que elas sobrevivam a reinicializações de contêineres do Cloud Run e eventos de redução da escala a zero.
  • support_tickets: armazena um registro de cada tíquete de suporte enviado pelo portal.
  • refreshTokenInFirestore(req): sempre que o Google Workspace Studio chama um dos seus endpoints de webhook, ele inclui um authorizationEventObject.userOAuthToken de curta duração. Esse auxiliar atualiza os documentos de acionamento ativos com o token mais recente para que as chamadas triggers.fire em segundo plano permaneçam autorizadas.
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: etapa de ativação personalizada (workflowTrigger)

Crie starter.js para implementar os dois endpoints de webhook necessários para uma etapa inicial personalizada do Google Workspace Studio:

  • onConfigTrigger (POST /onConfigTrigger): chamado quando um usuário seleciona Novo tíquete de suporte como o iniciador do fluxo no Google Workspace Studio. Retorna uma ação de navegação pushCard que descreve o iniciador.
  • onManageTrigger (POST /onManageTrigger): é chamado quando um usuário ativa (req.body.workflow.triggerCreation) ou desativa / exclui (req.body.workflow.triggerDeletion) um fluxo. Salva ou exclui o documento de assinatura na coleção studio_triggers do 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: etapa de ação personalizada (workflowAction) com o Gemini 3.8 Flash

Crie action.js para implementar os dois endpoints de webhook necessários em uma etapa de ação personalizada do Google Workspace Studio:

  • onConfigUrgency (POST /onConfigUrgency): retorna um card de configuração com um widget textInput que define hostAppDataSource.workflowDataSource.includeVariables: true, permitindo que os usuários selecionem a variável ticketDescription gerada pela etapa inicial.
  • onExecuteUrgency (POST /onExecuteUrgency): lê a entrada ticketDescription de req.body.workflow.actionInvocation.inputs, classifica o impacto e a urgência nos negócios usando o Gemini 3.8 Flash (gemini-3.8-flash) na Gemini Enterprise Agent Platform com saída estruturada (tipo enumerado responseSchema ["High", "Normal"]), volta para detectKeywordUrgency() (urgent, blocker, critical) se a chamada do modelo falhar e retorna a variável de saída urgencyLevel usando hostAppAction.workflowAction.returnOutputVariablesAction.
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: compatibilidade com a interface do usuário do Quick Portal e o dispatcher triggers.fire

Crie portal.js para veicular o portal da Web e enviar solicitações de tíquetes ao Google Workspace Studio:

  • renderPortal (GET /): consulta a coleção studio_triggers do Firestore para mostrar quantos gatilhos ativos do Google Workspace Studio estão registrados e veicula o formulário HTML de envio de tíquetes.
  • submitTicket (POST /api/submit-ticket): itera todos os documentos de acionamento ativos em studio_triggers, invoca o endpoint triggers.fire do Google Workspace Studio (https://workspacestudio.googleapis.com/v1/triggers/{triggerId}:fire) com as variáveis de saída ticketTitle e ticketDescription e um requestId exclusivo, e salva o tíquete na coleção support_tickets do 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: ponto de entrada do servidor Express

Crie index.js para conectar as rotas do portal e os endpoints de webhook do Google Workspace Studio em um servidor HTTP do Express que detecta PORT (o padrão é 8080 no Cloud Run):

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. Implantar no Cloud Run e instalar o complemento

Nesta etapa, você vai implantar o serviço Node.js no Cloud Run, conceder permissões do IAM para o invocador de complementos do Google Workspace e o acesso ao tempo de execução do Firestore e da Agent Platform, gerar o manifesto deployment.json e instalar a implantação do complemento.

  1. Implante o serviço support-extension no Cloud Run com as variáveis de ambiente do projeto e da coleção do Firestore:
    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. Conceda roles/run.invoker no serviço do Cloud Run à conta de serviço dos complementos do Google Workspace para que o Google Workspace Studio possa chamar seus endpoints de 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. Conceda roles/datastore.user (para leitura e gravação das coleções studio_triggers e support_tickets do Firestore) e roles/aiplatform.user (para chamar o Gemini na Gemini Enterprise Agent Platform) à conta de serviço do ambiente de execução do 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. Recupere o URL implantado do Cloud Run e gere o manifesto deployment.json definindo os elementos supportTicketTrigger (workflowTrigger) e urgencyDetectorStep (workflowAction):
    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. Crie (ou substitua) e instale a implantação do complemento do Google Workspace na sua conta e imprima o URL do Portal rápido de suporte:
    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. Testar etapas personalizadas no Google Workspace Studio

  1. Abra o Google Workspace Studio (atualize a guia do navegador se ela já estiver aberta) e clique em Novo fluxo.
  2. Clique em Selecionar uma etapa inicial > Extensão de suporte > Novo tíquete de suporte.
  3. Clique em Adicionar etapa (+) > Extensão de suporte > Detectar urgência. No campo Descrição da origem, clique em + Variáveis e selecione Descrição do tíquete.
  4. Clique em Adicionar etapa (+) e adicione uma etapa Chat > Me notificar pelo Chat que inclua as variáveis Título do tíquete, Nível de urgência e Descrição do tíquete na mensagem.
  5. Ative o fluxo Ativado no canto superior direito. O Google Workspace Studio chama imediatamente o endpoint /onManageTrigger no Cloud Run, que grava a assinatura de gatilho ativa na coleção studio_triggers do Firestore.
  6. Abra o URL do portal rápido de suporte (https://support-extension-...run.app) em uma nova guia, confirme se o portal mostra o gatilho ativo 1 no Firestore e envie dois tíquetes para testar a classificação do Gemini 3.8 Flash (gemini-3.8-flash):
    • Ticket de alto impacto (Título do ticket: London office lockout, Descrição do problema: All 500 employees in the London office are locked out of their workstations 20 minutes before the board presentation.): mesmo sem palavras-chave codificadas, como urgent ou blocker, gemini-3.8-flash classifica esse ticket como de urgência High.
    • Tíquete de rotina (Título do tíquete: Conference room adapters, Descrição do problema: Could we order two extra HDMI adapters for the third-floor conference room next month?): gemini-3.8-flash classifica esse tíquete como de urgência Normal.
  7. Verifique no Google Chat e na guia Atividade do Google Workspace Studio se o fluxo foi executado para os dois tíquetes com Urgency Level: High e Urgency Level: Normal.

6. Limpar

Para evitar cobranças contínuas na sua conta do Google Cloud, remova os recursos implantados no 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. Parabéns!

Você criou, implantou e testou uma extensão HTTP do Node.js para o Google Workspace Studio usando o Firestore, o Cloud Run e o Gemini 3.8 Flash na Gemini Enterprise Agent Platform.

O que aprendemos

  • Configuração de um manifesto de complemento HTTP do Google Workspace Studio (deployment.json) com elementos workflowTrigger e workflowAction.
  • Persistência das assinaturas do Google Workspace Studio workflowTrigger na coleção studio_triggers Firestore e envios do portal de geração de registros em support_tickets para que os gatilhos sobrevivam ao escalonamento e às novas implantações do Cloud Run.
  • Criar uma etapa workflowAction que classifica tíquetes de suporte com saída estruturada Gemini 3.8 Flash (gemini-3.8-flash) na Gemini Enterprise Agent Platform, com um fallback resiliente de correspondência de palavras-chave.

Próximas etapas