npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ftaip/sdk

v0.11.0

Published

React SDK for AI Paralegal — AI-powered legal assistant integration

Readme

@ftaip/sdk

React SDK for AI Paralegal — lets third-party apps embed inside the AI Paralegal host as an iframe tool.

npm version license

Quick Scaffold

Create a new SDK tool project with a single command:

npx @ftaip/create-app

This interactive CLI will prompt for your project name, description, and which capabilities you need, then generate a complete Vite + React + TypeScript project with the selected hooks pre-wired.

Installation

npm install @ftaip/sdk

Quick Start

When your app is loaded inside the AI Paralegal admin, the host injects token, baseUrl, and apiKey as URL query parameters. The SessionGate component handles everything:

import { SessionGate, useSubmitResult, useAskMatterAI } from "@ftaip/sdk";

function App() {
  return (
    <SessionGate>
      {(session, client) => <ToolPanel session={session} client={client} />}
    </SessionGate>
  );
}

Or use useSession directly for more control:

import { useSession } from "@ftaip/sdk";

function App() {
  const { session, client, loading, error } = useSession({});

  if (loading) return <p>Authenticating...</p>;
  if (error) return <p>Authentication failed: {error.message}</p>;
  if (!session || !client) return <p>No exchange token provided.</p>;

  return <ToolPanel session={session} client={client} />;
}

Authentication Modes

1. Token Exchange (recommended for iframe apps)

The host loads your app in an iframe with ?token=<exchange_token>&baseUrl=<host_origin>&apiKey=<sdk_api_key>. The useSession hook handles everything:

import { useSession, useAskMatterAI } from "@ftaip/sdk";
import type { AiParalegalClient, SessionContext } from "@ftaip/sdk";

function App() {
  const { session, client, loading, error } = useSession({});

  if (loading) return <p>Authenticating...</p>;
  if (error) return <p>Authentication failed: {error.message}</p>;
  if (!session || !client) return <p>No exchange token provided.</p>;

  return <AskPanel session={session} client={client} />;
}

function AskPanel({
  session,
  client,
}: {
  session: SessionContext;
  client: AiParalegalClient;
}) {
  const { ask, data, loading, error } = useAskMatterAI(client, session);

  return (
    <div>
      <button onClick={() => ask("Summarise this matter")} disabled={loading}>
        {loading ? "Asking..." : "Ask AI"}
      </button>
      {error && <p>Error: {error.message}</p>}
      {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
    </div>
  );
}

For local development you can override the URL params or use a dev token (see Local Development).

const { session, client } = useSession({
  apiKey: "your-api-key",
  baseUrl: "http://localhost:8000",
});

2. API Key + Explicit IDs (server-side / direct)

For server-side usage or when you manage firm/matter context yourself:

import { AiParalegalClient, useAskMatterAI } from "@ftaip/sdk";

const client = new AiParalegalClient({
  baseUrl: "https://your-aiparalegal-instance.com",
  apiKey: process.env.AI_PARALEGAL_API_KEY!,
});

function AskAI() {
  const { ask, data, loading, error, reset } = useAskMatterAI(client, {
    firmId: "firm-uuid",
    matterId: "matter-uuid",
    loadMatterFacts: true,
  });

  return (
    <div>
      <button
        onClick={() => ask("What is the status of this matter?")}
        disabled={loading}
      >
        {loading ? "Asking..." : "Ask AI"}
      </button>
      {error && <p>Error: {error.message}</p>}
      {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Local Development

To develop SDK apps locally without the iframe exchange flow, use a dev token:

  1. In AI Paralegal: Admin → SDK Applications → your app → App Preview tab
  2. Click Generate Dev Token, select firm and matter
  3. Copy the token (expires in 30 days)
  4. In your app, configure useSession:
const { session, client } = useSession({
  devToken: import.meta.env.VITE_DEV_TOKEN,
  baseUrl: import.meta.env.VITE_BASE_URL,
  apiKey: import.meta.env.VITE_API_KEY,
});

The kitchen sink app includes a full setup guide with .env.example, 10 working examples of every SDK capability, and troubleshooting.

Developing the SDK

To work on the SDK source and test against an app:

# In the SDK package
cd aiparalegalsdk
npm run build
npm link

# In your app
cd your-app
npm link @ftaip/sdk

After SDK changes, rebuild:

cd aiparalegalsdk && npm run build

Use npm run dev in the SDK package for watch mode during active development.

To revert to the published package:

npm unlink @ftaip/sdk
npm install @ftaip/sdk

Submitting Results

Use useSubmitResult to send tool results back to the AI agent:

import { useSubmitResult } from "@ftaip/sdk";

function ResultPanel({ client, session }) {
  const { submit, loading, submitted, error } = useSubmitResult(client, session);

  const handleSubmit = () => {
    submit({ stamp_duty: 5000, jurisdiction: "NSW" });
  };

  if (submitted) return <p>Result submitted successfully.</p>;

  return (
    <button onClick={handleSubmit} disabled={loading}>
      {loading ? "Submitting..." : "Submit Result"}
    </button>
  );
}

Available Hooks

Core

| Hook | Description | Key methods | |------|-------------|-------------| | useSession | Token exchange and session lifecycle | session, client | | useAskMatterAI | Ask the AI about the current matter | ask(prompt) | | useSubmitResult | Submit tool results back to the agent | submit(result) | | useLLM | LLM text generation with streaming and structured output | generate(prompt, opts) |

Documents & Files

| Hook | Description | Key methods | |------|-------------|-------------| | useDocs | Document CRUD, markdown-to-doc, download | create, createAndDownload, list, get, update, delete | | useOCR | Extract text from images and PDFs | extract(file, opts) | | useFiles | Upload files to the host | upload(files) | | useMarkItDown | Convert documents to markdown | convert(file) | | useTemplate | Template placeholder extraction, merge, value suggestion | extractPlaceholders, merge, suggestValues |

Audio & Speech

| Hook | Description | Key methods | |------|-------------|-------------| | useTranscribe | Audio transcription with streaming | transcribe(file, opts) | | useTextToSpeech | Text-to-speech audio generation | speak(text, opts) | | useDictation | Real-time dictation | start(), stop() |

Document Collections

| Hook | Description | Key methods | |------|-------------|-------------| | useCollections | Document collection management | create, list, remove | | useCollection | Single collection operations | get, uploadDocuments, search | | useCollectionSearch | Semantic search within a collection | search(query) | | useCollectionQuery | AI-powered collection Q&A with streaming | query(prompt) | | useCollectionTable | Generate structured tables from collections | generate(columns) | | useCollectionAnalyze | AI analysis of collection documents | analyze(prompt) | | useSuggestedPrompts | Get AI-suggested prompts for a collection | fetch(collectionId) |

Review & Storage

| Hook | Description | Key methods | |------|-------------|-------------| | useContractReview | Contract review with staged streaming | review(docId, opts) | | useStorage | Persist key-value data via host storage | get, put, remove, list |

Developer Experience

| Hook / Component | Description | Key methods | |------|-------------|-------------| | SessionGate | Component that handles session loading/error/no-session states | children(session, client) | | useLlmEffect | React to LLM completion with callbacks | onSuccess(text, structured) | | useLoadingMessages | Rotating status messages during long operations | Returns current message string | | useEditableList | Generic editable list state (add/update/remove) | items, addItem, updateItem, removeItem | | useHistory | In-memory prompt/result history | history, pushCurrent, lastPrompt |

Utility Functions

All utilities are imported directly from @ftaip/sdk:

import { downloadBlob, formatFileSize, parseLlmResponse } from "@ftaip/sdk";

File Operations

| Function | Description | |----------|-------------| | downloadBlob(content, filename, mimeType?) | Trigger browser download from Blob or string | | downloadText(content, filename, mimeType?) | Download string as text file | | fileToDataUrl(file) | Convert File to base64 data URL | | dataUrlToFile(dataUrl, filename, mimeType?) | Convert data URL back to File |

Formatting

| Function | Description | |----------|-------------| | formatFileSize(bytes) | 1.2 MB, 340 KB, etc. | | formatDate(date, locale?, options?) | Locale-aware date formatting | | formatDateLong(date?, locale?) | 04 March 2025 for document headers | | formatCurrency(amount, currency?, locale?) | $1,234.56 (defaults to AUD) | | wordCount(text) | Count words in a string | | sanitizeFilename(name, maxLength?) | Filesystem-safe filename | | escapeHtml(text) | Escape HTML entities | | capitalizeWords(text) | Title Case each word |

LLM Response Parsing

| Function | Description | |----------|-------------| | parseLlmResponse<T>(response) | Extract structured data from LLM responses (handles structured, text JSON, markdown fences) | | stripJsonMarkdown(text) | Remove ```json / ``` fences from LLM text |

Clipboard & Export

| Function | Description | |----------|-------------| | copyToClipboard(content, { asHtml? }) | Copy with optional rich HTML (preserves formatting in Word) | | toCsv(rows, columns, headerRows?) | Build CSV string from typed data |

File Validation

| Function / Constant | Description | |----------|-------------| | validateFile(file, options?) | Validate file size and type | | validateFiles(files, options?) | Validate multiple files, returns { valid, errors } | | isImageFile(file) | Check if file is an image | | isSupportedDocument(filename) | Check against platform-supported extensions | | getFileExtension(filename) | Get lowercase extension with dot | | SUPPORTED_DOCUMENT_EXTENSIONS | All supported extensions | | SUPPORTED_DOCUMENT_MIMES | All supported MIME types |

Standalone Functions

Token Exchange

import { AiParalegalClient, exchangeToken } from "@ftaip/sdk";

const client = new AiParalegalClient({
  baseUrl: "https://your-aiparalegal-instance.com",
  apiKey: process.env.AI_PARALEGAL_API_KEY!,
});

const session = await exchangeToken(client, "exchange-token-from-url");
// session.session_token, session.firm_id, session.matter_id, session.expires_at

Ask AI (with API key)

import { AiParalegalClient, askAi } from "@ftaip/sdk";

const response = await askAi(client, {
  prompt: "Summarise the key facts of this matter",
  firm_id: "firm-uuid",
  matter_id: "matter-uuid",
  load_matter_facts: true,
});

Ask AI (with session token)

import { askAiWithSession } from "@ftaip/sdk";

const response = await askAiWithSession(client, session.session_token, {
  prompt: "Summarise the key facts of this matter",
});

Submit Result

import { submitResult } from "@ftaip/sdk";

const response = await submitResult(client, session.session_token, {
  stamp_duty: 5000,
});

Token Exchange Flow

┌─────────────────┐     ┌──────────────────────┐     ┌──────────────────┐
│  Admin Panel     │     │  AI Paralegal API     │     │  Your App        │
└────────┬────────┘     └──────────┬───────────┘     └────────┬─────────┘
         │                         │                           │
         │  Generate exchange token│                           │
         │────────────────────────>│                           │
         │  { exchange_token }     │                           │
         │<────────────────────────│                           │
         │                         │                           │
         │  Load iframe ?token=xxx │                           │
         │─────────────────────────────────────────────────────>
         │                         │                           │
         │                         │  POST /token/exchange     │
         │                         │  X-API-KEY + token        │
         │                         │<──────────────────────────│
         │                         │  { session_token, ... }   │
         │                         │──────────────────────────>│
         │                         │                           │
         │                         │  POST /ai/ask             │
         │                         │  Bearer {session_token}   │
         │                         │<──────────────────────────│
         │                         │  { data: ... }            │
         │                         │──────────────────────────>│

API Reference

| Category | Exports | |----------|---------| | Hooks | useSession, useAskMatterAI, useSubmitResult, useLLM, useOCR, useDocs, useFiles, useCollections, useTemplate, useStorage, useContractReview, useHealth, and more | | Components | SessionGate, DevToolsProvider, DevToolsPanel | | Classes | AiParalegalClient, ApiError | | Functions | exchangeToken, verifyToken, askAi, askAiWithSession, submitResult, checkHealth, and capability-specific functions (e.g. extractText, uploadFiles) | | Types | SessionContext, AiParalegalClientConfig, AskAiRequest, and many capability-specific types |

See the tables below for detailed hook and utility documentation.

Error Handling

API failures throw ApiError, which extends Error with structured fields for programmatic handling:

import { useSession, ApiError } from "@ftaip/sdk";

const { error } = useSession({});

// Check error type
if (ApiError.is(error)) {
  console.error(error.code, error.status, error.details);
  if (error.code === "rate_limit_exceeded") {
    // Retry with backoff
  }
}

Properties:

| Property | Type | Description | |----------|------|-------------| | code | string | Machine-readable error code (e.g. invalid_token, rate_limit_exceeded) | | message | string | Human-readable error message | | status | number | HTTP status code | | details | Record<string, string[]> \| null | Optional validation or field-level details |

Use ApiError.is(error) as a type guard in catch blocks.

Troubleshooting

| Issue | Solution | |-------|----------| | "No exchange token provided" | Ensure the host passes ?token=...&baseUrl=...&apiKey=... in the iframe URL, or set devToken, baseUrl, and optionally apiKey for local dev | | "Token verification failed" / 401 | Dev token may be expired (30 days). Generate a new one from Admin → SDK Applications → App Preview | | "apiKey is required" | Use API key for token exchange; dev token flow does not require it for verify, but you may need it for other flows | | CORS errors | Ensure the host allows your dev origin (e.g. http://localhost:5173) in CORS config | | Changes to .env not reflected | Restart the Vite dev server; env vars are read at startup |

Security

  • Exchange tokens are single-use and expire in 5 minutes
  • Session tokens are scoped to one firm/matter and expire in 1 hour
  • The API key is stored as a private field and never exposed publicly on the client instance
  • postMessage is scoped to the host origin — never uses wildcard (*)
  • baseUrl is validated to be a proper HTTP/HTTPS URL
  • Session tokens are the recommended auth method for browser-based apps

Requirements

  • React 18+ (peer dependency)
  • A running AI Paralegal instance with an SDK application configured in the admin panel

License

MIT