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_PARTNERallowlist label. - Record
AGENTIS_AGENT_IDand uppercaseAGENTIS_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. createAgentisTokenHandlerrefuses token exposure unlessexposeBrowserAccessToken: true.OPTIONSpreflight mirrorsAccess-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
responseandoutput - snake_case/camelCase field drift
- recursive product extraction from nested arrays/maps
google.type.Moneyformatting- 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
| Symptom | Error code | Fix |
|---|---|---|
404 from Agentis | AGENTIS_TENANT_NOT_ALLOWLISTED | Ask your FDE to apply COMMERCE_PARTNER to the numeric project number and verify API enablement. |
401/403 Token cannot initialize sessions | AGENTIS_AUTH_PRINCIPAL_REJECTED | Confirm service-account impersonation, target principal, and cloud-platform scope. |
| reCAPTCHA missing or failed | AGENTIS_RECAPTCHA_REQUIRED | Pass the expected verification token to the customer route and verify it server-side. |
| shopping-event relay fails | AGENTIS_SHOPPING_EVENT_FAILED | Verify AGENTIS_PROJECT_NUMBER, relay route, OAuth token, and { parent, shoppingEvent } envelope. |
| stream buffers until the full reply finishes | STREAM_PARSE_ERROR or no SDK events | Disable 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.
docs/guides/agentis-integration.md