@verydia/react
v0.2.0
Published
React bindings for Verydia agents and workflows
Maintainers
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/reactUsing npm:
npm install @verydia/reactUsing yarn:
yarn add @verydia/reactQuick 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 implementationchildren: React.ReactNode- Your app components
useVerydiaAgent(agentName: string)
Returns:
runAgent(prompt: string, context?: unknown)- Function to run the agentresult: VerydiaReactAgentResult | null- Agent responsestatus: "idle" | "loading" | "success" | "error"- Current statusisLoading: boolean- Loading stateisIdle: boolean- Idle stateisSuccess: boolean- Success stateisError: boolean- Error stateerror: unknown- Error if any
useVerydiaWorkflow(workflowName: string)
Returns:
runWorkflow(payload: unknown, context?: unknown)- Function to run the workflowresult: VerydiaReactWorkflowResult | null- Workflow responsestatus: "idle" | "loading" | "success" | "error"- Current statusisLoading: boolean- Loading stateisIdle: boolean- Idle stateisSuccess: boolean- Success stateisError: boolean- Error stateerror: unknown- Error if any
AgentChat
Props:
agentName: string- Name of the agent to chat withplaceholder?: 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 breakdownProvidersPanel- Display configured LLM providers and modelsLlmStatusCard- Health status indicator for primary LLMVerydiaAdminDashboard- Complete admin page composing all panels
Hooks
useSafetyDashboard(endpoint)- Fetch safety scorecard datauseProviders(endpoint)- Fetch provider configurationuseModels(endpoint)- Fetch model listuseLlmStatus(endpoint)- Fetch LLM health statususeSafetyHistory(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
