GridCue
API reference

Protocol

The protocol types GridCue passes between the Host, the Intent Provider and the Grid Adapter, and every GridCueError code.

These are the shapes that pass between GridCue's parts: the schema a Host describes, the View State an adapter holds, the View Plan GridCue compiles, and the request and result an Intent Provider exchanges. Each is exported from gridcue both as a TypeScript type and as a Zod schema of the same name, so you can validate data at a boundary with ViewPlan.safeParse(value) and similar.

PROTOCOL_VERSION

const PROTOCOL_VERSION = "0.1";

Every ViewPlan and ResolutionRequest carries this version.

ViewSchema

interface ViewSchema {
  id: string;
  version: string;
  columns: ColumnDescriptor[];
  rowNoun?: string;
}
NameTypeDescription
idstringThe schema's ID.
versionstringThe schema's version.
columnsColumnDescriptor[]Every column, restricted ones included.
rowNounstringWhat one row is, such as "account". Its name in a request means the rows, not a column.

Build one with defineSchema.

ColumnDescriptor

interface ColumnDescriptor {
  id: string;
  label: string;
  description?: string;
  kind: ColumnKind;
  aliases?: string[];
  capabilities: ColumnCapability[];
  allowedOperators?: FilterOperator[];
  sensitivity: "public" | "internal" | "restricted";
  exposeToProvider?: boolean;
  enumValues?: EnumValue[];
  entity?: string;
  valueGroups?: Array<{ label: string; aliases?: string[]; values: string[] }>;
}

interface EnumValue {
  id: string;
  label: string;
  aliases?: string[];
}
NameTypeDescription
idstringThe column ID the grid uses.
labelstringThe name Users and the provider see.
descriptionstringA short description for the provider.
kindColumnKindstring, number, currency, percent, date, datetime, boolean or enum.
aliasesstring[]Other names for the column.
capabilitiesColumnCapability[]What GridCue may do with it: any of filter, sort, group, aggregate, show, hide, reorder, pin.
allowedOperatorsFilterOperator[]Narrows the kind's filter operators.
sensitivity"public" | "internal" | "restricted"A restricted column is never sent to a provider and never acted on.
exposeToProviderbooleanfalse keeps the column out of provider requests.
enumValuesEnumValue[]The approved values. Filters on an enum column must use these IDs.
entitystringThe other record this column names, such as "household", so "largest households first" can be read either way.
valueGroupsArray<{ label, aliases?, values }>Host-named categories over the enum values, such as Retirement = IRA and Roth IRA.

The default operators and capabilities for each kind are on the Schema page.

ViewState

interface ViewState {
  visibleColumnIds: string[];
  columnOrder: string[];
  pinnedColumnIds: { start: string[]; end: string[] };
  filters: FilterGroup | null;
  sorts: SortSpec[];
  groupBy: string[];
  aggregations: AggregationSpec[];
  density: "compact" | "comfortable" | "spacious";
}

interface VersionedViewState {
  revision: string;
  state: ViewState;
}

interface SortSpec {
  columnId: string;
  direction: "asc" | "desc";
}

interface AggregationSpec {
  columnId: string;
  function: "sum" | "avg" | "min" | "max" | "count";
}

The whole view of a grid. groupBy lists nested levels, outermost first. sorts lists levels in priority order. A VersionedViewState pairs it with a revision string that changes whenever the view changes; GridCue uses it to refuse stale plans and stale undos.

emptyViewState(columnIds) returns a view with every given column visible in that order, no pins, filters, sorts, groups or aggregations, and comfortable density.

FilterPredicate and FilterGroup

interface FilterPredicate {
  id: string;
  type: "predicate";
  columnId: string;
  operator: FilterOperator;
  value?: Scalar | Scalar[] | { min: Scalar; max: Scalar };
}

interface FilterGroup {
  id: string;
  type: "group";
  combinator: "and" | "or";
  children: Array<FilterPredicate | FilterGroup>;
}

type FilterOperator =
  | "eq" | "neq" | "gt" | "gte" | "lt" | "lte"
  | "between" | "contains" | "startsWith" | "in" | "isEmpty" | "isNotEmpty";

type Scalar = string | number | boolean | null;

A filter is a tree: a FilterGroup combines predicates and nested groups with and or or. The value's shape depends on the operator, and validation checks it against the column's kind:

OperatorValue
isEmpty, isNotEmptynone
between{ min, max }
ina non-empty array
every other operatorone value

A single value must fit the kind: a finite number for number, currency and percent; a boolean for boolean; a YYYY-MM-DD string for date; a parseable date string for datetime; one of the enumValues IDs for enum; a string for string.

ViewOperation

type ViewOperation =
  | { type: "filter.add"; predicate: FilterPredicate; combineWith: "and" | "or" }
  | { type: "filter.clear" }
  | { type: "sort.set"; sorts: SortSpec[] }
  | { type: "group.set"; columnIds: string[] }
  | { type: "columns.show"; columnIds: string[] }
  | { type: "columns.hide"; columnIds: string[] }
  | { type: "columns.order"; columnIds: string[] }
  | { type: "columns.pin"; columnIds: string[]; position: "start" | "end" | "none" }
  | { type: "aggregation.set"; aggregations: AggregationSpec[] }
  | { type: "density.set"; density: "compact" | "comfortable" | "spacious" }
  | { type: "view.reset" };

One change to the view. Operations apply in order.

TypeEffectIn MVP_OPERATIONS
filter.addAdds predicate to the filters. With no filters yet, it starts a group with combineWith. If the root group already uses combineWith, the predicate joins it; otherwise the old root and the predicate are wrapped in a new group.yes
filter.clearRemoves every filter.yes
sort.setReplaces the sorts. An empty list clears sorting.yes
group.setReplaces the grouping, outermost first. An empty list clears it.yes
columns.showMakes columns visible, keeping columnOrder. columnIds is non-empty.yes
columns.hideHides columns. columnIds is non-empty.yes
columns.orderMoves these columns to the front, in this order. columnIds is non-empty.yes
columns.pinPins columns to the start or end, or unpins them with none.no
aggregation.setReplaces the aggregations.no
density.setSets the row density.no
view.resetReturns to the adapter's default view.yes

GridCue changes the view only. aggregation.set exists in the protocol but is not in MVP_OPERATIONS, and neither are columns.pin or density.set: no first-release adapter supports them, and a plan that uses them fails validation with PLAN_OPERATION_UNSUPPORTED. See Adapters.

ViewPlan

interface ViewPlan {
  protocolVersion: "0.1";
  id: string;
  baseRevision: string;
  source: { channel: "typed" | "dictated" | "api"; text?: string };
  status: "ready" | "needs_clarification" | "unsupported";
  operations: ViewOperation[];
  confidence?: number;
  evidence: DecisionEvidence[];
  clarifications: Clarification[];
  unsupportedSegments: Array<{ text?: string; category: UnsupportedCategory }>;
}

What GridCue compiled from one Utterance.

NameTypeDescription
protocolVersion"0.1"The protocol version.
idstringThe plan's ID.
baseRevisionstringThe view revision the plan was made against. A plan is refused once the view has moved on.
source{ channel, text? }How the request arrived, and its text.
status"ready" | "needs_clarification" | "unsupported"Whether the plan can be applied, needs an answer first, or asks for something GridCue can't do.
operationsViewOperation[]The changes, in order.
confidencenumberBetween 0 and 1, when known.
evidenceDecisionEvidence[]The decisions behind the plan.
clarificationsClarification[]Questions for the User when status is needs_clarification.
unsupportedSegmentsArray<{ text?, category }>The parts GridCue can't do when status is unsupported.

A plan becomes applicable only through validatePlan, which checks it against the schema, the adapter's capabilities and the current revision, and returns a frozen ApplicableViewPlan. Adapters accept only those.

Clarification

interface Clarification {
  id: string;
  prompt: string;
  options?: Array<{ id: string; label: string }>;
  required: true;
}

A question GridCue asks instead of guessing. Answer it with controller.answer(clarification.id, option.id).

DecisionEvidence

interface DecisionEvidence {
  key: string;
  selectedId?: string;
  confidence?: number;
  source: "deterministic" | "provider" | "host" | "user";
}

One decision behind a plan: what was decided (key), what was chosen (selectedId), how sure (confidence, 0 to 1), and who decided. deterministic is GridCue's own rules, provider is the Intent Provider, and host is the Host's configuration (such as a restricted column). user is part of the protocol, but the compiler does not emit it today.

UnsupportedCategory

type UnsupportedCategory = "data_mutation" | "workflow_action" | "navigation" | "export" | "restricted_column" | "unknown";
ValueMeaning
data_mutationEditing, deleting or changing data.
workflow_actionA business action such as trading, emailing or approving.
navigationOpening another page or record.
exportExporting, downloading, printing or copying data.
restricted_columnThe request names a restricted column.
unknownAnything else GridCue can't do.

ResolutionRequest

interface ResolutionRequest {
  protocolVersion: "0.1";
  utterance: string;
  clauses: Array<{
    index: number;
    text: string;
    literals: Literal[];
    direction?: "asc" | "desc";
    continues?: "sort" | "group";
    mentions?: Array<{ columnId: string; valueId?: string; ambiguous?: true; text?: string }>;
  }>;
  candidates: { families: string[]; columns: CandidateColumn[]; rowNoun?: string };
  view: { visibleColumnIds: string[]; sorts: SortSpec[]; groupBy: string[]; hasFilters: boolean };
}

What an Intent Provider receives. It holds no rows and no restricted columns. utterance is at most 2,000 characters, and there are at most 12 Clauses.

NameDescription
clauses[].indexThe Clause's position in the Utterance.
clauses[].textThe Clause's text.
clauses[].literalsAmounts, percentages, dates and quoted text found in the Clause. See below.
clauses[].directionA sort direction core already read from the words, when there is one.
clauses[].continuesSet when a part with no verb ("then advisor") continued the sort or group of the part before it.
clauses[].mentionsColumns and enum values core already matched by a Host-declared name. A provider may skip asking about them. ambiguous marks a row or entity noun ("households") that could mean the column or the records; text is the words as written.
candidates.familiesThe families the provider may pick from: the view families this grid supports, plus the four unsupported.* families.
candidates.columnsThe columns the provider may pick from.
candidates.rowNounWhat one row is.
viewThe current view: visible columns, sorts, grouping, and whether any filter is on. Filter values are not sent.
interface Literal {
  kind: "number" | "currency" | "percent" | "date" | "text" | "unreadable";
  value: number | string;
  upper?: number | string;
  comparator?: "eq" | "gt" | "gte" | "lt" | "lte" | "between";
  at: number;
}

interface CandidateColumn {
  id: string;
  label: string;
  kind: ColumnKind;
  aliases?: string[];
  description?: string;
  families: string[];
  operators: FilterOperator[];
  enumValues?: EnumValue[];
  entity?: string;
  valueGroups?: Array<{ label: string; aliases?: string[]; values: string[] }>;
}

upper is the upper bound for between, and at is the literal's character offset within its Clause. unreadable marks a number whose format is ambiguous, such as "1.000.000"; GridCue asks rather than guesses. A candidate column's families are the families its capabilities allow, and operators are its legal filter operators.

Families

const VIEW_FAMILIES = [
  "filter", "sort", "group",
  "columns.show", "columns.hide", "columns.only",
  "filter.clear", "sort.clear", "group.clear", "view.reset",
] as const;

const UNSUPPORTED_FAMILIES = [
  "unsupported.data_mutation", "unsupported.workflow_action", "unsupported.navigation", "unsupported.export",
] as const;

The kinds of change a Clause can ask for. Providers choose among these; they never emit operations. A view family is offered only when the grid supports every operation it needs:

FamilyOperationsColumn capabilities needed
filterfilter.addfilter
sortsort.setsort
groupgroup.setgroup
columns.showcolumns.showshow
columns.hidecolumns.hidehide
columns.onlycolumns.show, columns.hide, columns.ordershow, hide, reorder
filter.clearfilter.clearnone
sort.clearsort.setnone
group.cleargroup.setnone
view.resetview.resetnone

ResolutionResult

interface ResolutionResult {
  clauses: ClauseResolution[];
}

interface Pick {
  id: string;
  confidence: number;
}

What an Intent Provider returns: one ClauseResolution per Clause. Every confidence is a probability between 0 and 1. The Controller checks the result against this shape and reports PROVIDER_MALFORMED if it does not match.

ClauseResolution

interface ClauseResolution {
  clauseIndex: number;
  families: Pick[];
  columns: Pick[];
  values: Array<{ columnId: string; valueId: string; confidence: number }>;
  direction?: Pick;
  unmatchedTerms: string[];
  roles?: Array<{ columnId: string; family: string; confidence: number }>;
  kind?: Pick;
  adds?: number;
  outer?: Array<{ outerId: string; innerId: string; confidence: number }>;
  readings?: Array<{ columnId: string; reading: "column" | "records" | "rows"; confidence: number }>;
  literalColumns?: Array<{ literalIndex: number; columnId: string; confidence: number }>;
}
NameTypeDescription
clauseIndexnumberWhich Clause this resolves.
familiesPick[]How likely the Clause asks for each family.
columnsPick[]Columns the Clause refers to, in the order they are mentioned.
valuesArray<{ columnId, valueId, confidence }>Approved enum value IDs, or "true" / "false" for boolean columns.
directionPickSort direction, asc or desc.
unmatchedTermsstring[]Phrases that look like column references but match no candidate. Never guessed at.

The optional fields below come from the fan-out and chassis signals. A provider may leave any of them out; the Jev provider fills them according to its strategy and signals.

NameTypeDescription
rolesArray<{ columnId, family, confidence }>Per column and column family: does the Clause ask for that change to that column?
kindPickThe kind of change the Clause mainly asks for, as a relative pick over the families.
addsnumberProbability that a sort or grouping adds a level to the current view's, rather than replacing it.
outerArray<{ outerId, innerId, confidence }>For reversal wording ("advisor within custodian"): is outerId the outer grouping or primary sort?
readingsArray<{ columnId, reading, confidence }>What an ambiguous row or entity noun refers to: the column's values (column), the other records (records), or this grid's rows (rows).
literalColumnsArray<{ literalIndex, columnId, confidence }>The column each literal applies to, by its index in the Clause's literals.

GridCueError

type ErrorStage = "INPUT" | "RESOLUTION" | "PLAN" | "POLICY" | "ADAPTER" | "PROVIDER";
type GridCueErrorCode = `${ErrorStage}_${string}`;

class GridCueError extends Error {
  readonly code: GridCueErrorCode;
  constructor(code: GridCueErrorCode, message: string);
}

function isGridCueError(value: unknown): value is GridCueError;

interface Issue {
  code: GridCueErrorCode;
  message: string;
  path?: string;
}

Every failure has a stable code starting with its stage. Some are thrown as a GridCueError; the rest appear as Issues on the Controller's issues, as adapter ApplyResult codes, or as the Server Handler's error.code. Issue messages are safe to show Users. These are the codes the source uses today:

CodeWhereMeaning
INPUT_TOO_LONGController issueThe Utterance is longer than maxUtteranceLength.
INPUT_TOO_COMPLEXController issueThe Utterance has more than 12 Clauses.
INPUT_SCHEMAthrown by defineSchemaThe schema is invalid. See Schema.
INPUT_CONFIGthrown by createJevProviderUnknown strategy or signal name.
INPUT_METHODServer Handler, 405The request is not a POST.
INPUT_CONTENT_TYPEServer Handler, 415The request is not JSON.
INPUT_TOO_LARGEServer Handler and toNodeHandler, 413The body is over maxBodyBytes.
INPUT_INVALIDServer Handler, 400The body is not JSON or not a valid ResolutionRequest.
PROVIDER_FAILEDprovider, handler, Controller issueThe provider failed, or threw something that was not a GridCueError.
PROVIDER_MALFORMEDprovider, Controller issueThe provider's result did not match ResolutionResult, or Jev omitted an answer.
PROVIDER_TIMEOUTController issueThe provider took longer than providerTimeoutMs.
PROVIDER_TOO_COMPLEXthrown by createJevProviderThe request needs more questions than maxQuestions.
PROVIDER_UNREACHABLEthrown by createRemoteProviderThe Server Handler could not be reached.
PLAN_SHAPEvalidation issueThe plan does not match the ViewPlan shape.
PLAN_STALE_REVISIONvalidation issue, undoThe view changed since the plan was made, or since the change to undo.
PLAN_NOT_READYvalidation issueThe plan still needs a Clarification or has an unsupported part.
PLAN_NOT_ATOMICvalidation issueThe adapter can't apply changes atomically.
PLAN_OPERATION_UNSUPPORTEDvalidation issueThe adapter doesn't support an operation in the plan.
PLAN_UNKNOWN_COLUMNvalidation issueThe plan names a column that does not exist.
PLAN_COLUMN_CAPABILITYvalidation issueA column can't be used for that change.
PLAN_OPERATOR_NOT_ALLOWEDvalidation issueA filter uses an operator the column doesn't allow.
PLAN_VALUE_TYPEvalidation issueA filter value doesn't fit the column's kind or operator.
PLAN_CARDINALITYvalidation issueMore sort or group levels than the adapter allows.
PLAN_INCONSISTENT_STATEvalidation issueThe result would have duplicate columns in the order, a visible column missing from it, or no visible columns.
PLAN_INVALIDaudit eventFallback errorCode for a plan rejected at apply time with no issue code.
POLICY_RESTRICTED_COLUMNvalidation issueThe plan names a restricted column.
ADAPTER_NOT_APPLICABLEadapter result, thrown by resultingStateThe plan did not come from validatePlan.
ADAPTER_STALE_REVISIONadapter resultThe view changed before the adapter applied the plan.
ADAPTER_UNSUPPORTED_FILTERTanStack adapter resultTanStack column filters can only be combined with AND.
ADAPTER_FAILEDController issueThe adapter threw while applying or undoing.

The RESOLUTION stage is defined in ErrorStage, but no code uses it yet.

On this page