AI Toolkit (Enterprise)

The AI Toolkit in apex-grid-enterprise turns a natural-language prompt into a grid change (sort, filter, group, pivot, …) or an answer about the data. It is provider-agnostic: you supply an aiAdapter, and the toolkit handles building the request from the grid's schema and applying the resulting state patch.

Wiring an adapter

Assign an adapter to aiAdapter. The toolkit ships a Claude adapter and a mock adapter, or you can provide your own (request) => Promise<AIResponse>:

import { createClaudeAdapter } from 'apex-grid-enterprise';

grid.aiAdapter = createClaudeAdapter({ endpoint: '/api/grid-ai' });

Without an adapter, runPrompt rejects. The entire AI Toolkit is an enterprise feature.

Running a prompt

runPrompt(prompt, options?) returns a discriminated AIResult:

// control mode (default): the AI produces a state patch, which is applied
const result = await grid.runPrompt('group by region and sum revenue');
if (result.mode === 'control') {
  console.log('applied:', result.result.applied);
  console.log('warnings:', result.warnings);
  // roll it back:
  // result.undo();
}

// ask mode: get an answer, change nothing
const answer = await grid.runPrompt('which region has the highest revenue?', { mode: 'ask' });
if (answer.mode === 'ask') console.log(answer.answer);
RunPromptOptionsDescription
mode'control' (default) applies a patch; 'ask' returns an answer only
signalAbortSignal forwarded to the adapter for cancellation

Control result carries the sanitized patch, the setState result (applied/skipped/warnings), merged warnings, and an idempotent undo() that restores the pre-prompt snapshot. Ask result carries just answer.

The Claude adapter

createClaudeAdapter(config) supports two transports:

// Production: POST { prompt, mode, schema, data } to your backend (key stays server-side)
grid.aiAdapter = createClaudeAdapter({ endpoint: '/api/grid-ai' });

// Development only: call Anthropic directly from the browser
grid.aiAdapter = createClaudeAdapter({
  apiKey: import.meta.env.VITE_ANTHROPIC_KEY,
  dangerouslyAllowBrowser: true,
});
ClaudeAdapterConfigDescription
endpointProduction transport: POST the request to your backend
apiKeyDev transport: call Anthropic directly (requires dangerouslyAllowBrowser)
dangerouslyAllowBrowserAcknowledge the in-browser key is exposed to the page (dev only)
modelModel id; defaults to claude-opus-4-8
maxTokensMax output tokens; defaults to 1024
maxDataRowsRows of current data included in the prompt (0 disables); defaults to 50
systemExtra system-prompt text appended to the built-in grid instructions
fetchOverride fetch (proxy transport), e.g. to add auth headers
clientSupply the Anthropic client instead of the bundled dynamic import

The recommended production setup is the endpoint proxy so your API key never reaches the browser. Your endpoint receives { prompt, mode, schema, data } and calls Anthropic server-side.

Mock adapter (no network)

For demos and tests, createMockAdapter returns an adapter driven by rules:

import { createMockAdapter } from 'apex-grid-enterprise';

grid.aiAdapter = createMockAdapter({
  rules: [
    { match: /group by region/i, patch: { modules: { grouping: { groupBy: ['region'] } } } },
  ],
});

How it stays safe

The toolkit builds the request from getSchema() and sanitizes the returned patch against that schema (sanitizePatch) before applying it via setState, so an out-of-range or malformed patch is degraded rather than throwing. Everything an AI can do is expressible as a GridState patch — see State Persistence.

React example

import { useEffect, useRef, useState } from 'react'
import 'apex-grid-enterprise/define'
import { createClaudeAdapter } from 'apex-grid-enterprise'

export default function AIGrid() {
  const ref = useRef<any>(null)
  const [prompt, setPrompt] = useState('')

  useEffect(() => {
    const grid = ref.current
    grid.columns = columns
    grid.data = data
    grid.aiAdapter = createClaudeAdapter({ endpoint: '/api/grid-ai' })
  }, [])

  const run = async () => {
    const result = await ref.current.runPrompt(prompt)
    if (result.mode === 'control') console.log('applied', result.result.applied)
  }

  return (
    <div>
      <input value={prompt} onChange={(e) => setPrompt(e.target.value)} />
      <button onClick={run}>Run</button>
      <apex-grid-enterprise ref={ref} style={{ height: 480 }} />
    </div>
  )
}