diff --git a/.changeset/dark-melons-go.md b/.changeset/dark-melons-go.md new file mode 100644 index 00000000..194ee765 --- /dev/null +++ b/.changeset/dark-melons-go.md @@ -0,0 +1,5 @@ +--- +'@openai/agents-core': patch +--- + +fix(interruptions): avoid double outputting function calls for approval requests diff --git a/.changeset/weak-views-care.md b/.changeset/weak-views-care.md new file mode 100644 index 00000000..cb1dcd9f --- /dev/null +++ b/.changeset/weak-views-care.md @@ -0,0 +1,5 @@ +--- +'@openai/agents-core': patch +--- + +fix(interruptions): avoid accidental infinite loop if all interruptions were not cleared. expose interruptions helper on state diff --git a/eslint.config.mjs b/eslint.config.mjs index f7938ad7..151a3817 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -22,6 +22,7 @@ export default tseslint.config( '**/docs/.astro/**', 'examples/realtime-next/**', 'examples/realtime-demo/**', + 'examples/nextjs/**', 'integration-tests//**', ]), eslint.configs.recommended, diff --git a/examples/nextjs/.gitignore b/examples/nextjs/.gitignore new file mode 100644 index 00000000..5ef6a520 --- /dev/null +++ b/examples/nextjs/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/examples/nextjs/README.md b/examples/nextjs/README.md new file mode 100644 index 00000000..95ca80ce --- /dev/null +++ b/examples/nextjs/README.md @@ -0,0 +1,28 @@ +# Next.js Demo + +This example shows a basic example of how to use human-in-the-loop in a Next.js application. + +Right now it only uses a synchronous approach without streaming and storing in an in-memory DB. + +Eventually we will add more examples. + +## Run the example + +Set the `OPENAI_API_KEY` environment variable and run: + +```bash +pnpm -F nextjs dev +``` + +Open [http://localhost:3000](http://localhost:3000) in your browser and ask `What is the weather in San Francisco and Oakland?` + +## Endpoints + +- **`/`** – The basic example that actually handles receiving the approval requests and sending messages to the API. Code in `src/app/page.tsx`. +- **`/api/basic`** – The endpoint that gets triggered to run the agent. Code in `src/app/websocket/page.tsx`. + +## Other files + +- `src/components/Approvals.tsx` — renders the approval dialog +- `src/agents.ts` — contains the basic Agent configuration +- `src/db.ts` — contains the mock database implementation diff --git a/examples/nextjs/components.json b/examples/nextjs/components.json new file mode 100644 index 00000000..3289f237 --- /dev/null +++ b/examples/nextjs/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/examples/nextjs/next.config.ts b/examples/nextjs/next.config.ts new file mode 100644 index 00000000..5e891cf0 --- /dev/null +++ b/examples/nextjs/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from 'next'; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json new file mode 100644 index 00000000..f9fed44d --- /dev/null +++ b/examples/nextjs/package.json @@ -0,0 +1,35 @@ +{ + "name": "nextjs", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint", + "build-check": "tsc --noEmit" + }, + "dependencies": { + "@openai/agents": "workspace:*", + "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-slot": "^1.2.3", + "@tanstack/react-query": "^5.80.7", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.515.0", + "next": "15.3.2", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwind-merge": "^3.3.1", + "wavtools": "^0.1.5", + "zod": "~3.25.40" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "tailwindcss": "^4", + "tw-animate-css": "^1.3.4", + "typescript": "^5" + } +} diff --git a/examples/nextjs/postcss.config.mjs b/examples/nextjs/postcss.config.mjs new file mode 100644 index 00000000..ba720fe5 --- /dev/null +++ b/examples/nextjs/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ['@tailwindcss/postcss'], +}; + +export default config; diff --git a/examples/nextjs/public/file.svg b/examples/nextjs/public/file.svg new file mode 100644 index 00000000..004145cd --- /dev/null +++ b/examples/nextjs/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nextjs/public/globe.svg b/examples/nextjs/public/globe.svg new file mode 100644 index 00000000..567f17b0 --- /dev/null +++ b/examples/nextjs/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nextjs/public/next.svg b/examples/nextjs/public/next.svg new file mode 100644 index 00000000..5174b28c --- /dev/null +++ b/examples/nextjs/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nextjs/public/vercel.svg b/examples/nextjs/public/vercel.svg new file mode 100644 index 00000000..77053960 --- /dev/null +++ b/examples/nextjs/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nextjs/public/window.svg b/examples/nextjs/public/window.svg new file mode 100644 index 00000000..b2b2a44f --- /dev/null +++ b/examples/nextjs/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nextjs/src/agents.ts b/examples/nextjs/src/agents.ts new file mode 100644 index 00000000..0bb4950c --- /dev/null +++ b/examples/nextjs/src/agents.ts @@ -0,0 +1,21 @@ +import { Agent, tool } from '@openai/agents'; +import z from 'zod'; + +const getWeather = tool({ + name: 'getWeather', + description: 'Get the weather for a given city', + parameters: z.object({ + city: z.string(), + }), + execute: async ({ city }) => { + return `The weather in ${city} is sunny.`; + }, + + needsApproval: true, +}); + +export const agent = new Agent({ + name: 'Basic Agent', + instructions: 'You are a basic agent.', + tools: [getWeather], +}); diff --git a/examples/nextjs/src/app/api/basic/route.ts b/examples/nextjs/src/app/api/basic/route.ts new file mode 100644 index 00000000..9af50520 --- /dev/null +++ b/examples/nextjs/src/app/api/basic/route.ts @@ -0,0 +1,114 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { randomUUID } from 'node:crypto'; + +import { agent } from '@/agents'; +import { + AgentInputItem, + Runner, + RunState, + RunToolApprovalItem, +} from '@openai/agents'; +import { db } from '@/db'; + +function generateConversationId() { + return `conv_${randomUUID().replace(/-/g, '').slice(0, 24)}`; +} + +export async function POST(req: NextRequest) { + try { + const data = await req.json(); + let { messages, conversationId, decisions } = data; + + if (!messages) { + messages = []; + } + + if (!conversationId) { + // we will generate a conversation ID so we can keep track of the state in case of conversations + // this is just a key that we can use to store information in the database + conversationId = generateConversationId(); + } + + if (!decisions) { + decisions = null; + } + + const runner = new Runner({ + groupId: conversationId, + }); + + let input: AgentInputItem[] | RunState; + if ( + Object.keys(decisions).length > 0 && + data.conversationId /* original conversationId */ + ) { + // If we receive a new request with decisions, we will look up the current state in the database + const stateString = await db().get(data.conversationId); + + if (!stateString) { + return NextResponse.json( + { error: 'Conversation not found' }, + { status: 404 }, + ); + } + + // We then deserialize the state so we can manipulate it and continue the run + const state = await RunState.fromString(agent, stateString); + + const interruptions = state.getInterruptions(); + + interruptions.forEach((item: RunToolApprovalItem) => { + // For each interruption, we will then check if the decision is to approve or reject the tool call + if (item.type === 'tool_approval_item' && 'callId' in item.rawItem) { + const callId = item.rawItem.callId; + + if (decisions[callId] === 'approved') { + state.approve(item); + } else if (decisions[callId] === 'rejected') { + state.reject(item); + } + } + }); + + // We will use the new updated state to continue the run + input = state; + } else { + // If we don't have any decisions, we will just assume this is a regular chat and use the messages + // as input for the next run + input = messages; + } + + const result = await runner.run(agent, input); + + if (result.interruptions.length > 0) { + // If the run resulted in one or more interruptions, we will store the current state in the database + + // store the state in the database + await db().set(conversationId, JSON.stringify(result.state)); + + // We will return all the interruptions as approval requests to the UI/client so it can generate + // the UI for approvals + // We will also still return the history that contains the tool calls and potentially any interim + // text response the agent might have generated (like announcing that it's calling a function) + return NextResponse.json({ + conversationId, + approvals: result.interruptions + .filter((item) => item.type === 'tool_approval_item') + .map((item) => item.toJSON()), + history: result.history, + }); + } + + return NextResponse.json({ + response: result.finalOutput, + history: result.history, + conversationId, + }); + } catch (error) { + console.error(error); + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 }, + ); + } +} diff --git a/examples/nextjs/src/app/favicon.ico b/examples/nextjs/src/app/favicon.ico new file mode 100644 index 00000000..718d6fea Binary files /dev/null and b/examples/nextjs/src/app/favicon.ico differ diff --git a/examples/nextjs/src/app/globals.css b/examples/nextjs/src/app/globals.css new file mode 100644 index 00000000..31ec2d75 --- /dev/null +++ b/examples/nextjs/src/app/globals.css @@ -0,0 +1,122 @@ +@import 'tailwindcss'; +@import 'tw-animate-css'; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/examples/nextjs/src/app/layout.tsx b/examples/nextjs/src/app/layout.tsx new file mode 100644 index 00000000..156a45bd --- /dev/null +++ b/examples/nextjs/src/app/layout.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from 'next'; +import './globals.css'; + +export const metadata: Metadata = { + title: 'Agent SDK Next.js Demo', + description: 'A demo of the Agent SDK in Next.js', +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/examples/nextjs/src/app/page.tsx b/examples/nextjs/src/app/page.tsx new file mode 100644 index 00000000..7c0e754a --- /dev/null +++ b/examples/nextjs/src/app/page.tsx @@ -0,0 +1,89 @@ +'use client'; + +import type { AgentInputItem, RunToolApprovalItem } from '@openai/agents'; +import { useState } from 'react'; +import { App } from '@/components/App'; +import { Approvals } from '@/components/Approvals'; + +export default function Home() { + const [history, setHistory] = useState([]); + const [conversationId, setConversationId] = useState(null); + const [approvals, setApprovals] = useState< + ReturnType[] + >([]); + + async function makeRequest({ + message, + decisions, + }: { + message?: string; + decisions?: Map; + }) { + const messages = [...history]; + + if (message) { + messages.push({ type: 'message', role: 'user', content: message }); + } + + setHistory([ + ...messages, + // This is just a placeholder to show on the UI to show the agent is working + { + type: 'message', + role: 'assistant', + content: [], + status: 'in_progress', + }, + ]); + + // We will send the messages to the API route along with the conversation ID if we have one + // and the decisions if we had any approvals in this turn + const response = await fetch('/api/basic', { + method: 'POST', + body: JSON.stringify({ + messages, + conversationId, + decisions: Object.fromEntries(decisions ?? []), + }), + }); + + const data = await response.json(); + + if (data.conversationId) { + setConversationId(data.conversationId); + } + + if (data.history) { + setHistory(data.history); + } + + if (data.approvals) { + setApprovals(data.approvals); + } else { + setApprovals([]); + } + } + + const handleSend = async (message: string) => { + await makeRequest({ message }); + }; + + async function handleDone(decisions: Map) { + await makeRequest({ decisions }); + } + + return ( + <> + + {/** + * If we have any approvals, we will show the approvals component to allow the user to + * approve or reject the tool calls. If we don't have any approvals, we will just show the + * history. Once all the approvals are done, we will call the handleDone function to continue + * the run. What kind of UI you render to show approval requests is up to you. You could also + * render them as part of the chat history. We are rendering them separately here to show + * that it can be an entirely different UI. + */} + + + ); +} diff --git a/examples/nextjs/src/components/App.tsx b/examples/nextjs/src/components/App.tsx new file mode 100644 index 00000000..9d84b3dd --- /dev/null +++ b/examples/nextjs/src/components/App.tsx @@ -0,0 +1,81 @@ +import type { AgentInputItem } from '@openai/agents'; +import { History } from '@/components/History'; +import { Button } from '@/components/ui/Button'; +import { useState, useRef, useEffect } from 'react'; +import ArrowUpIcon from './icons/ArrowUpIcon'; + +export type AppProps = { + title?: string; + history?: AgentInputItem[]; + onSend: (message: string) => void; +}; + +export function App({ title = 'Agent Demo', history, onSend }: AppProps) { + const [message, setMessage] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const inputRef = useRef(null); + + useEffect(() => { + if (!isLoading) { + inputRef.current?.focus(); + } + }, [isLoading]); + + const handleSend = async () => { + if (!message.trim()) return; + setIsLoading(true); + const msg = message; + setMessage(''); + await onSend(msg); + setIsLoading(false); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!message.trim()) return; + await handleSend(); + }; + + return ( +
+
+
+

{title}

+
+
+
+ {history && history.length > 0 ? ( + + ) : ( +
+ No history available +
+ )} +
+
+ setMessage(e.target.value)} + disabled={isLoading} + ref={inputRef} + /> + +
+
+
+
+ ); +} diff --git a/examples/nextjs/src/components/Approvals.tsx b/examples/nextjs/src/components/Approvals.tsx new file mode 100644 index 00000000..ef30a7d6 --- /dev/null +++ b/examples/nextjs/src/components/Approvals.tsx @@ -0,0 +1,153 @@ +import type { RunToolApprovalItem } from '@openai/agents'; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Button } from './ui/Button'; +import { useEffect, useState } from 'react'; + +type Item = ReturnType; + +function ToolApprovalEntry({ + approval, + onApprove, + onReject, + decision, +}: { + approval: Item; + onApprove: () => void; + onReject: () => void; + decision: 'approved' | 'rejected' | undefined; +}) { + if (approval.rawItem?.type !== 'function_call') { + return null; + } + + return ( +
+

+ Tool {approval.rawItem?.name} +

+
+        {approval.rawItem?.arguments}
+      
+ {decision === undefined && ( +
+ + +
+ )} + {decision === 'approved' && ( +

✔︎ Approved

+ )} + {decision === 'rejected' && ( +

✖︎ Rejected

+ )} +
+ ); +} + +/** + * This component just renders all of the approval requests and tracks whether they were approved + * or not by storing the callId in a decision Map with `approved` or `rejected` as the value. + * Once all the approvals are done, we will call the onDone function to let the parent component + * trigger the next run. + */ +export function Approvals({ + approvals, + onDone, +}: { + approvals: ReturnType[]; + onDone: (decisions: Map) => void; +}) { + const [decisions, setDecisions] = useState< + Map + >(new Map()); + const [isOpen, setIsOpen] = useState(approvals.length > 0); + + useEffect(() => { + setDecisions(new Map()); + if (approvals.length > 0) { + setIsOpen(true); + } + }, [approvals]); + + function handleApprove(approval: Item) { + setDecisions((prev) => { + if (approval.rawItem?.type !== 'function_call') { + return prev; + } + const newDecisions = new Map(prev); + newDecisions.set(approval.rawItem?.callId ?? '', 'approved'); + return newDecisions; + }); + } + + function handleReject(approval: Item) { + setDecisions((prev) => { + if (approval.rawItem?.type !== 'function_call') { + return prev; + } + const newDecisions = new Map(prev); + newDecisions.set(approval.rawItem?.callId ?? '', 'rejected'); + return newDecisions; + }); + } + + function handleDone() { + onDone(decisions); + setIsOpen(false); + } + + if (approvals.length === 0) { + return null; + } + + const agentName = approvals[0].agent.name; + + return ( + + + + Approval required + + The agent {agentName} is requesting approval for the following + action{approvals.length > 1 ? 's' : ''}: + + +
+ {approvals.map((approval) => + approval.rawItem?.type === 'function_call' ? ( + handleApprove(approval)} + onReject={() => handleReject(approval)} + /> + ) : null, + )} +
+ + + +
+
+ ); +} diff --git a/examples/nextjs/src/components/History.tsx b/examples/nextjs/src/components/History.tsx new file mode 100644 index 00000000..864c5d22 --- /dev/null +++ b/examples/nextjs/src/components/History.tsx @@ -0,0 +1,112 @@ +import type { AgentInputItem } from '@openai/agents'; +import { TextMessage } from './messages/TextMessage'; +import { + FunctionCallMessage, + ProcessedFunctionCallItem, +} from './messages/FunctionCall'; +import { useMemo } from 'react'; + +export type HistoryProps = { + history: AgentInputItem[]; +}; + +export type ProcessedMessageItem = { + type: 'message'; + role: 'user' | 'assistant'; + content: string; + id: string; +}; + +type ProcessedItem = ProcessedMessageItem | ProcessedFunctionCallItem; + +function processItems(items: AgentInputItem[]): ProcessedItem[] { + const processedItems: ProcessedItem[] = []; + + for (const item of items) { + if (item.type === 'function_call') { + processedItems.push({ + type: 'function_call', + name: item.name, + arguments: item.arguments, + id: item.id ?? '', + callId: item.callId ?? '', + status: 'in_progress', + }); + } + + if (item.type === 'function_call_result') { + const index = processedItems.findIndex( + (i) => i.type === 'function_call' && item.callId === i.callId, + ); + + if (index !== -1 && processedItems[index].type === 'function_call') { + processedItems[index].output = + item.output.type === 'text' + ? item.output.text + : item.output.type === 'image' + ? item.output.data + : ''; + processedItems[index].status = 'completed'; + } + } + + if (item.type === 'message') { + processedItems.push({ + type: 'message', + role: item.role === 'system' ? 'assistant' : item.role, + content: + typeof item.content === 'string' + ? item.content + : item.content + .map((content) => { + if ( + content.type === 'input_text' || + content.type === 'output_text' + ) { + return content.text; + } + if (content.type === 'audio') { + return content.transcript ?? '⚫︎⚫︎⚫︎'; + } + if (content.type === 'refusal') { + return content.refusal; + } + return ''; + }) + .join('\n') || '⚫︎⚫︎⚫︎', + id: item.id ?? JSON.stringify(item.content), + }); + } + } + + return processedItems; +} + +export function History({ history }: HistoryProps) { + const processedItems = useMemo(() => processItems(history), [history]); + + return ( +
+ {processedItems.map((item, idx) => { + if (item.type === 'function_call') { + return ; + } + + if (item.type === 'message') { + return ( + + ); + } + + return null; + })} +
+ ); +} diff --git a/examples/nextjs/src/components/icons/ArrowUpIcon.tsx b/examples/nextjs/src/components/icons/ArrowUpIcon.tsx new file mode 100644 index 00000000..282200e9 --- /dev/null +++ b/examples/nextjs/src/components/icons/ArrowUpIcon.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; + +const ArrowUpIcon = (props: React.SVGProps) => ( + + + +); + +export default ArrowUpIcon; diff --git a/examples/nextjs/src/components/icons/ClockIcon.tsx b/examples/nextjs/src/components/icons/ClockIcon.tsx new file mode 100644 index 00000000..5fc79955 --- /dev/null +++ b/examples/nextjs/src/components/icons/ClockIcon.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; + +const ClockIcon = (props: React.SVGProps) => ( + + + +); + +export default ClockIcon; diff --git a/examples/nextjs/src/components/icons/FunctionsIcon.tsx b/examples/nextjs/src/components/icons/FunctionsIcon.tsx new file mode 100644 index 00000000..7fa7031d --- /dev/null +++ b/examples/nextjs/src/components/icons/FunctionsIcon.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; + +const FunctionsIcon = (props: React.SVGProps) => ( + + + +); + +export default FunctionsIcon; diff --git a/examples/nextjs/src/components/messages/FunctionCall.tsx b/examples/nextjs/src/components/messages/FunctionCall.tsx new file mode 100644 index 00000000..4217c538 --- /dev/null +++ b/examples/nextjs/src/components/messages/FunctionCall.tsx @@ -0,0 +1,64 @@ +import React from 'react'; + +import ClockIcon from '@/components/icons/ClockIcon'; +import FunctionsIcon from '@/components/icons/FunctionsIcon'; + +export type ProcessedFunctionCallItem = { + type: 'function_call'; + name: string; + arguments: string; + id: string; + callId: string; + output?: string; + status: 'completed' | 'in_progress'; +}; + +type FunctionCallMessageProps = { + message: ProcessedFunctionCallItem; +}; + +export function FunctionCallMessage({ message }: FunctionCallMessageProps) { + let output = message?.output; + try { + if (message.output) { + output = JSON.stringify(JSON.parse(message.output), null, 2); + } + } catch { + output = message.output; + } + return ( +
+
+
+
+
+ +
+ {message.status === 'completed' + ? `Called ${message.name}` + : `Calling ${message.name}...`} +
+
+
+ +
+
+
+                {JSON.stringify(JSON.parse(message.arguments), null, 2)}
+              
+
+
+ {output ? ( +
{output}
+ ) : ( +
+ Waiting for result... +
+ )} +
+
+
+
+
+ ); +} diff --git a/examples/nextjs/src/components/messages/TextMessage.tsx b/examples/nextjs/src/components/messages/TextMessage.tsx new file mode 100644 index 00000000..13ea59c2 --- /dev/null +++ b/examples/nextjs/src/components/messages/TextMessage.tsx @@ -0,0 +1,26 @@ +import clsx from 'clsx'; +import React from 'react'; + +type TextMessageProps = { + text: string; + isUser: boolean; +}; + +export function TextMessage({ text, isUser }: TextMessageProps) { + return ( +
+
+ {text} +
+
+ ); +} diff --git a/examples/nextjs/src/components/ui/Button.tsx b/examples/nextjs/src/components/ui/Button.tsx new file mode 100644 index 00000000..b0e648fb --- /dev/null +++ b/examples/nextjs/src/components/ui/Button.tsx @@ -0,0 +1,58 @@ +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import * as React from 'react'; + +import { cn } from '@/components/ui/utils'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + { + variants: { + variant: { + ghost: + 'text-gray-800 disabled:text-gray-300 hover:bg-gray-100 hover:text-black', + primary: 'bg-black text-white hover:bg-gray-800 disabled:bg-gray-300', + secondary: + 'bg-gray-100 text-gray-800 hover:bg-gray-200 disabled:bg-gray-300', + outline: + 'border border-2 border-gray-100 text-gray-800 hover:bg-gray-100 hover:text-black', + stop: 'bg-red-500 text-white hover:bg-red-600 disabled:bg-red-300', + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + smRounded: 'h-8 rounded-full px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-10 w-10 rounded-full [&_svg]:size-6', + iconSmall: 'h-8 w-8 rounded-full [&_svg]:size-6', + iconTiny: 'h-6 w-6 rounded-full', + }, + }, + defaultVariants: { + variant: 'ghost', + size: 'default', + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + }, +); +Button.displayName = 'Button'; + +export { Button, buttonVariants }; diff --git a/examples/nextjs/src/components/ui/dialog.tsx b/examples/nextjs/src/components/ui/dialog.tsx new file mode 100644 index 00000000..f6c4e9fa --- /dev/null +++ b/examples/nextjs/src/components/ui/dialog.tsx @@ -0,0 +1,143 @@ +'use client'; + +import * as React from 'react'; +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { XIcon } from 'lucide-react'; + +import { cn } from '@/lib/utils'; + +function Dialog({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean; +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ); +} + +function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/examples/nextjs/src/components/ui/utils.ts b/examples/nextjs/src/components/ui/utils.ts new file mode 100644 index 00000000..2819a830 --- /dev/null +++ b/examples/nextjs/src/components/ui/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/examples/nextjs/src/db.ts b/examples/nextjs/src/db.ts new file mode 100644 index 00000000..8d504284 --- /dev/null +++ b/examples/nextjs/src/db.ts @@ -0,0 +1,29 @@ +/** + * This is just a super simple in-memory database for the demo. + * In a real application, you would use a proper database that persists the data. + */ + +export class Database { + #database: Map; + + constructor() { + this.#database = new Map(); + } + + async get(key: string) { + return this.#database.get(key); + } + + async set(key: string, value: Value) { + this.#database.set(key, value); + } +} + +let database: Database | undefined; + +export function db() { + if (!database) { + database = new Database(); + } + return database; +} diff --git a/examples/nextjs/src/lib/utils.ts b/examples/nextjs/src/lib/utils.ts new file mode 100644 index 00000000..2819a830 --- /dev/null +++ b/examples/nextjs/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/examples/nextjs/tsconfig.json b/examples/nextjs/tsconfig.json new file mode 100644 index 00000000..c1334095 --- /dev/null +++ b/examples/nextjs/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/examples/nextjs/vercel.json b/examples/nextjs/vercel.json new file mode 100644 index 00000000..a667db8c --- /dev/null +++ b/examples/nextjs/vercel.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": "nextjs" +} diff --git a/packages/agents-core/src/run.ts b/packages/agents-core/src/run.ts index 8271033b..49fde567 100644 --- a/packages/agents-core/src/run.ts +++ b/packages/agents-core/src/run.ts @@ -179,7 +179,9 @@ export function getTurnInput( originalInput: string | AgentInputItem[], generatedItems: RunItem[], ): AgentInputItem[] { - const rawItems = generatedItems.map((item) => item.rawItem); + const rawItems = generatedItems + .filter((item) => item.type !== 'tool_approval_item') // don't include approval items to avoid double function calls + .map((item) => item.rawItem); if (typeof originalInput === 'string') { originalInput = [{ type: 'message', role: 'user', content: originalInput }]; @@ -290,6 +292,12 @@ export class Runner extends RunHooks> { state._originalInput = turnResult.originalInput; state._generatedItems = turnResult.generatedItems; state._currentStep = turnResult.nextStep; + + if (turnResult.nextStep.type === 'next_step_interruption') { + // we are still in an interruption, so we need to avoid an infinite loop + return new RunResult(state); + } + continue; } @@ -641,6 +649,10 @@ export class Runner extends RunHooks> { result.state._originalInput = turnResult.originalInput; result.state._generatedItems = turnResult.generatedItems; result.state._currentStep = turnResult.nextStep; + if (turnResult.nextStep.type === 'next_step_interruption') { + // we are still in an interruption, so we need to avoid an infinite loop + return; + } continue; } diff --git a/packages/agents-core/src/runState.ts b/packages/agents-core/src/runState.ts index 980601e3..ca485ed2 100644 --- a/packages/agents-core/src/runState.ts +++ b/packages/agents-core/src/runState.ts @@ -324,6 +324,16 @@ export class RunState> { this._trace = getCurrentTrace(); } + /** + * Returns all interruptions if the current step is an interruption otherwise returns an empty array. + */ + getInterruptions() { + if (this._currentStep?.type !== 'next_step_interruption') { + return []; + } + return this._currentStep.data.interruptions; + } + /** * Approves a tool call requested by the agent through an interruption and approval item request. * diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfdbc640..ab4ee2da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -236,6 +236,70 @@ importers: specifier: ~3.25.40 version: 3.25.62 + examples/nextjs: + dependencies: + '@openai/agents': + specifier: workspace:* + version: link:../../packages/agents + '@radix-ui/react-dialog': + specifier: ^1.1.14 + version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': + specifier: ^1.2.3 + version: 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@tanstack/react-query': + specifier: ^5.80.7 + version: 5.80.7(react@19.1.0) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^0.515.0 + version: 0.515.0(react@19.1.0) + next: + specifier: 15.3.2 + version: 15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: + specifier: ^19.0.0 + version: 19.1.0 + react-dom: + specifier: ^19.0.0 + version: 19.1.0(react@19.1.0) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 + wavtools: + specifier: ^0.1.5 + version: 0.1.5 + zod: + specifier: ~3.25.40 + version: 3.25.62 + devDependencies: + '@tailwindcss/postcss': + specifier: ^4 + version: 4.1.10 + '@types/node': + specifier: ^20 + version: 20.19.0 + '@types/react': + specifier: ^19 + version: 19.1.8 + '@types/react-dom': + specifier: ^19 + version: 19.1.6(@types/react@19.1.8) + tailwindcss: + specifier: ^4 + version: 4.1.10 + tw-animate-css: + specifier: ^1.3.4 + version: 1.3.4 + typescript: + specifier: ^5 + version: 5.8.3 + examples/realtime-demo: dependencies: '@tailwindcss/vite': @@ -1416,6 +1480,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@radix-ui/primitive@1.1.2': + resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} + '@radix-ui/react-compose-refs@1.1.2': resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: @@ -1425,6 +1492,111 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.14': + resolution: {integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.10': + resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.2': + resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.4': + resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.2.3': resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: @@ -1434,6 +1606,51 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@rollup/pluginutils@5.1.4': resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} engines: {node: '>=14.0.0'} @@ -1703,6 +1920,14 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 + '@tanstack/query-core@5.80.7': + resolution: {integrity: sha512-s09l5zeUKC8q7DCCCIkVSns8zZrK4ZDT6ryEjxNBFi68G4z2EBobBS7rdOY3r6W1WbUDpc1fe5oY+YO/+2UVUg==} + + '@tanstack/react-query@5.80.7': + resolution: {integrity: sha512-u2F0VK6+anItoEvB3+rfvTO9GEh2vb00Je05OwlUe/A0lkJBgW1HckiY3f9YZa+jx6IOe4dHPh10dyp9aY3iRQ==} + peerDependencies: + react: ^18 || ^19 + '@types/braces@3.0.5': resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} @@ -2070,6 +2295,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -2271,9 +2500,6 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} - caniuse-lite@1.0.30001707: - resolution: {integrity: sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==} - caniuse-lite@1.0.30001722: resolution: {integrity: sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==} @@ -2569,6 +2795,9 @@ packages: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + deterministic-object-hash@2.0.2: resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} engines: {node: '>=18'} @@ -3053,6 +3282,10 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -3646,6 +3879,11 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + lucide-react@0.515.0: + resolution: {integrity: sha512-Sy7bY0MeicRm2pzrnoHm2h6C1iVoeHyBU2fjdQDsXGP51fhkhau1/ZV/dzrcxEmAKsxYb6bGaIsMnGHuQ5s0dw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} @@ -4480,6 +4718,36 @@ packages: peerDependencies: react: ^19.1.0 + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react@19.1.0: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} @@ -5129,6 +5397,9 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + tw-animate-css@1.3.4: + resolution: {integrity: sha512-dd1Ht6/YQHcNbq0znIT6dG8uhO7Ce+VIIhZUhjsryXsMPJQz3bZg7Q2eNzLwipb25bRZslGb2myio5mScd1TFg==} + tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} @@ -5335,6 +5606,26 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -6175,8 +6466,8 @@ snapshots: hast-util-to-html: 9.0.5 hast-util-to-text: 4.0.2 hastscript: 9.0.1 - postcss: 8.5.3 - postcss-nested: 6.2.0(postcss@8.5.3) + postcss: 8.5.5 + postcss-nested: 6.2.0(postcss@8.5.5) unist-util-visit: 5.0.0 unist-util-visit-parents: 6.0.1 @@ -6561,12 +6852,108 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@radix-ui/primitive@1.1.2': {} + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: '@types/react': 19.1.8 + '@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) @@ -6574,6 +6961,40 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + '@rollup/pluginutils@5.1.4(rollup@4.38.0)': dependencies: '@types/estree': 1.0.8 @@ -6807,7 +7228,7 @@ snapshots: '@alloc/quick-lru': 5.2.0 '@tailwindcss/node': 4.1.10 '@tailwindcss/oxide': 4.1.10 - postcss: 8.5.3 + postcss: 8.5.5 tailwindcss: 4.1.10 '@tailwindcss/vite@4.1.10(vite@6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.1)(yaml@2.7.1))': @@ -6817,6 +7238,13 @@ snapshots: tailwindcss: 4.1.10 vite: 6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.1)(yaml@2.7.1) + '@tanstack/query-core@5.80.7': {} + + '@tanstack/react-query@5.80.7(react@19.1.0)': + dependencies: + '@tanstack/query-core': 5.80.7 + react: 19.1.0 + '@types/braces@3.0.5': {} '@types/chai@5.2.2': @@ -6839,7 +7267,7 @@ snapshots: '@types/fontkit@2.0.8': dependencies: - '@types/node': 24.0.1 + '@types/node': 22.15.31 '@types/hast@3.0.4': dependencies: @@ -6867,7 +7295,7 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: - '@types/node': 18.19.111 + '@types/node': 22.15.31 form-data: 4.0.2 '@types/node@12.20.55': {} @@ -6889,6 +7317,7 @@ snapshots: '@types/node@24.0.1': dependencies: undici-types: 7.8.0 + optional: true '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: @@ -6900,7 +7329,7 @@ snapshots: '@types/sax@1.2.7': dependencies: - '@types/node': 17.0.45 + '@types/node': 22.15.31 '@types/unist@2.0.11': {} @@ -6908,7 +7337,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 24.0.1 + '@types/node': 22.15.31 '@typescript-eslint/eslint-plugin@8.34.0(@typescript-eslint/parser@8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: @@ -7311,6 +7740,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.2: {} array-flatten@1.1.1: {} @@ -7615,8 +8048,6 @@ snapshots: camelcase@8.0.0: {} - caniuse-lite@1.0.30001707: {} - caniuse-lite@1.0.30001722: {} caseless@0.12.0: {} @@ -7860,6 +8291,8 @@ snapshots: detect-libc@2.0.4: {} + detect-node-es@1.1.0: {} + deterministic-object-hash@2.0.2: dependencies: base-64: 1.0.0 @@ -8508,6 +8941,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -9199,6 +9634,10 @@ snapshots: lru-cache@7.18.3: {} + lucide-react@0.515.0(react@19.1.0): + dependencies: + react: 19.1.0 + lunr@2.3.9: {} magic-string@0.30.17: @@ -9790,7 +10229,7 @@ snapshots: '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001707 + caniuse-lite: 1.0.30001722 postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -10142,6 +10581,11 @@ snapshots: postcss: 8.5.3 postcss-selector-parser: 6.1.2 + postcss-nested@6.2.0(postcss@8.5.5): + dependencies: + postcss: 8.5.5 + postcss-selector-parser: 6.1.2 + postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 @@ -10255,6 +10699,33 @@ snapshots: react: 19.1.0 scheduler: 0.26.0 + react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0): + dependencies: + react: 19.1.0 + react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + + react-remove-scroll@2.7.1(@types/react@19.1.8)(react@19.1.0): + dependencies: + react: 19.1.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.1.8)(react@19.1.0) + react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.1.8)(react@19.1.0) + use-sidecar: 1.1.3(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + + react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.0): + dependencies: + get-nonce: 1.0.1 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + react@19.1.0: {} read-cache@1.0.0: @@ -11133,6 +11604,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + tw-animate-css@1.3.4: {} + tweetnacl@0.14.5: {} typanion@3.14.0: {} @@ -11198,7 +11671,8 @@ snapshots: undici-types@6.21.0: {} - undici-types@7.8.0: {} + undici-types@7.8.0: + optional: true unicode-properties@1.4.1: dependencies: @@ -11306,6 +11780,21 @@ snapshots: dependencies: punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.1.8)(react@19.1.0): + dependencies: + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + + use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.8 + util-deprecate@1.0.2: {} utils-merge@1.0.1: {}