Agentis Platform Integration

Date: 2026-05-17

This guide covers integrating a retail property with Google's Agentis Commerce Conversational AI Platform using the gecx-chat SDK.

Production integrations should use the proxy-first topology: the browser talks only to customer-owned routes, while Agentis OAuth tokens, service-account impersonation, reCAPTCHA verification, CORS policy, and shopping-event relay stay server-side.

Direct browser-to-Agentis streaming remains available for compatibility, but it requires explicit opt-ins on both the server and the client because current Agentis access tokens are broad Google OAuth bearer tokens.

Architecture

Browser                                 Customer backend                         Agentis Platform
───────                                 ────────────────                         ────────────────
createAgentisProxyTransport
  ├─ POST /api/agentis/init-session ──▶ createAgentisSessionHandler ───────────▶ :initializeSession
  │                                  ◀─ { sessionId }
  └─ POST /api/agentis/proxy ────────▶ createAgentisProxyHandler ──────────────▶ :executeChat?alt=sse
                                     ◀─ SDK TransportEvent SSE ◀─ normalized ◀─ Agentis SSE

ProductAnalyticsCollector ───────────▶ /api/agentis/shopping-event ───────────▶ :createShoppingEvent

The public Agentis SDK surface is:

  • gecx-chat/agentis: createAgentisProxyTransport, createAgentisTransport, connectAgentis, createMockAgentisBackend, normalizer utilities, analytics sink helpers.
  • gecx-chat/server: createAgentisSessionHandler, createAgentisProxyHandler, createAgentisTokenHandler, createAgentisShoppingEventHandler.

Onboarding Checklist

Work through this with your Google Forward Deployed Engineer:

  • Record the GCP project ID and numeric project number.
  • Enable agenticapplications.googleapis.com.
  • Confirm the numeric project has the TenantManager COMMERCE_PARTNER allowlist label.
  • Record AGENTIS_AGENT_ID and uppercase AGENTIS_CLIENT_ID.
  • Configure service-account impersonation for https://www.googleapis.com/auth/cloud-platform.
  • Record the shopping-event project number for :createShoppingEvent.
  • Decide the customer-owned route paths, allowed origins, CSP connect-src, reCAPTCHA hook, analytics relay, session-resume behavior, and logout invalidation.

Server Routes

Mount these routes in the customer backend. The SDK does not bundle google-auth; the host app supplies fetchAccessToken.

// app/api/agentis/_auth.ts
import { GoogleAuth, Impersonated } from 'google-auth-library';

export async function fetchAgentisAccessToken(): Promise<string> {
  const auth = new GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  });
  const sourceClient = await auth.getClient();
  const impersonated = new Impersonated({
    sourceClient,
    targetPrincipal: process.env.AGENTIS_SERVICE_ACCOUNT_EMAIL!,
    targetScopes: ['https://www.googleapis.com/auth/cloud-platform'],
    lifetime: 3600,
  });
  const { token } = await impersonated.getAccessToken();
  if (!token) throw new Error('Agentis impersonation returned no token');
  return token;
}
// app/api/agentis/init-session/route.ts
import { createAgentisSessionHandler } from 'gecx-chat/server';
import { fetchAgentisAccessToken } from '../_auth';

const handler = createAgentisSessionHandler({
  agentId: process.env.AGENTIS_AGENT_ID!,
  allowedOrigins: [process.env.NEXT_PUBLIC_APP_URL!],
  sessionMetadataDefaults: { integration: 'storefront' },
  fetchAccessToken: fetchAgentisAccessToken,
  verifyRecaptcha: async (token, context) => {
    // Verify with reCAPTCHA Enterprise and return false to fail closed.
    return Boolean(token && context.origin);
  },
});

export async function POST(request: Request) {
  return handler(request);
}

export async function OPTIONS(request: Request) {
  return handler(request);
}
// app/api/agentis/proxy/route.ts
import { createAgentisProxyHandler } from 'gecx-chat/server';
import { fetchAgentisAccessToken } from '../_auth';

const handler = createAgentisProxyHandler({
  clientId: process.env.AGENTIS_CLIENT_ID!,
  allowedOrigins: [process.env.NEXT_PUBLIC_APP_URL!],
  fetchAccessToken: fetchAgentisAccessToken,
});

export async function POST(request: Request) {
  return handler(request);
}

export async function OPTIONS(request: Request) {
  return handler(request);
}
// app/api/agentis/shopping-event/route.ts
import { createAgentisShoppingEventHandler } from 'gecx-chat/server';
import { fetchAgentisAccessToken } from '../_auth';

const handler = createAgentisShoppingEventHandler({
  projectNumber: process.env.AGENTIS_PROJECT_NUMBER!,
  allowedOrigins: [process.env.NEXT_PUBLIC_APP_URL!],
  fetchAccessToken: fetchAgentisAccessToken,
});

export async function POST(request: Request) {
  return handler(request);
}

export async function OPTIONS(request: Request) {
  return handler(request);
}

Server helper defaults:

  • OAuth tokens are not returned from createAgentisSessionHandler.
  • createAgentisTokenHandler refuses token exposure unless exposeBrowserAccessToken: true.
  • OPTIONS preflight mirrors Access-Control-Request-Headers.
  • JSON responses include Cache-Control: no-store.
  • streaming responses include X-Accel-Buffering: no.
  • non-streaming Agentis calls default to a 15 second timeout.
  • session metadata redacts secret/card-like keys and values before upstream calls.

Client Wiring

import { createChatClient } from 'gecx-chat';
import { createAgentisProxyTransport } from 'gecx-chat/agentis';
import { createAgentisShoppingEventSink } from 'gecx-chat/agentis';

const client = createChatClient({
  transport: createAgentisProxyTransport({
    sessionEndpoint: '/api/agentis/init-session',
    proxyEndpoint: '/api/agentis/proxy',
    storage: window.sessionStorage,
  }),
  analytics: {
    sink: createAgentisShoppingEventSink({
      endpoint: '/api/agentis/shopping-event',
      onError: (error) => console.warn('Agentis shopping event failed', error),
    }),
  },
});

The proxy transport declares auth.mode = 'transport-managed', so production clients do not need a fake browser AuthProvider. The Agentis session is allocated lazily on first send, not when the chat UI mounts.

Use storage for session resume. Call storage.removeItem(...) or create a fresh transport on logout if your app needs hard session invalidation.

Mock-First Development

Use createMockAgentisBackend() for CI and local fixture routes. It reproduces the messy Agentis SSE shapes the normalizer is designed to tolerate: dual roots, snake/camel drift, protobuf maps, google.type.Money, and markdown inventory bullets.

Generated apps and demos should default to mock transport, then switch live with one flag such as NEXT_PUBLIC_USE_AGENTIS=1. Keep all Agentis identifiers and credentials in server-only AGENTIS_* variables.

Direct-Browser Compatibility

Use direct mode only for controlled compatibility testing or when Google issues a browser-safe narrow token. It requires both:

  • server route opt-in: exposeBrowserAccessToken: true
  • client opt-in: allowBrowserOAuthToken: true
import { createChatClient, connectAgentis } from 'gecx-chat';

const client = createChatClient({
  ...connectAgentis({
    sessionEndpoint: '/api/agentis/init-session',
    tokenEndpoint: '/api/agentis/token',
    clientId: 'YOUR_UPPERCASE_CLIENT_ID',
    allowBrowserOAuthToken: true,
    storage: window.sessionStorage,
  }),
});

Direct mode still preserves PR 91's hardened lifecycle: shared token/session state, token-only refresh, lazy session allocation, storage resume, and invalidateSession().

Normalization

normalizeAgentisFrame converts Agentis executeChat chunks into SDK TransportEvents. It handles:

  • dual-root merge of response and output
  • snake_case/camelCase field drift
  • recursive product extraction from nested arrays/maps
  • google.type.Money formatting
  • markdown bullet carousel synthesis across streamed chunks
  • terminal flushing when the gateway omits an explicit turn-complete frame
  • reasoning/tool diagnostics hidden by default and surfaced only with surfaceReasoning

Troubleshooting

SymptomError codeFix
404 from AgentisAGENTIS_TENANT_NOT_ALLOWLISTEDAsk your FDE to apply COMMERCE_PARTNER to the numeric project number and verify API enablement.
401/403 Token cannot initialize sessionsAGENTIS_AUTH_PRINCIPAL_REJECTEDConfirm service-account impersonation, target principal, and cloud-platform scope.
reCAPTCHA missing or failedAGENTIS_RECAPTCHA_REQUIREDPass the expected verification token to the customer route and verify it server-side.
shopping-event relay failsAGENTIS_SHOPPING_EVENT_FAILEDVerify AGENTIS_PROJECT_NUMBER, relay route, OAuth token, and { parent, shoppingEvent } envelope.
stream buffers until the full reply finishesSTREAM_PARSE_ERROR or no SDK eventsDisable proxy buffering and confirm X-Accel-Buffering: no reaches the edge.

Run:

gecx doctor --backend agentis --proxy https://your-app.example/api/agentis/proxy

The Agentis doctor checks server-only env vars, public NEXT_PUBLIC_AGENTIS_* leaks, proxy readiness, CSP allowlisting, and the standard SDK footgun lint.

Source: docs/guides/agentis-integration.md