@lobu/connector-sdk
@lobu/connector-sdk contains two authoring surfaces:
- Connectors ingest or query external systems and expose governed operations.
- Reactions run deterministic TypeScript after an automation produces validated output.
Start with the Connector guide if you want a complete example.
Every symbol below is a re-export from the package’s top-level entry point:
import { ConnectorRuntime, type ConnectorDefinition, type SyncContext, type SyncResult, type EventEnvelope, type ActionContext, type ActionResult, type ReactionContext, type ReactionClient,} from "@lobu/connector-sdk";ConnectorRuntime
Section titled “ConnectorRuntime”Abstract base class. Every connector extends it.
abstract class ConnectorRuntime<C = Record<string, unknown>, F = Record<string, unknown>> { abstract readonly definition: ConnectorDefinition; abstract sync(ctx: SyncContext<C, F>): Promise<SyncResult<C>>; async execute(ctx: ActionContext): Promise<ActionResult>; // default: { success: false, error: "Actions not supported" } async query(ctx: QueryContext<F>): Promise<QueryResult>; // default: throws async search(ctx: SearchContext<F>): Promise<QueryResult>; // default: throws async registerWebhook(ctx: WebhookRegistrationContext<F>): Promise<WebhookRegistration>; // default: throws async unregisterWebhook(ctx: WebhookRegistrationContext<F>): Promise<void>; // default: no-op async reflectMetrics(ctx: ReflectContext<F>): Promise<ReflectResult>; // default: [] async authenticate(ctx: AuthContext): Promise<AuthResult>; // default: throws}| Member | Required | Description |
|---|---|---|
definition | yes | Static metadata: key, name, version, auth, feeds, actions. See ConnectorDefinition. |
sync(ctx) | yes | Pull data. Receives last checkpoint, returns events + new checkpoint. |
execute(ctx) | no | Run an action. Default returns { success: false, error: "Actions not supported" } — override only when actions are declared. |
query(ctx) | no | Run a live read-only query without persisting events. Used for virtual-feed reads. Default throws. |
search(ctx) | no | Run live keyword recall against a virtual feed. Default throws. |
registerWebhook(ctx) | no | Subscribe a connection to provider webhooks. Required for registered webhook delivery. |
unregisterWebhook(ctx) | no | Remove the provider subscription when a connection is deleted. Default is a no-op. |
reflectMetrics(ctx) | no | Federate entity types from the source’s native semantic layer (e.g. Snowflake). Default returns []. |
authenticate(ctx) | no | Only required when authSchema.methods includes { type: "interactive" }. Stream AuthArtifacts, await UI signals. |
IntegrationConnector is the base for kind: "integration" definitions with no feeds. BridgeOnlyConnector is the base for definitions whose sync and action handlers live on an authorized device rather than the server worker.
ConnectorDefinition
Section titled “ConnectorDefinition”interface ConnectorDefinition { key: string; // 'google.gmail' name: string; description?: string; version: string; // semver kind?: "data" | "integration"; // default: data authSchema?: ConnectorAuthSchema; feeds?: Record<string, FeedDefinition>; actions?: Record<string, ActionDefinition>; automationEvents?: ConnectorAutomationEvent[]; webhook?: ConnectorWebhookSchema; optionsSchema?: Record<string, unknown>; // JSON Schema for global options faviconDomain?: string; // e.g. 'x.com' mcpConfig?: { upstreamUrl: string }; // proxy to an upstream MCP server openapiConfig?: { // auto-generate from OpenAPI specUrl: string; includeOperations?: string[]; excludeOperations?: string[]; includeTags?: string[]; serverUrl?: string; }; requiredCapability?: string; // e.g. 'screentime' (device-only) runtime?: ConnectorRuntimeInfo; // pin to a device platform agentTooling?: ConnectorAgentTooling; // CLI, env, and domains contributed to agents}Use kind: "integration" for app installations that receive inbound chat or webhook traffic but expose no syncable feeds. Data connectors can combine polled feeds, virtual feeds, webhooks, and actions.
ConnectorRuntimeInfo
Section titled “ConnectorRuntimeInfo”interface ConnectorRuntimeInfo { platforms: Array< "ios" | "android" | "macos" | "windows" | "linux" | "chrome-extension" >; scopes?: string[]; // forwarded to the native platform adapter nix?: { packages: string[] }; // native deps provisioned at run time}platforms and scopes describe device-bound connectors; omit the runtime block for cloud-fleet connectors that do not pin to a device.
nix.packages lists native system dependencies as nixpkgs attribute refs (for example ["ffmpeg", "imagemagick"]) the connector needs on PATH at execution time. npm dependencies are bundled into the connector at compile time and do not go here. Backends that can run native deps (embedded, container, machine) provision them via nix-shell; backends that cannot (for example edge workers) reject a connector that declares them. See Dependencies in the guide for the npm-vs-native split.
ConnectorAgentTooling
Section titled “ConnectorAgentTooling”Use agentTooling when an installed connection should contribute a native CLI to the agent workspace:
interface ConnectorAgentTooling { nix?: { packages: string[] }; env?: Array<{ name: string; credential: "lease"; }>; domains?: string[];}This is separate from runtime.nix: runtime provisions the connector worker; agentTooling contributes packages and network access to the agent that uses the connection. Only short-lived leased credentials can be injected into agent tooling.
ConnectorWebhookSchema
Section titled “ConnectorWebhookSchema”The connector-level webhook block tells the gateway how to verify, deduplicate, and route inbound deliveries. Registered delivery uses a per-connection callback created by registerWebhook. App-installation delivery uses a shared provider endpoint and gateway-managed installation credentials.
ConnectorAuthSchema
Section titled “ConnectorAuthSchema”interface ConnectorAuthSchema { methods: ConnectorAuthMethod[];}
type ConnectorAuthMethod = | ConnectorAuthNone | ConnectorAuthEnvKeys | ConnectorAuthOAuth | ConnectorAuthBrowser | ConnectorAuthInteractive | ConnectorAuthAppInstallation;ConnectorAuthNone
Section titled “ConnectorAuthNone”{ type: "none" }ConnectorAuthEnvKeys
Section titled “ConnectorAuthEnvKeys”interface ConnectorAuthEnvKeys { type: "env_keys"; required?: boolean; scope?: "connection" | "organization"; fields: ConnectorAuthEnvField[]; description?: string;}
interface ConnectorAuthEnvField { key: string; label?: string; description?: string; example?: string; secret?: boolean; required?: boolean;}ConnectorAuthOAuth
Section titled “ConnectorAuthOAuth”interface ConnectorAuthOAuth { type: "oauth"; provider: string; requiredScopes: string[]; optionalScopes?: string[]; required?: boolean; description?: string; scope?: "connection" | "organization"; authorizationUrl?: string; tokenUrl?: string; userinfoUrl?: string; authParams?: Record<string, string>; tokenEndpointAuthMethod?: "client_secret_post" | "client_secret_basic" | "none"; usePkce?: boolean; loginScopes?: string[]; clientIdKey?: string; clientSecretKey?: string; setupInstructions?: string; loginProvisioning?: { autoCreateConnection?: boolean };}ConnectorAuthBrowser
Section titled “ConnectorAuthBrowser”interface ConnectorAuthBrowser { type: "browser"; required?: boolean; description?: string; capture?: "cli" | "cdp"; // cli = lobu memory browser-auth; cdp = remote debug requiredDomains?: string[]; defaultCdpUrl?: string; // default: http://127.0.0.1:9222}ConnectorAuthInteractive
Section titled “ConnectorAuthInteractive”interface ConnectorAuthInteractive { type: "interactive"; required?: boolean; description?: string; scope?: "connection" | "organization"; expectedArtifact?: "qr" | "code" | "redirect" | "prompt" | "status"; timeoutSec?: number; // default 300}Connectors declaring this method must implement authenticate(ctx) and stream AuthArtifacts.
ConnectorAuthAppInstallation
Section titled “ConnectorAuthAppInstallation”Use app_installation for organization- or workspace-scoped apps such as GitHub Apps, Slack apps, or Jira site installations. The gateway owns the installation handshake, stores the tenant identity, verifies shared webhook deliveries, and mints provider credentials.
interface ConnectorAuthAppInstallation { type: "app_installation"; provider: string; installShape?: "oauth-code-exchange" | "github-app"; authorizeUrl?: string; tokenUrl?: string; providerInstance?: string; appIdKey?: string; privateKeyKey?: string; appSlugKey?: string; clientIdKey?: string; clientSecretKey?: string; webhookSecretKey?: string; installUrlTemplate?: string; permissions?: string[]; events?: string[]; required?: boolean; description?: string;}FeedDefinition
Section titled “FeedDefinition”interface FeedDefinition { key: string; name: string; description?: string; requiredScopes?: string[]; // OAuth scopes that must be held displayNameTemplate?: string; // "{subreddit} - {content_type}" configSchema?: Record<string, unknown>; // JSON Schema for per-feed config userManaged?: boolean; // skip auto-wire creation virtual?: boolean; // query live; do not sync or persist webhook?: { events: string[]; mode?: "trigger" | "store"; }; eventKinds?: Record<string, { description?: string; metadataSchema?: Record<string, unknown>; attributions?: EventAttributionRule[]; }>;}Virtual feeds implement query(ctx) and optionally search(ctx). A webhook feed can either schedule a canonical sync (trigger) or store a complete delivery directly (store).
Event attribution
Section titled “Event attribution”Attribution connects each event to the people, accounts, or resources it is about.
interface EventAttributionRule { role: "authored_by" | "about" | "mentions" | "belongs_to"; target: { entityType?: string; identities?: EntityIdentitySpec[]; titlePath?: string; createWhen?: EntityLinkPredicate; }; autoCreate?: boolean; traits?: Record<string, EntityTraitSpec>;}
interface EntityIdentitySpec { namespace: string; // 'phone', 'email', or custom eventPath: string; // dot path into the event matchOnly?: boolean; primary?: boolean;}
interface EntityTraitSpec { eventPath: string; mergeStrategy: "init_only" | "prefer_non_empty" | "overwrite";}IDENTITY constants
Section titled “IDENTITY constants”Canonical namespaces for cross-connector identity. Use these when targeting $member so identities align automatically:
const IDENTITY = { PHONE: "phone", EMAIL: "email", EMAIL_DOMAIN: "email_domain", AUTH_USER_ID: "auth_user_id",} as const;
type IdentityNamespace = (typeof IDENTITY)[keyof typeof IDENTITY];IDENTITY contains provider-independent namespaces. Connector-specific namespaces such as github_user_id or slack_user_id are exported by their connector package.
ActionDefinition
Section titled “ActionDefinition”interface ActionDefinition { key: string; name: string; description?: string; requiresApproval: boolean; kind?: "read" | "write"; // default: write requiredScopes?: string[]; annotations?: { readOnlyHint?: boolean; destructiveHint?: boolean; openWorldHint?: boolean; idempotentHint?: boolean; }; inputSchema?: Record<string, unknown>; // JSON Schema outputSchema?: Record<string, unknown>;}SyncContext
Section titled “SyncContext”interface SyncContext<C = Record<string, unknown>, F = Record<string, unknown>> { feedKey: string; feedId?: number | null; // stable id of this feed instance; undefined for direct/programmatic calls config: F; checkpoint: C | null; credentials: SyncCredentials | null; entityIds: number[]; sessionState?: Record<string, unknown> | null; installation?: ConnectorInstallationContext; emitEvents?: (events: EventEnvelope[]) => Promise<void>; updateCheckpoint?: (checkpoint: C | null) => Promise<void>;}
interface SyncCredentials { provider: string; accessToken: string; refreshToken?: string | null; expiresAt?: string | null; scope?: string | null;}emitEvents and updateCheckpoint are present only on the long-running sync path. For short syncs return the full result and ignore both hooks.
Where credentials land. ctx.credentials is populated for oauth auth. For env_keys, the values are merged into ctx.config under the keys declared on each ConnectorAuthEnvField. For browser auth, captured cookies arrive on ctx.sessionState. The same rules apply to ActionContext.
SyncResult
Section titled “SyncResult”interface SyncResult<C = Record<string, unknown>> { events: EventEnvelope[]; checkpoint: C | null; auth_update?: Record<string, unknown> | null; metadata?: { items_found?: number; items_skipped?: number; [key: string]: unknown; };}auth_update is for connectors whose credentials rotate during sync (browser cookies, opaque session tokens). The gateway persists it back onto the linked auth profile.
Live query types
Section titled “Live query types”Virtual feeds use query(ctx) for structured reads and search(ctx) for keyword recall. Results are returned to the caller and are not written to the event log.
interface QueryContext<F = Record<string, unknown>> { feedKey?: string; query: string; config: F; credentials: SyncCredentials | null; sessionState?: Record<string, unknown> | null; installation?: ConnectorInstallationContext; limit?: number; offset?: number; sort?: { column: string; order: "asc" | "desc" };}
interface SearchContext<F = Record<string, unknown>> extends QueryContext<F> { terms: string[];}
interface QueryResult { rows: Record<string, unknown>[]; columns?: Array<{ name: string; type: string }>; total?: number;}EventEnvelope
Section titled “EventEnvelope”The durable event shape returned by connector syncs.
interface EventEnvelope { origin_id: string; // required: platform's unique ID origin_type?: string; // source-native item type payload_type?: "text" | "markdown" | "json_template" | "media" | "empty"; payload_text: string; // required: main content payload_data?: Record<string, unknown>; // structured data payload_template?: Record<string, unknown> | null; attachments?: unknown[]; title?: string; author_name?: string; source_url?: string; occurred_at: Date; // required: when it happened semantic_type?: string; // content/note/summary/fact/… score?: number; // 0-100 engagement origin_parent_id?: string; // hierarchical content metadata?: Record<string, unknown>; embedding?: number[]; // pre-computed automation_signals?: ConnectorAutomationSignalDraft[];}ActionContext / ActionResult
Section titled “ActionContext / ActionResult”interface ActionContext { actionKey: string; input: Record<string, unknown>; credentials: SyncCredentials | null; config: Record<string, unknown>; sessionState?: Record<string, unknown> | null; installation?: ConnectorInstallationContext;}
interface ActionResult { success: boolean; output?: Record<string, unknown>; error?: string;}AuthContext / AuthArtifact / AuthResult
Section titled “AuthContext / AuthArtifact / AuthResult”For connectors with { type: "interactive" } auth.
interface AuthContext { config: Record<string, unknown>; previousCredentials: Record<string, unknown> | null; emit: (artifact: AuthArtifact) => Promise<void>; awaitSignal: ( name: string, options?: { timeoutMs?: number } ) => Promise<Record<string, unknown>>; signal: AbortSignal;}
type AuthArtifact = | { type: "qr"; value: string; expiresAt?: string; instructions?: string } | { type: "code"; value: string; expiresAt?: string; instructions?: string } | { type: "redirect"; url: string; mode: "popup" | "same-tab"; awaitSignal: string; instructions?: string; } | { type: "prompt"; fields: Array<{ key: string; label: string; kind: "text" | "password" | "otp"; required?: boolean; }>; submitSignal: string; instructions?: string; } | { type: "status"; message: string };
interface AuthResult { credentials: Record<string, unknown>; metadata?: { account_id?: string; display_name?: string; expires_at?: string; [key: string]: unknown; };}ctx.emit(artifact) streams the next thing the UI should show. Each emit replaces the previously active artifact. Pause on ctx.awaitSignal("name") until the UI POSTs the matching signal.
Connection / Feed / Run
Section titled “Connection / Feed / Run”Read-only records passed to runtime APIs that inspect an existing connection, feed, or run.
interface Connection { id: number; organizationId: string; connectorKey: string; displayName?: string; status: "active" | "paused" | "error" | "revoked"; accountId?: string; credentials?: Record<string, unknown>; entityIds?: number[]; config?: Record<string, unknown>; errorMessage?: string; createdBy?: string; createdAt: Date; updatedAt: Date;}
interface Feed { id: number; organizationId: string; connectionId: number; feedKey: string; status: "active" | "paused" | "error"; entityIds?: number[]; config?: Record<string, unknown>; checkpoint?: Record<string, unknown>; syncIntervalMs?: number; nextSyncAt?: Date; lastSyncAt?: Date; lastSyncStatus?: string; lastError?: string; consecutiveFailures: number; itemsCollected: number; createdAt: Date; updatedAt: Date;}
type RunType = "sync" | "action" | "code" | "automation" | "auth";type RunStatus = "pending" | "running" | "completed" | "failed" | "cancelled" | "timeout";type ApprovalStatus = "pending" | "approved" | "rejected" | "expired" | "auto";
interface Run { id: number; organizationId: string; runType: RunType; feedId?: number; connectionId?: number; actionKey?: string; actionInput?: Record<string, unknown>; actionOutput?: Record<string, unknown>; approvalStatus: ApprovalStatus; status: RunStatus; claimedBy?: string; claimedAt?: Date; lastHeartbeatAt?: Date; completedAt?: Date; connectorKey?: string; connectorVersion?: string; checkpoint?: Record<string, unknown>; itemsCollected: number; errorMessage?: string; createdAt: Date;}Reaction types
Section titled “Reaction types”The same package exports the public types for automation reactions:
import type { KnowledgeSaveInput, KnowledgeSaveResult, ReactionClient, ReactionContext,} from "@lobu/connector-sdk";
export default async function react( ctx: ReactionContext, client: ReactionClient): Promise<void> { client.log("Reaction started", { automation_id: ctx.automation.id, window_id: ctx.window.id, });}ReactionContext contains validated output, attached entities, window provenance, automation identity, and both the organization id and stable workspace slug. ReactionClient exposes knowledge, entities, notifications, operations, read-only query, and structured log.
knowledge.save(input) accepts a stable idempotency_key, optional automation_source, source and parent-event provenance, and returns KnowledgeSaveResult:
interface KnowledgeSaveResult { id: number; created: boolean; metadata: Record<string, unknown>;}notifications.send(input) also accepts idempotency_key. Use the same key when retrying an effect. See Reactions for the complete contract and examples.
Helpers
Section titled “Helpers”The package also re-exports a few utilities so connectors share one implementation:
| Export | Purpose |
|---|---|
ky, HTTPError, KyInstance, Options | Shared HTTP client. |
withHttpRetry(fn, opts?) | Retry-with-backoff wrapper for transient HTTP failures. |
calculateEngagementScore(signals) | Maps raw engagement metrics into a normalised 0-100 score. |
Type, Static, Value | TypeBox helpers for connector config, action, and event schemas. Reactions use plain JSON Schema instead. |
sdkLogger (alias logger) | Connector-scoped logger; output is captured by the run record. |
normalizeEmail, normalizeEmailDomain, normalizePhone, normalizeAuthUserId, normalizeIdentifier | Normalizers for generic identity namespaces. Connector packages own provider-specific normalizers. |
AUTOMATION_TIME_GRANULARITIES, alignToAutomationWindowStart, … | Time helpers used by automation scheduling. |
Browser SDK: acquireBrowser, launchBrowser, CdpPage, browserNetworkSync, extensionNetworkSync, etc. | Headless / CDP / extension browser primitives for browser and cdp capture. |
See the source on GitHub for the full helper surface.