JSPM

@verydia/react

0.2.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • 0
  • Score
    100M100P100Q18998F
  • License MIT

React bindings for Verydia agents and workflows

Package Exports

  • @verydia/react
  • @verydia/react/package.json

Readme

@verydia/react

React bindings for Verydia agents and workflows. This package is backend-agnostic: you pass in a client that knows how to call your Verydia backend (Next.js API route, FastAPI, etc.).

Installation

Using pnpm:

pnpm add @verydia/react

Using npm:

npm install @verydia/react

Using yarn:

yarn add @verydia/react

Quick Start

1. Define a Client Implementation

Create a client that implements the VerydiaReactClient interface. This client handles HTTP calls to your backend:

// app/verydiaClient.ts
import type { VerydiaReactClient } from "@verydia/react";

export const verydiaClient: VerydiaReactClient = {
  async runAgent({ agentName, prompt, context }) {
    const res = await fetch("/api/verydia/agent", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ agentName, prompt, context }),
    });
    if (!res.ok) throw new Error("Agent request failed");
    return res.json();
  },

  async runWorkflow({ workflowName, payload, context }) {
    const res = await fetch("/api/verydia/workflow", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ workflowName, payload, context }),
    });
    if (!res.ok) throw new Error("Workflow request failed");
    return res.json();
  },
};

2. Wrap Your App with VerydiaProvider

// app/layout.tsx or app/page.tsx
import { VerydiaProvider } from "@verydia/react";
import { verydiaClient } from "./verydiaClient";

export default function App({ children }) {
  return (
    <VerydiaProvider client={verydiaClient}>
      {children}
    </VerydiaProvider>
  );
}

3. Use Hooks in Your Components

useVerydiaAgent

import { useVerydiaAgent } from "@verydia/react";

function MyAgentComponent() {
  const { runAgent, result, isLoading, error } = useVerydiaAgent("my-agent");

  const handleSubmit = async (prompt: string) => {
    await runAgent(prompt);
  };

  return (
    <div>
      {isLoading && <p>Loading...</p>}
      {error && <p>Error: {String(error)}</p>}
      {result?.messages && (
        <div>
          {result.messages.map((msg, idx) => (
            <div key={idx}>
              <strong>{msg.role}:</strong> {msg.content}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

useVerydiaWorkflow

import { useVerydiaWorkflow } from "@verydia/react";

function MyWorkflowComponent() {
  const { runWorkflow, result, isLoading, error } = useVerydiaWorkflow("my-workflow");

  const handleRun = async () => {
    await runWorkflow({ input: "some data" });
  };

  return (
    <div>
      <button onClick={handleRun} disabled={isLoading}>
        Run Workflow
      </button>
      {result && <pre>{JSON.stringify(result, null, 2)}</pre>}
    </div>
  );
}

4. Use the AgentChat Component

For quick prototyping, use the built-in AgentChat component:

import { AgentChat } from "@verydia/react";

function ChatPage() {
  return (
    <div>
      <h1>Chat with Verydia Agent</h1>
      <AgentChat agentName="customer-support" placeholder="How can I help you?" />
    </div>
  );
}

API Reference

VerydiaReactClient

Interface for backend communication:

interface VerydiaReactClient {
  runAgent(input: {
    agentName: string;
    prompt: string;
    context?: unknown;
  }): Promise<VerydiaReactAgentResult>;

  runWorkflow(input: {
    workflowName: string;
    payload: unknown;
    context?: unknown;
  }): Promise<VerydiaReactWorkflowResult>;
}

VerydiaProvider

Props:

  • client: VerydiaReactClient - Your backend client implementation
  • children: React.ReactNode - Your app components

useVerydiaAgent(agentName: string)

Returns:

  • runAgent(prompt: string, context?: unknown) - Function to run the agent
  • result: VerydiaReactAgentResult | null - Agent response
  • status: "idle" | "loading" | "success" | "error" - Current status
  • isLoading: boolean - Loading state
  • isIdle: boolean - Idle state
  • isSuccess: boolean - Success state
  • isError: boolean - Error state
  • error: unknown - Error if any

useVerydiaWorkflow(workflowName: string)

Returns:

  • runWorkflow(payload: unknown, context?: unknown) - Function to run the workflow
  • result: VerydiaReactWorkflowResult | null - Workflow response
  • status: "idle" | "loading" | "success" | "error" - Current status
  • isLoading: boolean - Loading state
  • isIdle: boolean - Idle state
  • isSuccess: boolean - Success state
  • isError: boolean - Error state
  • error: unknown - Error if any

AgentChat

Props:

  • agentName: string - Name of the agent to chat with
  • placeholder?: string - Input placeholder text (default: "Ask me anything...")

Admin Dashboard Starter

Verydia ships a light "admin SDK" for React that provides ready-made components for monitoring safety scores, LLM providers, and system health.

Components

  • SafetyPanel - Combined safety overview + category breakdown
  • ProvidersPanel - Display configured LLM providers and models
  • LlmStatusCard - Health status indicator for primary LLM
  • VerydiaAdminDashboard - Complete admin page composing all panels

Hooks

  • useSafetyDashboard(endpoint) - Fetch safety scorecard data
  • useProviders(endpoint) - Fetch provider configuration
  • useModels(endpoint) - Fetch model list
  • useLlmStatus(endpoint) - Fetch LLM health status
  • useSafetyHistory(endpoint) - Fetch historical safety scores

Quick Start: Admin Dashboard

import { VerydiaAdminDashboard } from "@verydia/react";

export default function AdminPage() {
  return (
    <VerydiaAdminDashboard
      safetyEndpoint="/api/verydia/safety/latest"
      providersEndpoint="/api/verydia/providers"
      llmStatusEndpoint="/api/verydia/llm-status"
    />
  );
}

Backend API Examples

The admin components are backend-agnostic and fetch data from HTTP endpoints you implement.

Safety Endpoint (Next.js)

// pages/api/verydia/safety/latest.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { createStoreFromEnv, computeTrend } from "@verydia/safety-insights";
import type { SafetyDashboardData } from "@verydia/react";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<SafetyDashboardData | { error: string }>
) {
  try {
    const store = createStoreFromEnv();
    const scorecards = await store.listScorecards({ limit: 2 });

    if (scorecards.length === 0) {
      return res.status(404).json({ error: "No scorecards found" });
    }

    const [current, previous] = scorecards;
    const trend = previous ? computeTrend(previous.result, current.result) : null;

    const dashboardData: SafetyDashboardData = {
      environment: (current.metadata?.environment as string) || "unknown",
      suiteName: current.metadata?.suiteName as string | undefined,
      timestamp: current.timestamp.toISOString(),
      totalScore: current.result.totalWeighted,
      classification: current.result.classification,
      categories: current.result.breakdown.map((cat) => ({
        id: cat.categoryId,
        label: cat.label,
        weight: cat.weight,
        weightedScore: cat.weightedScore,
      })),
      trend: trend
        ? {
            direction: trend.direction,
            delta: trend.delta,
            percentChange: trend.percentChange,
          }
        : null,
    };

    res.status(200).json(dashboardData);
  } catch (error) {
    console.error("Safety dashboard API error:", error);
    res.status(500).json({
      error: error instanceof Error ? error.message : "Internal server error",
    });
  }
}

Providers Endpoint (Next.js)

// pages/api/verydia/providers.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { getProviderRegistry } from "@verydia/providers";
import type { VerydiaProviderSummary } from "@verydia/react";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<VerydiaProviderSummary[] | { error: string }>
) {
  try {
    const registry = getProviderRegistry();
    const providers = registry.listProviders();

    const providerSummaries: VerydiaProviderSummary[] = providers.map((provider) => {
      const models = registry.listModels(provider.id);

      return {
        id: provider.id,
        label: provider.title || provider.id,
        kind: provider.kind,
        environment: (provider.metadata?.environment as string) || undefined,
        models: models.map((model) => ({
          id: model.modelId,
          label: model.displayName || model.modelId,
          providerId: model.providerId,
          contextWindowTokens: undefined, // Add if available in your provider config
          inputPricePer1KTokensUsd: model.cost?.inputPer1K,
          outputPricePer1KTokensUsd: model.cost?.outputPer1K,
          default: model.modelId === provider.metadata?.defaultModel,
        })),
      };
    });

    res.status(200).json(providerSummaries);
  } catch (error) {
    console.error("Providers API error:", error);
    res.status(500).json({
      error: error instanceof Error ? error.message : "Internal server error",
    });
  }
}

LLM Status Endpoint (Next.js)

// pages/api/verydia/llm-status.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { getProviderRegistry } from "@verydia/providers";
import type { LlmStatusSnapshot } from "@verydia/react";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<LlmStatusSnapshot | { error: string }>
) {
  try {
    const registry = getProviderRegistry();
    const primaryProvider = registry.listProviders()[0]; // Get first provider

    if (!primaryProvider) {
      return res.status(404).json({ error: "No providers configured" });
    }

    const models = registry.listModels(primaryProvider.id);
    const primaryModel = models[0];

    if (!primaryModel) {
      return res.status(404).json({ error: "No models found" });
    }

    // Perform a lightweight health check (ping)
    const startTime = Date.now();
    let status: "healthy" | "degraded" | "offline" | "unknown" = "unknown";
    let errorMessage: string | undefined;

    try {
      // Simple test request to check if provider is responsive
      await registry.chat(primaryProvider.id, {
        model: primaryModel.modelId,
        messages: [{ role: "user", content: "ping" }],
        maxTokens: 5,
      });
      status = "healthy";
    } catch (error) {
      status = "offline";
      errorMessage = error instanceof Error ? error.message : "Unknown error";
    }

    const latencyMs = Date.now() - startTime;

    const statusSnapshot: LlmStatusSnapshot = {
      providerId: primaryProvider.id,
      modelId: primaryModel.modelId,
      displayName: `${primaryProvider.title || primaryProvider.id}${primaryModel.displayName || primaryModel.modelId}`,
      environment: (primaryProvider.metadata?.environment as string) || undefined,
      status,
      latencyMs,
      lastCheckedAt: new Date().toISOString(),
      errorMessage,
    };

    res.status(200).json(statusSnapshot);
  } catch (error) {
    console.error("LLM status API error:", error);
    res.status(500).json({
      error: error instanceof Error ? error.message : "Internal server error",
    });
  }
}

Individual Components

You can also use admin components individually:

import {
  SafetyPanel,
  ProvidersPanel,
  LlmStatusCard,
  useSafetyDashboard,
  useProviders,
  useLlmStatus,
} from "@verydia/react";

function CustomAdminPage() {
  const { data: safety } = useSafetyDashboard("/api/verydia/safety/latest");
  const { data: providers } = useProviders("/api/verydia/providers");
  const { data: status } = useLlmStatus("/api/verydia/llm-status");

  return (
    <div style={{ padding: "24px", display: "flex", flexDirection: "column", gap: "24px" }}>
      {status && <LlmStatusCard status={status} />}
      {safety && <SafetyPanel data={safety} />}
      {providers && <ProvidersPanel providers={providers} />}
    </div>
  );
}

Admin Types

import type {
  VerydiaProviderSummary,
  VerydiaModelSummary,
  LlmStatusSnapshot,
  SafetyDashboardData,
  SafetyHistoryPoint,
} from "@verydia/react";

Backend-Agnostic Design

This package does NOT assume any specific backend. You can use it with:

  • Next.js API routes
  • FastAPI endpoints
  • Express.js servers
  • tRPC procedures
  • Any HTTP API that can run Verydia agents/workflows

The client interface is intentionally minimal to support any backend architecture.

TypeScript Support

Full TypeScript support with exported types:

import type {
  VerydiaReactClient,
  VerydiaReactAgentResult,
  VerydiaReactWorkflowResult,
} from "@verydia/react";

Contract-First Workflow Streaming with useWorkflow

The useWorkflow hook provides a first-class way to call Verydia workflows with full type safety from @verydia/contracts. It replaces the need for hacky useChat glue and works with both local dev server and cloud API.

Basic Usage

import { defineEventContract, z } from "@verydia/contracts";
import { useWorkflow } from "@verydia/react";

// Define your workflow's event contract
const StoryBookContract = defineEventContract({
  "chapter-generation": z.object({
    status: z.string(),
    chapterNumber: z.number(),
  }),
  "chapter-content": z.object({
    status: z.string(),
    content: z.string(),
    chapterNumber: z.number(),
  }),
});

function StoryGenerator() {
  const { run, events, status, lastEvent } = useWorkflow<typeof StoryBookContract>({
    workflowId: "story-book",
    mode: "dev", // or "cloud"
    devBaseUrl: "http://localhost:8787",
    onEvent: (event) => {
      // Fully typed discriminated union!
      if (event.type === "chapter-content") {
        console.log("Chapter:", event.data.content);
      }
    },
  });

  return (
    <div>
      <button onClick={() => run({ prompt: "Write a story about dragons" })}>
        Generate Story
      </button>

      <div>Status: {status}</div>

      {events.map((event, idx) => (
        <div key={idx}>
          {event.type === "chapter-content" && (
            <p>{event.data.content}</p> // Fully typed!
          )}
        </div>
      ))}
    </div>
  );
}

API Reference: useWorkflow

Options

interface UseWorkflowOptions<TContract> {
  workflowId: string;              // Workflow identifier
  initialInput?: unknown;          // Auto-run on mount with this input
  mode?: "cloud" | "dev";          // Execution mode (default: "cloud")
  devBaseUrl?: string;             // Dev server URL (default: "http://localhost:8787")
  cloudBaseUrl?: string;           // Cloud API URL (optional override)
  onEvent?: (event) => void;       // Callback for each event
  onComplete?: (output) => void;   // Callback on completion
  onError?: (error) => void;       // Callback on error
}

Return Value

interface UseWorkflowResult<TContract> {
  status: "idle" | "running" | "completed" | "error";
  error: Error | null;
  events: InferEventMap<TContract>[];  // Fully typed events!
  lastEvent: InferEventMap<TContract> | null;
  output: unknown;
  run: (input: unknown) => Promise<void>;
  cancel: () => void;
  reset: () => void;
}

Cloud Mode

const { run, events } = useWorkflow<typeof MyContract>({
  workflowId: "my-workflow",
  mode: "cloud",
  // Uses VERYDIA_API_KEY and VERYDIA_PROJECT_ID from environment
});

await run({ input: "data" });

Dev Mode

const { run, events } = useWorkflow<typeof MyContract>({
  workflowId: "my-workflow",
  mode: "dev",
  devBaseUrl: "http://localhost:8787",
});

await run({ input: "data" });

Type Safety

When you provide a contract, all events are fully typed:

const StoryContract = defineEventContract({
  progress: z.object({ percent: z.number() }),
  complete: z.object({ result: z.string() }),
});

const { events, lastEvent } = useWorkflow<typeof StoryContract>({
  workflowId: "story",
  mode: "dev",
});

// TypeScript knows the exact shape of events!
events.forEach((event) => {
  if (event.type === "progress") {
    console.log(event.data.percent); // number
  } else if (event.type === "complete") {
    console.log(event.data.result); // string
  }
});

Future: Streaming Support

The hook is designed to support streaming events once the dev-server and cloud API expose streaming endpoints. The API will remain the same - events will just arrive incrementally instead of all at once.

// TODO: Once streaming is implemented, events will arrive in real-time:
// - Dev server: SSE or WebSocket endpoint
// - Cloud API: Streaming response from client-sdk
// - Hook automatically processes events as they arrive
// - No API changes needed!

License

MIT