npm.io
0.6.0 • Published 6h ago

@possibl/rcrt-sdk

Licence
MIT
Version
0.6.0
Deps
0
Size
475 kB
Vulns
0
Weekly
0

@possibl/rcrt-sdk

TypeScript client for the RCRT multi-tenant AI BaaS. Isomorphic — works in browsers, Node 18+, and React Native with a small polyfill.

Install

npm install @possibl/rcrt-sdk
# or pnpm / yarn / bun

Quickstart

import { RcrtClient, staticTokenProvider } from '@possibl/rcrt-sdk';

const rcrt = new RcrtClient({
  apiUrl: 'https://rcrt-api-gateway-<hash>.run.app',
  tokenProvider: staticTokenProvider(process.env.WORKSPACE_API_KEY!),
});

// 1. Resolve workspace (for tests — UI apps go through Firebase):
const { tenants } = await rcrt.auth.me().then((m) => ({ tenants: m.tenants }));
rcrt.setTenantId(tenants[0].id);

// 2. Send a chat:
const { session_id } = await rcrt.chat.send({
  message: 'Summarise my inbox',
  target_agent: 'life-coordinator',
});

// 3. Stream the reply:
const stream = rcrt.chat.sessionStream(session_id, {
  onDelta: ({ delta }) => process.stdout.write(delta),
  onMessage: (bc) => {
    if (bc.tags.includes('interpret:pending-action')) {
      const card = extractCard(bc);
      console.log('card', card?.layout, card?.header.title);
    }
  },
});

// 4. When the user taps an action on a card:
await rcrt.cards.resolve(cardBreadcrumbId, { status: 'approved' });

stream.close();

Browser + Firebase

import { getAuth } from 'firebase/auth';
import { RcrtClient, TokenProvider } from '@possibl/rcrt-sdk';

const auth = getAuth();
const tokenProvider: TokenProvider = {
  async getIdToken() {
    const user = auth.currentUser;
    if (!user) throw new Error('not signed in');
    return user.getIdToken(false);
  },
};

const rcrt = new RcrtClient({
  apiUrl: import.meta.env.VITE_RCRT_API,
  tokenProvider,
});

React Native

React Native has no built-in EventSource. Install a polyfill and pass it through:

npm install react-native-sse
import EventSource from 'react-native-sse';

const stream = rcrt.chat.sessionStream(sessionId, handlers, {
  eventSource: EventSource,
  useHeaderAuth: true, // RN polyfill supports custom headers
});

Fleet fan-out (multi-workspace apps)

A console that reads dozens of workspaces in parallel should never mutate one client's tenant. Get a cheap, cached, permanently-bound client per workspace instead:

const reports = await Promise.all(
  workspaces.map(async (ws) => {
    const c = rcrt.forTenant(ws.tenantId);
    return c.marketplace.listInstalledBundles();
  }),
);

Escape hatch

Any gateway endpoint the SDK hasn't wrapped yet is one call away — with the same auth, tenant header, retries and error envelope:

const res = await rcrt.request<{ items: unknown[] }>('/v1/some/new/endpoint', {
  method: 'POST',
  body: { plain: 'object' },         // SDK serialises JSON
  query: { limit: 50 },
});

What's exported

Symbol Purpose
RcrtClient The client. One instance per workspace session; forTenant() for fan-out.
TokenProvider Interface — wire any IdP.
staticTokenProvider Static-token convenience (tests, tk_* workspace keys).
ApiError, SdkError Error classes. Typed + discriminable by .status and .detail.code.
MarketplaceModule, OrgModule, FilesModule Bundle lifecycle, org admin + LLM tiers, file upload.
Breadcrumb, Card, Row, ChartSpec, ... Every public type from the RCRT contract. Re-exportable as your UI types.

Browse src/index.ts for the full re-export list.

Design notes

  • Isomorphic: zero DOM or Node-only APIs in the core. The only platform-touching code is the SSE client, and it takes an EventSource constructor through config.
  • Token provider is the seam: the SDK never touches Firebase / Auth0 / Clerk directly. You bring a TokenProvider and it asks.
  • Everything is a breadcrumb: rcrt.breadcrumbs.* is the fundamental CRUD surface; all other modules build on it. cards, grants, auth are conveniences.
  • Optimistic locking just works: breadcrumbs.update(id, req, { autoRetryConflict: true }) and cards.resolve(...) both refetch and retry on 409.
  • Errors are normalised: the backend is mid-migration from {"error": "string"} to a typed {"error": {"code", "message", "details"}} envelope. ApiError.detail.code is set either way.

Matching docs

Full narrative + concepts + operations playbook in packages/docs/. Start with:

Status

0.2.0 — the convergence release: marketplace/org/files modules, forTenant() fan-out and the request() escape hatch cover the full surface the production MSP console uses. @possibl/rcrt-api is deprecated in favour of this package. Shape is stable for the documented surface; minor adjustments possible before 1.0.

Licence

MIT.

Keywords