diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index db754c9..fa0ba9a 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -16,6 +16,16 @@ updates:
dependencies:
patterns: ['*']
exclude-patterns: ['@aparte/*']
+ - package-ecosystem: pip
+ directories: ['/langgraph-ag-ui', '/pydantic-ai-ag-ui', '/google-adk-ag-ui']
+ schedule:
+ interval: weekly
+ ignore:
+ - dependency-name: '*'
+ update-types: ['version-update:semver-major']
+ groups:
+ dependencies:
+ patterns: ['*']
- package-ecosystem: github-actions
directory: /
schedule:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 94b1f4a..c5706c7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,10 +15,13 @@ jobs:
runs-on: ubuntu-latest
outputs:
examples: ${{ steps.find.outputs.examples }}
+ agents: ${{ steps.find.outputs.agents }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- id: find
- run: echo "examples=$(ls -d */package.json | xargs -n1 dirname | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"
+ run: |
+ echo "examples=$(ls -d */package.json | xargs -n1 dirname | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"
+ echo "agents=$(ls -d */requirements.txt | xargs -n1 dirname | jq -R . | jq -sc .)" >> "$GITHUB_OUTPUT"
example:
needs: list
@@ -42,11 +45,33 @@ jobs:
- run: npm run check
- run: npm run build
+ # Every folder with a requirements.txt holds a Python agent: it is installed and run, with its scripted model.
+ agent:
+ needs: list
+ name: ${{ matrix.example }} (agent)
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ example: ${{ fromJson(needs.list.outputs.agents) }}
+ defaults:
+ run:
+ working-directory: ${{ matrix.example }}
+ steps:
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
+ with:
+ python-version: '3.13'
+ cache: pip
+ cache-dependency-path: ${{ matrix.example }}/requirements.txt
+ - run: pip install -r requirements.txt
+ - run: python smoke_test.py
+
# The one check main requires: it passes when every example does, however many there are.
all:
name: all examples
if: always()
- needs: [list, example]
+ needs: [list, example, agent]
runs-on: ubuntu-latest
steps:
- - run: test "${{ needs.list.result }}" = success && test "${{ needs.example.result }}" = success
+ - run: test "${{ needs.list.result }}" = success && test "${{ needs.example.result }}" = success && test "${{ needs.agent.result }}" = success
diff --git a/.gitignore b/.gitignore
index a98acd0..e436021 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,7 @@
node_modules
+dist
+.venv
+__pycache__
.svelte-kit
build
.output
diff --git a/README.md b/README.md
index de997dc..e7ee114 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,13 @@
# aparté examples
-Apps built on [aparté](https://github.com/apartejs/aparte), an MIT chat UI for AI made of Web Components. Each folder is a whole app: open it in StackBlitz, or copy it and run it.
+Apps built on [aparté](https://github.com/apartejs/aparte), an MIT chat UI for AI made of Web Components. Each folder is a whole app: copy it and run it, or open it in StackBlitz when it runs there.
| Example | Stack | What it shows | Try it |
|---|---|---|---|
| [sveltekit-ai-sdk](./sveltekit-ai-sdk) | SvelteKit, Vercel AI SDK | A tool the model can only run once the user approves it, drawn by a Svelte component; Markdown formatted as it streams | [StackBlitz](https://stackblitz.com/github/apartejs/examples/tree/main/sveltekit-ai-sdk?file=src/lib/chat.ts) |
+| [langgraph-ag-ui](./langgraph-ag-ui) | LangGraph, AG-UI | A Python agent streaming into the chat, and a tool that waits for a person through `interrupt()` | On your machine |
+| [pydantic-ai-ag-ui](./pydantic-ai-ag-ui) | Pydantic AI, AG-UI | A Python agent streaming into the chat, and a tool that waits for a person through `requires_approval` | On your machine |
+| [google-adk-ag-ui](./google-adk-ag-ui) | Google ADK, AG-UI | A Python agent streaming into the chat, a tool that waits for a person through `require_confirmation`, and ADK's confirmation tool kept off the transcript | On your machine |
## Run one on your machine
@@ -15,7 +18,7 @@ npm install
npm run dev
```
-With no API key, an example answers with a scripted model, which is how it runs on StackBlitz. Each folder's `.env.example` names the key that switches it to a real model.
+With no API key, an example answers with a scripted model, which is how it runs on StackBlitz. Each folder's `.env.example` names the key that switches it to a real model. An example with a Python agent starts that agent first: its README has the commands.
## Adding an example
@@ -23,5 +26,5 @@ With no API key, an example answers with a scripted model, which is how it runs
- **Name it after its stack**, framework first: `sveltekit-ai-sdk`, `nextjs-ai-sdk`. Add the topic only when two examples share a stack.
- **aparté comes from npm**, at a published version, with the lockfile committed: the example installs what a reader's app would.
- **It runs without a key.** A scripted model answers when the key is missing, and its answer says it is scripted.
-- **It has `check` and `build` scripts.** CI runs `npm ci`, `npm run check` and `npm run build` in every folder that has a `package.json`, and Dependabot updates each folder's lockfile.
-- **Its README opens with a StackBlitz link**, and it gets a row in the table above.
+- **It has `check` and `build` scripts.** CI runs `npm ci`, `npm run check` and `npm run build` in every folder that has a `package.json`, and Dependabot updates each folder's lockfile. A folder with a `requirements.txt` holds a Python agent: CI installs it and runs its `smoke_test.py`.
+- **Its README says how to try it**, with a StackBlitz link when it runs there (a Python agent does not), and it gets a row in the table above.
diff --git a/google-adk-ag-ui/.env.example b/google-adk-ag-ui/.env.example
new file mode 100644
index 0000000..5f073a9
--- /dev/null
+++ b/google-adk-ag-ui/.env.example
@@ -0,0 +1,3 @@
+# Optional. Without MODEL, a scripted model answers (scripted_model.py).
+MODEL=
+GOOGLE_API_KEY=
diff --git a/google-adk-ag-ui/README.md b/google-adk-ag-ui/README.md
new file mode 100644
index 0000000..6648203
--- /dev/null
+++ b/google-adk-ag-ui/README.md
@@ -0,0 +1,34 @@
+# Google ADK + aparté
+
+A [Google ADK](https://google.github.io/adk-docs/) agent served over [AG-UI](https://docs.ag-ui.com), with [aparté](https://apartejs.dev) as its chat. Replies stream, tool calls show as they run, and one tool waits for a person before it runs.
+
+## Run it
+
+The agent runs in Python, so this one runs on your machine and not on StackBlitz. Tested with Python 3.13 and Node 24.
+
+```bash
+npx degit apartejs/examples/google-adk-ag-ui my-agent
+cd my-agent
+python -m venv .venv
+source .venv/bin/activate # on Windows: .venv\Scripts\activate
+pip install -r requirements.txt
+uvicorn agent:app --port 8000
+```
+
+In a second terminal:
+
+```bash
+npm install
+npm run dev
+```
+
+Open http://localhost:5173 and ask it to send an email.
+
+With nothing configured, a scripted model answers, and says it is scripted. For a real model, copy `.env.example` to `.env` and set `MODEL` to a Gemini model and `GOOGLE_API_KEY` to your key.
+
+## What to look at
+
+- [`agent.py`](./agent.py): the agent and its FastAPI route. `require_confirmation=True` on the tool pauses the run for a person, and `emit_interrupt_outcome=True` ends that run the way AG-UI 1.0 describes it.
+- [`src/main.ts`](./src/main.ts): the whole page. ADK asks for a confirmation through a tool of its own, `adk_request_confirmation`, so `registerToolDisplay` hides that row and has the question name the real tool. The rest knows nothing about ADK: `@aparte/runner-ag-ui` reads the agent's AG-UI stream.
+- [`scripted_model.py`](./scripted_model.py): the stand-in model.
+- [`smoke_test.py`](./smoke_test.py): what CI runs against the route: a reply, a pause, and a resume.
diff --git a/google-adk-ag-ui/agent.py b/google-adk-ag-ui/agent.py
new file mode 100644
index 0000000..499cd81
--- /dev/null
+++ b/google-adk-ag-ui/agent.py
@@ -0,0 +1,41 @@
+"""A Google ADK agent served over AG-UI, with a tool that waits for a person."""
+
+import os
+
+from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint
+from dotenv import load_dotenv
+from fastapi import FastAPI
+from google.adk.agents import LlmAgent
+from google.adk.tools import FunctionTool
+
+load_dotenv()
+
+
+def send_email(to: str, subject: str) -> str:
+ """Send an email. A person has to approve it first."""
+ return f"Email sent to {to}: {subject}"
+
+
+def make_model():
+ if os.environ.get("MODEL"):
+ return os.environ["MODEL"]
+
+ from scripted_model import ScriptedModel
+
+ return ScriptedModel()
+
+
+agent = LlmAgent(
+ name="assistant",
+ model=make_model(),
+ instruction="You help the user. Send an email only when asked to.",
+ tools=[FunctionTool(send_email, require_confirmation=True)],
+)
+
+app = FastAPI()
+add_adk_fastapi_endpoint(
+ app,
+ # emit_interrupt_outcome: a pause ends the run the way AG-UI 1.0 describes it, which is what the chat reads.
+ ADKAgent(adk_agent=agent, app_name="example", user_id="reader", emit_interrupt_outcome=True),
+ path="/agent",
+)
diff --git a/google-adk-ag-ui/index.html b/google-adk-ag-ui/index.html
new file mode 100644
index 0000000..8e71140
--- /dev/null
+++ b/google-adk-ag-ui/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Google ADK + aparté
+
+
+
+
+
+
+
diff --git a/google-adk-ag-ui/package-lock.json b/google-adk-ag-ui/package-lock.json
new file mode 100644
index 0000000..fd05f8a
--- /dev/null
+++ b/google-adk-ag-ui/package-lock.json
@@ -0,0 +1,891 @@
+{
+ "name": "google-adk-ag-ui",
+ "version": "0.0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "google-adk-ag-ui",
+ "version": "0.0.1",
+ "dependencies": {
+ "@aparte/core": "^1.0.2",
+ "@aparte/plugin-marked": "^1.0.2",
+ "@aparte/runner-ag-ui": "^1.0.2",
+ "marked": "^18.1.3"
+ },
+ "devDependencies": {
+ "typescript": "^6.0.3",
+ "vite": "^8.3.0"
+ }
+ },
+ "node_modules/@aparte/core": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/core/-/core-1.0.2.tgz",
+ "integrity": "sha512-fOZ7/B9lPPpGvzrF9YYWrqRFNGskm9g41b4QLE5eKa3UOySuWQIytfFUVHIkqV9y8F5NouO87BKGfj1Nmud5Qg==",
+ "license": "MIT",
+ "dependencies": {
+ "@aparte/engine": "1.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@aparte/engine": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/engine/-/engine-1.0.2.tgz",
+ "integrity": "sha512-ZkC5gL9tPanB48rm2bNXbK/0ZvV7h2K87ujJYLtFBaqZV/nUbRLRZtS4O2VLeeC/PbOQP6ve+UXIBaYYFSrFlw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@aparte/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@aparte/plugin-marked": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/plugin-marked/-/plugin-marked-1.0.2.tgz",
+ "integrity": "sha512-WVpKWNB+EZslxij8faXthOb16/H7EEruBvLfEmbvDC0FW55OT0L0k3yDy5yEp6yfiDkxnwZuXC4MT6OQeFxf8w==",
+ "license": "MIT",
+ "dependencies": {
+ "remend": "^1.4.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@aparte/core": ">=1.0.2 <2.0.0",
+ "marked": "^18.0.0"
+ }
+ },
+ "node_modules/@aparte/runner-ag-ui": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/runner-ag-ui/-/runner-ag-ui-1.0.2.tgz",
+ "integrity": "sha512-EaClxTttxf8Jim7Zqt2VLhTT39o5pIwuMOrRb4NQSG9xtIYm2dCFYf5y8+A3graZIklHGN/7B+zUaTxiFr+eiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@aparte/core": ">=1.0.2 <2.0.0"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.156.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz",
+ "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/oxc-project"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm-eabi": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz",
+ "integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz",
+ "integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz",
+ "integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz",
+ "integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz",
+ "integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz",
+ "integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz",
+ "integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz",
+ "integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz",
+ "integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz",
+ "integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz",
+ "integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz",
+ "integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz",
+ "integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz",
+ "integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz",
+ "integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "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"
+ },
+ "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/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "dev": true,
+ "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": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/marked": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-18.1.3.tgz",
+ "integrity": "sha512-9ChMH8EVVgjgID49nsL6IWxWk5c6GtAsq/xw7U1ggkjkU5AGuv68oLTVlFRtvcUYZtpbNyjyvGo3YDu0fGapiw==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz",
+ "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.20",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/remend": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/remend/-/remend-1.4.0.tgz",
+ "integrity": "sha512-mHiYhhwphs1IgIZmkToKLwJn0U99IjPXuutkA2acrNcG0vlEq2G4TGnd2eT0saIHwh2KjaCpvleHcsd5SAz96A==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz",
+ "integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.156.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm-eabi": "1.2.16",
+ "@rolldown/binding-android-arm64": "1.2.16",
+ "@rolldown/binding-darwin-arm64": "1.2.16",
+ "@rolldown/binding-darwin-x64": "1.2.16",
+ "@rolldown/binding-freebsd-x64": "1.2.16",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.16",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.16",
+ "@rolldown/binding-linux-arm64-musl": "1.2.16",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.16",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.16",
+ "@rolldown/binding-linux-x64-gnu": "1.2.16",
+ "@rolldown/binding-linux-x64-musl": "1.2.16",
+ "@rolldown/binding-openharmony-arm64": "1.2.16",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.16",
+ "@rolldown/binding-win32-x64-msvc": "1.2.16"
+ }
+ },
+ "node_modules/source-map-js": {
+ "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": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/typescript": {
+ "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": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.3.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.6.tgz",
+ "integrity": "sha512-UI7dK29NnS54Fk+mmo7o+jI6U8Fp7hDl+X5Q1k45WH+d4dSlbLVUETGQ+hfkNgvLQGyoElPj837nZyv4MBc0VA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.7",
+ "postcss": "^8.5.32",
+ "rolldown": "~1.2.16",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.7.1 || ^0.8.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "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"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/google-adk-ag-ui/package.json b/google-adk-ag-ui/package.json
new file mode 100644
index 0000000..0f5a5bb
--- /dev/null
+++ b/google-adk-ag-ui/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "google-adk-ag-ui",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "check": "tsc"
+ },
+ "dependencies": {
+ "@aparte/core": "^1.0.2",
+ "@aparte/plugin-marked": "^1.0.2",
+ "@aparte/runner-ag-ui": "^1.0.2",
+ "marked": "^18.1.3"
+ },
+ "devDependencies": {
+ "typescript": "^6.0.3",
+ "vite": "^8.3.0"
+ }
+}
diff --git a/google-adk-ag-ui/requirements.txt b/google-adk-ag-ui/requirements.txt
new file mode 100644
index 0000000..f1672df
--- /dev/null
+++ b/google-adk-ag-ui/requirements.txt
@@ -0,0 +1,6 @@
+ag-ui-adk==0.12.0
+fastapi==0.162.2
+google-adk==2.14.0
+httpx==0.28.1
+python-dotenv==1.2.4
+uvicorn==0.54.0
diff --git a/google-adk-ag-ui/scripted_model.py b/google-adk-ag-ui/scripted_model.py
new file mode 100644
index 0000000..953ca09
--- /dev/null
+++ b/google-adk-ag-ui/scripted_model.py
@@ -0,0 +1,37 @@
+"""Answers when no model is configured, and says so. It streams word by word, as a real model does."""
+
+from collections.abc import AsyncGenerator
+from uuid import uuid4
+
+from google.adk.models.base_llm import BaseLlm
+from google.adk.models.llm_request import LlmRequest
+from google.adk.models.llm_response import LlmResponse
+from google.genai import types
+
+NOTE = "This is a scripted reply: set MODEL in .env to talk to a real model."
+
+
+def text_response(text: str, partial: bool) -> LlmResponse:
+ return LlmResponse(content=types.Content(role="model", parts=[types.Part(text=text)]), partial=partial)
+
+
+class ScriptedModel(BaseLlm):
+ model: str = "scripted"
+
+ async def generate_content_async(self, llm_request: LlmRequest, stream: bool = False) -> AsyncGenerator[LlmResponse, None]:
+ last = llm_request.contents[-1].parts[-1]
+ if last.function_response:
+ response = last.function_response.response or {}
+ text = f"{response.get('result') or response.get('error')} {NOTE}"
+ elif "email" in (last.text or "").lower():
+ call = types.FunctionCall(id=f"call_{uuid4().hex}", name="send_email", args={"to": "ada@example.com", "subject": "Hello from Google ADK"})
+ yield LlmResponse(content=types.Content(role="model", parts=[types.Part(function_call=call)]))
+ return
+ else:
+ text = f"{NOTE} Ask me to **send an email** to see a tool wait for your approval."
+
+ if stream:
+ words = text.split(" ")
+ for i, word in enumerate(words):
+ yield text_response(word if i == 0 else f" {word}", partial=True)
+ yield text_response(text, partial=False)
diff --git a/google-adk-ag-ui/smoke_test.py b/google-adk-ag-ui/smoke_test.py
new file mode 100644
index 0000000..015c036
--- /dev/null
+++ b/google-adk-ag-ui/smoke_test.py
@@ -0,0 +1,46 @@
+"""Runs the agent with the scripted model, through the same route the page calls. CI runs this file."""
+
+import json
+from uuid import uuid4
+
+from fastapi.testclient import TestClient
+
+from agent import app
+
+client = TestClient(app)
+
+
+def run(thread_id: str, **body) -> list[dict]:
+ payload = {"threadId": thread_id, "runId": uuid4().hex, "state": {}, "messages": [], "tools": [], "context": [], "forwardedProps": {}, **body}
+ response = client.post("/agent", json=payload, headers={"accept": "text/event-stream"})
+ assert response.status_code == 200, response.text
+ return [json.loads(line[6:]) for line in response.text.splitlines() if line.startswith("data: ")]
+
+
+def user(text: str) -> dict:
+ return {"id": uuid4().hex, "role": "user", "content": text}
+
+
+def of_type(events: list[dict], name: str) -> list[dict]:
+ return [e for e in events if e["type"] == name]
+
+
+events = run(uuid4().hex, messages=[user("Hello")])
+assert events[0]["type"] == "RUN_STARTED" and events[-1]["type"] == "RUN_FINISHED", events
+text = "".join(e["delta"] for e in of_type(events, "TEXT_MESSAGE_CONTENT"))
+assert "scripted reply" in text, text
+assert len(of_type(events, "TEXT_MESSAGE_CONTENT")) > 1, "the reply did not stream"
+
+thread = uuid4().hex
+ask = user("Send an email")
+paused = run(thread, messages=[ask])
+assert of_type(paused, "TOOL_CALL_START")[0]["toolCallName"] == "send_email", paused
+interrupts = paused[-1]["outcome"]["interrupts"]
+assert interrupts[0]["reason"] == "confirmation", interrupts
+
+resumed = run(thread, messages=[ask], resume=[{"interruptId": interrupts[0]["id"], "status": "resolved", "payload": {"approved": True}}])
+result = of_type(resumed, "TOOL_CALL_RESULT")[0]["content"]
+assert "Email sent" in result, resumed
+assert resumed[-1]["type"] == "RUN_FINISHED" and resumed[-1].get("outcome", {"type": "success"})["type"] == "success", resumed
+
+print("ok")
diff --git a/google-adk-ag-ui/src/main.ts b/google-adk-ag-ui/src/main.ts
new file mode 100644
index 0000000..c177080
--- /dev/null
+++ b/google-adk-ag-ui/src/main.ts
@@ -0,0 +1,19 @@
+import '@aparte/core/styles.css';
+import { AparteClient, aparteGlobalConfig } from '@aparte/core';
+import { setupMarkedProvider } from '@aparte/plugin-marked';
+import { createAgUiRunner } from '@aparte/runner-ag-ui';
+
+setupMarkedProvider();
+
+// ADK asks for an approval through a tool of its own, whose arguments are its protocol.
+// Keep it off the transcript, and have the question name the tool it is really about.
+type Confirmation = { originalFunctionCall?: { name?: string; args?: unknown } };
+
+aparteGlobalConfig.registerToolDisplay('adk_request_confirmation', {
+ hidden: true,
+ label: (segment) => (segment.toolCall.input as Confirmation).originalFunctionCall?.name ?? 'this tool',
+ formatInput: (input) => JSON.stringify((input as Confirmation).originalFunctionCall?.args ?? {}, null, 2),
+});
+
+// The rest of the page knows nothing about Google ADK: it reads the agent's AG-UI stream.
+new AparteClient({ streamRunner: createAgUiRunner({ endpoint: '/agent' }) }).start();
diff --git a/google-adk-ag-ui/tsconfig.json b/google-adk-ag-ui/tsconfig.json
new file mode 100644
index 0000000..69fa3be
--- /dev/null
+++ b/google-adk-ag-ui/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "types": ["vite/client"]
+ },
+ "include": ["src", "vite.config.ts"]
+}
diff --git a/google-adk-ag-ui/vite.config.ts b/google-adk-ag-ui/vite.config.ts
new file mode 100644
index 0000000..f22eab8
--- /dev/null
+++ b/google-adk-ag-ui/vite.config.ts
@@ -0,0 +1,5 @@
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ server: { proxy: { '/agent': 'http://localhost:8000' } },
+});
diff --git a/langgraph-ag-ui/.env.example b/langgraph-ag-ui/.env.example
new file mode 100644
index 0000000..e959120
--- /dev/null
+++ b/langgraph-ag-ui/.env.example
@@ -0,0 +1,5 @@
+# Optional. Without MODEL, a scripted model answers (scripted_model.py).
+MODEL=
+OPENAI_API_KEY=
+# For any other OpenAI-compatible API, a local one included.
+OPENAI_BASE_URL=
diff --git a/langgraph-ag-ui/README.md b/langgraph-ag-ui/README.md
new file mode 100644
index 0000000..2e8ba98
--- /dev/null
+++ b/langgraph-ag-ui/README.md
@@ -0,0 +1,36 @@
+# LangGraph + aparté
+
+A [LangGraph](https://langchain-ai.github.io/langgraph/) agent served over [AG-UI](https://docs.ag-ui.com), with [aparté](https://apartejs.dev) as its chat. Replies stream, tool calls show as they run, and one tool waits for a person before it runs.
+
+The page knows nothing about LangGraph: `@aparte/runner-ag-ui` reads the agent's AG-UI stream.
+
+## Run it
+
+The agent runs in Python, so this one runs on your machine and not on StackBlitz. Tested with Python 3.13 and Node 24.
+
+```bash
+npx degit apartejs/examples/langgraph-ag-ui my-agent
+cd my-agent
+python -m venv .venv
+source .venv/bin/activate # on Windows: .venv\Scripts\activate
+pip install -r requirements.txt
+uvicorn agent:app --port 8000
+```
+
+In a second terminal:
+
+```bash
+npm install
+npm run dev
+```
+
+Open http://localhost:5173 and ask it to send an email.
+
+With nothing configured, a scripted model answers, and says it is scripted. For a real model, copy `.env.example` to `.env` and set `MODEL` and `OPENAI_API_KEY`. `OPENAI_BASE_URL` points it at any OpenAI-compatible API, a local one included.
+
+## What to look at
+
+- [`agent.py`](./agent.py): the graph and its FastAPI route. `send_email` calls `interrupt()` with a `reason` and the call's id, which is what opens the chat's approval panel. `emit_interrupt_outcome=True` ends a paused run the way AG-UI 1.0 describes it.
+- [`src/main.ts`](./src/main.ts): the whole page.
+- [`scripted_model.py`](./scripted_model.py): the stand-in model.
+- [`smoke_test.py`](./smoke_test.py): what CI runs against the route: a reply, a pause, and a resume.
diff --git a/langgraph-ag-ui/agent.py b/langgraph-ag-ui/agent.py
new file mode 100644
index 0000000..764865c
--- /dev/null
+++ b/langgraph-ag-ui/agent.py
@@ -0,0 +1,60 @@
+"""A LangGraph agent served over AG-UI, with a tool that waits for a person."""
+
+import os
+from typing import Annotated
+
+from ag_ui_langgraph import LangGraphAgent, add_langgraph_fastapi_endpoint
+from dotenv import load_dotenv
+from fastapi import FastAPI
+from langchain_core.tools import InjectedToolCallId, tool
+from langgraph.checkpoint.memory import MemorySaver
+from langgraph.graph import START, MessagesState, StateGraph
+from langgraph.prebuilt import ToolNode, tools_condition
+from langgraph.types import interrupt
+
+load_dotenv()
+
+
+@tool
+def send_email(to: str, subject: str, tool_call_id: Annotated[str, InjectedToolCallId]) -> str:
+ """Send an email. A person has to approve it first."""
+ # The reason and the call's id are what make the chat open its approval panel.
+ answer = interrupt({"reason": "tool_call", "toolCallId": tool_call_id, "message": f"Send this email to {to}?"})
+ if not (isinstance(answer, dict) and answer.get("approved")):
+ return "The user refused: the email was not sent."
+ return f"Email sent to {to}: {subject}"
+
+
+def make_model():
+ if os.environ.get("MODEL"):
+ from langchain_openai import ChatOpenAI
+
+ return ChatOpenAI(model=os.environ["MODEL"], streaming=True)
+
+ from scripted_model import ScriptedModel
+
+ return ScriptedModel()
+
+
+tools = [send_email]
+model = make_model().bind_tools(tools)
+
+
+async def call_model(state: MessagesState):
+ return {"messages": [await model.ainvoke(state["messages"])]}
+
+
+builder = StateGraph(MessagesState)
+builder.add_node("agent", call_model)
+builder.add_node("tools", ToolNode(tools))
+builder.add_edge(START, "agent")
+builder.add_conditional_edges("agent", tools_condition)
+builder.add_edge("tools", "agent")
+
+app = FastAPI()
+add_langgraph_fastapi_endpoint(
+ app,
+ # emit_interrupt_outcome: a pause ends the run the way AG-UI 1.0 describes it, which is what the chat reads.
+ LangGraphAgent(name="assistant", graph=builder.compile(checkpointer=MemorySaver()), emit_interrupt_outcome=True),
+ path="/agent",
+)
diff --git a/langgraph-ag-ui/index.html b/langgraph-ag-ui/index.html
new file mode 100644
index 0000000..fd5f975
--- /dev/null
+++ b/langgraph-ag-ui/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ LangGraph + aparté
+
+
+
+
+
+
+
diff --git a/langgraph-ag-ui/package-lock.json b/langgraph-ag-ui/package-lock.json
new file mode 100644
index 0000000..861c1a8
--- /dev/null
+++ b/langgraph-ag-ui/package-lock.json
@@ -0,0 +1,891 @@
+{
+ "name": "langgraph-ag-ui",
+ "version": "0.0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "langgraph-ag-ui",
+ "version": "0.0.1",
+ "dependencies": {
+ "@aparte/core": "^1.0.2",
+ "@aparte/plugin-marked": "^1.0.2",
+ "@aparte/runner-ag-ui": "^1.0.2",
+ "marked": "^18.1.3"
+ },
+ "devDependencies": {
+ "typescript": "^6.0.3",
+ "vite": "^8.3.0"
+ }
+ },
+ "node_modules/@aparte/core": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/core/-/core-1.0.2.tgz",
+ "integrity": "sha512-fOZ7/B9lPPpGvzrF9YYWrqRFNGskm9g41b4QLE5eKa3UOySuWQIytfFUVHIkqV9y8F5NouO87BKGfj1Nmud5Qg==",
+ "license": "MIT",
+ "dependencies": {
+ "@aparte/engine": "1.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@aparte/engine": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/engine/-/engine-1.0.2.tgz",
+ "integrity": "sha512-ZkC5gL9tPanB48rm2bNXbK/0ZvV7h2K87ujJYLtFBaqZV/nUbRLRZtS4O2VLeeC/PbOQP6ve+UXIBaYYFSrFlw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@aparte/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@aparte/plugin-marked": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/plugin-marked/-/plugin-marked-1.0.2.tgz",
+ "integrity": "sha512-WVpKWNB+EZslxij8faXthOb16/H7EEruBvLfEmbvDC0FW55OT0L0k3yDy5yEp6yfiDkxnwZuXC4MT6OQeFxf8w==",
+ "license": "MIT",
+ "dependencies": {
+ "remend": "^1.4.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@aparte/core": ">=1.0.2 <2.0.0",
+ "marked": "^18.0.0"
+ }
+ },
+ "node_modules/@aparte/runner-ag-ui": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/runner-ag-ui/-/runner-ag-ui-1.0.2.tgz",
+ "integrity": "sha512-EaClxTttxf8Jim7Zqt2VLhTT39o5pIwuMOrRb4NQSG9xtIYm2dCFYf5y8+A3graZIklHGN/7B+zUaTxiFr+eiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@aparte/core": ">=1.0.2 <2.0.0"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.156.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz",
+ "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/oxc-project"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm-eabi": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz",
+ "integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz",
+ "integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz",
+ "integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz",
+ "integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz",
+ "integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz",
+ "integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz",
+ "integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz",
+ "integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz",
+ "integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz",
+ "integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz",
+ "integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz",
+ "integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz",
+ "integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz",
+ "integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz",
+ "integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "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"
+ },
+ "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/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "dev": true,
+ "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": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/marked": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-18.1.3.tgz",
+ "integrity": "sha512-9ChMH8EVVgjgID49nsL6IWxWk5c6GtAsq/xw7U1ggkjkU5AGuv68oLTVlFRtvcUYZtpbNyjyvGo3YDu0fGapiw==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz",
+ "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.20",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/remend": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/remend/-/remend-1.4.0.tgz",
+ "integrity": "sha512-mHiYhhwphs1IgIZmkToKLwJn0U99IjPXuutkA2acrNcG0vlEq2G4TGnd2eT0saIHwh2KjaCpvleHcsd5SAz96A==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz",
+ "integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.156.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm-eabi": "1.2.16",
+ "@rolldown/binding-android-arm64": "1.2.16",
+ "@rolldown/binding-darwin-arm64": "1.2.16",
+ "@rolldown/binding-darwin-x64": "1.2.16",
+ "@rolldown/binding-freebsd-x64": "1.2.16",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.16",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.16",
+ "@rolldown/binding-linux-arm64-musl": "1.2.16",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.16",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.16",
+ "@rolldown/binding-linux-x64-gnu": "1.2.16",
+ "@rolldown/binding-linux-x64-musl": "1.2.16",
+ "@rolldown/binding-openharmony-arm64": "1.2.16",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.16",
+ "@rolldown/binding-win32-x64-msvc": "1.2.16"
+ }
+ },
+ "node_modules/source-map-js": {
+ "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": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/typescript": {
+ "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": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.3.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.6.tgz",
+ "integrity": "sha512-UI7dK29NnS54Fk+mmo7o+jI6U8Fp7hDl+X5Q1k45WH+d4dSlbLVUETGQ+hfkNgvLQGyoElPj837nZyv4MBc0VA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.7",
+ "postcss": "^8.5.32",
+ "rolldown": "~1.2.16",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.7.1 || ^0.8.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "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"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/langgraph-ag-ui/package.json b/langgraph-ag-ui/package.json
new file mode 100644
index 0000000..6661343
--- /dev/null
+++ b/langgraph-ag-ui/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "langgraph-ag-ui",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "check": "tsc"
+ },
+ "dependencies": {
+ "@aparte/core": "^1.0.2",
+ "@aparte/plugin-marked": "^1.0.2",
+ "@aparte/runner-ag-ui": "^1.0.2",
+ "marked": "^18.1.3"
+ },
+ "devDependencies": {
+ "typescript": "^6.0.3",
+ "vite": "^8.3.0"
+ }
+}
diff --git a/langgraph-ag-ui/requirements.txt b/langgraph-ag-ui/requirements.txt
new file mode 100644
index 0000000..fcd9b5c
--- /dev/null
+++ b/langgraph-ag-ui/requirements.txt
@@ -0,0 +1,7 @@
+ag-ui-langgraph==0.0.47
+fastapi==0.162.2
+httpx==0.28.1
+langchain-openai==1.7.4
+langgraph==1.2.20
+python-dotenv==1.2.4
+uvicorn==0.54.0
diff --git a/langgraph-ag-ui/scripted_model.py b/langgraph-ag-ui/scripted_model.py
new file mode 100644
index 0000000..54b498f
--- /dev/null
+++ b/langgraph-ag-ui/scripted_model.py
@@ -0,0 +1,43 @@
+"""Answers when no model is configured, and says so. It streams word by word, as a real model does."""
+
+import json
+from typing import Any, Iterator
+from uuid import uuid4
+
+from langchain_core.language_models.chat_models import BaseChatModel
+from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage, ToolMessage
+from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
+
+NOTE = "This is a scripted reply: set MODEL in .env to talk to a real model."
+
+
+class ScriptedModel(BaseChatModel):
+ @property
+ def _llm_type(self) -> str:
+ return "scripted"
+
+ def bind_tools(self, tools: Any, **kwargs: Any) -> "ScriptedModel":
+ return self
+
+ def _reply(self, messages: list[BaseMessage]) -> AIMessage:
+ last = messages[-1]
+ if isinstance(last, ToolMessage):
+ return AIMessage(content=f"{last.content} {NOTE}")
+ if "email" in str(last.content).lower():
+ args = {"to": "ada@example.com", "subject": "Hello from LangGraph"}
+ return AIMessage(content="", tool_calls=[{"id": f"call_{uuid4().hex}", "name": "send_email", "args": args}])
+ return AIMessage(content=f"{NOTE} Ask me to **send an email** to see a tool wait for your approval.")
+
+ def _generate(self, messages: list[BaseMessage], stop: Any = None, run_manager: Any = None, **kwargs: Any) -> ChatResult:
+ return ChatResult(generations=[ChatGeneration(message=self._reply(messages))])
+
+ def _stream(self, messages: list[BaseMessage], stop: Any = None, run_manager: Any = None, **kwargs: Any) -> Iterator[ChatGenerationChunk]:
+ reply = self._reply(messages)
+ if reply.tool_calls:
+ call = reply.tool_calls[0]
+ chunk = {"id": call["id"], "name": call["name"], "args": json.dumps(call["args"]), "index": 0}
+ yield ChatGenerationChunk(message=AIMessageChunk(content="", tool_call_chunks=[chunk]))
+ return
+ words = str(reply.content).split(" ")
+ for i, word in enumerate(words):
+ yield ChatGenerationChunk(message=AIMessageChunk(content=word if i == 0 else f" {word}"))
diff --git a/langgraph-ag-ui/smoke_test.py b/langgraph-ag-ui/smoke_test.py
new file mode 100644
index 0000000..782a61a
--- /dev/null
+++ b/langgraph-ag-ui/smoke_test.py
@@ -0,0 +1,45 @@
+"""Runs the agent with the scripted model, through the same route the page calls. CI runs this file."""
+
+import json
+from uuid import uuid4
+
+from fastapi.testclient import TestClient
+
+from agent import app
+
+client = TestClient(app)
+
+
+def run(thread_id: str, **body) -> list[dict]:
+ payload = {"threadId": thread_id, "runId": uuid4().hex, "state": {}, "messages": [], "tools": [], "context": [], "forwardedProps": {}, **body}
+ response = client.post("/agent", json=payload, headers={"accept": "text/event-stream"})
+ assert response.status_code == 200, response.text
+ return [json.loads(line[6:]) for line in response.text.splitlines() if line.startswith("data: ")]
+
+
+def user(text: str) -> dict:
+ return {"id": uuid4().hex, "role": "user", "content": text}
+
+
+def of_type(events: list[dict], name: str) -> list[dict]:
+ return [e for e in events if e["type"] == name]
+
+
+events = run(uuid4().hex, messages=[user("Hello")])
+assert events[0]["type"] == "RUN_STARTED" and events[-1]["type"] == "RUN_FINISHED", events
+text = "".join(e["delta"] for e in of_type(events, "TEXT_MESSAGE_CONTENT"))
+assert "scripted reply" in text, text
+assert len(of_type(events, "TEXT_MESSAGE_CONTENT")) > 1, "the reply did not stream"
+
+thread = uuid4().hex
+paused = run(thread, messages=[user("Send an email")])
+assert of_type(paused, "TOOL_CALL_START")[0]["toolCallName"] == "send_email", paused
+interrupts = paused[-1]["outcome"]["interrupts"]
+assert interrupts[0]["reason"] == "tool_call" and interrupts[0]["toolCallId"], interrupts
+
+resumed = run(thread, resume=[{"interruptId": interrupts[0]["id"], "status": "resolved", "payload": {"approved": True}}])
+result = of_type(resumed, "TOOL_CALL_RESULT")[0]["content"]
+assert "Email sent" in result, resumed
+assert of_type(resumed, "RUN_FINISHED")[-1].get("outcome", {"type": "success"})["type"] == "success", resumed
+
+print("ok")
diff --git a/langgraph-ag-ui/src/main.ts b/langgraph-ag-ui/src/main.ts
new file mode 100644
index 0000000..65b2f73
--- /dev/null
+++ b/langgraph-ag-ui/src/main.ts
@@ -0,0 +1,9 @@
+import '@aparte/core/styles.css';
+import { AparteClient } from '@aparte/core';
+import { setupMarkedProvider } from '@aparte/plugin-marked';
+import { createAgUiRunner } from '@aparte/runner-ag-ui';
+
+setupMarkedProvider();
+
+// The page knows nothing about LangGraph: it reads the agent's AG-UI stream.
+new AparteClient({ streamRunner: createAgUiRunner({ endpoint: '/agent' }) }).start();
diff --git a/langgraph-ag-ui/tsconfig.json b/langgraph-ag-ui/tsconfig.json
new file mode 100644
index 0000000..69fa3be
--- /dev/null
+++ b/langgraph-ag-ui/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "types": ["vite/client"]
+ },
+ "include": ["src", "vite.config.ts"]
+}
diff --git a/langgraph-ag-ui/vite.config.ts b/langgraph-ag-ui/vite.config.ts
new file mode 100644
index 0000000..f22eab8
--- /dev/null
+++ b/langgraph-ag-ui/vite.config.ts
@@ -0,0 +1,5 @@
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ server: { proxy: { '/agent': 'http://localhost:8000' } },
+});
diff --git a/pydantic-ai-ag-ui/.env.example b/pydantic-ai-ag-ui/.env.example
new file mode 100644
index 0000000..e959120
--- /dev/null
+++ b/pydantic-ai-ag-ui/.env.example
@@ -0,0 +1,5 @@
+# Optional. Without MODEL, a scripted model answers (scripted_model.py).
+MODEL=
+OPENAI_API_KEY=
+# For any other OpenAI-compatible API, a local one included.
+OPENAI_BASE_URL=
diff --git a/pydantic-ai-ag-ui/README.md b/pydantic-ai-ag-ui/README.md
new file mode 100644
index 0000000..411c627
--- /dev/null
+++ b/pydantic-ai-ag-ui/README.md
@@ -0,0 +1,36 @@
+# Pydantic AI + aparté
+
+A [Pydantic AI](https://ai.pydantic.dev) agent served over [AG-UI](https://docs.ag-ui.com), with [aparté](https://apartejs.dev) as its chat. Replies stream, tool calls show as they run, and one tool waits for a person before it runs.
+
+The page knows nothing about Pydantic AI: `@aparte/runner-ag-ui` reads the agent's AG-UI stream.
+
+## Run it
+
+The agent runs in Python, so this one runs on your machine and not on StackBlitz. Tested with Python 3.13 and Node 24.
+
+```bash
+npx degit apartejs/examples/pydantic-ai-ag-ui my-agent
+cd my-agent
+python -m venv .venv
+source .venv/bin/activate # on Windows: .venv\Scripts\activate
+pip install -r requirements.txt
+uvicorn agent:app --port 8000
+```
+
+In a second terminal:
+
+```bash
+npm install
+npm run dev
+```
+
+Open http://localhost:5173 and ask it to send an email.
+
+With nothing configured, a scripted model answers, and says it is scripted. For a real model, copy `.env.example` to `.env` and set `MODEL` and `OPENAI_API_KEY`. `OPENAI_BASE_URL` points it at any OpenAI-compatible API, a local one included.
+
+## What to look at
+
+- [`agent.py`](./agent.py): the agent and its route. `requires_approval=True` on the tool, with `DeferredToolRequests` in the agent's output, is all it takes to pause for a person. The agent keeps nothing between runs: the page sends the conversation back with its answer.
+- [`src/main.ts`](./src/main.ts): the whole page.
+- [`scripted_model.py`](./scripted_model.py): the stand-in model.
+- [`smoke_test.py`](./smoke_test.py): what CI runs against the route: a reply, a pause, and a resume.
diff --git a/pydantic-ai-ag-ui/agent.py b/pydantic-ai-ag-ui/agent.py
new file mode 100644
index 0000000..8bc0bdc
--- /dev/null
+++ b/pydantic-ai-ag-ui/agent.py
@@ -0,0 +1,41 @@
+"""A Pydantic AI agent served over AG-UI, with a tool that waits for a person."""
+
+import os
+
+from dotenv import load_dotenv
+from pydantic_ai import Agent, DeferredToolRequests
+from pydantic_ai.ui.ag_ui import AGUIAdapter
+from starlette.applications import Starlette
+from starlette.requests import Request
+from starlette.responses import Response
+from starlette.routing import Route
+
+load_dotenv()
+
+
+def make_model():
+ if os.environ.get("MODEL"):
+ from pydantic_ai.models.openai import OpenAIChatModel
+
+ return OpenAIChatModel(os.environ["MODEL"])
+
+ from scripted_model import scripted_model
+
+ return scripted_model
+
+
+# DeferredToolRequests in the output is what lets a run stop and wait for an approval.
+agent = Agent(make_model(), output_type=[str, DeferredToolRequests])
+
+
+@agent.tool_plain(requires_approval=True)
+def send_email(to: str, subject: str) -> str:
+ """Send an email. A person has to approve it first."""
+ return f"Email sent to {to}: {subject}"
+
+
+async def run_agent(request: Request) -> Response:
+ return await AGUIAdapter.dispatch_request(request, agent=agent)
+
+
+app = Starlette(routes=[Route("/agent", run_agent, methods=["POST"])])
diff --git a/pydantic-ai-ag-ui/index.html b/pydantic-ai-ag-ui/index.html
new file mode 100644
index 0000000..69787be
--- /dev/null
+++ b/pydantic-ai-ag-ui/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Pydantic AI + aparté
+
+
+
+
+
+
+
diff --git a/pydantic-ai-ag-ui/package-lock.json b/pydantic-ai-ag-ui/package-lock.json
new file mode 100644
index 0000000..99558ef
--- /dev/null
+++ b/pydantic-ai-ag-ui/package-lock.json
@@ -0,0 +1,891 @@
+{
+ "name": "pydantic-ai-ag-ui",
+ "version": "0.0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "pydantic-ai-ag-ui",
+ "version": "0.0.1",
+ "dependencies": {
+ "@aparte/core": "^1.0.2",
+ "@aparte/plugin-marked": "^1.0.2",
+ "@aparte/runner-ag-ui": "^1.0.2",
+ "marked": "^18.1.3"
+ },
+ "devDependencies": {
+ "typescript": "^6.0.3",
+ "vite": "^8.3.0"
+ }
+ },
+ "node_modules/@aparte/core": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/core/-/core-1.0.2.tgz",
+ "integrity": "sha512-fOZ7/B9lPPpGvzrF9YYWrqRFNGskm9g41b4QLE5eKa3UOySuWQIytfFUVHIkqV9y8F5NouO87BKGfj1Nmud5Qg==",
+ "license": "MIT",
+ "dependencies": {
+ "@aparte/engine": "1.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@aparte/engine": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/engine/-/engine-1.0.2.tgz",
+ "integrity": "sha512-ZkC5gL9tPanB48rm2bNXbK/0ZvV7h2K87ujJYLtFBaqZV/nUbRLRZtS4O2VLeeC/PbOQP6ve+UXIBaYYFSrFlw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@aparte/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@aparte/plugin-marked": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/plugin-marked/-/plugin-marked-1.0.2.tgz",
+ "integrity": "sha512-WVpKWNB+EZslxij8faXthOb16/H7EEruBvLfEmbvDC0FW55OT0L0k3yDy5yEp6yfiDkxnwZuXC4MT6OQeFxf8w==",
+ "license": "MIT",
+ "dependencies": {
+ "remend": "^1.4.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@aparte/core": ">=1.0.2 <2.0.0",
+ "marked": "^18.0.0"
+ }
+ },
+ "node_modules/@aparte/runner-ag-ui": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@aparte/runner-ag-ui/-/runner-ag-ui-1.0.2.tgz",
+ "integrity": "sha512-EaClxTttxf8Jim7Zqt2VLhTT39o5pIwuMOrRb4NQSG9xtIYm2dCFYf5y8+A3graZIklHGN/7B+zUaTxiFr+eiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@aparte/core": ">=1.0.2 <2.0.0"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.156.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz",
+ "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/oxc-project"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm-eabi": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz",
+ "integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz",
+ "integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz",
+ "integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz",
+ "integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz",
+ "integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz",
+ "integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz",
+ "integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz",
+ "integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz",
+ "integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz",
+ "integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz",
+ "integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz",
+ "integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz",
+ "integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz",
+ "integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz",
+ "integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "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"
+ },
+ "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/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "dev": true,
+ "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": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/marked": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-18.1.3.tgz",
+ "integrity": "sha512-9ChMH8EVVgjgID49nsL6IWxWk5c6GtAsq/xw7U1ggkjkU5AGuv68oLTVlFRtvcUYZtpbNyjyvGo3YDu0fGapiw==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz",
+ "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.20",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/remend": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/remend/-/remend-1.4.0.tgz",
+ "integrity": "sha512-mHiYhhwphs1IgIZmkToKLwJn0U99IjPXuutkA2acrNcG0vlEq2G4TGnd2eT0saIHwh2KjaCpvleHcsd5SAz96A==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz",
+ "integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.156.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm-eabi": "1.2.16",
+ "@rolldown/binding-android-arm64": "1.2.16",
+ "@rolldown/binding-darwin-arm64": "1.2.16",
+ "@rolldown/binding-darwin-x64": "1.2.16",
+ "@rolldown/binding-freebsd-x64": "1.2.16",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.16",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.16",
+ "@rolldown/binding-linux-arm64-musl": "1.2.16",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.16",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.16",
+ "@rolldown/binding-linux-x64-gnu": "1.2.16",
+ "@rolldown/binding-linux-x64-musl": "1.2.16",
+ "@rolldown/binding-openharmony-arm64": "1.2.16",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.16",
+ "@rolldown/binding-win32-x64-msvc": "1.2.16"
+ }
+ },
+ "node_modules/source-map-js": {
+ "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": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/typescript": {
+ "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": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.3.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.6.tgz",
+ "integrity": "sha512-UI7dK29NnS54Fk+mmo7o+jI6U8Fp7hDl+X5Q1k45WH+d4dSlbLVUETGQ+hfkNgvLQGyoElPj837nZyv4MBc0VA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.7",
+ "postcss": "^8.5.32",
+ "rolldown": "~1.2.16",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.7.1 || ^0.8.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "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"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/pydantic-ai-ag-ui/package.json b/pydantic-ai-ag-ui/package.json
new file mode 100644
index 0000000..80472a9
--- /dev/null
+++ b/pydantic-ai-ag-ui/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "pydantic-ai-ag-ui",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "check": "tsc"
+ },
+ "dependencies": {
+ "@aparte/core": "^1.0.2",
+ "@aparte/plugin-marked": "^1.0.2",
+ "@aparte/runner-ag-ui": "^1.0.2",
+ "marked": "^18.1.3"
+ },
+ "devDependencies": {
+ "typescript": "^6.0.3",
+ "vite": "^8.3.0"
+ }
+}
diff --git a/pydantic-ai-ag-ui/requirements.txt b/pydantic-ai-ag-ui/requirements.txt
new file mode 100644
index 0000000..075d9ca
--- /dev/null
+++ b/pydantic-ai-ag-ui/requirements.txt
@@ -0,0 +1,4 @@
+httpx==0.28.1
+pydantic-ai-slim[ag-ui,openai]==2.66.0
+python-dotenv==1.2.4
+uvicorn==0.54.0
diff --git a/pydantic-ai-ag-ui/scripted_model.py b/pydantic-ai-ag-ui/scripted_model.py
new file mode 100644
index 0000000..2a49449
--- /dev/null
+++ b/pydantic-ai-ag-ui/scripted_model.py
@@ -0,0 +1,27 @@
+"""Answers when no model is configured, and says so. It streams word by word, as a real model does."""
+
+import json
+from collections.abc import AsyncIterator
+from uuid import uuid4
+
+from pydantic_ai.messages import ModelMessage, ToolReturnPart
+from pydantic_ai.models.function import AgentInfo, DeltaToolCall, DeltaToolCalls, FunctionModel
+
+NOTE = "This is a scripted reply: set MODEL in .env to talk to a real model."
+
+
+async def stream(messages: list[ModelMessage], info: AgentInfo) -> AsyncIterator[str | DeltaToolCalls]:
+ last = messages[-1].parts[-1]
+ if isinstance(last, ToolReturnPart):
+ text = f"{last.content} {NOTE}"
+ elif "email" in str(last.content).lower():
+ args = {"to": "ada@example.com", "subject": "Hello from Pydantic AI"}
+ yield {0: DeltaToolCall(name="send_email", json_args=json.dumps(args), tool_call_id=f"call_{uuid4().hex}")}
+ return
+ else:
+ text = f"{NOTE} Ask me to **send an email** to see a tool wait for your approval."
+ for i, word in enumerate(text.split(" ")):
+ yield word if i == 0 else f" {word}"
+
+
+scripted_model = FunctionModel(stream_function=stream)
diff --git a/pydantic-ai-ag-ui/smoke_test.py b/pydantic-ai-ag-ui/smoke_test.py
new file mode 100644
index 0000000..cec093f
--- /dev/null
+++ b/pydantic-ai-ag-ui/smoke_test.py
@@ -0,0 +1,58 @@
+"""Runs the agent with the scripted model, through the same route the page calls. CI runs this file."""
+
+import json
+from uuid import uuid4
+
+from starlette.testclient import TestClient
+
+from agent import app
+
+client = TestClient(app)
+
+
+def run(thread_id: str, **body) -> list[dict]:
+ payload = {"threadId": thread_id, "runId": uuid4().hex, "state": {}, "messages": [], "tools": [], "context": [], "forwardedProps": {}, **body}
+ response = client.post("/agent", json=payload, headers={"accept": "text/event-stream"})
+ assert response.status_code == 200, response.text
+ return [json.loads(line[6:]) for line in response.text.splitlines() if line.startswith("data: ")]
+
+
+def user(text: str) -> dict:
+ return {"id": uuid4().hex, "role": "user", "content": text}
+
+
+def of_type(events: list[dict], name: str) -> list[dict]:
+ return [e for e in events if e["type"] == name]
+
+
+events = run(uuid4().hex, messages=[user("Hello")])
+assert events[0]["type"] == "RUN_STARTED" and events[-1]["type"] == "RUN_FINISHED", events
+text = "".join(e["delta"] for e in of_type(events, "TEXT_MESSAGE_CONTENT"))
+assert "scripted reply" in text, text
+assert len(of_type(events, "TEXT_MESSAGE_CONTENT")) > 1, "the reply did not stream"
+
+thread = uuid4().hex
+ask = user("Send an email")
+paused = run(thread, messages=[ask])
+call = of_type(paused, "TOOL_CALL_START")[0]
+assert call["toolCallName"] == "send_email", paused
+args = "".join(e["delta"] for e in of_type(paused, "TOOL_CALL_ARGS"))
+interrupts = paused[-1]["outcome"]["interrupts"]
+assert interrupts[0]["reason"] == "tool_call" and interrupts[0]["toolCallId"] == call["toolCallId"], interrupts
+
+# The agent keeps nothing between runs: the page sends the conversation back with its answer.
+assistant = {
+ "id": uuid4().hex,
+ "role": "assistant",
+ "toolCalls": [{"id": call["toolCallId"], "type": "function", "function": {"name": "send_email", "arguments": args}}],
+}
+resumed = run(
+ thread,
+ messages=[ask, assistant],
+ resume=[{"interruptId": interrupts[0]["id"], "status": "resolved", "payload": {"approved": True}}],
+)
+result = of_type(resumed, "TOOL_CALL_RESULT")[0]["content"]
+assert "Email sent" in result, resumed
+assert of_type(resumed, "RUN_FINISHED")[-1].get("outcome", {"type": "success"})["type"] == "success", resumed
+
+print("ok")
diff --git a/pydantic-ai-ag-ui/src/main.ts b/pydantic-ai-ag-ui/src/main.ts
new file mode 100644
index 0000000..5e182c6
--- /dev/null
+++ b/pydantic-ai-ag-ui/src/main.ts
@@ -0,0 +1,9 @@
+import '@aparte/core/styles.css';
+import { AparteClient } from '@aparte/core';
+import { setupMarkedProvider } from '@aparte/plugin-marked';
+import { createAgUiRunner } from '@aparte/runner-ag-ui';
+
+setupMarkedProvider();
+
+// The page knows nothing about Pydantic AI: it reads the agent's AG-UI stream.
+new AparteClient({ streamRunner: createAgUiRunner({ endpoint: '/agent' }) }).start();
diff --git a/pydantic-ai-ag-ui/tsconfig.json b/pydantic-ai-ag-ui/tsconfig.json
new file mode 100644
index 0000000..69fa3be
--- /dev/null
+++ b/pydantic-ai-ag-ui/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "types": ["vite/client"]
+ },
+ "include": ["src", "vite.config.ts"]
+}
diff --git a/pydantic-ai-ag-ui/vite.config.ts b/pydantic-ai-ag-ui/vite.config.ts
new file mode 100644
index 0000000..f22eab8
--- /dev/null
+++ b/pydantic-ai-ag-ui/vite.config.ts
@@ -0,0 +1,5 @@
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ server: { proxy: { '/agent': 'http://localhost:8000' } },
+});