All Docs
FeaturesSidekickUpdated March 11, 2026

Unified Integration Type System

Unified Integration Type System

Sidekick's integration layer is built on a single, shared type system. Every connector — whether it handles Telegram messages, GitHub issues, Gmail threads, or any of the 100+ supported services — normalises its data to UnifiedEvent and UnifiedAction. The agent engine only ever works with these two types, which means consistent behaviour, predictable logging, and safe autonomous action across your entire connected account.


Core Types

All types are exported from @/lib/integrations.

UnifiedEvent

Represents any inbound event from any integration.

interface UnifiedEvent {
  id: string;           // UUID v4
  source: string;       // e.g. "telegram", "gmail", "github"
  type: string;         // noun.verb — e.g. "message.received", "issue.opened"
  actor: UnifiedActor;
  content: UnifiedEventContent;
  actionable: boolean;
  timestamp: string;    // ISO 8601
}

UnifiedAction

Represents any outbound action the agent can execute.

interface UnifiedAction {
  type: string;                          // noun.verb — e.g. "message.send", "email.reply"
  target: UnifiedActionTarget;
  payload: Record<string, unknown>;
}

UnifiedActor

The person or entity that triggered an event.

interface UnifiedActor {
  id: string;
  name: string;
  platform: string;
  email?: string;      // validated as a proper email address
  avatarUrl?: string;
}

UnifiedEventContent

The payload carried by an event.

interface UnifiedEventContent {
  text?: string;
  media?: UnifiedEventMedia[];
  metadata?: Record<string, unknown>;
}

UnifiedEventMedia

An attachment or media item within event content.

interface UnifiedEventMedia {
  url: string;    // validated as a proper URL
  type: string;
  name?: string;
}

UnifiedActionTarget

The integration and resource an action is directed at.

interface UnifiedActionTarget {
  integration: string;
  resourceId?: string;
}

Validation Rules

All types are enforced at runtime via Zod schemas. Validation runs automatically inside every factory function — invalid data throws before it can enter the system.

FieldRule
event.id / action.idMust be a valid UUID v4
event.type / action.typeMust match noun.verb — regex: /^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*$/
event.timestampMust be a valid ISO 8601 datetime string
All non-empty string fieldsMinimum length of 1 enforced
media[].urlMust be a valid URL
actor.emailMust be a valid email address (when present)

Valid type examples: message.received, issue.opened, email.reply, calendar.event_created

Invalid type examples: MessageReceived, message, Message.Received, 123.verb


Helper Functions

All helpers are exported from @/lib/integrations and validate through Zod before returning.

createEvent(input)

Constructs and validates a UnifiedEvent. Throws if any field fails validation.

import { createEvent } from '@/lib/integrations';

const event = createEvent({
  source: 'telegram',
  type: 'message.received',
  actor: { id: 'user-123', name: 'Alice', platform: 'telegram' },
  content: { text: 'Hey, can you reschedule my 3pm?' },
  actionable: true,
  timestamp: new Date().toISOString(),
});
// event.id is auto-assigned as a UUID v4

createAction(input)

Constructs and validates a UnifiedAction. Throws if any field fails validation.

import { createAction } from '@/lib/integrations';

const action = createAction({
  type: 'message.send',
  target: { integration: 'telegram', resourceId: 'chat-456' },
  payload: { text: 'Done — your 3pm has been moved to 4pm.' },
});

isActionable(event)

Returns true if the event requires agent action.

import { isActionable } from '@/lib/integrations';

if (isActionable(event)) {
  // route to agent engine
}

summarizeEvent(event)

Returns a human-readable string summary of a UnifiedEvent.

import { summarizeEvent } from '@/lib/integrations';

console.log(summarizeEvent(event));
// "message.received from Alice on telegram: Hey, can you reschedule my 3pm?"

normalizeTimestamp(input)

Normalises a timestamp string to ISO 8601. Throws if the input cannot be parsed.

import { normalizeTimestamp } from '@/lib/integrations';

const ts = normalizeTimestamp('March 11 2026 09:00:00');
// "2026-03-11T09:00:00.000Z"

Importing

All types, schemas, and helpers are available from the single barrel export:

import {
  UnifiedEvent,
  UnifiedAction,
  UnifiedActor,
  UnifiedEventContent,
  UnifiedEventMedia,
  UnifiedActionTarget,
  createEvent,
  createAction,
  isActionable,
  summarizeEvent,
  normalizeTimestamp,
} from '@/lib/integrations';

Database Storage

user_connections

Stores the connection credentials for each integration a user has linked.

ColumnTypeNotes
idtext PKUUID
user_idtext FKReferences users.id
integrationtexte.g. "telegram", "gmail"
credentials_enctextAES-256-GCM encrypted credentials
unique(user_id, integration)

integration_events

Append-only log of every UnifiedEvent received across all integrations.

ColumnTypeNotes
idtext PKUUID
user_idtext FKReferences users.id
sourcetextIntegration name
event_datajsonbFull UnifiedEvent + adapter metadata
occurred_attimestampEvent time
processedbooleanWhether the agent has acted on this event

Indexes:

  • B-tree on user_id, source, occurred_at, processed
  • GIN index on event_data (jsonb) — applied via raw SQL migration for deep jsonb querying

Writing an Adapter

Every integration adapter must normalise its data to UnifiedEvent before passing it to the agent engine. The pattern is:

  1. Receive the raw webhook payload or polling response from the third-party service.
  2. Extract the relevant fields.
  3. Call createEvent() to produce a validated UnifiedEvent.
  4. Write the event to the integration_events table.
  5. If isActionable(event) is true, enqueue the event for the agent engine.

The agent engine then constructs one or more UnifiedAction objects via createAction() and dispatches them back through the appropriate adapter.