From 05a7d801162eb042fa0e7bdaef355eaca5577bef Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 23:34:12 +0000 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20rich=20chat=20=E2=80=94=20per-answe?= =?UTF-8?q?r=20activity=20and=20usage,=20composer,=20history,=20workspace,?= =?UTF-8?q?=20i18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AgentChat: every answer keeps its steps, tool calls (≈tokens, duration, returned images), thoughts, subagents and consent decisions; tokens by kind per answer and in total; saved chats in IndexedDB (ChatHistoryStore, useChatHistory, ChatHistoryList) and a workspace panel over the core's VirtualFileSystem (FilesPanel, useVirtualFiles). Side panels overlay a narrow panel. Configurable: show* props, labels (AgentLabelsProvider), render props, slots, swappable components, theme, classNames. - AgentComposer: notices, "+" / Ctrl+V / drag & drop (self, window or a ref) for images, PDFs and files with capability checks and optional conversion, image downscaling before upload, slash commands (built-ins + skills), run timer, running agents, model + thinking chip, consent chip, speech to text. - Reducer: per-message activity, tool timing, totalUsage, load action. - useAgent: run(goal, { images, files, attachments, label }), sessionId, loadChat; useMcpServers for several MCP servers. - Demo: three tabs (notes, MCP servers, chess vs agent with worker subagents), agent + chat UI settings, Russian labels, PDF → Markdown via @dudko.dev/pdf-to-md-core, a shared workspace with fs_* tools. - Tests: chat helpers/labels/history/reducer units; e2e for several MCP servers and the agent against a scripted Gemini endpoint. - Docs: docs/chat.md, README, demo README, CLAUDE.md. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01A7jHa5Pim4ufrdgxg561G5 --- CLAUDE.md | 30 +- README.md | 91 +- demo/README.md | 45 +- demo/package-lock.json | 2042 ++++++-------------- demo/package.json | 22 +- demo/src/App.tsx | 411 +++- demo/src/app.css | 350 +++- demo/src/chess/ChessBoard.tsx | 86 + demo/src/chess/ChessPanel.tsx | 129 ++ demo/src/chess/analyst-tools.ts | 35 + demo/src/chess/analyst.worker.ts | 35 + demo/src/chess/engine.ts | 154 ++ demo/src/chess/game.ts | 208 ++ demo/src/components/AgentSettingsPanel.tsx | 326 ++++ demo/src/components/McpPanel.tsx | 326 ++-- demo/src/i18n.ts | 115 ++ demo/src/mockCatalog.ts | 94 + demo/src/notes.ts | 15 +- demo/src/settings.ts | 130 ++ demo/src/skills.ts | 50 + demo/src/workspace.ts | 25 + demo/vite.config.ts | 11 +- docs/chat.md | 241 +++ e2e/agent.spec.ts | 164 ++ e2e/gemini.ts | 110 ++ e2e/mcp.spec.ts | 76 +- src/chat-history.ts | 126 ++ src/components/AgentChat.tsx | 463 ++++- src/components/AgentComposer.tsx | 887 +++++++++ src/components/ApprovalModeSwitch.tsx | 60 + src/components/ChatHistoryList.tsx | 91 + src/components/ContextMeter.tsx | 79 + src/components/FilesPanel.tsx | 171 ++ src/components/MessageList.tsx | 169 +- src/components/StepList.tsx | 205 +- src/components/ToolApprovalPrompt.tsx | 82 + src/components/UsageBadge.tsx | 27 +- src/components/format.ts | 37 + src/components/icons.tsx | 87 + src/composer.ts | 195 ++ src/hooks/use-agent.ts | 248 ++- src/hooks/use-chat-history.ts | 126 ++ src/hooks/use-mcp-servers.ts | 493 +++++ src/hooks/use-mcp.ts | 14 +- src/hooks/use-speech-to-text.ts | 123 ++ src/hooks/use-virtual-files.ts | 89 + src/image-resize.ts | 77 + src/index.ts | 107 +- src/labels.tsx | 277 +++ src/mcp-types.ts | 10 +- src/state.ts | 387 +++- src/styles.css | 859 +++++++- src/types.ts | 107 + tests/chat.test.ts | 260 +++ tests/state.test.ts | 134 +- 55 files changed, 9294 insertions(+), 2017 deletions(-) create mode 100644 demo/src/chess/ChessBoard.tsx create mode 100644 demo/src/chess/ChessPanel.tsx create mode 100644 demo/src/chess/analyst-tools.ts create mode 100644 demo/src/chess/analyst.worker.ts create mode 100644 demo/src/chess/engine.ts create mode 100644 demo/src/chess/game.ts create mode 100644 demo/src/components/AgentSettingsPanel.tsx create mode 100644 demo/src/i18n.ts create mode 100644 demo/src/mockCatalog.ts create mode 100644 demo/src/settings.ts create mode 100644 demo/src/skills.ts create mode 100644 demo/src/workspace.ts create mode 100644 docs/chat.md create mode 100644 e2e/agent.spec.ts create mode 100644 e2e/gemini.ts create mode 100644 src/chat-history.ts create mode 100644 src/components/AgentComposer.tsx create mode 100644 src/components/ApprovalModeSwitch.tsx create mode 100644 src/components/ChatHistoryList.tsx create mode 100644 src/components/ContextMeter.tsx create mode 100644 src/components/FilesPanel.tsx create mode 100644 src/components/ToolApprovalPrompt.tsx create mode 100644 src/composer.ts create mode 100644 src/hooks/use-chat-history.ts create mode 100644 src/hooks/use-mcp-servers.ts create mode 100644 src/hooks/use-speech-to-text.ts create mode 100644 src/hooks/use-virtual-files.ts create mode 100644 src/image-resize.ts create mode 100644 src/labels.tsx create mode 100644 tests/chat.test.ts diff --git a/CLAUDE.md b/CLAUDE.md index b011752..c8e8e24 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,19 +30,32 @@ The repo also contains a **Vite demo** (`demo/`) that is auto-deployed to - `src/state.ts` — the pure `agentStateReducer` + `createInitialAgentState`. - `src/types.ts` — `AgentUiState`, `ChatMessage`, `StepView`, `ToolCallView`, … - `src/hooks/` — `use-agent` (the hook), `use-credentials` (vault), - `use-webllm-model`, `use-mcp` (remote MCP + OAuth round-trip). + `use-webllm-model`, `use-mcp` (remote MCP + OAuth round-trip), + `use-mcp-servers` (several servers), `use-chat-history`, `use-virtual-files`, + `use-speech-to-text`. +- `src/chat-history.ts` — `ChatHistoryStore` (raw IndexedDB, memory fallback). +- `src/composer.ts` — the composer's pure helpers (slash commands, attachment + kinds/refusals, notices); `src/image-resize.ts` — `downscaleImage`. +- `src/labels.tsx` — every UI string (`defaultLabels`, `AgentLabelsProvider`, + `useLabels`); components take `labels` overrides. - `src/mcp-types.ts` — structural types for the core's optional `./mcp` subpath, which `use-mcp` loads with a dynamic import (never a static one). - `src/context.tsx` — `AgentProvider` + `useAgentContext`. -- `src/components/` — `AgentChat`, `MessageList`, `Composer`, `PlanView`, - `StepList`, `ModelLoadBar`, `UsageBadge`, `ApiKeyForm`, `icons`, `format`. +- `src/components/` — `AgentChat` (panel: history sidebar, transcript, + composer, files panel; slots / render props / components / theme), + `AgentComposer`, `MessageList`, `StepList` (+ `ToolCallRow`), + `ToolApprovalPrompt`, `ApprovalModeSwitch`, `ChatHistoryList`, `FilesPanel`, + `ContextMeter`, `Composer` (minimal), `PlanView`, `ModelLoadBar`, + `UsageBadge`, `ApiKeyForm`, `icons`, `format`. Docs: `docs/chat.md`. - `demo/src/components/McpPanel.tsx` — the demo's "bring your own MCP" panel. - `src/styles.css` — optional theme (light + dark). - `src/index.ts` — public surface (+ curated re-exports from the core). -- `tests/state.test.ts` — `node --test` over the reducer (imports from `dist`). -- `e2e/` — Playwright over the built demo: the OAuth redirect round-trip, - IndexedDB reuse, CORS. `servers.ts` starts a real MCP server + authorization - server on loopback. +- `tests/state.test.ts`, `tests/chat.test.ts` — `node --test` over the reducer, + composer helpers, labels and the history store (imports from `dist`). +- `e2e/` — Playwright over the built demo: MCP (several servers, the OAuth + redirect round-trip, IndexedDB reuse, CORS — `servers.ts` starts a real MCP + server + authorization server on loopback) and the agent end to end against + a scripted Gemini endpoint (`gemini.ts`, routed by the stage markers). - `demo/` — the Vite + React demo (aliases the library to `../dist`). ## Commands @@ -75,5 +88,8 @@ publishes to npm via Trusted Publishing after CI passes on `main`. Pages build the library before building the demo. - Keep the event→UI logic in the reducer, not the components. New event fields from the core map to new `AgentUiState` fields + a reducer case + a test. +- Every user-visible string goes through `labels` (add it to `AgentLabels` and + `defaultLabels`); every new part of a component gets a `show*` prop or a + render prop / slot — hosts must be able to hide, relabel or replace it. - Don't import model providers, `ai`, or `zod` at runtime here — the core owns them. Type-only imports from `@dudko.dev/agent-web` are fine. diff --git a/README.md b/README.md index 1069798..2b5173c 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,17 @@ app with a single hook and (optionally) a set of pre-styled components: a ready-to-render state (plan, steps, tool calls, streamed answer, token usage, model-load progress, chat transcript), and get `run` / `stop` / `reset` / `reload`. -- 🧩 **`` + components** — a drop-in `` panel plus - ``, ``, ``, ``, ``, - and ``. Bring your own CSS or import the optional stylesheet. +- 💬 **``** — a drop-in chat: every tool call with its cost, tokens by + kind per answer and in total, thoughts, subagents, consent prompts; a + composer with "+" / Ctrl+V / drag & drop attachments (images, PDFs, files), + "/" commands, run timer, agents, model + thinking chip, the "⚡ Auto" consent + chip and speech to text; saved chats in IndexedDB and a workspace (virtual + file system) panel. Everything is a prop: show/hide, labels (i18n), render + props, slots, swappable components, theme — see [docs/chat.md](docs/chat.md). +- 🧩 **Building blocks** — ``, ``, ``, + ``, ``, ``, ``, + ``, ``, ``; hooks `useChatHistory`, + `useVirtualFiles`, `useSpeechToText`, `useMcpServers`. - 🔑 **`useCredentials`** — store BYOK API keys **encrypted at rest** (WebCrypto + IndexedDB). - 🖥️ **`useWebLLMModel`** — load a local WebGPU model with download progress. @@ -22,9 +30,11 @@ app with a single hook and (optionally) a set of pre-styled components: [![license](https://img.shields.io/npm/l/@dudko.dev/agent-web-react.svg)](https://www.npmjs.com/package/@dudko.dev/agent-web-react) ![GitHub last commit](https://img.shields.io/github/last-commit/dudko-dev/agent-web-react.svg) -> **▶︎ [Live demo](https://dudko-dev.github.io/agent-web-react/)** — an agent that -> edits a sticky-notes board via tools. Cloud BYOK or local WebGPU, all in your -> browser. Source in [`demo/`](demo/). +> **▶︎ [Live demo](https://dudko-dev.github.io/agent-web-react/)** — three agents: +> one edits a sticky-notes board, one drives any number of MCP servers (plus a +> 185-tool mock catalogue to show tool search), and one plays chess against you +> — your move triggers it, analyst subagents run in Web Workers. Cloud BYOK or +> local WebGPU, all in your browser. Source in [`demo/`](demo/). ## Install @@ -155,20 +165,26 @@ function Custom() { | Field | Description | | --- | --- | -| `run(goal)` | Start a run; resolves with the `RunResult` | +| `run(goal, { images, files, attachments, label })` | Start a run (with attachments); resolves with the `RunResult` | | `stop()` | Abort the in-flight run | -| `reset()` | Clear the whole conversation | +| `reset()` | Clear the whole conversation (and the agent's memory of it) | | `reload()` | Rebuild the agent (e.g. after storing a new key) | | `status` | `idle` \| `initializing` \| `ready` \| `running` \| `error` | | `isReady` / `isRunning` | convenience booleans | -| `messages` | chat transcript (`{ role, content, pending }[]`) | -| `plan` / `steps` | the live plan and per-step tool calls | -| `finalText` | the streamed final answer | -| `usage` | running token total | +| `messages` | transcript; each assistant message keeps its steps, tool calls, thoughts, approvals, subagents, usage and duration | +| `plan` / `steps` | the live plan and per-step tool calls (with timing) | +| `finalText` / `finalReasoning` | the streamed answer and its thoughts | +| `usage` / `totalUsage` | tokens of this run / of the conversation, by kind | +| `pendingApprovals`, `approve(id, { remember })`, `deny(id)` | tool consent | +| `approvalMode` / `setApprovalMode(mode)` | the autopilot switch, live | +| `compact()` | summarise the stored transcript now | +| `sessionId` / `loadChat(chat)` | the memory session; switch conversations | +| `subagents`, `skills`, `discoveredTools`, `compactions`, `budget` | the rest of the run, folded | | `modelLoad` | WebLLM download progress, when loading | **Options:** `deps` (rebuild the agent when these change — e.g. on a model -switch), `onEvent` (tap the raw event stream), `autoStart`, `maxEvents`. +switch), `onEvent` (tap the raw event stream), `autoStart`, `maxEvents`, +`approvals` (route consent requests into the hook; default true). > Keep `tools` and `describeState` referentially stable (`useMemo` / > `useCallback`); pass a changed `deps` array to rebuild the agent on a @@ -292,22 +308,44 @@ Two notes: peer floor is `>=0.0.11` — that is the first core whose own peer ranges resolve against AI SDK v7. +## Several MCP servers — `useMcpServers` + +```tsx +const mcp = useMcpServers({ clientName: 'my-app' }) // the list persists; reconnects on load +await mcp.add({ name: 'docs', url, auth: 'oauth' }) // 'none' | 'bearer' (+ token) | 'oauth' +mcp.servers // [{ name, url, status, catalog, error, … }] +const config = { model, tools: { ...myTools, ...mcp.tools } } // "__" +``` + +Servers connect in parallel, each with its own deadline; paginated tool lists +are read to the end; tools a server marks read-only skip the "ask before +changes" prompt. With hundreds of tools the core switches to **tool search** +(a compact catalogue + `find_tools`) above `toolSearchThreshold` — see the +core's [capabilities doc](https://github.com/dudko-dev/agent-web/blob/main/docs/capabilities.md). + ## Components All components are optional and styled by `styles.css` (class-prefixed `awr-`, -themeable via `--awr-*` custom properties, light + dark). Each accepts a -`className`; data components take plain props so you can use them standalone. +themeable via `--awr-*` custom properties, light + dark, or `theme`). Each +accepts a `className` and `labels`; data components take plain props so you can +use them standalone. The full guide: [docs/chat.md](docs/chat.md). | Component | Purpose | | --- | --- | -| `` | Full panel: transcript + live activity + composer. Reads a `controller` prop or the `` context. | -| `` | The chat transcript. | -| `` | Textarea + send/stop button (Enter to send). | +| `` | Full panel: transcript with per-answer activity + composer + optional history and files panels. Reads a `controller` prop or the `` context. | +| `` | The input and its toolbar: attachments, commands, timer, agents, model/thinking, consent, mic, send/stop. | +| `` | The transcript; each answer with its steps, tool calls, thoughts, usage. | +| `` / `` | Execution steps / one tool call with its cost and images. | +| `` | Consent cards (Allow once / Always / Deny). | +| `` | Segmented consent-mode switch. | +| `` | Saved chats (from `useChatHistory`). | +| `` | A `VirtualFileSystem`, live: preview, download, delete, upload. | +| `` | Tokens by kind, budget bar, last compaction, Compact. | | `` | A plan's reasoning + step list. | -| `` | Live execution steps with tool calls. | | `` | WebLLM download/init progress. | | `` | Compact token readout. | | `` | BYOK key entry that writes to the encrypted vault. | +| `` | The minimal textarea + send/stop (kept for existing apps). | ### Build your own UI @@ -344,10 +382,17 @@ from one place. ## Browser tests -`npm run test:e2e` builds the demo and drives it in Chromium: connecting to a -remote MCP server, the full OAuth redirect round-trip (authorize → come back -with a code → connected), reuse of the stored tokens after a reload, and the -error path for an unreachable server. The MCP and authorization servers it +`npm run test:e2e` builds the demo and drives it in Chromium: + +- MCP: connecting to one and to several servers, the full OAuth redirect + round-trip (authorize → come back with a code → connected), reconnecting + with the stored tokens after a reload, and an unreachable server; +- the agent, against a scripted Gemini endpoint ([`e2e/gemini.ts`](e2e/gemini.ts), + the real `@ai-sdk/google` provider underneath): tool calls and tokens by + kind in the transcript, a chat saved and restored after a reload, an image + attached (shown, sent as a file part, saved in the workspace), a file + dropped anywhere on the page, consent in "Ask" mode, slash commands, the + chess move that triggers the agent, and Russian labels. The MCP and authorization servers it talks to are started on loopback by [`e2e/servers.ts`](e2e/servers.ts), CORS headers included — so the test also pins the deployment requirement that the challenge header be exposed. diff --git a/demo/README.md b/demo/README.md index fd58b40..5c7e20c 100644 --- a/demo/README.md +++ b/demo/README.md @@ -4,20 +4,41 @@ A Vite + React app showcasing [`@dudko.dev/agent-web-react`](../): an in-browser LLM agent driving tools. Pick a cloud model (bring your own key, stored encrypted) or load a local WebGPU model — everything runs in the browser. -Two panels: +Three tabs, sharing one "Agent settings" panel: - **Sticky notes** — the agent edits a board through locally defined tools. -- **Your MCP server** — paste any remote MCP endpoint and the agent picks up - *its* tools. Auth is none, a bearer token, or **OAuth 2.1 + dynamic client - registration**: the app registers itself with your authorization server, runs - PKCE, keeps the tokens encrypted in IndexedDB and refreshes them on expiry. - The server must send CORS headers for this origin (including - `Access-Control-Expose-Headers: WWW-Authenticate, mcp-session-id`) — the - browser talks to it directly, nothing is proxied. - -> The OAuth panel needs a core that exports `BrowserOAuthProvider` -> (`@dudko.dev/agent-web` ≥ 0.0.9). On an older core the panel says so and -> disables Connect for that mode; header auth is unaffected. + Attach files (the "+" button, Ctrl+V for images, or drop them anywhere on the + page): images go to vision models, PDFs to models that read them — and are + converted to Markdown in the browser with + [`@dudko.dev/pdf-to-md-core`](https://www.npmjs.com/package/@dudko.dev/pdf-to-md-core) + for the ones that don't. Attachments land in a workspace (a virtual file + system in IndexedDB) the agent reads and writes with its `fs_*` tools. +- **MCP servers** — connect **any number** of remote MCP endpoints; their tools + merge into one agent (`__`). Auth per server is none, a bearer + token, or **OAuth 2.1 + dynamic client registration**: the app registers + itself, runs PKCE, keeps the tokens encrypted in IndexedDB and refreshes them. + The list is remembered and reconnects on load. Tick the **mock catalogue** to + add 185 tools on six pretend servers and watch the agent switch to **tool + search** (a compact catalogue + `find_tools`) instead of sending every schema. + Each server must send CORS headers for this origin (including + `Access-Control-Expose-Headers: WWW-Authenticate, mcp-session-id`). +- **Chess vs agent** — you play White; **your move is the trigger**: the agent + reads the position with `get_position`, weighs candidates (`evaluate_moves`, + or analyst **subagents** — each in its own **Web Worker**, or in-process), + and plays with `make_move`. No chat message needed. + +The **Agent settings** panel drives the core's features live: tool consent +(⚡ autopilot / ask before changes / ask everything / read-only), thinking +level, token / tool-call / step limits, context window and auto-compaction, +skills (built-in examples, your own, or imported `SKILL.md`), the chess +analysts — and the **chat UI**: tool calls, token usage, saved chats +(IndexedDB), the files panel, theme, and the labels (English / Russian — every +string of the chat is a prop). + +The chat itself shows every tool call with its cost, tokens by kind per answer +and in total, thoughts, subagents and consent prompts; the composer has +attachments, "/" commands, a run timer, the running-agents count, the model + +thinking chip, the consent chip and a speech-to-text mic. **Live:** https://dudko-dev.github.io/agent-web-react/ diff --git a/demo/package-lock.json b/demo/package-lock.json index d080482..82601aa 100644 --- a/demo/package-lock.json +++ b/demo/package-lock.json @@ -8,14 +8,16 @@ "name": "agent-web-react-demo", "version": "0.0.0", "dependencies": { - "@ai-sdk/anthropic": "^4.0.39", - "@ai-sdk/google": "^4.0.44", - "@ai-sdk/openai": "^4.0.42", - "@browser-ai/web-llm": "^3.0.2", + "@ai-sdk/anthropic": "^4.0.71", + "@ai-sdk/google": "^4.0.87", + "@ai-sdk/openai": "^4.0.83", + "@browser-ai/web-llm": "^3.0.4", "@dudko.dev/agent-web": "^0.0.18", - "@mlc-ai/web-llm": "^0.2.84", - "@modelcontextprotocol/sdk": "^1.30.1", - "ai": "^7.0.107", + "@dudko.dev/pdf-to-md-core": "^0.3.5", + "@mlc-ai/web-llm": "^0.2.85", + "@modelcontextprotocol/sdk": "^1.32.0", + "ai": "^7.0.127", + "chess.js": "^1.4.0", "react": "^19.3.0", "react-dom": "^19.3.0", "zod": "^4.6.5" @@ -23,19 +25,19 @@ "devDependencies": { "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", - "@vitejs/plugin-react": "^4.3.0", - "typescript": "^5.6.0", - "vite": "^6.0.0" + "@vitejs/plugin-react": "^6.1.1", + "typescript": "^6.0.3", + "vite": "^8.3.2" } }, "node_modules/@ai-sdk/anthropic": { - "version": "4.0.39", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-4.0.39.tgz", - "integrity": "sha512-JAMGtYeEuaBzqbsPO4fkho6vQyNoVhsHASM4o59wmJRU6Vh7prjOp490Kmc7YQTY+ioU1/xYzXvWOtxZBup0Xw==", + "version": "4.0.71", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-4.0.71.tgz", + "integrity": "sha512-wiv3jhUH0RrvGzSolJMZfZ2cI9As45LhwYmcAoWvNivXjC7wzJ4W/Udq9/u7EGnGsT8lpmFKEKBrMXVeUO97Dg==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.7", - "@ai-sdk/provider-utils": "5.0.27" + "@ai-sdk/provider": "4.0.21", + "@ai-sdk/provider-utils": "5.0.53" }, "engines": { "node": ">=22" @@ -45,13 +47,13 @@ } }, "node_modules/@ai-sdk/gateway": { - "version": "4.0.87", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.87.tgz", - "integrity": "sha512-6wJmX/D5OO7tvDJbtFdXdBuRt8AkCnW+FLK+W35UHe/tHgolAa4hclXtJcMSYnlTDy/t5DNRgdesVaoMXtwzow==", + "version": "4.0.103", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.103.tgz", + "integrity": "sha512-nnGTUHzPRQ14jhv9CL4OQrzcRBIFTBMIPKGIi9Z9u/4GtwSRxa/nxUX6np2Iwp5phOzO0a0njj/j9Jolf5AEaA==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.17", - "@ai-sdk/provider-utils": "5.0.45", + "@ai-sdk/provider": "4.0.21", + "@ai-sdk/provider-utils": "5.0.53", "@vercel/oidc": "3.2.0" }, "engines": { @@ -61,45 +63,14 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.17.tgz", - "integrity": "sha512-VYMBxIQdcHqbIf1j+YZlI9Ati6LZ4wJe0GGd4z4a5H/KxTggjeOiyaVYTnfF7LHZK5jMQ+rofmzz4QPqf++NUw==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": { - "version": "5.0.45", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.45.tgz", - "integrity": "sha512-gLuaCups8OCIRz3eO6WWz5op7VLYNNtFzyH0sWUr0FE9v7cTxR0qxDpQdrKnGcql+9WWttobjnZsJVsuEbsPQQ==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "4.0.17", - "@standard-schema/spec": "^1.1.0", - "@workflow/serde": "4.1.0", - "eventsource-parser": "^3.0.8", - "undici": "^7.29.0" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, "node_modules/@ai-sdk/google": { - "version": "4.0.44", - "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-4.0.44.tgz", - "integrity": "sha512-bmRTDg06jQD+eX8nf214pET9+Oe8O1+lUIRGbWsGXj9IN2UJkpl1O1x7cvtiboyTtKSLvSRdVtItUfSl8sQ2GA==", + "version": "4.0.87", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-4.0.87.tgz", + "integrity": "sha512-s0tWc+QeSce7O2ZLm+VWSRQy16dGfqEcMIUxEWh+LFvXX3h0TLH8jxCQ2H1qhiBgTta+6A97eHPhBlRFu1xy+g==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.7", - "@ai-sdk/provider-utils": "5.0.27" + "@ai-sdk/provider": "4.0.21", + "@ai-sdk/provider-utils": "5.0.53" }, "engines": { "node": ">=22" @@ -109,13 +80,13 @@ } }, "node_modules/@ai-sdk/openai": { - "version": "4.0.42", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-4.0.42.tgz", - "integrity": "sha512-ZxDca6jJalYuXrIGVrw6dnkpz1Io9AWy+/b/wVWIbjigHCbd+zWLpPi8NnK0OFU+U3YCpP+KWfUvEnG5pFhltA==", + "version": "4.0.83", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-4.0.83.tgz", + "integrity": "sha512-NgqZVWoya7hfmtLkWyIjX9THbyYMLfxFqpShdAmvMnkoT+R9AZbdULGan8+4BiBWIZ1aHiAsc90l3SL8QU/0+Q==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.7", - "@ai-sdk/provider-utils": "5.0.27" + "@ai-sdk/provider": "4.0.21", + "@ai-sdk/provider-utils": "5.0.53" }, "engines": { "node": ">=22" @@ -125,9 +96,9 @@ } }, "node_modules/@ai-sdk/provider": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.7.tgz", - "integrity": "sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q==", + "version": "4.0.21", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.21.tgz", + "integrity": "sha512-UpbC9C1oht8dhfKPbVXSLRZS3DI8uk8n5v2uMjBPNIYGsC2kL045ywq7D8Hh9KgnH9rP5W/GxQdYtWScRouqlA==", "license": "Apache-2.0", "dependencies": { "json-schema": "^0.4.0" @@ -137,16 +108,16 @@ } }, "node_modules/@ai-sdk/provider-utils": { - "version": "5.0.27", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.27.tgz", - "integrity": "sha512-EzAn4pdgG5g0xXtH6lE2zyNmfjDQIDjATkfqzuidEI35g++hh4+07vnjzkT/RmGmIClPZiRj/Q2GMPV2V7mkHw==", + "version": "5.0.53", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.53.tgz", + "integrity": "sha512-VVe6UDd0y0/B4TfauuKKzcppqvhTmSluwwUVT4WG2/8h7gFbyFm7PM4UGM+ZXe3/RI6bv+Vl1cw/7aiBoQqdxg==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.7", + "@ai-sdk/provider": "4.0.21", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", - "undici": "^7.28.0" + "undici": "^7.29.0" }, "engines": { "node": ">=22" @@ -155,292 +126,10 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", - "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", - "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@browser-ai/web-llm": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@browser-ai/web-llm/-/web-llm-3.0.2.tgz", - "integrity": "sha512-pCjC9OGi7u1fOU0rof7TWVvjhMjR0oH1LAQkhB74eASWOFFkL2/iJ0HV3v3Q5OJ7qEyBgx/StmJQ+vLTXS2bSA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@browser-ai/web-llm/-/web-llm-3.0.4.tgz", + "integrity": "sha512-716/SO/NqrbYjy7IZiBBNGfYB0hu5ObUSdtMc/8BsiiCz6FTE45fwva8WhCJl3QNh1M2q2qBw1T+u8t8abBq2A==", "license": "Apache-2.0", "peerDependencies": { "@mlc-ai/web-llm": "^0.2.79", @@ -515,456 +204,20 @@ "@browser-ai/web-llm": { "optional": true }, - "@mlc-ai/web-llm": { - "optional": true - }, - "@modelcontextprotocol/sdk": { - "optional": true - } - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" + "@mlc-ai/web-llm": { + "optional": true + }, + "@modelcontextprotocol/sdk": { + "optional": true + } } }, + "node_modules/@dudko.dev/pdf-to-md-core": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@dudko.dev/pdf-to-md-core/-/pdf-to-md-core-0.3.5.tgz", + "integrity": "sha512-m5NYUU+jznFOuMx8BUkqUWwlg7IV7VY3RP/BLp/xJpGVgL+V8khTc0ndES6PoHVpI3leskvUNC8FqiA8Lv/YLA==", + "license": "PolyForm-Noncommercial-1.0.0" + }, "node_modules/@hono/node-server": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", @@ -977,69 +230,19 @@ "hono": "^4" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@mlc-ai/web-llm": { - "version": "0.2.84", - "resolved": "https://registry.npmjs.org/@mlc-ai/web-llm/-/web-llm-0.2.84.tgz", - "integrity": "sha512-hrOWzK4/nGNmgoRKT8pgVmZZ2oEPpbblIWQOwpqNyvK2dysHw3KVB1gNJOuRcQfKOPhucEhX1NJzXzgMDnwSCQ==", + "version": "0.2.85", + "resolved": "https://registry.npmjs.org/@mlc-ai/web-llm/-/web-llm-0.2.85.tgz", + "integrity": "sha512-CwUiSAvDCYnmAZlpPMkurPHDfBLq6fvdRB3vRgoA894hRG/V/BSeY9bmMm6iIIU5VpjJxfb/dLPpJX2at0L9rw==", "license": "Apache-2.0", "dependencies": { "loglevel": "^1.9.1" } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.1.tgz", - "integrity": "sha512-H2HxLvC3HDNybePJaLdSrU1hhUK5iQw+WvV1b01myFyI7sdVGe1u/IPTE5D9fGCiJDVtgMV/lmFkQXLmQyIFYA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.0.tgz", + "integrity": "sha512-8BviX/hK4Gd2eL1KdTwm0gfl4d3wdoErW0Jd00h6nomUAtk6IJk5vwQJc6kfWtJTzN5OEV/lgIiJKI8fiDGAlA==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", @@ -1076,17 +279,20 @@ } } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "node_modules/@oxc-project/types": { + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", + "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", - "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", + "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", "cpu": [ "arm" ], @@ -1095,12 +301,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", - "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", + "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", "cpu": [ "arm64" ], @@ -1109,12 +318,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", - "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", + "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", "cpu": [ "arm64" ], @@ -1123,12 +335,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", - "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", + "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", "cpu": [ "x64" ], @@ -1137,26 +352,15 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", - "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", - "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", + "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", "cpu": [ "x64" ], @@ -1165,26 +369,15 @@ "optional": true, "os": [ "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", - "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", - "cpu": [ - "arm" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", - "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", + "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", "cpu": [ "arm" ], @@ -1193,26 +386,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", - "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", - "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", + "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", "cpu": [ "arm64" ], @@ -1221,54 +403,32 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", - "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", - "cpu": [ - "loong64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", - "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", + "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", "cpu": [ - "loong64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", - "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", - "cpu": [ - "ppc64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", - "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", + "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", "cpu": [ "ppc64" ], @@ -1277,40 +437,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", - "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", - "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", - "cpu": [ - "riscv64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", - "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", + "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", "cpu": [ "s390x" ], @@ -1319,12 +454,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", - "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", + "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", "cpu": [ "x64" ], @@ -1333,12 +471,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", - "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", + "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", "cpu": [ "x64" ], @@ -1347,26 +488,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", - "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", - "cpu": [ - "x64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", - "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", + "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", "cpu": [ "arm64" ], @@ -1375,12 +505,15 @@ "optional": true, "os": [ "openharmony" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", - "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", + "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", "cpu": [ "arm64" ], @@ -1389,26 +522,15 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", - "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", - "cpu": [ - "ia32" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", - "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", + "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", "cpu": [ "x64" ], @@ -1417,21 +539,17 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", - "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", - "cpu": [ - "x64" ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", @@ -1439,58 +557,6 @@ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/react": { "version": "19.3.0", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", @@ -1521,24 +587,33 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } } }, "node_modules/@workflow/serde": { @@ -1561,45 +636,14 @@ } }, "node_modules/ai": { - "version": "7.0.107", - "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.107.tgz", - "integrity": "sha512-PVYQ3W9kR8oYiGQxbg4aFIlhQzKskZm8UV+4E0zB2uKOzFr8ju3bXQncOOZtLsOgJY8vdpgyu3cXtomsfcdQYg==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/gateway": "4.0.87", - "@ai-sdk/provider": "4.0.17", - "@ai-sdk/provider-utils": "5.0.45" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/ai/node_modules/@ai-sdk/provider": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-4.0.17.tgz", - "integrity": "sha512-VYMBxIQdcHqbIf1j+YZlI9Ati6LZ4wJe0GGd4z4a5H/KxTggjeOiyaVYTnfF7LHZK5jMQ+rofmzz4QPqf++NUw==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/ai/node_modules/@ai-sdk/provider-utils": { - "version": "5.0.45", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-5.0.45.tgz", - "integrity": "sha512-gLuaCups8OCIRz3eO6WWz5op7VLYNNtFzyH0sWUr0FE9v7cTxR0qxDpQdrKnGcql+9WWttobjnZsJVsuEbsPQQ==", + "version": "7.0.127", + "resolved": "https://registry.npmjs.org/ai/-/ai-7.0.127.tgz", + "integrity": "sha512-JNsNPk4ZvRGEJuqdVo2lYFkm7uAuf3p/RwKFWn1Wh/+IK3U1RyFWw2GXoFnhzqNfxEuj7XJB4WpIUhl1sHToWg==", "license": "Apache-2.0", "dependencies": { - "@ai-sdk/provider": "4.0.17", - "@standard-schema/spec": "^1.1.0", - "@workflow/serde": "4.1.0", - "eventsource-parser": "^3.0.8", - "undici": "^7.29.0" + "@ai-sdk/gateway": "4.0.103", + "@ai-sdk/provider": "4.0.21", + "@ai-sdk/provider-utils": "5.0.53" }, "engines": { "node": ">=22" @@ -1641,19 +685,6 @@ } } }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.41", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.41.tgz", - "integrity": "sha512-WwS7MHhqGHHlaVsqRZnhvCEMS0owDX+SxRlve7JkuH7My1Ara3ZriTmCQupPfYjxMZ8I/tgxtJYr2t7taHaH4A==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/body-parser": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", @@ -1691,40 +722,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1763,26 +760,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001800", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", - "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" + "node_modules/chess.js": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/chess.js/-/chess.js-1.4.0.tgz", + "integrity": "sha512-BBJgrrtKQOzFLonR0l+k64A98NLemPwNsCskwb+29bRwobUa4iTm51E1kwGPbWXAcfdDa18nad6vpPPKPWarqw==", + "license": "BSD-2-Clause" }, "node_modules/content-disposition": { "version": "1.1.0", @@ -1806,13 +788,6 @@ "node": ">= 0.6" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -1892,7 +867,17 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" } }, "node_modules/dunder-proto": { @@ -1915,13 +900,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/electron-to-chromium": { - "version": "1.5.384", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.384.tgz", - "integrity": "sha512-g6KAKY1vkYsADvSPWvdJsuYT0ixdcu6lUtD9P/wJKGBEDlZVXh2AX42j1mPqqaQPDluWjara9ziQ7xqAeXCt5A==", - "dev": true, - "license": "ISC" - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1961,58 +939,6 @@ "node": ">= 0.4" } }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2214,16 +1140,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2393,26 +1309,6 @@ "url": "https://github.com/sponsors/panva" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -2431,17 +1327,265 @@ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "license": "BSD-2-Clause" }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/loglevel": { @@ -2457,16 +1601,6 @@ "url": "https://tidelift.com/funding/github/npm/loglevel" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2533,9 +1667,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "dev": true, "funding": [ { @@ -2560,16 +1694,6 @@ "node": ">= 0.6" } }, - "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2648,9 +1772,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -2670,9 +1794,9 @@ } }, "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dev": true, "funding": [ { @@ -2690,7 +1814,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -2776,16 +1900,6 @@ "react": "^19.3.0" } }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -2795,49 +1909,38 @@ "node": ">=0.10.0" } }, - "node_modules/rollup": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", - "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "node_modules/rolldown": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.9" + "@oxc-project/types": "=0.152.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.62.2", - "@rollup/rollup-android-arm64": "4.62.2", - "@rollup/rollup-darwin-arm64": "4.62.2", - "@rollup/rollup-darwin-x64": "4.62.2", - "@rollup/rollup-freebsd-arm64": "4.62.2", - "@rollup/rollup-freebsd-x64": "4.62.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", - "@rollup/rollup-linux-arm-musleabihf": "4.62.2", - "@rollup/rollup-linux-arm64-gnu": "4.62.2", - "@rollup/rollup-linux-arm64-musl": "4.62.2", - "@rollup/rollup-linux-loong64-gnu": "4.62.2", - "@rollup/rollup-linux-loong64-musl": "4.62.2", - "@rollup/rollup-linux-ppc64-gnu": "4.62.2", - "@rollup/rollup-linux-ppc64-musl": "4.62.2", - "@rollup/rollup-linux-riscv64-gnu": "4.62.2", - "@rollup/rollup-linux-riscv64-musl": "4.62.2", - "@rollup/rollup-linux-s390x-gnu": "4.62.2", - "@rollup/rollup-linux-x64-gnu": "4.62.2", - "@rollup/rollup-linux-x64-musl": "4.62.2", - "@rollup/rollup-openbsd-x64": "4.62.2", - "@rollup/rollup-openharmony-arm64": "4.62.2", - "@rollup/rollup-win32-arm64-msvc": "4.62.2", - "@rollup/rollup-win32-ia32-msvc": "4.62.2", - "@rollup/rollup-win32-x64-gnu": "4.62.2", - "@rollup/rollup-win32-x64-msvc": "4.62.2", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm-eabi": "1.2.12", + "@rolldown/binding-android-arm64": "1.2.12", + "@rolldown/binding-darwin-arm64": "1.2.12", + "@rolldown/binding-darwin-x64": "1.2.12", + "@rolldown/binding-freebsd-x64": "1.2.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", + "@rolldown/binding-linux-arm64-gnu": "1.2.12", + "@rolldown/binding-linux-arm64-musl": "1.2.12", + "@rolldown/binding-linux-ppc64-gnu": "1.2.12", + "@rolldown/binding-linux-s390x-gnu": "1.2.12", + "@rolldown/binding-linux-x64-gnu": "1.2.12", + "@rolldown/binding-linux-x64-musl": "1.2.12", + "@rolldown/binding-openharmony-arm64": "1.2.12", + "@rolldown/binding-win32-arm64-msvc": "1.2.12", + "@rolldown/binding-win32-x64-msvc": "1.2.12" } }, "node_modules/router": { @@ -2868,16 +1971,6 @@ "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", "license": "MIT" }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -3023,9 +2116,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3099,9 +2192,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3113,9 +2206,9 @@ } }, "node_modules/undici": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", - "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", + "integrity": "sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==", "license": "MIT", "engines": { "node": ">=20.18.1" @@ -3130,37 +2223,6 @@ "node": ">= 0.8" } }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3171,24 +2233,23 @@ } }, "node_modules/vite": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", - "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", + "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.11", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -3197,14 +2258,15 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -3213,13 +2275,16 @@ "@types/node": { "optional": true }, - "jiti": { + "@vitejs/devtools": { "optional": true }, - "less": { + "esbuild": { "optional": true }, - "lightningcss": { + "jiti": { + "optional": true + }, + "less": { "optional": true }, "sass": { @@ -3266,13 +2331,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/zod": { "version": "4.6.5", "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", diff --git a/demo/package.json b/demo/package.json index d040e78..9b0d5b8 100644 --- a/demo/package.json +++ b/demo/package.json @@ -10,14 +10,16 @@ "preview": "vite preview" }, "dependencies": { - "@ai-sdk/anthropic": "^4.0.39", - "@ai-sdk/google": "^4.0.44", - "@ai-sdk/openai": "^4.0.42", - "@browser-ai/web-llm": "^3.0.2", + "@ai-sdk/anthropic": "^4.0.71", + "@ai-sdk/google": "^4.0.87", + "@ai-sdk/openai": "^4.0.83", + "@browser-ai/web-llm": "^3.0.4", "@dudko.dev/agent-web": "^0.0.18", - "@mlc-ai/web-llm": "^0.2.84", - "@modelcontextprotocol/sdk": "^1.30.1", - "ai": "^7.0.107", + "@dudko.dev/pdf-to-md-core": "^0.3.5", + "@mlc-ai/web-llm": "^0.2.85", + "@modelcontextprotocol/sdk": "^1.32.0", + "ai": "^7.0.127", + "chess.js": "^1.4.0", "react": "^19.3.0", "react-dom": "^19.3.0", "zod": "^4.6.5" @@ -25,8 +27,8 @@ "devDependencies": { "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", - "@vitejs/plugin-react": "^4.3.0", - "typescript": "^5.6.0", - "vite": "^6.0.0" + "@vitejs/plugin-react": "^6.1.1", + "typescript": "^6.0.3", + "vite": "^8.3.2" } } diff --git a/demo/src/App.tsx b/demo/src/App.tsx index 39952db..1f0e9c5 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -1,41 +1,76 @@ -import { useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' +import type { Square } from 'chess.js' import type { BrowserAgentConfig, ModelInput } from '@dudko.dev/agent-web' import { AgentChat, + ChatHistoryStore, + IndexedDBStore, + MemoryStore, + createFileTools, + createSubagentTool, useAgent, useCredentials, - useMcp, + useMcpServers, useWebLLMModel, + type AgentChatProps, + type AgentToolSet, } from '@dudko.dev/agent-web-react' import type { LanguageModel } from 'ai' +import { ANALYST_PROMPT, engineTools } from './chess/analyst-tools' +import { ChessPanel } from './chess/ChessPanel' +import { useChessGame } from './chess/game' +import { AgentSettingsPanel } from './components/AgentSettingsPanel' import { McpPanel } from './components/McpPanel' import { NotesBoard } from './components/NotesBoard' import { Settings } from './components/Settings' +import { buildMockCatalog } from './mockCatalog' import { isLocal, MODELS } from './models' import { useNotesBoard } from './notes' import { buildCloudModel, createLocalModel } from './providers' +import { RU_LABELS } from './i18n' +import { + useDemoSettings, + useSettingsConfig, + type AnalystMode, + type ThinkingChoice, +} from './settings' +import { pdfToMarkdown, workspace } from './workspace' -const SYSTEM_PROMPT = `You manage a sticky-notes board through the provided tools. +const NOTES_PROMPT = `You manage a sticky-notes board through the provided tools. Add, update, remove and list notes to satisfy the user's request. Keep each note short (a few words). When asked for a list or a plan, create one note per item. -Use colors meaningfully — e.g. red for urgent, green for done.` +Use colors meaningfully — e.g. red for urgent, green for done. +Files the user attaches are in the workspace under /attachments — read them with fs_read when asked about them.` + +const MCP_PROMPT = `You drive the remote MCP servers the user connected (tools are prefixed "__"). +You know nothing about their tools beyond names and descriptions — read them, pick the ones that fit, and call them. +Never invent a tool or a parameter. Answer questions with the data the tools return. +The user's workspace is reachable with the fs_* tools (attachments are under /attachments; save longer reports there as Markdown).` -const MCP_SYSTEM_PROMPT = `You drive a remote MCP server the user connected themselves. -You have no prior knowledge of what its tools do beyond their names and descriptions — -read them, pick the ones that fit, and call them. Never invent a tool or a parameter. -If the tools cannot satisfy the request, say so plainly instead of guessing.` +const chessPrompt = ( + analysts: AnalystMode, +) => `You play chess as Black against the user (White) on the board shown in this app. Each run starts right after the user's move. +On your turn: 1) call get_position; 2) pick 2-4 candidate moves and ${ + analysts === 'off' + ? 'check them with evaluate_moves' + : 'ask consult_analyst about EACH candidate — one call per candidate, all in the same step so they run in parallel — and weigh their verdicts' +}; 3) play the best one with make_move — exactly one move per turn. +Then reply with one or two friendly sentences: your idea and any threat. Never ask the user anything. +If make_move fails, read its error (it lists the legal moves) and try again. If the game is over, say so and do not move.` -type View = 'notes' | 'mcp' +type View = 'notes' | 'mcp' | 'chess' +const VIEWS: View[] = ['notes', 'mcp', 'chess'] /** * Which panel to open on load. An OAuth round-trip comes back to the bare page * URL (a redirect_uri may not carry a fragment), so `?code=…` is what tells us - * the visitor was in the middle of connecting their MCP server. + * the visitor was in the middle of connecting an MCP server. */ const initialView = (): View => { const params = new URLSearchParams(window.location.search) if (params.has('code') || params.has('error')) return 'mcp' - return window.location.hash.replace(/^#\/?/, '') === 'mcp' ? 'mcp' : 'notes' + const hash = window.location.hash.replace(/^#\/?/, '') as View + return VIEWS.includes(hash) ? hash : 'notes' } export const App = () => { @@ -44,15 +79,12 @@ export const App = () => { const local = isLocal(model) const credentials = useCredentials() - const board = useNotesBoard() // Inject a statically-imported WebLLM factory so the weights actually bundle // (the core's dynamic import gets stubbed to an empty module by Vite). const webllm = useWebLLMModel(model.model, { create: createLocalModel }) - // Cloud models are built in-app from the vault-stored key. We pass the agent - // a direct LanguageModel rather than a ProviderModelSpec, because the core's - // dynamic `import('@ai-sdk/…')` can't be resolved from a browser bundle (see - // providers.ts). Rebuilds whenever the key or selected model changes. + // Cloud models are built in-app from the vault-stored key and passed to the + // agent directly (see providers.ts). Rebuilds when the key or model changes. const [cloudModel, setCloudModel] = useState(undefined) useEffect(() => { if (local) { @@ -72,70 +104,204 @@ export const App = () => { active = false } }, [local, model, credentials.store, credentials.version]) - - // Local → the loaded WebGPU model; cloud → the built provider model. Either is - // undefined until ready, which keeps the agent idle (useAgent won't build - // without a model) rather than constructing a broken one. const resolvedModel = local ? webllm.model : cloudModel - const config = useMemo( + // ── Agent settings shared by every tab ───────────────────────────────────── + const { settings, update, reset: resetSettings } = useDemoSettings() + const { config: settingsConfig, rebuildKey } = useSettingsConfig(settings) + // The model's memory per conversation. With saved chats it lives in + // IndexedDB too (keyed by the chat id), so a reopened chat continues with its + // context; the chess game is not saved, so its memory isn't either. + const memories = useMemo(() => { + const persistent = settings.history ? new IndexedDBStore() : undefined + return { + notes: persistent ?? new MemoryStore(), + mcp: persistent ?? new MemoryStore(), + chess: new MemoryStore(), + } + }, [settings.history]) + // Saved chats (transcripts with tool calls, usage and images), one DB per tab. + const histories = useMemo( () => ({ - model: resolvedModel as ModelInput, - credentials: credentials.store, - tools: board.tools, - describeState: board.describeState, - systemPrompt: SYSTEM_PROMPT, - maxIterations: 6, - // Stream the agent's internal phases (planning, tool dispatch, raw model - // output) to the browser console — handy for poking at the demo. - logLevel: 'debug', + notes: new ChatHistoryStore({ dbName: 'agent-web-demo-chats-notes' }), + mcp: new ChatHistoryStore({ dbName: 'agent-web-demo-chats-mcp' }), }), - [resolvedModel, credentials.store, board.tools, board.describeState], + [], ) - - const agent = useAgent(config, { - // Rebuild when the resolved model changes identity: model loaded, key - // added/replaced, or provider switched. - deps: [modelId, resolvedModel], + // The agents can read the attachments and write files into the workspace. + const fileTools = useMemo(() => createFileTools(workspace), []) + const base = (tab: View): Partial => ({ + model: resolvedModel as ModelInput, + credentials: credentials.store, + memory: memories[tab], + sessionId: tab, + ...settingsConfig, + // The consent mode is applied live by useAgent — no rebuild on a switch. + toolApproval: { mode: settings.approvalMode }, + // Stream the agent's internal phases to the console — handy for poking. + logLevel: 'debug', }) + const deps = [modelId, resolvedModel, rebuildKey, memories] + + // ── Tab 1: sticky notes ───────────────────────────────────────────────────── + const board = useNotesBoard() + const notesAgent = useAgent( + { + ...base('notes'), + model: resolvedModel as ModelInput, + tools: { ...board.tools, ...fileTools }, + describeState: board.describeState, + systemPrompt: NOTES_PROMPT, + }, + { deps }, + ) - // ── Panel 2: the same agent, but driving whatever MCP server the visitor - // connects. Its own agent instance so the two chats keep separate histories. + // ── Tab 2: any number of MCP servers (+ an optional mock catalogue) ──────── const [view, setView] = useState(initialView) - const mcp = useMcp({ clientName: 'agent-web-demo' }) + const mcp = useMcpServers({ clientName: 'agent-web-demo' }) + const [mockCatalog, setMockCatalog] = useState(false) + const mcpTools = useMemo( + () => ({ ...mcp.tools, ...fileTools, ...(mockCatalog ? buildMockCatalog() : {}) }), + [mcp.tools, mockCatalog, fileTools], + ) + const mcpAgent = useAgent( + { + ...base('mcp'), + model: resolvedModel as ModelInput, + tools: mcpTools, + systemPrompt: MCP_PROMPT, + }, + { deps: [...deps, mcpTools] }, + ) + + // ── Tab 3: chess against the agent, with analyst subagents ───────────────── + const game = useChessGame() + const workerAnalystsBlocked = settings.analysts === 'worker' && local + const analysts = useMemo(() => { + if (settings.analysts === 'off' || !resolvedModel) return {} as AgentToolSet + const description = + 'Ask a chess analyst sub-agent to assess ONE candidate move in the current position (it runs an engine search). Call it once per candidate, several in the same step to run them in parallel.' + const hostTools = { get_position: game.tools.get_position } + const inWorker = settings.analysts === 'worker' && !local + return { + consult_analyst: inWorker + ? createSubagentTool({ + name: 'analyst', + description, + // Each delegation gets a fresh worker: a 3-ply search per candidate, + // in parallel, without ever blocking the page. + worker: () => + new Worker(new URL('./chess/analyst.worker.ts', import.meta.url), { type: 'module' }), + workerConfig: { + model: { + providerType: model.providerType, + model: model.model, + credentialRef: model.credentialRef, + }, + systemPrompt: ANALYST_PROMPT, + maxIterations: 3, + maxStepsPerTask: 4, + replan: false, + }, + credentials: credentials.store, + tools: hostTools, + maxConcurrent: 4, + timeoutMs: 120_000, + readOnly: true, + }) + : createSubagentTool({ + name: 'analyst', + description, + config: { + model: resolvedModel, + tools: engineTools(2), + systemPrompt: ANALYST_PROMPT, + maxIterations: 3, + maxStepsPerTask: 4, + replan: false, + }, + tools: hostTools, + maxConcurrent: 2, + readOnly: true, + }), + } + }, [settings.analysts, resolvedModel, local, model, credentials.store, game.tools]) + const chessAgent = useAgent( + { + ...base('chess'), + model: resolvedModel as ModelInput, + tools: { ...game.tools, ...analysts }, + describeState: game.describeState, + systemPrompt: chessPrompt(settings.analysts), + }, + { deps: [...deps, analysts] }, + ) + const chessRun = chessAgent.run + const onUserMove = useCallback( + (from: Square, to: Square) => { + const san = game.userMove(from, to) + if (!san) return + // The move IS the trigger: no chat message, the agent just answers it. + void chessRun(`White played ${san}. Your move.`) + }, + [game, chessRun], + ) + const onNewGame = () => { + game.newGame() + chessAgent.reset() + } + + // ── Routing & one-model-at-a-time ────────────────────────────────────────── useEffect(() => { - const next = view === 'mcp' ? '#/mcp' : '' + const next = view === 'notes' ? '' : `#/${view}` if (window.location.hash !== next) { - // Keep the query string: useMcp reads (and clears) the OAuth callback - // params from it, and this effect runs while that is still in flight. + // Keep the query string: useMcpServers reads (and clears) the OAuth + // callback params from it, and this effect runs while that is in flight. const { pathname, search } = window.location window.history.replaceState(null, '', `${pathname}${search}${next}`) } }, [view]) - const mcpConfig = useMemo( - () => ({ - model: resolvedModel as ModelInput, - credentials: credentials.store, - tools: mcp.tools ?? {}, - systemPrompt: MCP_SYSTEM_PROMPT, - maxIterations: 6, - logLevel: 'debug', - }), - [resolvedModel, credentials.store, mcp.tools], - ) - - const mcpAgent = useAgent(mcpConfig, { deps: [modelId, resolvedModel, mcp.tools] }) - - // One model, two agents: stop whichever panel the user just left, so a local - // WebGPU engine never has two generations running against it at once. - const stopNotes = agent.stop - const stopMcp = mcpAgent.stop + // One model, three agents: stop the panels the user left, so a local WebGPU + // engine never has two generations running against it at once. + const stops = { notes: notesAgent.stop, mcp: mcpAgent.stop, chess: chessAgent.stop } useEffect(() => { - if (view === 'mcp') stopNotes() - else stopMcp() - }, [view, stopNotes, stopMcp]) + for (const v of VIEWS) if (v !== view) stops[v]() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [view]) + + // Everything below is a prop of — the "Chat UI" settings flip them. + const ru = settings.lang === 'ru' + const chatCommon: Partial = { + showContext: settings.maxTotalTokens || true, + showThoughts: settings.showThoughts, + showToolCalls: settings.showToolCalls, + showUsage: settings.showUsage, + showTotalUsage: settings.showUsage, + theme: settings.theme, + labels: ru ? RU_LABELS : undefined, + files: settings.filesPanel ? workspace : undefined, + } + const composer = (placeholder: string): AgentChatProps['composer'] => ({ + placeholder: ru ? undefined : placeholder, + // The model and thinking chips switch the same state as the settings panel. + model: { + label: model.label.split(' — ')[0], + options: MODELS.map((m) => ({ id: m.id, label: m.label, group: m.group })), + value: modelId, + onSelect: setModelId, + }, + thinking: { + value: settings.thinking, + onChange: (thinking) => update({ thinking: thinking as ThinkingChoice }), + }, + // PDFs the model can't read become Markdown in the browser. + convertFile: pdfToMarkdown, + // Drop files anywhere on the page. + dropZone: 'window', + }) + const mcpToolCount = Object.keys(mcp.tools).length + (mockCatalog ? 1 : 0) return (
@@ -145,7 +311,8 @@ export const App = () => {

agent-web-react

- An in-browser LLM agent editing a board via tools — plan, execute, replan. + An in-browser LLM agent that plans, calls tools, thinks, asks for consent, delegates + to sub-agents in Web Workers — and plays chess.

@@ -164,22 +331,23 @@ export const App = () => {
@@ -191,38 +359,85 @@ export const App = () => { credentials={credentials} webllm={webllm} onKeyChange={() => { - agent.reload() + notesAgent.reload() mcpAgent.reload() + chessAgent.reload() }} /> +
- {view === 'notes' ? ( + {view === 'notes' && ( - ) : mcp.status === 'connected' ? ( + )} + {view === 'mcp' && + (mcpToolCount > 0 ? ( + + ) : ( +
+ Connect one or more servers on the right (or add the mock catalogue), and their + tools become this agent’s toolbox. +
+ ))} + {view === 'chess' && ( - ) : ( -
- Connect a server on the right, and its tools become this agent’s toolbox. -
)}
- {view === 'notes' ? ( - - ) : ( - + {view === 'notes' && } + {view === 'mcp' && ( + 40 ? 'search' : 'all')) === 'search' + } + /> + )} + {view === 'chess' && ( + update({ analysts })} + onUserMove={onUserMove} + onAskAgent={() => void chessRun("It's your move.")} + onEngineMove={() => void game.engineMove()} + onNewGame={onNewGame} + /> )}
diff --git a/demo/src/app.css b/demo/src/app.css index 794c821..f1d3b27 100644 --- a/demo/src/app.css +++ b/demo/src/app.css @@ -29,7 +29,14 @@ body, body { background: var(--bg); color: var(--fg); - font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; + font-family: + system-ui, + -apple-system, + Segoe UI, + Roboto, + Helvetica, + Arial, + sans-serif; font-size: 15px; } a { @@ -440,3 +447,344 @@ a { .mcp__hint code { font-size: 11px; } + +/* ── Several MCP servers ──────────────────────────────────────────────────── */ +.mcp__summary { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; +} +.mcp__badge { + font-size: 12px; + padding: 1px 8px; + border-radius: 999px; + background: color-mix(in srgb, var(--accent) 15%, transparent); + color: var(--accent); +} +.mcp__badge--muted { + background: var(--bg); + color: var(--muted); +} +.mcp__servers { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 8px; +} +.mcp__server { + border: 1px solid var(--border); + border-radius: 10px; + padding: 10px 12px; + display: flex; + flex-direction: column; + gap: 6px; +} +.mcp__server-head { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} +.mcp__server-name { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; +} +.mcp__server-status { + color: var(--muted); + font-size: 13px; +} +.mcp__server-actions { + margin-left: auto; + display: flex; + gap: 6px; +} +.mcp__server-actions .settings__btn, +.mcp__server-actions .mcp__btn-ghost { + padding: 4px 10px; + font-size: 12px; +} +.mcp__server-url { + font-size: 12px; + color: var(--muted); + word-break: break-all; +} +.mcp__server--connected .mcp__dot { + background: #16a34a; +} +.mcp__server--connecting .mcp__dot { + background: #f59e0b; +} +.mcp__server--needs-authorization .mcp__dot { + background: #6366f1; +} +.mcp__server--error .mcp__dot { + background: #dc2626; +} +.mcp__tools-toggle { + cursor: pointer; + font-size: 13px; + color: var(--muted); +} +.mcp__ro { + font-size: 11px; + color: #16a34a !important; +} +.mcp__form-row { + display: flex; + gap: 8px; + align-items: flex-end; +} +.mcp__grow { + flex: 1 1 auto; +} +.mcp__name { + width: 150px; + flex: none; +} +.mcp__mock { + display: flex; + gap: 8px; + align-items: flex-start; + font-size: 13px; + color: var(--muted); + padding: 10px; + border: 1px dashed var(--border); + border-radius: 10px; +} + +/* ── Agent settings ───────────────────────────────────────────────────────── */ +.agentset { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 12px; + padding: 12px 14px; + display: flex; + flex-direction: column; + gap: 10px; +} +.agentset[open] { + gap: 10px; +} +.agentset > *:not(summary) { + margin-top: 10px; +} +.agentset__title { + cursor: pointer; + font-weight: 600; +} +.agentset__group { + display: flex; + flex-direction: column; + gap: 6px; +} +.agentset__grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; +} +.agentset__grid .settings__select { + width: 100%; +} +.agentset__checks { + display: flex; + gap: 14px; + flex-wrap: wrap; + font-size: 13px; +} +.agentset__skills { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 4px; + font-size: 13px; +} +.agentset__skills li { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.agentset__skills .mcp__btn-ghost { + padding: 2px 8px; + font-size: 12px; +} +.agentset__add > summary { + cursor: pointer; + font-size: 13px; + color: var(--muted); +} +.agentset__add { + display: flex; + flex-direction: column; + gap: 8px; +} +.agentset__textarea { + width: 100%; + margin: 8px 0; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 12px; + padding: 8px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--bg); + color: var(--fg); + resize: vertical; +} + +/* ── Chess ────────────────────────────────────────────────────────────────── */ +.chess { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 12px; + padding: 14px; + display: flex; + flex-direction: column; + gap: 12px; +} +.chess__status { + font-size: 14px; + min-height: 24px; + display: flex; + align-items: center; +} +.chess__status .mcp__btn-ghost { + padding: 3px 10px; + font-size: 13px; +} +.chess__thinking { + color: var(--accent); + animation: chess-pulse 1.2s ease-in-out infinite; +} +@keyframes chess-pulse { + 50% { + opacity: 0.45; + } +} +.chess__controls { + display: flex; + gap: 12px; + align-items: center; + flex-wrap: wrap; +} +.chess__analysts { + display: flex; + gap: 8px; + align-items: center; + font-size: 13px; + color: var(--muted); +} +.chess__moves { + margin: 0; + padding-left: 0; + list-style: none; + display: flex; + flex-wrap: wrap; + gap: 4px 12px; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 13px; +} + +.cboard { + display: grid; + grid-template-columns: repeat(8, 1fr); + width: min(100%, 480px); + aspect-ratio: 1; + border-radius: 8px; + overflow: hidden; + border: 1px solid var(--border); + user-select: none; +} +.cboard__sq { + position: relative; + border: none; + padding: 0; + display: grid; + place-items: center; + cursor: default; + font: inherit; +} +.cboard__sq--light { + background: #eedcbb; +} +.cboard__sq--dark { + background: #b58863; +} +.cboard__sq.is-movable { + cursor: pointer; +} +.cboard__sq.is-selected { + box-shadow: inset 0 0 0 3px #4f46e5; +} +.cboard__sq.is-last { + background-image: linear-gradient(rgba(250, 204, 21, 0.38), rgba(250, 204, 21, 0.38)); +} +.cboard__sq.is-check { + background-image: radial-gradient(circle, rgba(220, 38, 38, 0.85) 0%, rgba(220, 38, 38, 0) 70%); +} +.cboard__sq.is-target { + cursor: pointer; +} +.cboard__sq.is-target::after { + content: ''; + width: 26%; + height: 26%; + border-radius: 50%; + background: rgba(79, 70, 229, 0.45); +} +.cboard__sq.is-capture { + cursor: pointer; + box-shadow: inset 0 0 0 4px rgba(79, 70, 229, 0.55); +} +.cboard__piece { + font-size: clamp(22px, 5vw, 44px); + line-height: 1; + pointer-events: none; +} +.cboard__piece--w { + color: #fff; + text-shadow: + 0 0 2px #000, + 0 0 1px #000; +} +.cboard__piece--b { + color: #111; +} +.cboard__rank, +.cboard__file { + position: absolute; + font-size: 10px; + color: rgba(0, 0, 0, 0.55); + pointer-events: none; +} +.cboard__rank { + top: 2px; + left: 3px; +} +.cboard__file { + bottom: 1px; + right: 3px; +} +@media (max-width: 820px) { + .agentset__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} +.chess__agent-turn { + display: inline-flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} +.agentset__peek { + font-weight: 400; + font-size: 12px; + color: var(--muted); + margin-left: 6px; +} +.app__left { + overflow-y: auto; +} diff --git a/demo/src/chess/ChessBoard.tsx b/demo/src/chess/ChessBoard.tsx new file mode 100644 index 0000000..b0ad109 --- /dev/null +++ b/demo/src/chess/ChessBoard.tsx @@ -0,0 +1,86 @@ +import { useState } from 'react' +import type { Square } from 'chess.js' +import type { ChessGame } from './game' + +const GLYPH: Record = { + wk: '♔', + wq: '♕', + wr: '♖', + wb: '♗', + wn: '♘', + wp: '♙', + bk: '♚', + bq: '♛', + br: '♜', + bb: '♝', + bn: '♞', + bp: '♟', +} + +const FILES = 'abcdefgh' + +export interface ChessBoardProps { + game: ChessGame + /** The user can move (their turn, agent idle). */ + interactive: boolean + onMove: (from: Square, to: Square) => void +} + +/** A click-to-move board, White at the bottom. Click a piece, then a highlighted square. */ +export const ChessBoard = ({ game, interactive, onMove }: ChessBoardProps) => { + const [selected, setSelected] = useState() + const targets = selected ? new Set(game.targets(selected)) : new Set() + + const click = (sq: Square, own: boolean) => { + if (!interactive) return + if (selected && targets.has(sq)) { + onMove(selected, sq) + setSelected(undefined) + return + } + setSelected(own ? sq : undefined) + } + + return ( +
+ {game.board.map((row, r) => + row.map((piece, f) => { + const sq = `${FILES[f]}${8 - r}` as Square + const dark = (r + f) % 2 === 1 + const own = piece?.color === 'w' + const isLast = game.lastMove && (game.lastMove.from === sq || game.lastMove.to === sq) + const inCheck = game.inCheck && piece?.type === 'k' && piece.color === game.turn + const cls = [ + 'cboard__sq', + dark ? 'cboard__sq--dark' : 'cboard__sq--light', + selected === sq ? 'is-selected' : '', + targets.has(sq) ? (piece ? 'is-capture' : 'is-target') : '', + isLast ? 'is-last' : '', + inCheck ? 'is-check' : '', + interactive && own ? 'is-movable' : '', + ] + .filter(Boolean) + .join(' ') + return ( + + ) + }), + )} +
+ ) +} diff --git a/demo/src/chess/ChessPanel.tsx b/demo/src/chess/ChessPanel.tsx new file mode 100644 index 0000000..ba3ef1c --- /dev/null +++ b/demo/src/chess/ChessPanel.tsx @@ -0,0 +1,129 @@ +import type { Square } from 'chess.js' +import type { AnalystMode } from '../settings' +import { ChessBoard } from './ChessBoard' +import type { ChessGame } from './game' + +export interface ChessPanelProps { + game: ChessGame + /** The agent is thinking about / playing its move. */ + agentBusy: boolean + agentReady: boolean + analysts: AnalystMode + /** Why worker analysts are unavailable (e.g. a local model), if they are. */ + analystsNote?: string + onAnalysts: (mode: AnalystMode) => void + onUserMove: (from: Square, to: Square) => void + /** Ask the agent to move (when it is its turn but it is idle). */ + onAskAgent: () => void + /** Let the built-in engine play the agent's move (no model needed). */ + onEngineMove: () => void + onNewGame: () => void +} + +const RESULT: Record = { + checkmate: 'Checkmate', + stalemate: 'Stalemate', + draw: 'Draw', +} + +/** + * The board, the move list and the controls. Every user move TRIGGERS the + * agent: the app calls `agent.run("White played …")`, and the agent answers by + * calling `make_move` — no chat message needed. + */ +export const ChessPanel = ({ + game, + agentBusy, + agentReady, + analysts, + analystsNote, + onAnalysts, + onUserMove, + onAskAgent, + onEngineMove, + onNewGame, +}: ChessPanelProps) => { + const over = game.status !== 'playing' + const agentTurn = game.turn === game.agentColor && !over + const pairs: string[] = [] + for (let i = 0; i < game.history.length; i += 2) { + pairs.push( + `${i / 2 + 1}. ${game.history[i]}${game.history[i + 1] ? ` ${game.history[i + 1]}` : ''}`, + ) + } + + return ( +
+
+ {over ? ( + + {RESULT[game.status]} + {game.winner ? ` — ${game.winner === 'w' ? 'you win' : 'the agent wins'}` : ''} + + ) : agentTurn ? ( + agentBusy ? ( + The agent is choosing its move… + ) : ( + + Agent to move. + + + + ) + ) : ( + Your move (White){game.inCheck ? ' — you are in check' : ''}. + )} +
+ + + +
+ + +
+ {!agentReady && !over && ( +

+ Add an API key (or load a local model) on the left so the agent can play — until then, + “Engine move” answers for it. +

+ )} + {analystsNote &&

{analystsNote}

} +

+ {analysts === 'off' + ? 'The agent reads the board, checks candidates with a 2-ply engine and plays.' + : analysts === 'worker' + ? 'Before moving, the agent sends its candidates to analyst sub-agents that run in parallel Web Workers with a 3-ply search — the page never freezes.' + : 'Analyst sub-agents run in this thread with the same model (local models cannot be shared with workers).'} +

+ +
    + {pairs.map((p) => ( +
  1. {p}
  2. + ))} +
+
+ ) +} diff --git a/demo/src/chess/analyst-tools.ts b/demo/src/chess/analyst-tools.ts new file mode 100644 index 0000000..928e196 --- /dev/null +++ b/demo/src/chess/analyst-tools.ts @@ -0,0 +1,35 @@ +import { markReadOnly, type AgentToolSet } from '@dudko.dev/agent-web' +import { tool } from 'ai' +import { z } from 'zod' +import { analyseMove, bestMove } from './engine' + +/** + * The analyst's own tools — a real search, not a model guess. In a Web Worker + * they run off the main thread; the in-process analyst (local models) runs the + * same tools at a lower depth so the page stays responsive. + */ +export const engineTools = (maxDepth: number): AgentToolSet => ({ + engine_analyse: markReadOnly( + tool({ + description: `Search one candidate move with the engine (up to ${maxDepth} ply). Returns the score for the side that plays it (centipawns, + is good), the expected continuation, the node count and a verdict.`, + inputSchema: z.object({ + fen: z.string().describe('The position, as FEN'), + move: z.string().describe('The candidate move in SAN'), + depth: z.number().optional().describe(`Search depth in plies (1-${maxDepth})`), + }), + execute: async ({ fen, move, depth }) => + analyseMove(fen, move, Math.min(Math.max(1, depth ?? maxDepth), maxDepth)), + }), + ), + engine_best: markReadOnly( + tool({ + description: "The engine's own best move in a position, with its score and line.", + inputSchema: z.object({ fen: z.string().describe('The position, as FEN') }), + execute: async ({ fen }) => + bestMove(fen, Math.min(2, maxDepth)) ?? { error: 'no legal moves' }, + }), + ), +}) + +export const ANALYST_PROMPT = `You are a chess analyst. You receive ONE task: assess a candidate move (or find the best move) in a given position. +Get the position (get_position) unless the task contains a FEN, run engine_analyse on the candidate (and engine_best for comparison), and answer in two sentences: the verdict with the score, and the main line or risk. Do not ask questions.` diff --git a/demo/src/chess/analyst.worker.ts b/demo/src/chess/analyst.worker.ts new file mode 100644 index 0000000..0e29d1b --- /dev/null +++ b/demo/src/chess/analyst.worker.ts @@ -0,0 +1,35 @@ +/// +import { createAnthropic } from '@ai-sdk/anthropic' +import { createGoogleGenerativeAI } from '@ai-sdk/google' +import { createOpenAI } from '@ai-sdk/openai' +import { serveSubagentWorker, type ProviderModelSpec } from '@dudko.dev/agent-web' +import { engineTools } from './analyst-tools' + +/** + * A chess analyst subagent, served from a Web Worker. The parent (the player + * agent on the page) delegates "analyse this candidate move"; this worker runs + * its own plan → execute → synthesize loop with a CPU-heavy search tool that + * would otherwise stall the page, and answers with a verdict. + * + * The model is built here from the spec the parent posted (its key arrives with + * the task and is never stored). Provider factories are imported statically — + * a bundler cannot resolve the core's dynamic provider imports in a worker. + */ +const resolveModel = (spec: ProviderModelSpec) => { + const apiKey = spec.apiKey + switch (spec.providerType) { + case 'google': + return createGoogleGenerativeAI({ apiKey })(spec.model) + case 'anthropic': + return createAnthropic({ + apiKey, + headers: { 'anthropic-dangerous-direct-browser-access': 'true' }, + })(spec.model) + case 'openai': + return createOpenAI({ apiKey })(spec.model) + default: + throw new Error(`the analyst worker has no factory for "${spec.providerType}"`) + } +} + +serveSubagentWorker({ resolveModel, tools: engineTools(3) }) diff --git a/demo/src/chess/engine.ts b/demo/src/chess/engine.ts new file mode 100644 index 0000000..674a4a3 --- /dev/null +++ b/demo/src/chess/engine.ts @@ -0,0 +1,154 @@ +import { Chess, type Move, type PieceSymbol } from 'chess.js' + +/** + * A small, honest chess engine: material + piece-square tables, alpha-beta + * negamax with capture-first move ordering. Not strong — strong enough that + * the agent can't blunder a queen when it bothers to ask, and slow enough at + * depth 3 that running it in a Web Worker visibly keeps the page responsive. + */ + +const VALUE: Record = { p: 100, n: 320, b: 330, r: 500, q: 900, k: 0 } + +// Piece-square tables from White's point of view, rank 8 first (index 0 = a8). +const PST: Record = { + p: [ + 0, 0, 0, 0, 0, 0, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 10, 10, 20, 30, 30, 20, 10, 10, 5, 5, + 10, 25, 25, 10, 5, 5, 0, 0, 0, 20, 20, 0, 0, 0, 5, -5, -10, 0, 0, -10, -5, 5, 5, 10, 10, -20, + -20, 10, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, + ], + n: [ + -50, -40, -30, -30, -30, -30, -40, -50, -40, -20, 0, 0, 0, 0, -20, -40, -30, 0, 10, 15, 15, 10, + 0, -30, -30, 5, 15, 20, 20, 15, 5, -30, -30, 0, 15, 20, 20, 15, 0, -30, -30, 5, 10, 15, 15, 10, + 5, -30, -40, -20, 0, 5, 5, 0, -20, -40, -50, -40, -30, -30, -30, -30, -40, -50, + ], + b: [ + -20, -10, -10, -10, -10, -10, -10, -20, -10, 0, 0, 0, 0, 0, 0, -10, -10, 0, 5, 10, 10, 5, 0, + -10, -10, 5, 5, 10, 10, 5, 5, -10, -10, 0, 10, 10, 10, 10, 0, -10, -10, 10, 10, 10, 10, 10, 10, + -10, -10, 5, 0, 0, 0, 0, 5, -10, -20, -10, -10, -10, -10, -10, -10, -20, + ], + r: [ + 0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 10, 10, 10, 10, 10, 5, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, 0, 0, 0, + 0, 0, -5, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, 0, 0, 0, 0, 0, -5, 0, 0, 0, + 5, 5, 0, 0, 0, + ], + q: [ + -20, -10, -10, -5, -5, -10, -10, -20, -10, 0, 0, 0, 0, 0, 0, -10, -10, 0, 5, 5, 5, 5, 0, -10, + -5, 0, 5, 5, 5, 5, 0, -5, 0, 0, 5, 5, 5, 5, 0, -5, -10, 5, 5, 5, 5, 5, 0, -10, -10, 0, 5, 0, 0, + 0, 0, -10, -20, -10, -10, -5, -5, -10, -10, -20, + ], + k: [ + -30, -40, -40, -50, -50, -40, -40, -30, -30, -40, -40, -50, -50, -40, -40, -30, -30, -40, -40, + -50, -50, -40, -40, -30, -30, -40, -40, -50, -50, -40, -40, -30, -20, -30, -30, -40, -40, -30, + -30, -20, -10, -20, -20, -20, -20, -20, -20, -10, 20, 20, 0, 0, 0, 0, 20, 20, 20, 30, 10, 0, 0, + 10, 30, 20, + ], +} + +const MATE = 100_000 + +/** Static evaluation in centipawns from White's point of view. */ +export const evaluateWhite = (chess: Chess): number => { + if (chess.isCheckmate()) return chess.turn() === 'w' ? -MATE : MATE + if (chess.isDraw()) return 0 + let score = 0 + const board = chess.board() + for (let r = 0; r < 8; r += 1) { + for (let f = 0; f < 8; f += 1) { + const sq = board[r][f] + if (!sq) continue + // Tables are written for White (rank 8 first); mirror ranks for Black. + const idx = sq.color === 'w' ? r * 8 + f : (7 - r) * 8 + f + const v = VALUE[sq.type] + PST[sq.type][idx] + score += sq.color === 'w' ? v : -v + } + } + return score +} + +const order = (moves: Move[]): Move[] => + moves.sort((a, b) => { + const va = a.captured ? VALUE[a.captured] * 10 - VALUE[a.piece] : a.promotion ? 800 : 0 + const vb = b.captured ? VALUE[b.captured] * 10 - VALUE[b.piece] : b.promotion ? 800 : 0 + return vb - va + }) + +interface SearchStats { + nodes: number +} + +/** Negamax with alpha-beta; returns the score for the side to move. */ +const negamax = ( + chess: Chess, + depth: number, + alpha: number, + beta: number, + stats: SearchStats, +): { score: number; pv: string[] } => { + stats.nodes += 1 + if (depth === 0 || chess.isGameOver()) { + const white = evaluateWhite(chess) + return { score: chess.turn() === 'w' ? white : -white, pv: [] } + } + let best = { score: -Infinity, pv: [] as string[] } + for (const m of order(chess.moves({ verbose: true }))) { + chess.move({ from: m.from, to: m.to, promotion: m.promotion }) + const child = negamax(chess, depth - 1, -beta, -alpha, stats) + chess.undo() + const score = -child.score + if (score > best.score) best = { score, pv: [m.san, ...child.pv] } + if (score > alpha) alpha = score + if (alpha >= beta) break + } + return best +} + +export interface MoveAnalysis { + move: string + /** Centipawns for the side that plays `move` (positive = good for it). */ + score: number + /** Expected continuation after the move. */ + line: string[] + nodes: number + /** A plain-words read of the score. */ + verdict: string +} + +const verdictOf = (score: number): string => { + if (score >= MATE / 2) return 'forced mate for you' + if (score <= -MATE / 2) return 'gets you mated' + if (score >= 300) return 'winning' + if (score >= 80) return 'clearly better' + if (score > -80) return 'roughly equal' + if (score > -300) return 'clearly worse' + return 'losing material' +} + +/** Analyse one candidate move in a position (throws on an illegal move). */ +export const analyseMove = (fen: string, move: string, depth = 2): MoveAnalysis => { + const chess = new Chess(fen) + const played = chess.move(move) + const stats = { nodes: 0 } + const reply = negamax(chess, Math.max(0, depth - 1), -Infinity, Infinity, stats) + const score = -reply.score + return { move: played.san, score, line: reply.pv, nodes: stats.nodes, verdict: verdictOf(score) } +} + +/** The engine's own best move (for the "engine move" fallback). */ +export const bestMove = (fen: string, depth = 2): MoveAnalysis | undefined => { + const chess = new Chess(fen) + const stats = { nodes: 0 } + const best = negamax(chess, depth, -Infinity, Infinity, stats) + const [move, ...line] = best.pv + return move + ? { move, score: best.score, line, nodes: stats.nodes, verdict: verdictOf(best.score) } + : undefined +} + +/** Material balance in pawns, from White's point of view. */ +export const materialWhite = (chess: Chess): number => { + let sum = 0 + for (const row of chess.board()) { + for (const sq of row) if (sq) sum += (sq.color === 'w' ? 1 : -1) * VALUE[sq.type] + } + return sum / 100 +} diff --git a/demo/src/chess/game.ts b/demo/src/chess/game.ts new file mode 100644 index 0000000..8bcc55e --- /dev/null +++ b/demo/src/chess/game.ts @@ -0,0 +1,208 @@ +import { useCallback, useMemo, useRef, useState } from 'react' +import { Chess, type Square } from 'chess.js' +import { markReadOnly, type AgentToolSet } from '@dudko.dev/agent-web' +import { tool } from 'ai' +import { z } from 'zod' +import { analyseMove, bestMove, materialWhite } from './engine' + +export type GameStatus = 'playing' | 'checkmate' | 'stalemate' | 'draw' + +export interface ChessGame { + fen: string + /** Side to move. */ + turn: 'w' | 'b' + /** The colour the agent plays. */ + agentColor: 'b' + history: string[] + lastMove?: { from: Square; to: Square } + inCheck: boolean + status: GameStatus + /** 'w' | 'b' when the game is over with a winner. */ + winner?: 'w' | 'b' + board: ReturnType + /** Squares the piece on `from` can move to (for the user's highlights). */ + targets: (from: Square) => Square[] + /** Play the user's move; returns its SAN, or undefined when illegal / not their turn. */ + userMove: (from: Square, to: Square) => string | undefined + /** Let the built-in engine move for the agent (fallback button). */ + engineMove: () => string | undefined + newGame: () => void + /** Take back the last full move (agent's and user's). */ + undo: () => void + /** The agent's tools: read the position, evaluate candidates, play a move. */ + tools: AgentToolSet + describeState: () => string +} + +const statusOf = (c: Chess): { status: GameStatus; winner?: 'w' | 'b' } => { + if (c.isCheckmate()) return { status: 'checkmate', winner: c.turn() === 'w' ? 'b' : 'w' } + if (c.isStalemate()) return { status: 'stalemate' } + if (c.isDraw()) return { status: 'draw' } + return { status: 'playing' } +} + +const sideName = (c: 'w' | 'b') => (c === 'w' ? 'White' : 'Black') + +/** The position as the agent reads it — everything needed to choose a move. */ +export const positionReport = (c: Chess, agentColor: 'w' | 'b') => { + const history = c.history() + const { status, winner } = statusOf(c) + const material = materialWhite(c) * (agentColor === 'w' ? 1 : -1) + return { + fen: c.fen(), + yourColor: sideName(agentColor), + sideToMove: sideName(c.turn()), + yourTurn: c.turn() === agentColor && status === 'playing', + moveNumber: Math.floor(history.length / 2) + 1, + lastMove: history.at(-1) ?? null, + recentMoves: history.slice(-10).join(' '), + inCheck: c.inCheck(), + status, + winner: winner ? sideName(winner) : null, + materialForYou: material, + legalMoves: c.moves(), + board: c.ascii(), + } +} + +/** + * The chess game the agent plays: React state for the board UI, and tools + * that read and change the SAME game. The tools read a ref, so they stay valid + * without rebuilding the agent. + */ +export const useChessGame = (): ChessGame => { + const chessRef = useRef(new Chess()) + const [version, setVersion] = useState(0) + const [lastMove, setLastMove] = useState<{ from: Square; to: Square } | undefined>() + const agentColor = 'b' as const + const bump = () => setVersion((v) => v + 1) + + const play = useCallback((move: string | { from: Square; to: Square; promotion?: string }) => { + const c = chessRef.current + const m = c.move(move) + setLastMove({ from: m.from, to: m.to }) + bump() + return m.san + }, []) + + const userMove = useCallback( + (from: Square, to: Square): string | undefined => { + const c = chessRef.current + if (c.turn() === agentColor || c.isGameOver()) return undefined + const legal = c.moves({ verbose: true }).find((m) => m.from === from && m.to === to) + if (!legal) return undefined + // Always promote to a queen — a click-to-move board has no picker. + return play({ from, to, promotion: legal.promotion ? 'q' : undefined }) + }, + [play], + ) + + const engineMove = useCallback((): string | undefined => { + const c = chessRef.current + if (c.turn() !== agentColor || c.isGameOver()) return undefined + const best = bestMove(c.fen(), 2) + return best ? play(best.move) : undefined + }, [play]) + + const newGame = useCallback(() => { + chessRef.current = new Chess() + setLastMove(undefined) + bump() + }, []) + + const undo = useCallback(() => { + const c = chessRef.current + c.undo() + if (c.turn() === agentColor) c.undo() + setLastMove(undefined) + bump() + }, []) + + const targets = useCallback( + (from: Square): Square[] => + chessRef.current.moves({ square: from, verbose: true }).map((m) => m.to), + // eslint-disable-next-line react-hooks/exhaustive-deps + [version], + ) + + const describeState = useCallback(() => { + const r = positionReport(chessRef.current, agentColor) + return `Chess game — you play ${r.yourColor}. ${r.sideToMove} to move${r.inCheck ? ' (in check)' : ''}. Status: ${r.status}. Last move: ${r.lastMove ?? 'none'}. FEN: ${r.fen}` + }, []) + + const tools = useMemo( + () => ({ + get_position: markReadOnly( + tool({ + description: + 'Read the current chess position: FEN, whose turn it is, your colour, the last move, every legal move (SAN) and an ASCII board.', + inputSchema: z.object({}), + execute: async () => positionReport(chessRef.current, agentColor), + }), + ), + evaluate_moves: markReadOnly( + tool({ + description: + 'Quick engine check (2 ply) of up to 6 candidate moves in the current position. Returns a score for you (centipawns, + is good), the expected reply and a verdict. Use it to avoid blunders.', + inputSchema: z.object({ + moves: z.array(z.string()).describe('Candidate moves in SAN, e.g. ["e5", "Nf6"]'), + }), + execute: async ({ moves }) => { + const fen = chessRef.current.fen() + return moves.slice(0, 6).map((move) => { + try { + return analyseMove(fen, move, 2) + } catch { + return { move, error: 'illegal move in this position' } + } + }) + }, + }), + ), + make_move: tool({ + description: + 'Play YOUR move on the board (SAN like "e5", "Nf6", "O-O", "exd5", "e8=Q"). Only when it is your turn; exactly one move per turn.', + inputSchema: z.object({ move: z.string().describe('The move in SAN') }), + execute: async ({ move }) => { + const c = chessRef.current + if (c.isGameOver()) throw new Error('The game is over — no more moves.') + if (c.turn() !== agentColor) { + throw new Error( + `It is not your turn: ${sideName(c.turn())} to move. Wait for the user.`, + ) + } + let san: string + try { + san = play(move) + } catch { + throw new Error(`"${move}" is not legal here. Legal moves: ${c.moves().join(', ')}`) + } + const after = positionReport(chessRef.current, agentColor) + return { played: san, status: after.status, inCheck: after.inCheck, fen: after.fen } + }, + }), + }), + [play], + ) + + const c = chessRef.current + const { status, winner } = statusOf(c) + return { + fen: c.fen(), + turn: c.turn(), + agentColor, + history: c.history(), + lastMove, + inCheck: c.inCheck(), + status, + winner, + board: c.board(), + targets, + userMove, + engineMove, + newGame, + undo, + tools, + describeState, + } +} diff --git a/demo/src/components/AgentSettingsPanel.tsx b/demo/src/components/AgentSettingsPanel.tsx new file mode 100644 index 0000000..2d3a5c1 --- /dev/null +++ b/demo/src/components/AgentSettingsPanel.tsx @@ -0,0 +1,326 @@ +import { useState } from 'react' +import { + ApprovalModeSwitch, + loadSkillFromUrl, + parseSkillMarkdown, +} from '@dudko.dev/agent-web-react' +import { allSkills, type DemoSettings, type ThinkingChoice } from '../settings' +import { BUILTIN_SKILLS, SKILL_TEMPLATE } from '../skills' + +export interface AgentSettingsPanelProps { + settings: DemoSettings + update: (patch: Partial) => void + onReset: () => void +} + +const TOKEN_BUDGETS = [0, 20_000, 50_000, 100_000, 250_000] +const TOOL_CALL_CAPS = [0, 5, 10, 25, 50] +const WINDOWS = [8_000, 32_000, 128_000, 1_000_000] +const k = (n: number) => (n >= 1_000_000 ? `${n / 1_000_000}M` : `${n / 1000}k`) + +/** + * Everything the agent loop exposes, as settings: tool consent (the autopilot + * switch), thinking, run limits, context compaction and skills. Shared by all + * three tabs. + */ +export const AgentSettingsPanel = ({ settings, update, onReset }: AgentSettingsPanelProps) => { + const [draft, setDraft] = useState('') + const [skillUrl, setSkillUrl] = useState('') + const [skillError, setSkillError] = useState() + const builtin = new Set(BUILTIN_SKILLS.map((s) => s.name)) + + const toggleSkill = (name: string, on: boolean) => + update({ + enabledSkills: on + ? [...settings.enabledSkills, name] + : settings.enabledSkills.filter((n) => n !== name), + }) + + const addSkill = (skill: ReturnType) => { + if (allSkills(settings).some((s) => s.name === skill.name)) { + throw new Error(`a skill named "${skill.name}" already exists`) + } + update({ + customSkills: [...settings.customSkills, skill], + enabledSkills: [...settings.enabledSkills, skill.name], + }) + } + + const addFromDraft = () => { + try { + addSkill(parseSkillMarkdown(draft)) + setDraft('') + setSkillError(undefined) + } catch (err) { + setSkillError(err instanceof Error ? err.message : String(err)) + } + } + + const addFromUrl = async () => { + try { + addSkill(await loadSkillFromUrl(skillUrl.trim())) + setSkillUrl('') + setSkillError(undefined) + } catch (err) { + setSkillError(err instanceof Error ? err.message : String(err)) + } + } + + return ( +
+ + Agent settings{' '} + + {settings.approvalMode === 'autopilot' ? 'autopilot' : settings.approvalMode} · thinking{' '} + {settings.thinking} · {settings.enabledSkills.length} skill(s) + + + +
+ Tool consent + update({ approvalMode })} + /> +

+ {settings.approvalMode === 'autopilot' + ? 'Autopilot: the agent runs every tool on its own.' + : settings.approvalMode === 'ask-writes' + ? 'Read-only tools run freely; you approve anything that changes state (a note, a chess move, a write to your MCP server).' + : settings.approvalMode === 'ask-all' + ? 'You approve every single tool call.' + : 'Read-only: changes are refused without asking — the agent can only look.'}{' '} + Switches apply instantly, even mid-run. +

+
+ +
+ + + + + + +
+ +
+ + +
+ +
+ Chat UI — every part is a prop +
+ {( + [ + ['showToolCalls', 'Tool calls'], + ['showUsage', 'Token usage'], + ['history', 'Saved chats (IndexedDB)'], + ['filesPanel', 'Files panel'], + ] as const + ).map(([key, label]) => ( + + ))} +
+
+ + +
+
+ +
+ Skills +
    + {allSkills(settings).map((s) => ( +
  • + + {!builtin.has(s.name) && ( + + )} +
  • + ))} +
+
+ Add a skill (SKILL.md) +