Sprinter Platform

@sprinterai/core

Types, store interfaces, builders, and the module system. The foundation package with zero runtime dependencies beyond Zod.

Install

pnpm add @sprinterai/core

Overview

@sprinterai/core is the foundation of the platform. It defines all TypeScript types, abstract store interfaces, builder functions, and the module system. It has a single dependency: Zod.

Everything else in the platform depends on core. Core depends on nothing else.

Exports at a Glance

CategoryKey Exports
BuildersdefineAgent, defineTool, defineEntityType, defineWorkflow, defineModule, defineApp, field, relation
Entity typesEntityTypeRecord, EntityRecord, FieldConfig, EntityJsonSchema, CreateEntityInput, UpdateEntityInput
Agent typesAgentDefinition, AgentConfig, AgentConnection, HeartbeatSchedule
Tool typesToolDefinition, ToolSession, ToolRun
Workflow typesWorkflowDefinition, WorkflowRunRecord, WorkflowNodeRunRecord
Response typesCriteriaSetRecord, EntityResponseRecord, ResponseAggregation
Tenant typesTenantContext, AppRole, AppPermission, ROLE_IDS
Task typesTaskRecord, TaskStatus, TASK_STATUSES
Chat typesChatType, ConversationListItem, SendMessageInput, ChatParticipant, ParticipantType, ParticipantRole
Memory typesUserMemory, MemorySource, AgentMemoryConfig
Store interfacesEntityStore, AgentStore, ChatStore, TenantStore, ToolStore, ViewStore, TaskStore, MemoryStore
Render typesRenderFamily, RendererDescriptor, UIIntent, SurfaceType, SurfaceCategory, SurfaceTypeMeta, SurfaceDefinition, SurfaceDefinitionRegistry, SurfaceIntrospection
Block typesBlockType, BLOCK_TYPES, BlockConfig, BlockConfigSchema, DataSourceConfig, DataSourceConfigSchema, BlockWorkflow, ClickAction, ViewLayoutStyle, ViewTheme, FormFlowConfig, FormFlowStep, FormFlowField, FileUploadFieldConfig, ResourceDownloadBlockConfig, FormFlowOutputConfig
Block data schemasBLOCK_DATA_SCHEMAS, getBlockDataSchema, ActivityBlockDataSchema, ChildEntityListBlockDataSchema, ConnectionListBlockDataSchema, DocumentsBlockDataSchema, EntityCardBlockDataSchema, EntityFilterBlockDataSchema, FieldCardBlockDataSchema, ImageBlockDataSchema, NotesBlockDataSchema, RadarBlockDataSchema, RankingBlockDataSchema, ResponseFormBlockDataSchema, RichTextBlockDataSchema, StatusBannerBlockDataSchema, SummaryBlockDataSchema, VideoBlockDataSchema
View typesParsedViewRecord, ViewPageKind, ViewInventoryRecord, ViewHealth, parseViewBlocks, orderedBlocks, blockCount
View responseViewResponse, ViewResponseStore, ViewResponseSchema
Entity markdowngenerateEntityMarkdown, parseEntityMarkdown, extractWikilinkTitles, GenerateEntityMarkdownInput, ParsedEntityMarkdown
Plugin typesPluginManifest, PluginManifestSchema, PluginRecord, PluginVersionRecord, RendererBindingRecord, PLUGIN_STATUSES, VERSION_STATUSES, RENDERER_SOURCES, SANDBOX_ACTIONS
Aggregation typesAggregationFunction, AggregationOperator, AggregationFilter, AggregationItem, AggregationParams, AggregationResult, AggregationResultRow
Relation column typesRelationColumn, RelationAggregation, FilterRule, FilterOperator, RelationColumnResult
Model typesModelCapability, MODEL_CAPABILITIES, ModelCatalogEntry, ModelInfo, ModelProvider, FALLBACK_MODEL_CATALOG, groupModelsByProvider, PROVIDER_ORDER, PROVIDER_LABELS, PROVIDER_DEFAULT_MODEL, DEFAULT_CHAT_MODEL_ID, DEFAULT_IMAGE_MODEL_ID
PDF typesPdfDocumentProxy, PdfHighlight, SelectionContext, SelectionAction, PdfWorkerConfig, PdfLoadOptions
Email typesEmailChannel, EmailMessage, SendEmailInput, SendEmailResult, EmailProvider, NotificationEmailContext, DigestEmailContext, NotificationPreferences
Email schemasSendEmailInputSchema, NotificationPreferencesSchema, DEFAULT_NOTIFICATION_PREFERENCES
Automation typesAutomationType, UnifiedAutomation, AutomationRun, AutomationStore, AUTOMATION_TYPE_LABELS, AUTOMATION_TYPE_ICONS
Settings typesTenantSetting, SettingKey, JobFunction, DashboardPreferences, SettingsStore, JOB_FUNCTIONS, JOB_FUNCTION_LABELS
Branding typesBrandingConfig, BrandingConfigSchema, DEFAULT_BRANDING
Template typesWorkspaceTemplate, TemplateEntityType, TemplateAgent, TemplateView, TemplateSampleEntity, TemplateCategory, ApplyTemplateOptions, ApplyTemplateResult, TemplateStore, TEMPLATE_CATEGORIES, TEMPLATE_CATEGORY_LABELS
Module typesSprinterModule, AppDefinition
Eval typesEvalScorer, EvalDataset, EvalRun
Guardrail typesInputGuardrail, OutputGuardrail, GuardrailConfig
ObservabilityObservabilityEvent, ObservabilityConfig
External data typesExternalDataSource, ExternalDataPoint, DataPointMetadata, MetricItem, WebhookPayload, ExternalDataStore, EXTERNAL_DATA_SOURCE_TYPES
External data schemasMetricItemSchema, WebhookPayloadSchema, CreateDataSourceSchema, UpdateDataSourceSchema, PollConfigSchema, ApiConfigSchema
Analytics typesAnalyticsEvent, AnalyticsProvider, AnalyticsEventType, ANALYTICS_EVENTS
View permissionsViewAccessContext, canAccessView, canModifyView
Audit actionsAUDIT_ACTIONS, AuditAction, AuditStore, AuditLogFilters, ListAuditLogsResult

Builders

defineEntityType

Creates a frozen entity type definition for seeding or module registration.

import { defineEntityType, field } from "@sprinterai/core";

const opportunity = defineEntityType({
  slug: "opportunity",
  name: "Opportunity",
  description: "A deal in the pipeline",
  icon: "target",
  color: "#3B82F6",
  schema: {
    type: "object",
    properties: {
      name: field.text({ title: "Deal Name" }),
      stage: field.select({
        title: "Stage",
        options: ["Sourced", "Screening", "Due Diligence", "Closed"],
      }),
      value: field.currency({ title: "Deal Value" }),
      probability: field.percentage({ title: "Close Probability" }),
      target_close: field.date({ title: "Target Close Date" }),
      website: field.url({ title: "Company Website" }),
      tags: field.array({ title: "Tags", itemType: "string" }),
    },
    required: ["name", "stage"],
  },
  config: {
    ui: { defaultLayout: "kanban" },
  },
});

field builders

The field namespace provides typed helpers for JSON Schema properties:

BuilderJSON SchemaNotes
field.text(){ type: "string" }Plain text
field.select({ options }){ type: "string", enum: [...] }Enum select
field.number(){ type: "number" }With optional min/max
field.integer(){ type: "integer" }Integer variant
field.boolean(){ type: "boolean" }Toggle
field.currency(){ type: "number", format: "currency" }Money field
field.percentage(){ type: "number", min: 0, max: 100 }0-100 range
field.date(){ type: "string", format: "date" }Date picker
field.url(){ type: "string", format: "uri" }URL field
field.email(){ type: "string", format: "email" }Email field
field.array(){ type: "array", items: {...} }List/tags

defineAgent

import { defineAgent } from "@sprinterai/core";

const researcher = defineAgent({
  slug: "researcher",
  name: "Research Agent",
  description: "Researches entities using web search and analysis",
  systemPrompt: "You are a thorough research analyst...",
  model: "claude-sonnet-4-20250514",
  toolGroups: ["entity", "web"],
  customTools: ["roi-calculator"],
});

defineTool

import { defineTool } from "@sprinterai/core";
import { z } from "zod";

const scorer = defineTool({
  slug: "deal-scorer",
  name: "Deal Scorer",
  description: "Score a deal on multiple dimensions",
  category: "analysis",
  requiredPermission: "entities.team.update",
  inputSchema: z.object({
    entityId: z.string(),
    dimensions: z.array(z.string()),
  }),
  execute: async (input) => {
    // scoring logic
    return { scores: {} };
  },
});

defineWorkflow

import { defineWorkflow } from "@sprinterai/core";

const enrichment = defineWorkflow([
  { id: "fetch-basics", agentSlug: "researcher", fieldKey: "name" },
  { id: "fetch-financials", agentSlug: "analyst", fieldKey: "revenue", dependsOn: ["fetch-basics"] },
  { id: "score", agentSlug: "scorer", fieldKey: "score", dependsOn: ["fetch-financials"], outputType: "field" },
]);

defineModule and defineApp

import { defineModule, defineApp } from "@sprinterai/core";

const dealFlow = defineModule({
  name: "deal-flow",
  description: "Deal pipeline and scoring",
  entityTypes: [opportunity],
  agents: [researcher],
  tools: [scorer],
  dependencies: ["core-entities"],
});

const app = defineApp({
  name: "portfolio-intel",
  description: "PE portfolio intelligence",
  modules: [dealFlow],
});

Surface Types

Surface type catalogue

30 named surface types are shipped in @sprinterai/core. They are organised into 7 categories:

CategoryTypes
coregrid, sequence, slides, page, form
datasplit, kanban, timeline, calendar, gallery, table
interactiveswipe, wizard, chat, compare, rank
spatialcanvas, map, graph
narrativestory, scroll, carousel
outputpdf, email, embed-card
powerterminal, notebook, inbox, dashboard, focus

Each type carries metadata (label, description, icon, interactive, fullscreen) available via SURFACE_TYPE_META or the getSurfaceTypeMeta helper.

import {
  SURFACE_TYPES,
  SURFACE_TYPE_META,
  SurfaceTypeSchema,
  getSurfaceTypeMeta,
  surfaceTypesByCategory,
} from "@sprinterai/core";

// Validate a string against the known set
const result = SurfaceTypeSchema.safeParse(userInput);

// Metadata lookup with safe fallback
const meta = getSurfaceTypeMeta("kanban");
// { label: "Kanban", category: "data", interactive: true, fullscreen: false, ... }

// All types grouped by category
const grouped = surfaceTypesByCategory();
// { core: ["grid", "sequence", ...], data: ["split", "kanban", ...], ... }

SurfaceDefinitionRegistry

For surfaces that need typed configuration schemas or capability flags beyond what SURFACE_TYPE_META carries, use SurfaceDefinitionRegistry and defineSurface:

import {
  SurfaceDefinitionRegistry,
  defineSurface,
  surfaceDefinitions,
} from "@sprinterai/core";
import { z } from "zod";

// Define a surface with a typed config schema
const kanbanDef = defineSurface({
  type: "kanban",
  meta: { label: "Kanban", description: "...", category: "data", icon: "Columns3" },
  capabilities: {
    interactive: true,
    fullscreen: false,
    supportsPublishing: false,
    requiresClientSide: true,
  },
  configSchema: z.object({ groupByField: z.string() }),
});

// Register in the singleton
surfaceDefinitions.register(kanbanDef);

// Introspect at runtime
const info = surfaceDefinitions.introspect("kanban");
// { type, category, label, description, interactive, fullscreen, supportsPublishing }

// All registered definitions
const all = surfaceDefinitions.introspectAll();

SurfaceDefinitionRegistry is a class so applications can also instantiate their own scoped registry rather than using the singleton.

Aggregation Schemas

@sprinterai/core exports Zod schemas for parameterising entity aggregation queries. These schemas validate inputs at runtime boundaries (API routes, tool calls) before any query runs.

import {
  AggregationParamsSchema,
  AggregationFunctionSchema,
  AggregationOperatorSchema,
} from "@sprinterai/core";
import type {
  AggregationParams,
  AggregationFunction,
  AggregationFilter,
  AggregationResult,
} from "@sprinterai/core";

// Validate aggregation query parameters
const params = AggregationParamsSchema.parse({
  entityTypeSlug: "opportunity",
  groupBy: "stage",
  aggregations: [
    { field: "value", function: "sum", alias: "total_value" },
    { field: "id",    function: "count", alias: "deal_count" },
  ],
  filters: [
    { field: "status", operator: "eq", value: "active" },
  ],
});

Functions: count, sum, avg, min, max

Operators: eq, neq, gt, gte, lt, lte, in, contains, is_null, is_not_null

AggregationFilterSchema enforces that value is required for all operators except is_null and is_not_null via a Zod .refine().

Result shape:

interface AggregationResultRow {
  group?: string | null;          // Present when groupBy was requested
  [alias: string]: number | string | null | undefined;
}

interface AggregationResult {
  results: AggregationResultRow[];
  total: number;
}

Relation Column Types

Relation columns are computed columns derived from related entities (for example, the number of activities attached to an account, or the latest interaction date). Core defines the configuration types; runtime provides the resolver.

import { RelationColumnSchema, FilterRuleSchema } from "@sprinterai/core";
import type { RelationColumn, FilterRule, RelationColumnResult } from "@sprinterai/core";

const activityCount: RelationColumn = {
  label: "Activity Count",
  relatedTypeSlug: "activity",
  relationshipType: "has_activity",
  aggregation: "count",
};

const lastCallDate: RelationColumn = {
  label: "Last Call",
  relatedTypeSlug: "activity",
  relationshipType: "has_activity",
  aggregation: "latest",
  field: "date",
  filters: [{ field: "activity_type", operator: "eq", value: "call" }],
};

Aggregations: count, latest, sum, avg, min, max

Filter operators: eq, neq, gt, gte, lt, lte, in, contains

RelationColumnResult is a map of entityId → { columnKey → computed value } returned by resolveRelationColumns in @sprinterai/runtime.

See @sprinterai/runtime for resolveRelationColumns.

Analytics Events

@sprinterai/core defines the analytics contract so that @sprinterai/runtime and @sprinterai/supabase can both reference event types without a circular dependency.

import { ANALYTICS_EVENTS } from "@sprinterai/core";
import type { AnalyticsEvent, AnalyticsProvider, AnalyticsEventType } from "@sprinterai/core";

ANALYTICS_EVENTS

15 standard event type constants, organized by domain:

DomainConstants
EntityENTITY_CREATED, ENTITY_UPDATED, ENTITY_DELETED
WorkflowWORKFLOW_STARTED, WORKFLOW_COMPLETED, WORKFLOW_FAILED
AgentAGENT_INVOKED, AGENT_DELEGATED
ToolTOOL_EXECUTED
SkillSKILL_LOADED
ChatCHAT_MESSAGE_SENT
DocumentDOCUMENT_PROCESSED
SearchSEARCH_PERFORMED
CommentCOMMENT_CREATED
NotificationNOTIFICATION_SENT

Use these constants at call sites to avoid raw strings and enable easy grep-based usage discovery.

AnalyticsProvider

Provider interface for any analytics backend (PostHog, Mixpanel, custom DB):

interface AnalyticsProvider {
  track(event: AnalyticsEvent): void | Promise<void>;
}

See @sprinterai/runtime for createAnalyticsRecorder and createAnalyticsHooks. See @sprinterai/supabase for SupabaseAnalyticsProvider.

View Permissions

Pure permission functions for scope-based view access control. Both functions are in @sprinterai/core/view:

import { canAccessView, canModifyView } from "@sprinterai/core/view";
import type { ViewAccessContext } from "@sprinterai/core/view";

ViewAccessContext

interface ViewAccessContext {
  tenantId: string;
  userId: string;
  /** Role-based permission strings — e.g. 'views.team.update', 'admin'. */
  permissions: string[];
}

canAccessView

Determines whether a user can read a view based on its scope:

ScopeWho can access
systemAll authenticated users in the tenant
tenantAll users in the tenant
userCreator only

Tenant ID must always match. Returns false for cross-tenant access regardless of scope.

canModifyView

Determines whether a user can update or delete a view:

ScopeWho can modify
systemAdmins only (admin or manage_views permission)
tenantAdmins or the creator
userCreator only

See @sprinterai/runtime for createViewPermissionEnforcer, which wraps these functions around a ViewStore.

Audit Actions

import { AUDIT_ACTIONS } from "@sprinterai/core";
import type { AuditAction, AuditStore, AuditLogFilters } from "@sprinterai/core";

AUDIT_ACTIONS

Standard operation constants for AuditLogRecord.action:

INSERT  UPDATE  DELETE  LOGIN  LOGOUT
PERMISSION_CHANGE  EXPORT  IMPORT  BULK_OPERATION

AuditStore interface

interface AuditStore {
  listAuditLogs(tenantId: string, filters?: AuditLogFilters): Promise<ListAuditLogsResult>;
  getAuditLog(tenantId: string, id: string): Promise<AuditLogRecord | null>;
  createAuditLog(tenantId: string, entry: Omit<AuditLogRecord, 'id' | 'tenant_id' | 'created_at'>): Promise<AuditLogRecord>;
  batchCreateAuditLogs(
    tenantId: string,
    entries: Array<Omit<AuditLogRecord, 'id' | 'tenant_id' | 'created_at'>>,
    correlationId?: string,
  ): Promise<AuditLogRecord[]>;
}

AuditLogFilters

FieldTypeDescription
resourceTypestring?Filter by table name
resourceIdstring?Filter by resource ID
userIdstring?Filter by who made the change
operationstring?Single operation type (e.g. 'INSERT')
operationsstring[]?Multiple operation types at once
startDatestring?ISO timestamp lower bound (inclusive)
endDatestring?ISO timestamp upper bound (inclusive)
correlationIdstring?Find related entries from the same batch
limitnumber?Default 50
offsetnumber?For pagination

See @sprinterai/runtime for createAuditContext, which auto-injects correlation IDs.

Model Catalog

@sprinterai/core owns the canonical model metadata layer. This lets any package (supabase, typespec, UI) read model capabilities and defaults without depending on @sprinterai/runtime.

Import from the root barrel:

import {
  FALLBACK_MODEL_CATALOG,
  PROVIDER_ORDER,
  PROVIDER_LABELS,
  PROVIDER_DEFAULT_MODEL,
  DEFAULT_CHAT_MODEL_ID,
  DEFAULT_IMAGE_MODEL_ID,
  groupModelsByProvider,
} from "@sprinterai/core";
import type {
  ModelCapability,
  ModelCatalogEntry,
  ModelInfo,
  ModelProvider,
} from "@sprinterai/core";

ModelCapability

11 capability flags grouped into three categories:

CategoryCapabilities
Inputtext_input, image_input, video_input, audio_input
Outputtext_output, image_output, audio_output
Featurestool_use, reasoning, code, web_search

MODEL_CAPABILITY_LABELS maps each flag to a display string. MODEL_CAPABILITY_CATEGORIES groups them for UI rendering.

ModelCatalogEntry

Rich catalog entry with full metadata for model-selection UIs:

interface ModelCatalogEntry {
  id: string;                   // Canonical ID, e.g. 'claude-sonnet-4-6'
  provider: ModelProvider;      // 'anthropic' | 'openai' | 'google' | 'xai' | 'deepseek'
  name: string;
  description?: string | null;
  capabilities: ModelCapability[];
  speedTier: 'fast' | 'medium' | 'slow';
  reasoningLevel?: 'low' | 'medium' | 'high' | null; // Only for 'reasoning' models
  costPer1mInput: number;       // USD per 1 million input tokens
  costPer1mOutput: number;      // USD per 1 million output tokens
  enabled?: boolean;
}

ModelInfo

DB row shape for tenant-configurable model catalogs. Used when a tenant overrides the default catalog via a models table (not yet shipped in @sprinterai/supabase):

interface ModelInfo {
  id?: string;
  model_id: string;
  display_name: string;
  provider: ModelProvider;
  speed_tier: 'fast' | 'medium' | 'slow';
  capabilities?: ModelCapability[];
  description?: string | null;
  reasoning_level?: 'low' | 'medium' | 'high' | null;
  cost_per_1m_input?: number;
  cost_per_1m_output?: number;
  enabled?: boolean;
  sort_order?: number;
}

FALLBACK_MODEL_CATALOG

17-entry default catalog used when a tenant has no custom model configuration:

ProviderModels
anthropicclaude-haiku-4-5, claude-sonnet-4-5, claude-sonnet-4-6, claude-opus-4-6
openaigpt-4.1, gpt-4o, o3, o4-mini, gpt-image-1.5, gpt-image-1, dall-e-3
googlegemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-image
xaigrok-3, grok-3-mini
deepseekdeepseek-chat, deepseek-reasoner

groupModelsByProvider

Groups any list of ModelInfo (or ModelCatalogEntry) objects by provider, preserving PROVIDER_ORDER for display:

const { grouped, sortedProviders } = groupModelsByProvider(FALLBACK_MODEL_CATALOG);
// grouped: Map<ModelProvider, ModelCatalogEntry[]>
// sortedProviders: ['anthropic', 'openai', 'google', 'xai', 'deepseek'] (only those present)

Provider constants

ConstantTypeDescription
PROVIDER_ORDERModelProvider[]Display order for provider groups
PROVIDER_LABELSRecord<ModelProvider, string>Human-readable provider names
PROVIDER_DEFAULT_MODELRecord<ModelProvider, string>Per-provider fallback model ID
DEFAULT_CHAT_MODEL_IDstring'claude-sonnet-4-6'
DEFAULT_IMAGE_MODEL_IDstring'gpt-image-1.5'

See @sprinterai/runtime for ModelConfig, MODEL_REGISTRY, and resolveModel, which add provider-SDK wiring on top of these core types.

Block Types

@sprinterai/core defines the complete block configuration schema used by views, the renderer layer, and the plugin system.

import {
  BLOCK_TYPES,
  BlockConfigSchema,
  DataSourceConfigSchema,
  VIEW_LAYOUTS,
  VIEW_THEMES,
  BLOCK_SIZES,
} from "@sprinterai/core";
import type {
  BlockType,
  BlockConfig,
  DataSourceConfig,
  BlockWorkflow,
  ViewLayoutStyle,
  ViewTheme,
  BlockSize,
  BlockRole,
  FormFlowConfig,
} from "@sprinterai/core";

BLOCK_TYPES catalog

33 canonical block types:

stat-cards, table, chart, radar, ranking, kanban, summary,
field-card, connection-list, data-table, activity, entity-card,
entity-feed, rich-text, status-banner, child-entity-list, custom,
image, video, excalidraw, react-flow, bubble-chart, entity-graph,
form-flow, canvas, menu, pdf-viewer, response-form, entity-filter,
notes, documents, tool, resource-download

The resource-download block renders a completion screen with a headline, message, and optional download button — used as the final step in a form-flow sequence.

BlockConfig

Serializable block config stored in the views.blocks JSONB column:

const block: BlockConfig = {
  id: "blk-abc123",
  type: "chart",
  label: "Revenue by Stage",
  size: "half",          // legacy: 'full' | 'half' | 'third'
  span: 6,               // preferred: 12-column grid span (1-12)
  row: 2,                // optional explicit row placement (1-based)
  height: 3,             // grid row units
  dataSourceId: "opportunities",
  clickAction: { type: "navigate", target: "/entities/:id" },
};

BlockConfigFlexibleSchema accepts both a BlockConfig[] array (legacy) and a Record<string, BlockConfig> flat map at Zod parse boundaries.

DataSourceConfig

Named entity query binding that blocks reference via dataSourceId. Views store a dataSources map of these at the view level:

const ds: DataSourceConfig = {
  entityTypeSlug: "opportunity",
  filters: [{ field: "status", operator: "eq", value: "active" }],
  sort: { field: "updated_at", order: "desc" },
  limit: 50,
  relationColumns: {
    activity_count: {
      label: "Activities",
      relatedTypeSlug: "activity",
      relationshipType: "has_activity",
      aggregation: "count",
    },
  },
};

FormFlowConfig

Multi-step form configuration for the form-flow block type:

interface FormFlowConfig {
  steps: FormFlowStep[];
  completionMessage?: string;
  allowSkipOptional?: boolean;
  output?: FormFlowOutputConfig;
}

interface FormFlowStep {
  id: string;
  title: string;
  description?: string;
  fields: FormFlowField[];
  instructions?: string;
}

Field types: text, textarea, select, multi-select, number, slider, date, entity-picker, tag-input, rich-text, file-upload.

FileUploadFieldConfig

Per-field config for file-upload form-flow fields:

const FileUploadFieldConfigSchema = z.object({
  accept: z.array(z.string()).optional(),  // MIME types or extensions e.g. ['image/*', '.pdf']
  maxBytes: z.number().optional(),         // Maximum file size per file
  multiple: z.boolean().optional(),        // Allow multiple files
});

ResourceDownloadBlockConfig

Config for the resource-download block, typically used as a form-flow completion screen:

const ResourceDownloadBlockConfigSchema = z.object({
  title: z.string().optional(),
  message: z.string().optional(),
  downloadUrl: z.string().optional(),
  downloadLabel: z.string().optional(),
});

FormFlowOutputConfig

Discriminated union describing what happens when a form-flow completes:

type FormFlowOutputConfig =
  | { type: 'entity'; entityTypeSlug: string }   // Create an entity from collected fields
  | { type: 'webhook'; webhookUrl: string }       // POST collected data to an endpoint
  | { type: 'none' };                             // No action — show completion message only

Use FormFlowOutputConfigSchema to validate at parse boundaries.

Block Data Schemas

@sprinterai/core exports a Zod schema for every block's runtime data prop. These schemas are the single source of truth for what shape a block component expects to receive and can be used to validate data before rendering.

import {
  BLOCK_DATA_SCHEMAS,
  getBlockDataSchema,
  ActivityBlockDataSchema,
  EntityCardBlockDataSchema,
  RadarBlockDataSchema,
} from "@sprinterai/core";
import type {
  ActivityBlockData,
  EntityCardBlockData,
  RadarBlockData,
} from "@sprinterai/core";

// Look up by block type string
const schema = getBlockDataSchema("activity");
// Returns ActivityBlockDataSchema, or undefined for unknown types

// Validate before rendering
const result = schema?.safeParse(rawData);

// All schemas in a keyed map
const allSchemas = BLOCK_DATA_SCHEMAS;
// Record<string, z.ZodType>

Schema catalogue

Block typeSchemaData shape
activityActivityBlockDataSchemaActivityItem[]id, action, timestamp, optional actor, details
child-entity-listChildEntityListBlockDataSchemaChildEntityItem[]id, title, optional status, type, createdAt, href
connection-listConnectionListBlockDataSchemaConnectionItem[]id, title, optional type, relationship, href
documentsDocumentsBlockDataSchemaDocumentItem[]id, name, optional type, size, url
entity-cardEntityCardBlockDataSchemaSingle entity object — title required, optional id, type, description, status, href, fields
entity-filterEntityFilterBlockDataSchemaConfig-driven — optional filters array of { field, operator, value, active? }
field-cardFieldCardBlockDataSchemaRecord<string, unknown> — arbitrary key-value payload; config controls which fields are shown
imageImageBlockDataSchemaConfig-driven — optional src, alt, caption, fit ('cover' | 'contain')
notesNotesBlockDataSchemastring — raw note text
radarRadarBlockDataSchemaRadarDataPoint[]subject, value, optional fullMark
rankingRankingBlockDataSchemaRankingItem[]title required, optional rank, value, change ('up' | 'down' | 'neutral')
response-formResponseFormBlockDataSchemaConfig-driven — optional criteria array of { id, label, type, max? }
rich-textRichTextBlockDataSchemastring — markdown content
status-bannerStatusBannerBlockDataSchemaConfig-driven — optional status ('info' | 'success' | 'warning' | 'error'), message
summarySummaryBlockDataSchemastring — summary text
videoVideoBlockDataSchemaConfig-driven — optional src, poster, caption

Schema design principle: schemas are permissive on read. Optional fields degrade gracefully in the block UI. Required fields are only those without which the block renders nothing useful. Config-driven blocks (image, video, entity-filter, response-form, status-banner) expose their config shape under the same *BlockDataSchema naming convention for consistency.

View Types

ParsedViewRecord is the fully-typed representation of a view DB row with all JSONB fields deserialized.

import { parseViewBlocks, orderedBlocks, blockCount } from "@sprinterai/core";
import type { ParsedViewRecord, ViewPageKind, ViewInventoryRecord } from "@sprinterai/core";

ParsedViewRecord

interface ParsedViewRecord {
  id: string;
  tenant_id: string;
  entity_type_id: string | null;
  entity_type_slug: string | null;
  entity_id: string | null;
  title: string;
  slug: string;
  page_type: ViewPageKind;        // 'list' | 'workspace'
  blocks: Record<string, BlockConfig>;
  blockOrder: string[];
  layout: ViewLayoutStyle;        // 'stack' | 'grid-2' | 'grid-3' | 'bento' | 'single'
  scope: 'default' | 'shared' | 'user';
  dataSources: Record<string, DataSourceConfig>;
  theme: ViewTheme;               // 'dashboard' | 'page' | 'minimal' | 'embed'
  surface_type: string;
  surface_config: Record<string, unknown>;
  is_default: boolean | null;
  parent_view_id: string | null;
  created_at: string;
  updated_at: string;
}

parseViewBlocks

Normalises raw JSONB from the database into { blocks, blockOrder }. Handles two wire formats:

  • Legacy array (BlockConfig[]) — assigns IDs where missing, converts to keyed map
  • Flat map (Record<string, BlockConfig>) — uses as-is; blockOrder falls back to key insertion order
const { blocks, blockOrder } = parseViewBlocks(rawBlocks, rawBlockOrder);

Rendering helpers

// Ordered array of BlockConfig for rendering loops
const rendered = orderedBlocks(view);

// Block count (length of blockOrder)
const count = blockCount(view);

ViewInventoryRecord

Admin health-check shape. Pairs a ParsedViewRecord with its linked entity type, entity, and parent view, plus a health classification ('healthy' | 'needs-repair' | 'malformed') and an issues array with code, severity, and message for each problem.

Plugin Types

The plugin system lets tenants install custom renderers that override built-in block rendering. Core defines the manifest schema and DB row types; resolver logic lives in the supabase and runtime layers.

import {
  PluginManifestSchema,
  CreatePluginInputSchema,
  UpdatePluginInputSchema,
  CreateVersionInputSchema,
  CreateBindingInputSchema,
  PLUGIN_STATUSES,
  VERSION_STATUSES,
  RENDERER_SOURCES,
  SANDBOX_ACTIONS,
} from "@sprinterai/core";
import type {
  PluginManifest,
  PluginRecord,
  PluginVersionRecord,
  RendererBindingRecord,
} from "@sprinterai/core";

PluginManifest

Stored as JSONB in plugin_versions.manifest and validated at the API boundary with PluginManifestSchema:

const manifest: PluginManifest = {
  name: "custom-chart",
  version: "1.2.0",
  family: "block",
  description: "Custom D3 chart renderer",
  targets: ["record:chart", "record:chart:opportunity"],
  dataBindings: { record: true, schema: true, viewConfig: true },
  actions: ["update-field", "navigate"],
  configSchema: { colorScheme: { type: "string" } },
};

targets use family:blockType[:entityTypeSlug] notation. More-specific targets take priority during renderer resolution.

Status constants

ConstantValues
PLUGIN_STATUSESdraft, published, archived
VERSION_STATUSESdraft, review, approved, rejected
RENDERER_SOURCEScustom, plugin, preset, builtin
SANDBOX_ACTIONSnavigate, update-field, submit-form, open-dialog, copy-to-clipboard, refresh-data

CRUD input schemas

SchemaPurpose
CreatePluginInputSchemaPOST /plugins — slug (lowercase alphanumeric), name, family
UpdatePluginInputSchemaPATCH /plugins/:id — name, description, author, icon, status
CreateVersionInputSchemaPOST /plugins/:id/versions — semver version string, manifest, optional bundle
CreateBindingInputSchemaPOST /bindings — plugin_version_id, family, target_key, optional priority/config
UpdateBindingInputSchemaPATCH /bindings/:id — priority, enabled, config

PDF Types

Server-safe structural types for the PDF viewer. No pdfjs-dist import — all types are plain TypeScript interfaces usable in server components without the browser-only library.

import type {
  PdfDocumentProxy,
  PdfHighlight,
  SelectionContext,
  SelectionAction,
  PdfWorkerConfig,
  PdfLoadOptions,
} from "@sprinterai/core";

PdfDocumentProxy

Minimal structural type matching the pdfjs-dist PDFDocumentProxy shape. Allows document-handling code to be typed without server-side pdfjs-dist imports:

type PdfDocumentProxy = {
  numPages: number;
  getPage: (pageNum: number) => Promise<unknown>;
  getDestination: (dest: string) => Promise<unknown>;
  getPageIndex: (pageRef: unknown) => Promise<number>;
  destroy: () => void;
};

PdfHighlight

Highlight annotation with normalized (0–1) coordinates relative to the page:

interface PdfHighlight {
  id: string;
  page: number;    // 0-based page index
  x: number;      // normalized 0-1 within page
  y: number;
  width: number;
  height: number;
  color?: string;  // CSS color string
  text?: string;   // captured text content of the highlighted region
}

SelectionContext and SelectionAction

Types for the text-selection toolbar shown when a user highlights text:

interface SelectionContext {
  text: string;
  pageIndex: number;
  coordinates: { x: number; y: number; width: number; height: number };
  rect: { top: number; left: number; bottom: number; right: number; width: number; height: number };
}

interface SelectionAction {
  id: string;
  label: string;
  icon?: string;   // Lucide icon name
  onAction: (context: SelectionContext) => void;
}

Email Types

@sprinterai/core defines the email contracts used by @sprinterai/runtime's email engine and available to any package lower in the dependency chain.

Import from the ./email subpath export:

import type {
  EmailChannel,
  EmailMessage,
  EmailProvider,
  SendEmailInput,
  SendEmailResult,
  NotificationEmailContext,
  DigestEmailContext,
  NotificationPreferences,
} from "@sprinterai/core/email";

import {
  SendEmailInputSchema,
  NotificationPreferencesSchema,
  DEFAULT_NOTIFICATION_PREFERENCES,
} from "@sprinterai/core/email";

Core types

TypeDescription
EmailChannel'transactional' | 'digest' | 'notification'
EmailMessageWire-level message: to, subject, html, optional from, replyTo, tags
SendEmailInputValidated subset of EmailMessage; inferred from SendEmailInputSchema
SendEmailResult{ id: string; success: boolean } returned by a provider
EmailProviderInterface with send(message) and optional sendBatch(messages)

Render context types

TypeRequired fieldsOptional fields
NotificationEmailContexttitle, bodyrecipientName, link, linkLabel, tenantName
DigestEmailContextperiod, items[]recipientName, tenantName, summary

items entries each carry title (required) plus optional description, link, and timestamp.

Notification preferences

NotificationPreferencesSchema validates user-level notification settings at API boundaries:

const prefs = NotificationPreferencesSchema.parse(rawUserSettings);
// {
//   emailFrequency: "realtime" | "daily" | "weekly" | "never"  (default: "never")
//   emailNotifications: boolean                                 (default: false)
//   inAppNotifications: boolean                                 (default: true)
//   activityUpdates: boolean                                    (default: true)
// }

Use DEFAULT_NOTIFICATION_PREFERENCES as the fallback when a user has no saved preferences.

EmailConfig

EmailConfig (API key, from address) lives in packages/core/src/provider/provider-types.ts as part of the top-level ProviderConfig. Import it from the root:

import type { EmailConfig, ProviderConfig } from "@sprinterai/core";

See @sprinterai/runtime for createEmailEngine, which consumes EmailProvider and EmailConfig.

Store Interfaces

Core defines abstract store interfaces that backends implement:

interface EntityStore {
  getEntityType(slugOrId: string): Promise<EntityTypeRecord | null>;
  listEntityTypes(): Promise<EntityTypeRecord[]>;
  getEntity(id: string): Promise<EntityRecord | null>;
  searchEntities(query: string, options?: { typeSlug?: string; tag?: string; limit?: number }): Promise<EntityRecord[]>;
  createEntity(input: CreateEntityInput): Promise<EntityRecord>;
  updateEntity(id: string, input: UpdateEntityInput): Promise<EntityRecord>;
  deleteEntity(id: string): Promise<void>;
  createRelation(fromId: string, toId: string, type: string): Promise<void>;
  getEntityCounts(tenantId: string): Promise<Record<string, number>>;
}

ChatStore

ChatStore covers message history, participant management, and unread tracking:

interface ChatStore {
  // Core message operations
  getChat(chatId: string): Promise<ConversationListItem | null>;
  createChat(options: { title?: string; chatType: 'ai' | 'direct' | 'group'; agentSlug?: string; userId?: string }): Promise<ConversationListItem>;
  getMessages(chatId: string, options?: { limit?: number; before?: string }): Promise<MessageRecord[]>;
  saveMessage(chatId: string, input: SaveMessageInput): Promise<MessageRecord>;
  getChatsByUser(userId: string, options?: { limit?: number }): Promise<ConversationListItem[]>;

  // Participant management
  getParticipants(chatId: string): Promise<ChatParticipant[]>;
  addParticipant(chatId: string, tenantId: string, participantId: string, participantType: ParticipantType, options?: { role?: ParticipantRole; autoRespond?: boolean }): Promise<ChatParticipant>;
  removeParticipant(chatId: string, participantId: string): Promise<void>;

  // Unread tracking
  markAsRead(chatId: string, userId: string): Promise<void>;
  getUnreadCount(userId: string): Promise<number>;
}

ChatParticipant is the full participant record returned by getParticipants and addParticipant:

interface ChatParticipant {
  id: string;
  chat_id: string;
  tenant_id: string;
  participant_type: ParticipantType; // 'user' | 'agent'
  participant_id: string;
  role: ParticipantRole;             // 'owner' | 'member'
  auto_respond: boolean;
  joined_at: string;
  last_read_at: string;
}

Import the chat types from the root barrel:

import type {
  ChatParticipant,
  ParticipantType,
  ParticipantRole,
  ConversationListItem,
  ChatStore,
} from "@sprinterai/core";

Other stores follow the same pattern: AgentStore, TenantStore, ToolStore, ViewStore, TaskStore, MemoryStore.

See @sprinterai/runtime for in-memory implementations (including InMemoryChatStore) and inbox helpers (buildConversationList, computeUnreadCount, parseMentions, filterRespondingAgents). See @sprinterai/supabase for Supabase implementations.

Tenant and Permissions

import { ROLE_IDS, isAdminRole, mapRoleSlug } from "@sprinterai/core";

// ROLE_IDS maps slug -> UUID
const adminRoleId = ROLE_IDS.tenant_admin;

// Check if a role is admin-level
isAdminRole("system_admin"); // true
isAdminRole("viewer");       // false

// Map DB role slug to app role
mapRoleSlug("system_admin"); // "owner"

Available roles: system_admin, tenant_admin, editor, member, viewer, guest.

63 granular permissions in the AppPermission type, format: {resource}.{level}.{action}.

Response Scoring

import { computeResponseScore, isNumericDimension } from "@sprinterai/core";

// Compute weighted score from dimension scores
const score = computeResponseScore(dimensionScores, criteriaSet);

Automation Types

@sprinterai/core defines the unified automation contracts used by any UI or backend that schedules and monitors automated agent runs.

import type {
  AutomationType,
  UnifiedAutomation,
  AutomationRun,
  AutomationStore,
} from "@sprinterai/core";
import { AUTOMATION_TYPES, AUTOMATION_TYPE_LABELS, AUTOMATION_TYPE_ICONS } from "@sprinterai/core";

AutomationType

type AutomationType = 'heartbeat' | 'source-sync';
ValueLabelIcon
'heartbeat'Agent Heartbeatheart-pulse
'source-sync'Source Syncrefresh-cw

UnifiedAutomation

A single view across both automation types, suitable for list/detail UIs:

interface UnifiedAutomation {
  id: string;
  name: string;
  type: AutomationType;
  icon: string;
  schedule: string | null;       // cron expression, or null if not scheduled
  scheduleHuman: string;         // human-readable schedule description
  enabled: boolean;
  lastRunAt: string | null;
  lastRunStatus: string | null;
  nextRunAt: string | null;
  agentId?: string;              // heartbeat automations only
  entityId?: string;             // source-sync automations only
}

AutomationRun

Execution record for a single automation run:

interface AutomationRun {
  id: string;
  status: string;
  started_at: string | null;
  completed_at: string | null;
  duration_ms: number | null;
  tokens_used: number | null;
  cost_cents: number | null;
  error_log: string | null;
  metadata: Record<string, unknown> | null;
  chat_id: string | null;        // linked chat session, if any
}

AutomationStore

interface AutomationStore {
  listAutomations(tenantId: string): Promise<UnifiedAutomation[]>;
  getAutomation(tenantId: string, id: string): Promise<UnifiedAutomation | null>;
  setEnabled(tenantId: string, id: string, enabled: boolean): Promise<void>;
  listRuns(tenantId: string, automationId: string, limit?: number): Promise<AutomationRun[]>;
}

See @sprinterai/runtime for cronToHuman, getNextRun, and AutomationPlan — the runtime utilities for schedule display and AI-assisted automation creation.

Settings Types

The settings module provides a typed key-value store contract for tenant and user preferences.

import type { TenantSetting, SettingKey, JobFunction, DashboardPreferences, SettingsStore } from "@sprinterai/core";
import { SETTING_KEYS, JOB_FUNCTIONS, JOB_FUNCTION_LABELS } from "@sprinterai/core";

SettingKey

Seven built-in keys:

KeyStores
'theme'Tenant color/theme config
'navigation'Nav tree config
'ai_limits'Model usage limits
'branding'BrandingConfig (see below)
'agent_context'Default agent context
'workflow_agent_defaults'Workflow agent defaults
'dashboard'DashboardPreferences

TenantSetting

interface TenantSetting {
  id: string;
  tenantId: string;
  userId: string | null;    // null = tenant-level; string = user-scoped
  key: SettingKey;
  value: Record<string, unknown>;
  createdAt: string;
  updatedAt: string;
}

DashboardPreferences

Stored under key 'dashboard', used for onboarding personalization:

interface DashboardPreferences {
  jobFunction?: JobFunction;          // 'executive' | 'sales' | 'content_ops' | 'operations' | 'analyst' | 'other'
  pinnedEntityTypes?: string[];
  onboardingCompleted?: boolean;
}

SettingsStore

interface SettingsStore {
  getSetting(tenantId: string, key: SettingKey, userId?: string): Promise<TenantSetting | null>;
  setSetting(tenantId: string, key: SettingKey, value: Record<string, unknown>, userId?: string): Promise<TenantSetting>;
  listSettings(tenantId: string, userId?: string): Promise<TenantSetting[]>;
  deleteSetting(tenantId: string, key: SettingKey, userId?: string): Promise<void>;
}

See @sprinterai/runtime for resolveSettings (cascade merger) and shouldShowDashboardRoleHint (onboarding gate).

Branding Types

Per-tenant app customization config, typically stored in SettingsStore under key 'branding'.

import { BrandingConfigSchema, DEFAULT_BRANDING } from "@sprinterai/core";
import type { BrandingConfig } from "@sprinterai/core";

// Validate incoming branding payload at the API boundary
const branding = BrandingConfigSchema.parse(rawValue);
interface BrandingConfig {
  appName?: string;    // Sidebar header text (max 50 chars)
  logoUrl?: string;    // Custom logo image URL (takes precedence over logoIcon)
  logoIcon?: string;   // Icon name from the platform icon map (max 50 chars)
  tagline?: string;    // Subtitle under app name (max 100 chars)
}

When no tenant setting is configured, fall back to:

const DEFAULT_BRANDING = {
  appName: 'Sprinter',
  tagline: 'AI-powered workspace',
};

Workspace Templates

The template store contract supports rich workspace templates with views, sample entities, and industry categorization.

import type {
  WorkspaceTemplate,
  TemplateView,
  TemplateSampleEntity,
  TemplateCategory,
  ApplyTemplateOptions,
  ApplyTemplateResult,
  TemplateStore,
} from "@sprinterai/core";
import { TEMPLATE_CATEGORIES, TEMPLATE_CATEGORY_LABELS } from "@sprinterai/core";

WorkspaceTemplate fields

FieldTypeDescription
entity_typesTemplateEntityType[]Entity type definitions to create
agentsTemplateAgent[]Agent definitions to create
viewsTemplateView[]View snapshots to create
sample_entitiesTemplateSampleEntity[]Seed data (created only when opted in)
nav_configRecord<string, unknown> | nullNavigation tree to apply
themeRecord<string, unknown> | nullTheme to apply
slugstring?Unique reference slug
categoryTemplateCategory?Industry/use-case classification
is_featuredboolean?Show in public gallery
tenant_idstring | null?null = global template

Template categories

ValueLabel
'pe'Private Equity
'manufacturing'Manufacturing
'legal'Legal
'consulting'Consulting
'general'General

ApplyTemplateOptions

interface ApplyTemplateOptions {
  createSampleEntities?: boolean;   // default: false
  updateNavigation?: boolean;       // default: true
  applyTheme?: boolean;             // default: false
}

ApplyTemplateResult

interface ApplyTemplateResult {
  entityTypesCreated: number;
  agentsCreated: number;
  navigationUpdated: boolean;
  themeUpdated: boolean;
  viewsCreated?: number;
  sampleEntitiesCreated?: number;
}

See @sprinterai/runtime for createTemplateCloner, which provides the store-agnostic engine for applying templates.

External Data Types

@sprinterai/core defines the contracts for ingesting external metric data via webhooks and API polling. Available from the root barrel or the ./external-data subpath:

import {
  EXTERNAL_DATA_SOURCE_TYPES,
  MetricItemSchema,
  WebhookPayloadSchema,
  CreateDataSourceSchema,
  UpdateDataSourceSchema,
  PollConfigSchema,
  ApiConfigSchema,
} from "@sprinterai/core";
import type {
  ExternalDataSourceType,
  ExternalDataSource,
  ExternalDataPoint,
  DataPointMetadata,
  MetricItem,
  WebhookPayload,
  CreateDataSourceInput,
  UpdateDataSourceInput,
  PollConfig,
  ApiConfig,
  CreateDataPointInput,
  ExternalDataStore,
} from "@sprinterai/core";

ExternalDataSource

A registered data source with routing token and optional signature secret:

interface ExternalDataSource {
  id: string;
  tenant_id: string;
  name: string;
  description: string | null;
  source_type: ExternalDataSourceType; // 'webhook' | 'api'
  token: string;                        // unique routing token in webhook URL
  signature_secret: string | null;      // HMAC-SHA256 secret; null = unsigned
  signature_header: string;             // header carrying the signature value
  enabled: boolean;
  last_received_at: string | null;
  push_count: number;
  created_at: string;
  updated_at: string;
}

ExternalDataPoint

A single recorded metric value from an external source:

interface ExternalDataPoint {
  id: string;
  data_source_id: string;
  tenant_id: string;
  metric_key: string;    // e.g. 'monthly_revenue', 'active_users'
  label: string;         // display label
  value: number;
  metadata: DataPointMetadata;
  recorded_at: string;   // ISO timestamp — may be back-dated by the sender
  created_at: string;
}

interface DataPointMetadata {
  change?: string;  // relative change string, e.g. "+3.2%" or "-120"
  icon?: string;    // Lucide icon name
  unit?: string;    // unit label, e.g. "%", "$", "users"
  group?: string;   // grouping key for chart series
  [key: string]: unknown;
}

Webhook payload schema

POST bodies sent to POST /api/webhooks/:token are validated against WebhookPayloadSchema:

// Validated payload shape
const payload = WebhookPayloadSchema.parse(rawBody);
// { metrics: MetricItem[] }

// MetricItem — one measurement per key
const item: MetricItem = {
  key: "monthly_revenue",
  label: "Monthly Revenue",
  value: 125000,
  change: "+12.4%",     // optional
  unit: "$",            // optional
  icon: "trending-up",  // optional
  recorded_at: "2026-04-01T00:00:00Z", // optional — defaults to now
};

WebhookPayloadSchema enforces metrics.length >= 1 and metrics.length <= 100.

Poll / API config schemas

For API-polling sources:

// Polling source: scheduled pull
const pollConfig = PollConfigSchema.parse({
  pollUrl: "https://api.example.com/metrics",
  pollInterval: "1h",       // '5m' | '15m' | '1h' | '6h' | '24h'
  pollTransform: "data.metrics", // JSONPath to extract metrics array
  pollHeaders: { Authorization: "Bearer token" },
});

// On-demand API source
const apiConfig = ApiConfigSchema.parse({
  apiUrl: "https://api.example.com/stats",
  apiHeaders: { "X-API-Key": "..." },
  apiTransform: "results",
  cacheTtl: 300,  // seconds; default 300
});

ExternalDataStore

Abstract store interface — implement per backend:

interface ExternalDataStore {
  listDataSources(tenantId: string): Promise<ExternalDataSource[]>;
  getDataSourceByToken(token: string): Promise<ExternalDataSource | null>;
  createDataSource(tenantId: string, input: CreateDataSourceInput): Promise<ExternalDataSource>;
  updateDataSource(id: string, tenantId: string, input: UpdateDataSourceInput): Promise<ExternalDataSource | null>;
  deleteDataSource(id: string, tenantId: string): Promise<void>;
  recordDataPoints(points: CreateDataPointInput[]): Promise<ExternalDataPoint[]>;
  getLatestMetrics(dataSourceId: string): Promise<ExternalDataPoint[]>;
  getMetricHistory(dataSourceId: string, metricKey: string, limit?: number): Promise<ExternalDataPoint[]>;
}

See @sprinterai/runtime for createExternalDataReceiver, which handles token routing, signature verification, and bulk data point insertion.

Entity Markdown

@sprinterai/core provides three pure functions for serializing entities to and from Obsidian-compatible markdown. Import from the root barrel:

import {
  generateEntityMarkdown,
  parseEntityMarkdown,
  extractWikilinkTitles,
} from "@sprinterai/core";
import type {
  GenerateEntityMarkdownInput,
  ParsedEntityMarkdown,
} from "@sprinterai/core";

generateEntityMarkdown

Produces an Obsidian-compatible markdown string: YAML frontmatter with type, tags, and all content fields, followed by an H1 heading and optional body:

const md = generateEntityMarkdown({
  title: "Acme Corp",
  typeSlug: "account",
  content: { stage: "active", arr: "1200000" },
  tags: ["portfolio", "saas"],
  body: "Notes about Acme Corp. See also [[Beta Inc]].",
});

// ---
// type: account
// tags: [portfolio, saas]
// stage: active
// arr: 1200000
// ---
//
// # Acme Corp
//
// Notes about Acme Corp. See also [[Beta Inc]].

Array values in content are serialized as [a, b, c]. Null/undefined values are omitted.

parseEntityMarkdown

Inverse of generateEntityMarkdown. Extracts all frontmatter fields, the H1 title, body text, and any [[wikilink]] references found in the body:

const parsed = parseEntityMarkdown(markdown);
// {
//   typeSlug: "account",
//   title: "Acme Corp",
//   body: "Notes about Acme Corp. See also [[Beta Inc]].",
//   content: { stage: "active", arr: "1200000" },
//   tags: ["portfolio", "saas"],
//   wikilinkTitles: ["Beta Inc"],
// }

Throws if the frontmatter delimiters (---) are missing, the type field is absent, or no H1 heading is found.

extractWikilinkTitles

Extracts all unique [[title]] patterns from a markdown string. Trims whitespace and deduplicates results:

extractWikilinkTitles("See [[Acme Corp]] and [[Acme Corp]] and [[Beta Inc]].");
// ["Acme Corp", "Beta Inc"]

View Response

@sprinterai/core defines the ViewResponse type and ViewResponseStore interface for persisting embed form progress and completion. Import from the root barrel:

import type { ViewResponse, ViewResponseStore } from "@sprinterai/core";

ViewResponse

Persists the state of an in-progress or completed embed form session:

interface ViewResponse {
  id: string;
  viewId: string;
  publishToken: string;
  sessionId: string;
  data: Record<string, unknown>;  // Collected form field values
  completedAt: string | null;     // ISO timestamp, or null if still in progress
  createdAt: string;
  updatedAt: string;
}

ViewResponseStore

Abstract store interface for view response persistence:

interface ViewResponseStore {
  // Create or update a response for the given session
  upsertResponse(params: UpsertViewResponseParams): Promise<ViewResponse>;

  // Look up an in-progress or completed response by session
  getBySession(params: GetViewResponseBySessionParams): Promise<ViewResponse | null>;

  // Mark a response as completed (sets completedAt to now)
  completeResponse(params: CompleteViewResponseParams): Promise<ViewResponse | null>;

  // List all responses for a view (requires tenantId for scoping)
  listByView(viewId: string, tenantId: string): Promise<ViewResponse[]>;
}

upsertResponse creates the record on first save and updates data on subsequent saves. completeResponse returns null when no matching session exists rather than throwing.

On this page

InstallOverviewExports at a GlanceBuildersdefineEntityTypefield buildersdefineAgentdefineTooldefineWorkflowdefineModule and defineAppSurface TypesSurface type catalogueSurfaceDefinitionRegistryAggregation SchemasRelation Column TypesAnalytics EventsANALYTICS_EVENTSAnalyticsProviderView PermissionsViewAccessContextcanAccessViewcanModifyViewAudit ActionsAUDIT_ACTIONSAuditStore interfaceAuditLogFiltersModel CatalogModelCapabilityModelCatalogEntryModelInfoFALLBACK_MODEL_CATALOGgroupModelsByProviderProvider constantsBlock TypesBLOCK_TYPES catalogBlockConfigDataSourceConfigFormFlowConfigFileUploadFieldConfigResourceDownloadBlockConfigFormFlowOutputConfigBlock Data SchemasSchema catalogueView TypesParsedViewRecordparseViewBlocksRendering helpersViewInventoryRecordPlugin TypesPluginManifestStatus constantsCRUD input schemasPDF TypesPdfDocumentProxyPdfHighlightSelectionContext and SelectionActionEmail TypesCore typesRender context typesNotification preferencesEmailConfigStore InterfacesChatStoreTenant and PermissionsResponse ScoringAutomation TypesAutomationTypeUnifiedAutomationAutomationRunAutomationStoreSettings TypesSettingKeyTenantSettingDashboardPreferencesSettingsStoreBranding TypesWorkspace TemplatesWorkspaceTemplate fieldsTemplate categoriesApplyTemplateOptionsApplyTemplateResultExternal Data TypesExternalDataSourceExternalDataPointWebhook payload schemaPoll / API config schemasExternalDataStoreEntity MarkdowngenerateEntityMarkdownparseEntityMarkdownextractWikilinkTitlesView ResponseViewResponseViewResponseStore