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.
| Field | Rule |
|---|---|
event.id / action.id | Must be a valid UUID v4 |
event.type / action.type | Must match noun.verb — regex: /^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*$/ |
event.timestamp | Must be a valid ISO 8601 datetime string |
| All non-empty string fields | Minimum length of 1 enforced |
media[].url | Must be a valid URL |
actor.email | Must 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.
| Column | Type | Notes |
|---|---|---|
id | text PK | UUID |
user_id | text FK | References users.id |
integration | text | e.g. "telegram", "gmail" |
credentials_enc | text | AES-256-GCM encrypted credentials |
| — | unique | (user_id, integration) |
integration_events
Append-only log of every UnifiedEvent received across all integrations.
| Column | Type | Notes |
|---|---|---|
id | text PK | UUID |
user_id | text FK | References users.id |
source | text | Integration name |
event_data | jsonb | Full UnifiedEvent + adapter metadata |
occurred_at | timestamp | Event time |
processed | boolean | Whether 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:
- Receive the raw webhook payload or polling response from the third-party service.
- Extract the relevant fields.
- Call
createEvent()to produce a validatedUnifiedEvent. - Write the event to the
integration_eventstable. - 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.