diff --git a/apps/agent-app/.env.example b/apps/agent-app/.env.example
new file mode 100644
index 00000000..c062af54
--- /dev/null
+++ b/apps/agent-app/.env.example
@@ -0,0 +1,11 @@
+# Databricks workspace (auto-injected by platform on deploy)
+DATABRICKS_HOST=https://e2-dogfood.staging.cloud.databricks.com
+
+# Agent LLM endpoint
+DATABRICKS_AGENT_ENDPOINT=databricks-claude-sonnet-4-5
+
+# Analytics plugin — SQL warehouse ID
+DATABRICKS_WAREHOUSE_ID=dd43ee29fedd958d
+
+# Files plugin — Volume path
+DATABRICKS_VOLUME_FILES=/Volumes/main/mario/mario-vol
diff --git a/apps/agent-app/.gitignore b/apps/agent-app/.gitignore
new file mode 100644
index 00000000..9c97bbd4
--- /dev/null
+++ b/apps/agent-app/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+dist
+.env
diff --git a/apps/agent-app/app.yaml b/apps/agent-app/app.yaml
new file mode 100644
index 00000000..215b89ec
--- /dev/null
+++ b/apps/agent-app/app.yaml
@@ -0,0 +1,8 @@
+command: ['node', '--import', 'tsx', 'server.ts']
+env:
+ - name: DATABRICKS_WAREHOUSE_ID
+ valueFrom: sql-warehouse
+ - name: DATABRICKS_AGENT_ENDPOINT
+ valueFrom: serving-endpoint
+ - name: DATABRICKS_VOLUME_FILES
+ valueFrom: volume
diff --git a/apps/agent-app/config/agents/assistant.md b/apps/agent-app/config/agents/assistant.md
new file mode 100644
index 00000000..b2fe30fd
--- /dev/null
+++ b/apps/agent-app/config/agents/assistant.md
@@ -0,0 +1,11 @@
+---
+
+## default: true
+
+You are a helpful data assistant running on Databricks.
+
+Use the available tools to query data, browse files, and help users with their analysis.
+
+When using analytics.query, write Databricks SQL. When results are large, summarize the key findings rather than dumping raw data.
+
+You also have access to additional tools from MCP servers — use them when relevant.
\ No newline at end of file
diff --git a/apps/agent-app/databricks.yml b/apps/agent-app/databricks.yml
new file mode 100644
index 00000000..3ed6e50a
--- /dev/null
+++ b/apps/agent-app/databricks.yml
@@ -0,0 +1,50 @@
+bundle:
+ name: appkit-agent-app
+
+variables:
+ sql_warehouse_id:
+ description: SQL Warehouse ID for analytics queries
+ serving_endpoint_name:
+ description: Model Serving endpoint name for the agent LLM
+ volume_full_name:
+ description: "UC Volume full name (e.g. catalog.schema.volume_name)"
+
+resources:
+ apps:
+ agent_app:
+ name: "appkit-agent-app"
+ description: "AppKit agent with auto-discovered tools from analytics, files, and genie plugins"
+ source_code_path: ./
+
+ user_api_scopes:
+ - sql
+ - files.files
+ - dashboards.genie
+
+ resources:
+ - name: sql-warehouse
+ sql_warehouse:
+ id: ${var.sql_warehouse_id}
+ permission: CAN_USE
+
+ - name: serving-endpoint
+ serving_endpoint:
+ name: ${var.serving_endpoint_name}
+ permission: CAN_QUERY
+
+ - name: volume
+ uc_securable:
+ securable_type: VOLUME
+ securable_full_name: ${var.volume_full_name}
+ permission: WRITE_VOLUME
+
+targets:
+ dogfood:
+ default: true
+ workspace:
+ host: https://e2-dogfood.staging.cloud.databricks.com
+
+ variables:
+ sql_warehouse_id: dd43ee29fedd958d
+ serving_endpoint_name: databricks-claude-sonnet-4-5
+ volume_full_name: main.mario.mario-vol
diff --git a/apps/agent-app/index.html b/apps/agent-app/index.html
new file mode 100644
index 00000000..80e54faf
--- /dev/null
+++ b/apps/agent-app/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ AppKit Agent
+
+
+
+
+
+
diff --git a/apps/agent-app/package.json b/apps/agent-app/package.json
new file mode 100644
index 00000000..ed159ca8
--- /dev/null
+++ b/apps/agent-app/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "agent-app",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "NODE_ENV=development tsx watch server.ts",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@databricks/appkit": "workspace:*",
+ "@databricks/appkit-ui": "workspace:*",
+ "@databricks/sdk-experimental": "^0.16.0",
+ "dotenv": "^16.6.1",
+ "lucide-react": "^0.511.0",
+ "react": "19.2.0",
+ "react-dom": "19.2.0",
+ "marked": "^15.0.0",
+ "zod": "^4.0.0"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "4.1.17",
+ "@types/node": "24.10.1",
+ "@types/react": "19.2.7",
+ "@types/react-dom": "19.2.3",
+ "@vitejs/plugin-react": "5.1.1",
+ "autoprefixer": "10.4.21",
+ "postcss": "8.5.6",
+ "tailwindcss": "4.1.17",
+ "tailwindcss-animate": "1.0.7",
+ "tw-animate-css": "1.4.0",
+ "tsx": "4.20.6",
+ "typescript": "5.9.3",
+ "vite": "npm:rolldown-vite@7.1.14"
+ },
+ "overrides": {
+ "vite": "npm:rolldown-vite@7.1.14"
+ }
+}
diff --git a/apps/agent-app/postcss.config.js b/apps/agent-app/postcss.config.js
new file mode 100644
index 00000000..f69c5d41
--- /dev/null
+++ b/apps/agent-app/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ autoprefixer: {},
+ },
+};
diff --git a/apps/agent-app/server.ts b/apps/agent-app/server.ts
new file mode 100644
index 00000000..488ef211
--- /dev/null
+++ b/apps/agent-app/server.ts
@@ -0,0 +1,41 @@
+import {
+ analytics,
+ createAgent,
+ files,
+ mcpServer,
+ tool,
+} from "@databricks/appkit";
+import { z } from "zod";
+
+const port = Number(process.env.DATABRICKS_APP_PORT) || 8003;
+
+createAgent({
+ plugins: [analytics(), files()],
+ tools: [
+ tool({
+ name: "get_weather",
+ description: "Get the current weather for a city",
+ schema: z.object({
+ city: z.string().describe("City name"),
+ }),
+ execute: async ({ city }) => `The weather in ${city} is sunny, 22°C`,
+ }),
+ mcpServer(
+ "mario-mcp-hello",
+ "https://mario-mcp-hello-6051921418418893.staging.aws.databricksapps.com/mcp",
+ ),
+ mcpServer(
+ "vector-search",
+ "https://e2-dogfood.staging.cloud.databricks.com/api/2.0/mcp/vector-search/main/default",
+ ),
+ mcpServer(
+ "uc-greet",
+ "https://e2-dogfood.staging.cloud.databricks.com/api/2.0/mcp/functions/main/mario/greet",
+ ),
+ ],
+ port,
+}).then((agent) => {
+ console.log(
+ `Agent running on port ${port} with ${agent.getTools().length} tools`,
+ );
+});
diff --git a/apps/agent-app/src/App.css b/apps/agent-app/src/App.css
new file mode 100644
index 00000000..1928960d
--- /dev/null
+++ b/apps/agent-app/src/App.css
@@ -0,0 +1,362 @@
+:root {
+ --bg: #fafafa;
+ --card: #ffffff;
+ --border: #e5e5e5;
+ --text: #171717;
+ --text-muted: #737373;
+ --text-faint: #a3a3a3;
+ --primary: #2563eb;
+ --primary-fg: #ffffff;
+ --muted: #f5f5f5;
+ --ring: #93c5fd;
+ --radius: 10px;
+ --font: system-ui, -apple-system, sans-serif;
+ --mono: "SF Mono", "Cascadia Code", "Fira Code", monospace;
+}
+
+:root.dark {
+ --bg: #0a0a0a;
+ --card: #171717;
+ --border: #262626;
+ --text: #fafafa;
+ --text-muted: #a3a3a3;
+ --text-faint: #525252;
+ --primary: #3b82f6;
+ --primary-fg: #ffffff;
+ --muted: #262626;
+ --ring: #1d4ed8;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: var(--font);
+ background: var(--bg);
+ color: var(--text);
+ -webkit-font-smoothing: antialiased;
+}
+
+.app {
+ min-height: 100vh;
+}
+
+.container {
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 2.5rem 1.5rem;
+}
+
+.header {
+ margin-bottom: 1.5rem;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+}
+
+.header h1 {
+ font-size: 1.75rem;
+ font-weight: 700;
+ letter-spacing: -0.025em;
+}
+
+.subtitle {
+ color: var(--text-muted);
+ font-size: 0.875rem;
+ margin-top: 0.25rem;
+}
+
+.thread-id {
+ font-family: var(--mono);
+ font-size: 0.75rem;
+ opacity: 0.6;
+}
+
+.main-layout {
+ display: flex;
+ gap: 1.25rem;
+ height: 700px;
+}
+
+.chat-panel {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ background: var(--card);
+ min-width: 0;
+ overflow: hidden;
+}
+
+.messages {
+ flex: 1;
+ overflow-y: auto;
+ padding: 1.25rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 5rem 1rem;
+ color: var(--text-muted);
+}
+
+.empty-title {
+ font-size: 1.1rem;
+ font-weight: 500;
+}
+
+.empty-sub {
+ font-size: 0.85rem;
+ margin-top: 0.5rem;
+ color: var(--text-faint);
+}
+
+.message-row {
+ display: flex;
+}
+
+.message-row.user {
+ justify-content: flex-end;
+}
+
+.message-row.assistant {
+ justify-content: flex-start;
+}
+
+.bubble {
+ max-width: 80%;
+ padding: 0.625rem 0.875rem;
+ border-radius: var(--radius);
+ font-size: 0.875rem;
+ line-height: 1.5;
+ word-break: break-word;
+}
+
+.bubble.user {
+ white-space: pre-wrap;
+ background: var(--primary);
+ color: var(--primary-fg);
+ border-bottom-right-radius: 3px;
+}
+
+.bubble.assistant {
+ background: var(--muted);
+ color: var(--text);
+ border-bottom-left-radius: 3px;
+}
+
+.bubble.thinking {
+ color: var(--text-muted);
+ animation: pulse 1.5s ease-in-out infinite;
+}
+
+.bubble.assistant > * + * {
+ margin-top: 0.5em;
+}
+
+.bubble.assistant p {
+ margin: 0;
+}
+
+.bubble.assistant p + p {
+ margin-top: 0.4em;
+}
+
+.bubble.assistant code {
+ font-family: var(--mono);
+ font-size: 0.8em;
+ background: color-mix(in srgb, var(--text) 8%, transparent);
+ padding: 0.15em 0.35em;
+ border-radius: 4px;
+}
+
+.bubble.assistant pre {
+ margin: 0.5em 0;
+ padding: 0.75em;
+ border-radius: 6px;
+ background: color-mix(in srgb, var(--text) 6%, transparent);
+ overflow-x: auto;
+}
+
+.bubble.assistant pre code {
+ background: none;
+ padding: 0;
+ font-size: 0.8em;
+}
+
+.bubble.assistant ul,
+.bubble.assistant ol {
+ margin: 0.4em 0;
+ padding-left: 1.5em;
+}
+
+.bubble.assistant li {
+ margin: 0.15em 0;
+}
+
+.bubble.assistant h1,
+.bubble.assistant h2,
+.bubble.assistant h3 {
+ font-weight: 600;
+}
+
+.bubble.assistant h1 {
+ font-size: 1.1em;
+}
+.bubble.assistant h2 {
+ font-size: 1em;
+}
+.bubble.assistant h3 {
+ font-size: 0.95em;
+}
+
+.bubble.assistant blockquote {
+ margin: 0.4em 0;
+ padding-left: 0.75em;
+ border-left: 3px solid var(--border);
+ color: var(--text-muted);
+}
+
+.bubble.assistant table {
+ border-collapse: collapse;
+ margin: 0.5em 0;
+ font-size: 0.85em;
+}
+
+.bubble.assistant th,
+.bubble.assistant td {
+ border: 1px solid var(--border);
+ padding: 0.35em 0.6em;
+}
+
+.bubble.assistant th {
+ background: color-mix(in srgb, var(--text) 4%, transparent);
+ font-weight: 600;
+}
+
+@keyframes pulse {
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+}
+
+.input-bar {
+ display: flex;
+ gap: 0.5rem;
+ padding: 0.875rem 1rem;
+ border-top: 1px solid var(--border);
+}
+
+.input-bar textarea {
+ flex: 1;
+ padding: 0.5rem 0.75rem;
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ background: var(--bg);
+ color: var(--text);
+ font-family: var(--font);
+ font-size: 0.875rem;
+ resize: none;
+ outline: none;
+ transition: border-color 0.15s;
+}
+
+.input-bar textarea:focus {
+ border-color: var(--ring);
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--ring) 25%, transparent);
+}
+
+.input-bar textarea:disabled {
+ opacity: 0.5;
+}
+
+.input-bar button {
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 8px;
+ background: var(--primary);
+ color: var(--primary-fg);
+ font-family: var(--font);
+ font-size: 0.875rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: opacity 0.15s;
+ align-self: flex-end;
+}
+
+.input-bar button:hover:not(:disabled) {
+ opacity: 0.9;
+}
+
+.input-bar button:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
+.event-panel {
+ width: 300px;
+ flex-shrink: 0;
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ background: var(--card);
+ overflow: hidden;
+}
+
+.event-header {
+ padding: 0.625rem 0.875rem;
+ border-bottom: 1px solid var(--border);
+ font-size: 0.8rem;
+ font-weight: 600;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.event-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: 0.75rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.event-empty {
+ text-align: center;
+ padding: 2.5rem 0;
+ font-size: 0.75rem;
+ color: var(--text-faint);
+}
+
+.event-row {
+ font-family: var(--mono);
+ font-size: 0.7rem;
+ line-height: 1.4;
+ display: flex;
+ gap: 0.5rem;
+}
+
+.event-type {
+ flex-shrink: 0;
+ width: 90px;
+ text-align: right;
+ color: var(--text-faint);
+}
+
+.event-detail {
+ color: var(--text-muted);
+ word-break: break-all;
+}
diff --git a/apps/agent-app/src/App.tsx b/apps/agent-app/src/App.tsx
new file mode 100644
index 00000000..5c54997a
--- /dev/null
+++ b/apps/agent-app/src/App.tsx
@@ -0,0 +1,292 @@
+import { TooltipProvider } from "@databricks/appkit-ui/react";
+import { useCallback, useEffect, useRef, useState } from "react";
+import "./App.css";
+import { ThemeSelector } from "./components/theme-selector";
+
+interface SSEEvent {
+ type: string;
+ delta?: string;
+ item_id?: string;
+ item?: {
+ type?: string;
+ id?: string;
+ call_id?: string;
+ name?: string;
+ arguments?: string;
+ output?: string;
+ status?: string;
+ };
+ content?: string;
+ data?: Record;
+ error?: string;
+ sequence_number?: number;
+ output_index?: number;
+}
+
+interface ChatMessage {
+ id: number;
+ role: "user" | "assistant";
+ content: string;
+}
+
+export default function App() {
+ const [messages, setMessages] = useState([]);
+ const [events, setEvents] = useState([]);
+ const [input, setInput] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+ const [threadId, setThreadId] = useState(null);
+ const messagesEndRef = useRef(null);
+ const idRef = useRef(0);
+
+ const [toolCount, setToolCount] = useState(0);
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ fetch("/api/agent/info")
+ .then((r) => r.json())
+ .then((data) => setToolCount(data.toolCount ?? 0))
+ .catch(() => {});
+ }, 500);
+ return () => clearTimeout(timer);
+ }, []);
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies: scroll on new messages
+ useEffect(() => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ }, [messages]);
+
+ const sendMessage = useCallback(async () => {
+ if (!input.trim() || isLoading) return;
+
+ const text = input.trim();
+ setInput("");
+ setMessages((prev) => [
+ ...prev,
+ { id: ++idRef.current, role: "user", content: text },
+ ]);
+ setEvents([]);
+ setIsLoading(true);
+
+ try {
+ const res = await fetch("/api/agent/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ message: text,
+ ...(threadId && { threadId }),
+ }),
+ });
+
+ if (!res.ok) {
+ const err = await res.json();
+ setMessages((prev) => [
+ ...prev,
+ {
+ id: ++idRef.current,
+ role: "assistant",
+ content: `Error: ${err.error}`,
+ },
+ ]);
+ return;
+ }
+
+ const reader = res.body?.getReader();
+ if (!reader) return;
+
+ const decoder = new TextDecoder();
+ let content = "";
+ let buffer = "";
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ buffer += decoder.decode(value, { stream: true });
+ const lines = buffer.split("\n");
+ buffer = lines.pop() ?? "";
+
+ for (const line of lines) {
+ if (!line.startsWith("data: ")) continue;
+ const data = line.slice(6).trim();
+ if (!data || data === "[DONE]") continue;
+ try {
+ const event: SSEEvent = JSON.parse(data);
+ if (!event.type) continue;
+ setEvents((prev) => [...prev, event]);
+
+ if (event.type === "appkit.metadata" && event.data?.threadId) {
+ setThreadId(event.data.threadId as string);
+ }
+ if (event.type === "response.output_text.delta" && event.delta) {
+ content += event.delta;
+ setMessages((prev) => {
+ const updated = [...prev];
+ const last = updated[updated.length - 1];
+ if (last?.role === "assistant") {
+ updated[updated.length - 1] = { ...last, content };
+ } else {
+ updated.push({
+ id: ++idRef.current,
+ role: "assistant",
+ content,
+ });
+ }
+ return updated;
+ });
+ }
+ } catch {
+ /* skip */
+ }
+ }
+ }
+ } catch (err) {
+ setMessages((prev) => [
+ ...prev,
+ {
+ id: ++idRef.current,
+ role: "assistant",
+ content: `Error: ${err instanceof Error ? err.message : "Unknown error"}`,
+ },
+ ]);
+ } finally {
+ setIsLoading(false);
+ }
+ }, [input, isLoading, threadId]);
+
+ return (
+
+
+
+
+
+
+
+
+ {messages.length === 0 && (
+
+
+ Send a message to start a conversation
+
+
+ The agent can query data, browse files, and more
+
+
+ )}
+
+ {messages.map((msg) => (
+
+ ))}
+
+ {isLoading &&
+ messages[messages.length - 1]?.role === "user" && (
+
+ )}
+
+
+
+
+
+
+
+
+
Event Stream
+
+ {events.length === 0 && (
+
Events will appear here
+ )}
+ {events.map((event, i) => {
+ let detail: string;
+ switch (event.type) {
+ case "response.output_text.delta":
+ detail = event.delta?.slice(0, 60) ?? "";
+ break;
+ case "response.output_item.added":
+ case "response.output_item.done":
+ detail =
+ event.item?.type === "function_call"
+ ? `${event.item.name}(${(event.item.arguments ?? "").slice(0, 40)})`
+ : event.item?.type === "function_call_output"
+ ? (event.item.output?.slice(0, 60) ?? "")
+ : (event.item?.status ?? event.item?.type ?? "");
+ break;
+ case "response.completed":
+ detail = "done";
+ break;
+ case "error":
+ detail = event.error ?? "unknown";
+ break;
+ case "appkit.metadata":
+ detail = JSON.stringify(event.data).slice(0, 60);
+ break;
+ case "appkit.thinking":
+ detail = event.content?.slice(0, 60) ?? "";
+ break;
+ default:
+ detail = JSON.stringify(event).slice(0, 60);
+ }
+ return (
+
+
+ {event.type
+ .replace("response.", "")
+ .replace("appkit.", "")}
+
+ {detail}
+
+ );
+ })}
+
+
+
+
+
+
+ );
+}
diff --git a/apps/agent-app/src/components/theme-selector.tsx b/apps/agent-app/src/components/theme-selector.tsx
new file mode 100644
index 00000000..18bb4f14
--- /dev/null
+++ b/apps/agent-app/src/components/theme-selector.tsx
@@ -0,0 +1,135 @@
+import {
+ Button,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@databricks/appkit-ui/react";
+import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+
+type Theme = "light" | "dark" | "system";
+
+const THEME_STORAGE_KEY = "agent-app-theme";
+
+function getSystemTheme(): "light" | "dark" {
+ if (typeof window === "undefined") return "light";
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
+ ? "dark"
+ : "light";
+}
+
+function getStoredTheme(): Theme {
+ if (typeof window === "undefined") return "system";
+ const stored = localStorage.getItem(THEME_STORAGE_KEY);
+ return (stored as Theme) || "system";
+}
+
+function applyTheme(theme: Theme) {
+ if (typeof window === "undefined") return;
+
+ const root = document.documentElement;
+ root.classList.remove("light", "dark");
+
+ if (theme === "system") {
+ const systemTheme = getSystemTheme();
+ root.classList.add(systemTheme);
+ } else {
+ root.classList.add(theme);
+ }
+}
+
+export function ThemeSelector() {
+ const [theme, setTheme] = useState(() => getStoredTheme());
+ const [mounted, setMounted] = useState(false);
+ const [systemTheme, setSystemTheme] = useState<"light" | "dark">(() =>
+ getSystemTheme(),
+ );
+
+ useEffect(() => {
+ setMounted(true);
+ applyTheme(theme);
+ }, [theme]);
+
+ useEffect(() => {
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
+ const handleChange = (e: MediaQueryListEvent | MediaQueryList) => {
+ const isDark = e.matches;
+ setSystemTheme(isDark ? "dark" : "light");
+ if (theme === "system") {
+ applyTheme("system");
+ }
+ };
+
+ handleChange(mediaQuery);
+
+ if (mediaQuery.addEventListener) {
+ mediaQuery.addEventListener("change", handleChange);
+ return () => mediaQuery.removeEventListener("change", handleChange);
+ } else {
+ mediaQuery.addListener(handleChange);
+ return () => mediaQuery.removeListener(handleChange);
+ }
+ }, [theme]);
+
+ const handleThemeChange = (newTheme: Theme) => {
+ setTheme(newTheme);
+ localStorage.setItem(THEME_STORAGE_KEY, newTheme);
+ applyTheme(newTheme);
+ };
+
+ const effectiveTheme = theme === "system" ? systemTheme : theme;
+
+ if (!mounted) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {effectiveTheme === "dark" ? (
+
+ ) : (
+
+ )}
+
+
+
+ handleThemeChange("light")}
+ className="cursor-pointer"
+ >
+
+ Light
+ {theme === "light" && ✓ }
+
+ handleThemeChange("dark")}
+ className="cursor-pointer"
+ >
+
+ Dark
+ {theme === "dark" && ✓ }
+
+ handleThemeChange("system")}
+ className="cursor-pointer"
+ >
+
+ System
+ {theme === "system" && ✓ }
+
+
+
+ );
+}
diff --git a/apps/agent-app/src/index.css b/apps/agent-app/src/index.css
new file mode 100644
index 00000000..5dcc4cf8
--- /dev/null
+++ b/apps/agent-app/src/index.css
@@ -0,0 +1 @@
+@import "@databricks/appkit-ui/styles.css";
diff --git a/apps/agent-app/src/main.tsx b/apps/agent-app/src/main.tsx
new file mode 100644
index 00000000..98b62364
--- /dev/null
+++ b/apps/agent-app/src/main.tsx
@@ -0,0 +1,15 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App.tsx";
+import "./index.css";
+
+const rootElement = document.getElementById("root");
+if (!rootElement) {
+ throw new Error("Root element not found");
+}
+
+createRoot(rootElement).render(
+
+
+ ,
+);
diff --git a/apps/agent-app/tailwind.config.ts b/apps/agent-app/tailwind.config.ts
new file mode 100644
index 00000000..fad89bf6
--- /dev/null
+++ b/apps/agent-app/tailwind.config.ts
@@ -0,0 +1,11 @@
+import path from "node:path";
+import type { Config } from "tailwindcss";
+
+export default {
+ darkMode: ["class", "media"],
+ content: [
+ path.resolve(__dirname, "./index.html"),
+ path.resolve(__dirname, "./src/**/*.{js,ts,jsx,tsx}"),
+ ],
+ plugins: [require("tailwindcss-animate")],
+} satisfies Config;
diff --git a/apps/agent-app/tsconfig.app.json b/apps/agent-app/tsconfig.app.json
new file mode 100644
index 00000000..2877c218
--- /dev/null
+++ b/apps/agent-app/tsconfig.app.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/apps/agent-app/tsconfig.json b/apps/agent-app/tsconfig.json
new file mode 100644
index 00000000..1ffef600
--- /dev/null
+++ b/apps/agent-app/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/apps/agent-app/tsconfig.node.json b/apps/agent-app/tsconfig.node.json
new file mode 100644
index 00000000..35bcd118
--- /dev/null
+++ b/apps/agent-app/tsconfig.node.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "types": ["node"],
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/apps/agent-app/vite.config.ts b/apps/agent-app/vite.config.ts
new file mode 100644
index 00000000..bd1cea62
--- /dev/null
+++ b/apps/agent-app/vite.config.ts
@@ -0,0 +1,31 @@
+import path from "node:path";
+import react from "@vitejs/plugin-react";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [react()],
+ optimizeDeps: {
+ include: [
+ "react",
+ "react-dom",
+ "react/jsx-dev-runtime",
+ "react/jsx-runtime",
+ ],
+ exclude: ["@databricks/appkit-ui", "@databricks/appkit"],
+ },
+ server: {
+ hmr: {
+ port: 24679,
+ },
+ },
+ resolve: {
+ dedupe: ["react", "react-dom"],
+ preserveSymlinks: true,
+ alias: {
+ "@databricks/appkit-ui": path.resolve(
+ __dirname,
+ "../../packages/appkit-ui/dist",
+ ),
+ },
+ },
+});
diff --git a/apps/dev-playground/client/src/appkit-types/analytics.d.ts b/apps/dev-playground/client/src/appkit-types/analytics.d.ts
index 0e0ae0b0..43666dd0 100644
--- a/apps/dev-playground/client/src/appkit-types/analytics.d.ts
+++ b/apps/dev-playground/client/src/appkit-types/analytics.d.ts
@@ -119,10 +119,10 @@ declare module "@databricks/appkit-ui/react" {
result: Array<{
/** @sqlType STRING */
string_value: string;
- /** @sqlType STRING */
- number_value: string;
- /** @sqlType STRING */
- boolean_value: string;
+ /** @sqlType INT */
+ number_value: number;
+ /** @sqlType BOOLEAN */
+ boolean_value: boolean;
/** @sqlType STRING */
date_value: string;
/** @sqlType STRING */
diff --git a/apps/dev-playground/client/src/routes/__root.tsx b/apps/dev-playground/client/src/routes/__root.tsx
index bc7f1e34..0cfee693 100644
--- a/apps/dev-playground/client/src/routes/__root.tsx
+++ b/apps/dev-playground/client/src/routes/__root.tsx
@@ -104,20 +104,12 @@ function RootComponent() {
Files
-
+
- Serving
-
-
-
-
- Vector Search
+ Agent
diff --git a/apps/dev-playground/client/src/routes/agent.route.tsx b/apps/dev-playground/client/src/routes/agent.route.tsx
new file mode 100644
index 00000000..613d4d1f
--- /dev/null
+++ b/apps/dev-playground/client/src/routes/agent.route.tsx
@@ -0,0 +1,466 @@
+import { getPluginClientConfig } from "@databricks/appkit-ui/js";
+import { Button } from "@databricks/appkit-ui/react";
+import { createFileRoute } from "@tanstack/react-router";
+import { useCallback, useEffect, useRef, useState } from "react";
+
+export const Route = createFileRoute("/agent")({
+ component: AgentRoute,
+});
+
+interface SSEEvent {
+ type: string;
+ delta?: string;
+ item_id?: string;
+ item?: {
+ type?: string;
+ id?: string;
+ call_id?: string;
+ name?: string;
+ arguments?: string;
+ output?: string;
+ status?: string;
+ };
+ content?: string;
+ data?: Record;
+ error?: string;
+ sequence_number?: number;
+ output_index?: number;
+}
+
+interface ChatMessage {
+ id: number;
+ role: "user" | "assistant";
+ content: string;
+}
+
+function useAutocomplete(enabled: boolean) {
+ const [suggestion, setSuggestion] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+ const abortRef = useRef(null);
+ const timerRef = useRef | null>(null);
+
+ const requestSuggestion = useCallback(
+ (text: string) => {
+ setSuggestion("");
+
+ if (timerRef.current) clearTimeout(timerRef.current);
+ if (abortRef.current) abortRef.current.abort();
+
+ if (!text.trim() || text.length < 3 || !enabled) {
+ return;
+ }
+
+ timerRef.current = setTimeout(async () => {
+ const controller = new AbortController();
+ abortRef.current = controller;
+ setIsLoading(true);
+
+ try {
+ const response = await fetch("/api/agent/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ message: text, agent: "autocomplete" }),
+ signal: controller.signal,
+ });
+
+ if (!response.ok || !response.body) return;
+
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder();
+ let result = "";
+ let buffer = "";
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ buffer += decoder.decode(value, { stream: true });
+ const lines = buffer.split("\n");
+ buffer = lines.pop() ?? "";
+
+ for (const line of lines) {
+ if (!line.startsWith("data: ")) continue;
+ const data = line.slice(6).trim();
+ if (!data || data === "[DONE]") continue;
+ try {
+ const event = JSON.parse(data);
+ if (
+ event.type === "response.output_text.delta" &&
+ event.delta
+ ) {
+ result += event.delta;
+ setSuggestion(result);
+ }
+ } catch {
+ /* skip */
+ }
+ }
+ }
+ } catch {
+ /* aborted or failed */
+ } finally {
+ setIsLoading(false);
+ }
+ }, 500);
+ },
+ [enabled],
+ );
+
+ const clear = useCallback(() => {
+ setSuggestion("");
+ if (timerRef.current) clearTimeout(timerRef.current);
+ if (abortRef.current) abortRef.current.abort();
+ }, []);
+
+ return {
+ suggestion,
+ isLoading: isLoading && !suggestion,
+ requestSuggestion,
+ clear,
+ };
+}
+
+function AgentRoute() {
+ const [messages, setMessages] = useState([]);
+ const [events, setEvents] = useState([]);
+ const [input, setInput] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+ const [threadId, setThreadId] = useState(null);
+ const messagesEndRef = useRef(null);
+ const inputRef = useRef(null);
+ const msgIdCounter = useRef(0);
+
+ const agentConfig = getPluginClientConfig<{
+ agents?: string[];
+ defaultAgent?: string;
+ }>("agent");
+ const hasAutocomplete = (agentConfig.agents ?? []).includes("autocomplete");
+
+ const {
+ suggestion,
+ isLoading: isAutocompleting,
+ requestSuggestion,
+ clear: clearSuggestion,
+ } = useAutocomplete(hasAutocomplete);
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies: scroll on new messages
+ useEffect(() => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ }, [messages]);
+
+ const sendMessage = useCallback(async () => {
+ if (!input.trim() || isLoading) return;
+
+ clearSuggestion();
+ const userMessage = input.trim();
+ setInput("");
+ setMessages((prev) => [
+ ...prev,
+ { id: ++msgIdCounter.current, role: "user", content: userMessage },
+ ]);
+ setEvents([]);
+ setIsLoading(true);
+
+ try {
+ const response = await fetch("/api/agent/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ message: userMessage,
+ ...(threadId && { threadId }),
+ }),
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ setMessages((prev) => [
+ ...prev,
+ {
+ id: ++msgIdCounter.current,
+ role: "assistant",
+ content: `Error: ${error.error}`,
+ },
+ ]);
+ return;
+ }
+
+ const reader = response.body?.getReader();
+ if (!reader) return;
+
+ const decoder = new TextDecoder();
+ let assistantContent = "";
+ let buffer = "";
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ buffer += decoder.decode(value, { stream: true });
+ const lines = buffer.split("\n");
+ buffer = lines.pop() ?? "";
+
+ for (const line of lines) {
+ if (!line.startsWith("data: ")) continue;
+ const data = line.slice(6).trim();
+ if (!data || data === "[DONE]") continue;
+
+ try {
+ const event: SSEEvent = JSON.parse(data);
+ if (!event.type) continue;
+ setEvents((prev) => [...prev, event]);
+
+ if (event.type === "appkit.metadata" && event.data?.threadId) {
+ setThreadId(event.data.threadId as string);
+ }
+
+ if (event.type === "response.output_text.delta" && event.delta) {
+ assistantContent += event.delta;
+ setMessages((prev) => {
+ const updated = [...prev];
+ const last = updated[updated.length - 1];
+ if (last?.role === "assistant") {
+ updated[updated.length - 1] = {
+ ...last,
+ content: assistantContent,
+ };
+ } else {
+ updated.push({
+ id: ++msgIdCounter.current,
+ role: "assistant",
+ content: assistantContent,
+ });
+ }
+ return updated;
+ });
+ }
+ } catch {
+ // skip malformed events
+ }
+ }
+ }
+ } catch (err) {
+ setMessages((prev) => [
+ ...prev,
+ {
+ id: ++msgIdCounter.current,
+ role: "assistant",
+ content: `Error: ${err instanceof Error ? err.message : "Unknown error"}`,
+ },
+ ]);
+ } finally {
+ setIsLoading(false);
+ }
+ }, [input, isLoading, threadId, clearSuggestion]);
+
+ const handleInputChange = (value: string) => {
+ setInput(value);
+ requestSuggestion(value);
+ };
+
+ const acceptSuggestion = () => {
+ if (!suggestion) return;
+ const newValue = input + suggestion;
+ setInput(newValue);
+ clearSuggestion();
+ inputRef.current?.focus();
+ };
+
+ return (
+
+
+
+
+
Agent Chat
+
+ AI agent with auto-discovered tools from all AppKit plugins.
+ {threadId && (
+
+ Thread: {threadId.slice(0, 8)}...
+
+ )}
+
+
+ {hasAutocomplete && (
+
+ Autocomplete enabled
+
+ )}
+
+
+
+
+
+ {messages.length === 0 && (
+
+
+ Send a message to start a conversation
+
+
+ The agent can use analytics, files, genie, and lakebase
+ tools.
+ {hasAutocomplete && " Start typing for inline suggestions."}
+
+
+ )}
+
+ {messages.map((msg) => (
+
+ ))}
+
+ {isLoading && messages[messages.length - 1]?.role === "user" && (
+
+ )}
+
+
+
+
+
+ {hasAutocomplete && (suggestion || isAutocompleting) && (
+
+ {isAutocompleting && (
+ Thinking...
+ )}
+ {suggestion && (
+
+ Press{" "}
+
+ Tab
+ {" "}
+ to accept suggestion
+
+ )}
+
+ )}
+
{
+ e.preventDefault();
+ sendMessage();
+ }}
+ className="flex gap-2"
+ >
+
+
+ {input}
+
+ {suggestion}
+
+
+
handleInputChange(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Tab" && suggestion) {
+ e.preventDefault();
+ acceptSuggestion();
+ }
+ if (e.key === "Escape" && suggestion) {
+ clearSuggestion();
+ }
+ if (e.key === "Enter" && !e.shiftKey && !suggestion) {
+ e.preventDefault();
+ sendMessage();
+ }
+ }}
+ placeholder="Ask a question..."
+ disabled={isLoading}
+ rows={1}
+ className="w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50 resize-none"
+ />
+
+
+ Send
+
+
+
+
+
+
+
+
+ Event Stream
+
+
+
+ {events.length === 0 && (
+
+ Events will appear here
+
+ )}
+ {events.map((event, i) => {
+ let detail: string;
+ switch (event.type) {
+ case "response.output_text.delta":
+ detail = event.delta?.slice(0, 60) ?? "";
+ break;
+ case "response.output_item.added":
+ case "response.output_item.done":
+ detail =
+ event.item?.type === "function_call"
+ ? `${event.item.name}(${(event.item.arguments ?? "").slice(0, 40)})`
+ : event.item?.type === "function_call_output"
+ ? (event.item.output?.slice(0, 60) ?? "")
+ : (event.item?.status ?? event.item?.type ?? "");
+ break;
+ case "response.completed":
+ detail = "done";
+ break;
+ case "error":
+ detail = event.error ?? "unknown";
+ break;
+ case "appkit.metadata":
+ detail = JSON.stringify(event.data).slice(0, 60);
+ break;
+ case "appkit.thinking":
+ detail = event.content?.slice(0, 60) ?? "";
+ break;
+ default:
+ detail = JSON.stringify(event).slice(0, 60);
+ }
+ return (
+
+
+ {event.type
+ .replace("response.", "")
+ .replace("appkit.", "")}
+
+ {detail}
+
+ );
+ })}
+
+
+
+
+
+ );
+}
diff --git a/apps/dev-playground/client/src/routes/index.tsx b/apps/dev-playground/client/src/routes/index.tsx
index 934b1467..896a6e9d 100644
--- a/apps/dev-playground/client/src/routes/index.tsx
+++ b/apps/dev-playground/client/src/routes/index.tsx
@@ -222,17 +222,18 @@ function IndexRoute() {
- Model Serving
+ Custom Agent
- Chat with a Databricks Model Serving endpoint using streaming
- completions with real-time SSE responses.
+ AI agent powered by Databricks Model Serving with
+ auto-discovered tools from all AppKit plugins. Chat with your
+ data using natural language.
navigate({ to: "/serving" })}
+ onClick={() => navigate({ to: "/agent" })}
className="w-full"
>
- Try Model Serving
+ Chat with Agent
diff --git a/apps/dev-playground/config/agents/assistant.md b/apps/dev-playground/config/agents/assistant.md
new file mode 100644
index 00000000..aa6701ec
--- /dev/null
+++ b/apps/dev-playground/config/agents/assistant.md
@@ -0,0 +1,5 @@
+---
+
+## default: true
+
+You are a helpful data assistant. Use the available tools to query data and help users with their analysis.
\ No newline at end of file
diff --git a/apps/dev-playground/config/agents/autocomplete.md b/apps/dev-playground/config/agents/autocomplete.md
new file mode 100644
index 00000000..3475562a
--- /dev/null
+++ b/apps/dev-playground/config/agents/autocomplete.md
@@ -0,0 +1,6 @@
+---
+
+## endpoint: databricks-gemini-3-1-flash-lite
+maxSteps: 1
+
+You are an autocomplete engine. The user will give you the beginning of a sentence or paragraph. Continue the text naturally, as if you are the same author. Do NOT repeat the input. Only output the continuation. Do NOT use tools. Do NOT explain. Just write the next words.
\ No newline at end of file
diff --git a/apps/dev-playground/package.json b/apps/dev-playground/package.json
index c8256100..460f0866 100644
--- a/apps/dev-playground/package.json
+++ b/apps/dev-playground/package.json
@@ -36,6 +36,8 @@
"dotenv": "16.6.1",
"tsdown": "0.20.3",
"tsx": "4.20.6",
+ "@ai-sdk/openai": "1.0.0",
+ "ai": "4.0.0",
"vite": "npm:rolldown-vite@7.1.14"
},
"overrides": {
diff --git a/apps/dev-playground/server/index.ts b/apps/dev-playground/server/index.ts
index 913f547c..bf9207cb 100644
--- a/apps/dev-playground/server/index.ts
+++ b/apps/dev-playground/server/index.ts
@@ -1,15 +1,13 @@
import "reflect-metadata";
import {
+ agent,
analytics,
createApp,
files,
genie,
server,
- serving,
} from "@databricks/appkit";
import { WorkspaceClient } from "@databricks/sdk-experimental";
-// TODO: re-enable once vector-search is exported from @databricks/appkit
-// import { vectorSearch } from "@databricks/appkit";
import { lakebaseExamples } from "./lakebase-examples-plugin";
import { reconnect } from "./reconnect-plugin";
import { telemetryExamples } from "./telemetry-example-plugin";
@@ -35,18 +33,7 @@ createApp({
}),
lakebaseExamples(),
files(),
- serving(),
- // TODO: re-enable once vector-search is exported from @databricks/appkit
- // vectorSearch({
- // indexes: {
- // demo: {
- // indexName:
- // process.env.DATABRICKS_VS_INDEX_NAME ?? "catalog.schema.index",
- // columns: ["id", "text", "title"],
- // queryType: "hybrid",
- // },
- // },
- // }),
+ agent(),
],
...(process.env.APPKIT_E2E_TEST && { client: createMockClient() }),
}).then((appkit) => {
diff --git a/biome.json b/biome.json
index c9b8a4a1..31996d33 100644
--- a/biome.json
+++ b/biome.json
@@ -20,7 +20,8 @@
"!**/*.gen.css",
"!**/*.gen.ts",
"!**/typedoc-sidebar.ts",
- "!**/template"
+ "!**/template",
+ "!**/config/agents"
]
},
"formatter": {
diff --git a/docs/docs/api/appkit/Class.Plugin.md b/docs/docs/api/appkit/Class.Plugin.md
index 06e558dc..dcb963f7 100644
--- a/docs/docs/api/appkit/Class.Plugin.md
+++ b/docs/docs/api/appkit/Class.Plugin.md
@@ -136,6 +136,14 @@ protected config: TConfig;
***
+### context?
+
+```ts
+protected optional context: PluginContext;
+```
+
+***
+
### devFileReader
```ts
diff --git a/docs/docs/api/appkit/Function.createAgent.md b/docs/docs/api/appkit/Function.createAgent.md
new file mode 100644
index 00000000..6981a315
--- /dev/null
+++ b/docs/docs/api/appkit/Function.createAgent.md
@@ -0,0 +1,52 @@
+# Function: createAgent()
+
+```ts
+function createAgent(config: CreateAgentConfig): Promise;
+```
+
+Creates an agent-powered app with batteries included.
+
+Wraps `createApp` with `server()` and `agent()` pre-configured.
+Automatically starts an HTTP server with agent chat routes.
+
+For apps that need custom routes or manual server control,
+use `createApp` with `server()` and `agent()` directly.
+
+## Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `config` | [`CreateAgentConfig`](Interface.CreateAgentConfig.md) |
+
+## Returns
+
+`Promise`\<[`AgentHandle`](Interface.AgentHandle.md)\>
+
+## Examples
+
+```ts
+import { createAgent, analytics } from "@databricks/appkit";
+import { DatabricksAdapter } from "@databricks/appkit/agents/databricks";
+
+createAgent({
+ plugins: [analytics()],
+ adapter: DatabricksAdapter.fromServingEndpoint({
+ workspaceClient: new WorkspaceClient({}),
+ endpointName: "databricks-claude-sonnet-4-5",
+ systemPrompt: "You are a data assistant...",
+ }),
+}).then(agent => {
+ console.log("Tools:", agent.getTools());
+});
+```
+
+```ts
+createAgent({
+ plugins: [analytics(), files()],
+ agents: {
+ assistant: DatabricksAdapter.fromServingEndpoint({ ... }),
+ autocomplete: DatabricksAdapter.fromServingEndpoint({ ... }),
+ },
+ defaultAgent: "assistant",
+});
+```
diff --git a/docs/docs/api/appkit/Function.isFunctionTool.md b/docs/docs/api/appkit/Function.isFunctionTool.md
new file mode 100644
index 00000000..ebd84ee4
--- /dev/null
+++ b/docs/docs/api/appkit/Function.isFunctionTool.md
@@ -0,0 +1,15 @@
+# Function: isFunctionTool()
+
+```ts
+function isFunctionTool(value: unknown): value is FunctionTool;
+```
+
+## Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `value` | `unknown` |
+
+## Returns
+
+`value is FunctionTool`
diff --git a/docs/docs/api/appkit/Function.isHostedTool.md b/docs/docs/api/appkit/Function.isHostedTool.md
new file mode 100644
index 00000000..73be7e16
--- /dev/null
+++ b/docs/docs/api/appkit/Function.isHostedTool.md
@@ -0,0 +1,15 @@
+# Function: isHostedTool()
+
+```ts
+function isHostedTool(value: unknown): value is HostedTool;
+```
+
+## Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `value` | `unknown` |
+
+## Returns
+
+`value is HostedTool`
diff --git a/docs/docs/api/appkit/Function.mcpServer.md b/docs/docs/api/appkit/Function.mcpServer.md
new file mode 100644
index 00000000..cafd4657
--- /dev/null
+++ b/docs/docs/api/appkit/Function.mcpServer.md
@@ -0,0 +1,26 @@
+# Function: mcpServer()
+
+```ts
+function mcpServer(name: string, url: string): CustomMcpServerTool;
+```
+
+Factory for declaring a custom MCP server tool.
+
+Replaces the verbose `{ type: "custom_mcp_server", custom_mcp_server: { app_name, app_url } }`
+wrapper with a concise positional call.
+
+Example:
+```ts
+mcpServer("my-app", "https://my-app.databricksapps.com/mcp")
+```
+
+## Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `name` | `string` |
+| `url` | `string` |
+
+## Returns
+
+`CustomMcpServerTool`
diff --git a/docs/docs/api/appkit/Function.tool.md b/docs/docs/api/appkit/Function.tool.md
new file mode 100644
index 00000000..d6799cfd
--- /dev/null
+++ b/docs/docs/api/appkit/Function.tool.md
@@ -0,0 +1,29 @@
+# Function: tool()
+
+```ts
+function tool(config: ToolConfig): FunctionTool;
+```
+
+Factory for defining function tools with Zod schemas.
+
+- Generates JSON Schema (for the LLM) from the Zod schema via `z.toJSONSchema()`.
+- Infers the `execute` argument type from the schema.
+- Validates tool call arguments at runtime. On validation failure, returns
+ a formatted error string to the LLM instead of throwing, so the model
+ can self-correct on its next turn.
+
+## Type Parameters
+
+| Type Parameter |
+| ------ |
+| `S` *extends* `ZodType`\<`unknown`, `unknown`, `$ZodTypeInternals`\<`unknown`, `unknown`\>\> |
+
+## Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `config` | [`ToolConfig`](Interface.ToolConfig.md)\<`S`\> |
+
+## Returns
+
+[`FunctionTool`](Interface.FunctionTool.md)
diff --git a/docs/docs/api/appkit/Interface.AgentAdapter.md b/docs/docs/api/appkit/Interface.AgentAdapter.md
new file mode 100644
index 00000000..52083157
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.AgentAdapter.md
@@ -0,0 +1,20 @@
+# Interface: AgentAdapter
+
+## Methods
+
+### run()
+
+```ts
+run(input: AgentInput, context: AgentRunContext): AsyncGenerator;
+```
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `input` | [`AgentInput`](Interface.AgentInput.md) |
+| `context` | [`AgentRunContext`](Interface.AgentRunContext.md) |
+
+#### Returns
+
+`AsyncGenerator`\<[`AgentEvent`](TypeAlias.AgentEvent.md), `void`, `unknown`\>
diff --git a/docs/docs/api/appkit/Interface.AgentHandle.md b/docs/docs/api/appkit/Interface.AgentHandle.md
new file mode 100644
index 00000000..d630567d
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.AgentHandle.md
@@ -0,0 +1,86 @@
+# Interface: AgentHandle
+
+## Properties
+
+### addTools()
+
+```ts
+addTools: (tools: FunctionTool[]) => void;
+```
+
+Add function tools at runtime (HostedTools must be configured at setup).
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `tools` | [`FunctionTool`](Interface.FunctionTool.md)[] |
+
+#### Returns
+
+`void`
+
+***
+
+### getThreads()
+
+```ts
+getThreads: (userId: string) => Promise;
+```
+
+List threads for a user.
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `userId` | `string` |
+
+#### Returns
+
+`Promise`\<`unknown`\>
+
+***
+
+### getTools()
+
+```ts
+getTools: () => AgentToolDefinition[];
+```
+
+Get all tool definitions available to agents.
+
+#### Returns
+
+[`AgentToolDefinition`](Interface.AgentToolDefinition.md)[]
+
+***
+
+### plugins
+
+```ts
+plugins: Record;
+```
+
+Access to user-provided plugin APIs.
+
+***
+
+### registerAgent()
+
+```ts
+registerAgent: (name: string, adapter: AgentAdapter) => void;
+```
+
+Register an additional agent at runtime.
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `name` | `string` |
+| `adapter` | [`AgentAdapter`](Interface.AgentAdapter.md) |
+
+#### Returns
+
+`void`
diff --git a/docs/docs/api/appkit/Interface.AgentInput.md b/docs/docs/api/appkit/Interface.AgentInput.md
new file mode 100644
index 00000000..6d2eff8b
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.AgentInput.md
@@ -0,0 +1,33 @@
+# Interface: AgentInput
+
+## Properties
+
+### messages
+
+```ts
+messages: Message[];
+```
+
+***
+
+### signal?
+
+```ts
+optional signal: AbortSignal;
+```
+
+***
+
+### threadId
+
+```ts
+threadId: string;
+```
+
+***
+
+### tools
+
+```ts
+tools: AgentToolDefinition[];
+```
diff --git a/docs/docs/api/appkit/Interface.AgentRunContext.md b/docs/docs/api/appkit/Interface.AgentRunContext.md
new file mode 100644
index 00000000..c9bfcb79
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.AgentRunContext.md
@@ -0,0 +1,28 @@
+# Interface: AgentRunContext
+
+## Properties
+
+### executeTool()
+
+```ts
+executeTool: (name: string, args: unknown) => Promise;
+```
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `name` | `string` |
+| `args` | `unknown` |
+
+#### Returns
+
+`Promise`\<`unknown`\>
+
+***
+
+### signal?
+
+```ts
+optional signal: AbortSignal;
+```
diff --git a/docs/docs/api/appkit/Interface.AgentToolDefinition.md b/docs/docs/api/appkit/Interface.AgentToolDefinition.md
new file mode 100644
index 00000000..51c37595
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.AgentToolDefinition.md
@@ -0,0 +1,33 @@
+# Interface: AgentToolDefinition
+
+## Properties
+
+### annotations?
+
+```ts
+optional annotations: ToolAnnotations;
+```
+
+***
+
+### description
+
+```ts
+description: string;
+```
+
+***
+
+### name
+
+```ts
+name: string;
+```
+
+***
+
+### parameters
+
+```ts
+parameters: JSONSchema7;
+```
diff --git a/docs/docs/api/appkit/Interface.CreateAgentConfig.md b/docs/docs/api/appkit/Interface.CreateAgentConfig.md
new file mode 100644
index 00000000..67b6c2a2
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.CreateAgentConfig.md
@@ -0,0 +1,105 @@
+# Interface: CreateAgentConfig
+
+## Properties
+
+### adapter?
+
+```ts
+optional adapter:
+ | AgentAdapter
+| Promise;
+```
+
+Single agent adapter (mutually exclusive with `agents`). Registered as "assistant".
+
+***
+
+### agents?
+
+```ts
+optional agents: Record>;
+```
+
+Multiple named agents (mutually exclusive with `adapter`).
+
+***
+
+### cache?
+
+```ts
+optional cache: CacheConfig;
+```
+
+Cache configuration.
+
+***
+
+### client?
+
+```ts
+optional client: WorkspaceClient;
+```
+
+Pre-configured WorkspaceClient.
+
+***
+
+### defaultAgent?
+
+```ts
+optional defaultAgent: string;
+```
+
+Which agent to use when the client doesn't specify one.
+
+***
+
+### host?
+
+```ts
+optional host: string;
+```
+
+Server host. Defaults to FLASK_RUN_HOST or 0.0.0.0.
+
+***
+
+### plugins?
+
+```ts
+optional plugins: PluginData[];
+```
+
+Tool-providing plugins (analytics, files, genie, lakebase, etc.)
+
+***
+
+### port?
+
+```ts
+optional port: number;
+```
+
+Server port. Defaults to DATABRICKS_APP_PORT or 8000.
+
+***
+
+### telemetry?
+
+```ts
+optional telemetry: TelemetryConfig;
+```
+
+Telemetry configuration.
+
+***
+
+### tools?
+
+```ts
+optional tools: AgentTool[];
+```
+
+Explicit tools (FunctionTool, HostedTool) alongside auto-discovered ToolProvider tools.
diff --git a/docs/docs/api/appkit/Interface.FunctionTool.md b/docs/docs/api/appkit/Interface.FunctionTool.md
new file mode 100644
index 00000000..c096daca
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.FunctionTool.md
@@ -0,0 +1,59 @@
+# Interface: FunctionTool
+
+## Properties
+
+### description?
+
+```ts
+optional description: string | null;
+```
+
+***
+
+### execute()
+
+```ts
+execute: (args: Record) => string | Promise;
+```
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `args` | `Record`\<`string`, `unknown`\> |
+
+#### Returns
+
+`string` \| `Promise`\<`string`\>
+
+***
+
+### name
+
+```ts
+name: string;
+```
+
+***
+
+### parameters?
+
+```ts
+optional parameters: Record | null;
+```
+
+***
+
+### strict?
+
+```ts
+optional strict: boolean | null;
+```
+
+***
+
+### type
+
+```ts
+type: "function";
+```
diff --git a/docs/docs/api/appkit/Interface.Message.md b/docs/docs/api/appkit/Interface.Message.md
new file mode 100644
index 00000000..ed818408
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.Message.md
@@ -0,0 +1,49 @@
+# Interface: Message
+
+## Properties
+
+### content
+
+```ts
+content: string;
+```
+
+***
+
+### createdAt
+
+```ts
+createdAt: Date;
+```
+
+***
+
+### id
+
+```ts
+id: string;
+```
+
+***
+
+### role
+
+```ts
+role: "user" | "assistant" | "system" | "tool";
+```
+
+***
+
+### toolCallId?
+
+```ts
+optional toolCallId: string;
+```
+
+***
+
+### toolCalls?
+
+```ts
+optional toolCalls: ToolCall[];
+```
diff --git a/docs/docs/api/appkit/Interface.Thread.md b/docs/docs/api/appkit/Interface.Thread.md
new file mode 100644
index 00000000..e9f15fee
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.Thread.md
@@ -0,0 +1,41 @@
+# Interface: Thread
+
+## Properties
+
+### createdAt
+
+```ts
+createdAt: Date;
+```
+
+***
+
+### id
+
+```ts
+id: string;
+```
+
+***
+
+### messages
+
+```ts
+messages: Message[];
+```
+
+***
+
+### updatedAt
+
+```ts
+updatedAt: Date;
+```
+
+***
+
+### userId
+
+```ts
+userId: string;
+```
diff --git a/docs/docs/api/appkit/Interface.ThreadStore.md b/docs/docs/api/appkit/Interface.ThreadStore.md
new file mode 100644
index 00000000..215b76a2
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.ThreadStore.md
@@ -0,0 +1,98 @@
+# Interface: ThreadStore
+
+## Methods
+
+### addMessage()
+
+```ts
+addMessage(
+ threadId: string,
+ userId: string,
+message: Message): Promise;
+```
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `threadId` | `string` |
+| `userId` | `string` |
+| `message` | [`Message`](Interface.Message.md) |
+
+#### Returns
+
+`Promise`\<`void`\>
+
+***
+
+### create()
+
+```ts
+create(userId: string): Promise;
+```
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `userId` | `string` |
+
+#### Returns
+
+`Promise`\<[`Thread`](Interface.Thread.md)\>
+
+***
+
+### delete()
+
+```ts
+delete(threadId: string, userId: string): Promise;
+```
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `threadId` | `string` |
+| `userId` | `string` |
+
+#### Returns
+
+`Promise`\<`boolean`\>
+
+***
+
+### get()
+
+```ts
+get(threadId: string, userId: string): Promise;
+```
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `threadId` | `string` |
+| `userId` | `string` |
+
+#### Returns
+
+`Promise`\<[`Thread`](Interface.Thread.md) \| `null`\>
+
+***
+
+### list()
+
+```ts
+list(userId: string): Promise;
+```
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `userId` | `string` |
+
+#### Returns
+
+`Promise`\<[`Thread`](Interface.Thread.md)[]\>
diff --git a/docs/docs/api/appkit/Interface.ToolConfig.md b/docs/docs/api/appkit/Interface.ToolConfig.md
new file mode 100644
index 00000000..48828a38
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.ToolConfig.md
@@ -0,0 +1,49 @@
+# Interface: ToolConfig\
+
+## Type Parameters
+
+| Type Parameter |
+| ------ |
+| `S` *extends* `z.ZodType` |
+
+## Properties
+
+### description?
+
+```ts
+optional description: string;
+```
+
+***
+
+### execute()
+
+```ts
+execute: (args: output) => string | Promise;
+```
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `args` | `output`\<`S`\> |
+
+#### Returns
+
+`string` \| `Promise`\<`string`\>
+
+***
+
+### name
+
+```ts
+name: string;
+```
+
+***
+
+### schema
+
+```ts
+schema: S;
+```
diff --git a/docs/docs/api/appkit/Interface.ToolProvider.md b/docs/docs/api/appkit/Interface.ToolProvider.md
new file mode 100644
index 00000000..9c8851a0
--- /dev/null
+++ b/docs/docs/api/appkit/Interface.ToolProvider.md
@@ -0,0 +1,36 @@
+# Interface: ToolProvider
+
+## Methods
+
+### executeAgentTool()
+
+```ts
+executeAgentTool(
+ name: string,
+ args: unknown,
+signal?: AbortSignal): Promise;
+```
+
+#### Parameters
+
+| Parameter | Type |
+| ------ | ------ |
+| `name` | `string` |
+| `args` | `unknown` |
+| `signal?` | `AbortSignal` |
+
+#### Returns
+
+`Promise`\<`unknown`\>
+
+***
+
+### getAgentTools()
+
+```ts
+getAgentTools(): AgentToolDefinition[];
+```
+
+#### Returns
+
+[`AgentToolDefinition`](Interface.AgentToolDefinition.md)[]
diff --git a/docs/docs/api/appkit/TypeAlias.AgentEvent.md b/docs/docs/api/appkit/TypeAlias.AgentEvent.md
new file mode 100644
index 00000000..7c7cd92c
--- /dev/null
+++ b/docs/docs/api/appkit/TypeAlias.AgentEvent.md
@@ -0,0 +1,38 @@
+# Type Alias: AgentEvent
+
+```ts
+type AgentEvent =
+ | {
+ content: string;
+ type: "message_delta";
+}
+ | {
+ content: string;
+ type: "message";
+}
+ | {
+ args: unknown;
+ callId: string;
+ name: string;
+ type: "tool_call";
+}
+ | {
+ callId: string;
+ error?: string;
+ result: unknown;
+ type: "tool_result";
+}
+ | {
+ content: string;
+ type: "thinking";
+}
+ | {
+ error?: string;
+ status: "running" | "waiting" | "complete" | "error";
+ type: "status";
+}
+ | {
+ data: Record;
+ type: "metadata";
+};
+```
diff --git a/docs/docs/api/appkit/TypeAlias.AgentTool.md b/docs/docs/api/appkit/TypeAlias.AgentTool.md
new file mode 100644
index 00000000..8a9da8f0
--- /dev/null
+++ b/docs/docs/api/appkit/TypeAlias.AgentTool.md
@@ -0,0 +1,7 @@
+# Type Alias: AgentTool
+
+```ts
+type AgentTool =
+ | FunctionTool
+ | HostedTool;
+```
diff --git a/docs/docs/api/appkit/TypeAlias.HostedTool.md b/docs/docs/api/appkit/TypeAlias.HostedTool.md
new file mode 100644
index 00000000..433c0ac8
--- /dev/null
+++ b/docs/docs/api/appkit/TypeAlias.HostedTool.md
@@ -0,0 +1,9 @@
+# Type Alias: HostedTool
+
+```ts
+type HostedTool =
+ | GenieTool
+ | VectorSearchIndexTool
+ | CustomMcpServerTool
+ | ExternalMcpServerTool;
+```
diff --git a/docs/docs/api/appkit/index.md b/docs/docs/api/appkit/index.md
index f5163db4..31c79fe8 100644
--- a/docs/docs/api/appkit/index.md
+++ b/docs/docs/api/appkit/index.md
@@ -30,13 +30,21 @@ plugin architecture, and React integration.
| Interface | Description |
| ------ | ------ |
+| [AgentAdapter](Interface.AgentAdapter.md) | - |
+| [AgentHandle](Interface.AgentHandle.md) | - |
+| [AgentInput](Interface.AgentInput.md) | - |
+| [AgentRunContext](Interface.AgentRunContext.md) | - |
+| [AgentToolDefinition](Interface.AgentToolDefinition.md) | - |
| [BasePluginConfig](Interface.BasePluginConfig.md) | Base configuration interface for AppKit plugins |
| [CacheConfig](Interface.CacheConfig.md) | Configuration for the CacheInterceptor. Controls TTL, size limits, storage backend, and probabilistic cleanup. |
+| [CreateAgentConfig](Interface.CreateAgentConfig.md) | - |
| [DatabaseCredential](Interface.DatabaseCredential.md) | Database credentials with OAuth token for Postgres connection |
| [EndpointConfig](Interface.EndpointConfig.md) | - |
+| [FunctionTool](Interface.FunctionTool.md) | - |
| [GenerateDatabaseCredentialRequest](Interface.GenerateDatabaseCredentialRequest.md) | Request parameters for generating database OAuth credentials |
| [ITelemetry](Interface.ITelemetry.md) | Plugin-facing interface for OpenTelemetry instrumentation. Provides a thin abstraction over OpenTelemetry APIs for plugins. |
| [LakebasePoolConfig](Interface.LakebasePoolConfig.md) | Configuration for creating a Lakebase connection pool |
+| [Message](Interface.Message.md) | - |
| [PluginManifest](Interface.PluginManifest.md) | Plugin manifest that declares metadata and resource requirements. Attached to plugin classes as a static property. Extends the shared PluginManifest with strict resource types. |
| [RequestedClaims](Interface.RequestedClaims.md) | Optional claims for fine-grained Unity Catalog table permissions When specified, the returned token will be scoped to only the requested tables |
| [RequestedResource](Interface.RequestedResource.md) | Resource to request permissions for in Unity Catalog |
@@ -47,14 +55,21 @@ plugin architecture, and React integration.
| [ServingEndpointRegistry](Interface.ServingEndpointRegistry.md) | Registry interface for serving endpoint type generation. Empty by default — augmented by the Vite type generator's `.d.ts` output via module augmentation. When populated, provides autocomplete for alias names and typed request/response/chunk per endpoint. |
| [StreamExecutionSettings](Interface.StreamExecutionSettings.md) | Execution settings for streaming endpoints. Extends PluginExecutionSettings with SSE stream configuration. |
| [TelemetryConfig](Interface.TelemetryConfig.md) | OpenTelemetry configuration for AppKit applications |
+| [Thread](Interface.Thread.md) | - |
+| [ThreadStore](Interface.ThreadStore.md) | - |
+| [ToolConfig](Interface.ToolConfig.md) | - |
+| [ToolProvider](Interface.ToolProvider.md) | - |
| [ValidationResult](Interface.ValidationResult.md) | Result of validating all registered resources against the environment. |
## Type Aliases
| Type Alias | Description |
| ------ | ------ |
+| [AgentEvent](TypeAlias.AgentEvent.md) | - |
+| [AgentTool](TypeAlias.AgentTool.md) | - |
| [ConfigSchema](TypeAlias.ConfigSchema.md) | Configuration schema definition for plugin config. Re-exported from the standard JSON Schema Draft 7 types. |
| [ExecutionResult](TypeAlias.ExecutionResult.md) | Discriminated union for plugin execution results. |
+| [HostedTool](TypeAlias.HostedTool.md) | - |
| [IAppRouter](TypeAlias.IAppRouter.md) | Express router type for plugin route registration |
| [PluginData](TypeAlias.PluginData.md) | Tuple of plugin class, config, and name. Created by `toPlugin()` and passed to `createApp()`. |
| [ResourcePermission](TypeAlias.ResourcePermission.md) | Union of all possible permission levels across all resource types. |
@@ -73,6 +88,7 @@ plugin architecture, and React integration.
| ------ | ------ |
| [appKitServingTypesPlugin](Function.appKitServingTypesPlugin.md) | Vite plugin to generate TypeScript types for AppKit serving endpoints. Fetches OpenAPI schemas from Databricks and generates a .d.ts with ServingEndpointRegistry module augmentation. |
| [appKitTypesPlugin](Function.appKitTypesPlugin.md) | Vite plugin to generate types for AppKit queries. Calls generateFromEntryPoint under the hood. |
+| [createAgent](Function.createAgent.md) | Creates an agent-powered app with batteries included. |
| [createApp](Function.createApp.md) | Bootstraps AppKit with the provided configuration. |
| [createLakebasePool](Function.createLakebasePool.md) | Create a Lakebase pool with appkit's logger integration. Telemetry automatically uses appkit's OpenTelemetry configuration via global registry. |
| [extractServingEndpoints](Function.extractServingEndpoints.md) | Extract serving endpoint config from a server file by AST-parsing it. Looks for `serving({ endpoints: { alias: { env: "..." }, ... } })` calls and extracts the endpoint alias names and their environment variable mappings. |
@@ -85,4 +101,8 @@ plugin architecture, and React integration.
| [getResourceRequirements](Function.getResourceRequirements.md) | Gets the resource requirements from a plugin's manifest. |
| [getUsernameWithApiLookup](Function.getUsernameWithApiLookup.md) | Resolves the PostgreSQL username for a Lakebase connection. |
| [getWorkspaceClient](Function.getWorkspaceClient.md) | Get workspace client from config or SDK default auth chain |
+| [isFunctionTool](Function.isFunctionTool.md) | - |
+| [isHostedTool](Function.isHostedTool.md) | - |
| [isSQLTypeMarker](Function.isSQLTypeMarker.md) | Type guard to check if a value is a SQL type marker |
+| [mcpServer](Function.mcpServer.md) | Factory for declaring a custom MCP server tool. |
+| [tool](Function.tool.md) | Factory for defining function tools with Zod schemas. |
diff --git a/docs/docs/api/appkit/typedoc-sidebar.ts b/docs/docs/api/appkit/typedoc-sidebar.ts
index 720c78ea..b44d2318 100644
--- a/docs/docs/api/appkit/typedoc-sidebar.ts
+++ b/docs/docs/api/appkit/typedoc-sidebar.ts
@@ -82,6 +82,31 @@ const typedocSidebar: SidebarsConfig = {
type: "category",
label: "Interfaces",
items: [
+ {
+ type: "doc",
+ id: "api/appkit/Interface.AgentAdapter",
+ label: "AgentAdapter"
+ },
+ {
+ type: "doc",
+ id: "api/appkit/Interface.AgentHandle",
+ label: "AgentHandle"
+ },
+ {
+ type: "doc",
+ id: "api/appkit/Interface.AgentInput",
+ label: "AgentInput"
+ },
+ {
+ type: "doc",
+ id: "api/appkit/Interface.AgentRunContext",
+ label: "AgentRunContext"
+ },
+ {
+ type: "doc",
+ id: "api/appkit/Interface.AgentToolDefinition",
+ label: "AgentToolDefinition"
+ },
{
type: "doc",
id: "api/appkit/Interface.BasePluginConfig",
@@ -92,6 +117,11 @@ const typedocSidebar: SidebarsConfig = {
id: "api/appkit/Interface.CacheConfig",
label: "CacheConfig"
},
+ {
+ type: "doc",
+ id: "api/appkit/Interface.CreateAgentConfig",
+ label: "CreateAgentConfig"
+ },
{
type: "doc",
id: "api/appkit/Interface.DatabaseCredential",
@@ -102,6 +132,11 @@ const typedocSidebar: SidebarsConfig = {
id: "api/appkit/Interface.EndpointConfig",
label: "EndpointConfig"
},
+ {
+ type: "doc",
+ id: "api/appkit/Interface.FunctionTool",
+ label: "FunctionTool"
+ },
{
type: "doc",
id: "api/appkit/Interface.GenerateDatabaseCredentialRequest",
@@ -117,6 +152,11 @@ const typedocSidebar: SidebarsConfig = {
id: "api/appkit/Interface.LakebasePoolConfig",
label: "LakebasePoolConfig"
},
+ {
+ type: "doc",
+ id: "api/appkit/Interface.Message",
+ label: "Message"
+ },
{
type: "doc",
id: "api/appkit/Interface.PluginManifest",
@@ -167,6 +207,26 @@ const typedocSidebar: SidebarsConfig = {
id: "api/appkit/Interface.TelemetryConfig",
label: "TelemetryConfig"
},
+ {
+ type: "doc",
+ id: "api/appkit/Interface.Thread",
+ label: "Thread"
+ },
+ {
+ type: "doc",
+ id: "api/appkit/Interface.ThreadStore",
+ label: "ThreadStore"
+ },
+ {
+ type: "doc",
+ id: "api/appkit/Interface.ToolConfig",
+ label: "ToolConfig"
+ },
+ {
+ type: "doc",
+ id: "api/appkit/Interface.ToolProvider",
+ label: "ToolProvider"
+ },
{
type: "doc",
id: "api/appkit/Interface.ValidationResult",
@@ -178,6 +238,16 @@ const typedocSidebar: SidebarsConfig = {
type: "category",
label: "Type Aliases",
items: [
+ {
+ type: "doc",
+ id: "api/appkit/TypeAlias.AgentEvent",
+ label: "AgentEvent"
+ },
+ {
+ type: "doc",
+ id: "api/appkit/TypeAlias.AgentTool",
+ label: "AgentTool"
+ },
{
type: "doc",
id: "api/appkit/TypeAlias.ConfigSchema",
@@ -188,6 +258,11 @@ const typedocSidebar: SidebarsConfig = {
id: "api/appkit/TypeAlias.ExecutionResult",
label: "ExecutionResult"
},
+ {
+ type: "doc",
+ id: "api/appkit/TypeAlias.HostedTool",
+ label: "HostedTool"
+ },
{
type: "doc",
id: "api/appkit/TypeAlias.IAppRouter",
@@ -240,6 +315,11 @@ const typedocSidebar: SidebarsConfig = {
id: "api/appkit/Function.appKitTypesPlugin",
label: "appKitTypesPlugin"
},
+ {
+ type: "doc",
+ id: "api/appkit/Function.createAgent",
+ label: "createAgent"
+ },
{
type: "doc",
id: "api/appkit/Function.createApp",
@@ -300,10 +380,30 @@ const typedocSidebar: SidebarsConfig = {
id: "api/appkit/Function.getWorkspaceClient",
label: "getWorkspaceClient"
},
+ {
+ type: "doc",
+ id: "api/appkit/Function.isFunctionTool",
+ label: "isFunctionTool"
+ },
+ {
+ type: "doc",
+ id: "api/appkit/Function.isHostedTool",
+ label: "isHostedTool"
+ },
{
type: "doc",
id: "api/appkit/Function.isSQLTypeMarker",
label: "isSQLTypeMarker"
+ },
+ {
+ type: "doc",
+ id: "api/appkit/Function.mcpServer",
+ label: "mcpServer"
+ },
+ {
+ type: "doc",
+ id: "api/appkit/Function.tool",
+ label: "tool"
}
]
}
diff --git a/package.json b/package.json
index 0d3af43f..29d590dd 100644
--- a/package.json
+++ b/package.json
@@ -14,10 +14,12 @@
"generate:app-templates": "tsx tools/generate-app-templates.ts",
"check:licenses": "tsx tools/check-licenses.ts",
"build:notice": "tsx tools/build-notice.ts > NOTICE.md",
+ "deploy:agent": "pnpm pack:sdk && tsx tools/deploy-agent-app.ts",
"deploy:playground": "pnpm pack:sdk && tsx tools/playground/deploy-playground.ts",
"clean:full": "rm -rf node_modules dist coverage && pnpm -r clean:full",
"clean": "pnpm -r clean",
"dev": "pnpm build && NODE_ENV=development turbo watch build:watch dev",
+ "dev:agent": "pnpm build && NODE_ENV=development pnpm --filter=agent-app dev",
"dev:inspect": "NODE_ENV=development pnpm --filter=dev-playground dev:inspect",
"docs:dev": "pnpm --filter=docs dev",
"docs:build": "pnpm --filter=docs build",
diff --git a/packages/appkit/src/core/create-agent.ts b/packages/appkit/src/core/create-agent.ts
new file mode 100644
index 00000000..367ad70f
--- /dev/null
+++ b/packages/appkit/src/core/create-agent.ts
@@ -0,0 +1,150 @@
+import type { WorkspaceClient } from "@databricks/sdk-experimental";
+import type {
+ AgentAdapter,
+ AgentToolDefinition,
+ CacheConfig,
+ PluginConstructor,
+ PluginData,
+} from "shared";
+import { agent } from "../plugins/agent";
+import type { FunctionTool } from "../plugins/agent/tools/function-tool";
+import type { AgentTool } from "../plugins/agent/types";
+import { server } from "../plugins/server";
+import type { TelemetryConfig } from "../telemetry";
+import { createApp } from "./appkit";
+
+export interface CreateAgentConfig {
+ /** Single agent adapter (mutually exclusive with `agents`). Registered as "assistant". */
+ adapter?: AgentAdapter | Promise;
+ /** Multiple named agents (mutually exclusive with `adapter`). */
+ agents?: Record>;
+ /** Which agent to use when the client doesn't specify one. */
+ defaultAgent?: string;
+ /** Tool-providing plugins (analytics, files, genie, lakebase, etc.) */
+ plugins?: PluginData[];
+ /** Server port. Defaults to DATABRICKS_APP_PORT or 8000. */
+ port?: number;
+ /** Server host. Defaults to FLASK_RUN_HOST or 0.0.0.0. */
+ host?: string;
+ /** Telemetry configuration. */
+ telemetry?: TelemetryConfig;
+ /** Cache configuration. */
+ cache?: CacheConfig;
+ /** Explicit tools (FunctionTool, HostedTool) alongside auto-discovered ToolProvider tools. */
+ tools?: AgentTool[];
+ /** Pre-configured WorkspaceClient. */
+ client?: WorkspaceClient;
+}
+
+export interface AgentHandle {
+ /** Register an additional agent at runtime. */
+ registerAgent: (name: string, adapter: AgentAdapter) => void;
+ /** Add function tools at runtime (HostedTools must be configured at setup). */
+ addTools: (tools: FunctionTool[]) => void;
+ /** Get all tool definitions available to agents. */
+ getTools: () => AgentToolDefinition[];
+ /** List threads for a user. */
+ getThreads: (userId: string) => Promise;
+ /** Access to user-provided plugin APIs. */
+ plugins: Record;
+}
+
+/**
+ * Creates an agent-powered app with batteries included.
+ *
+ * Wraps `createApp` with `server()` and `agent()` pre-configured.
+ * Automatically starts an HTTP server with agent chat routes.
+ *
+ * For apps that need custom routes or manual server control,
+ * use `createApp` with `server()` and `agent()` directly.
+ *
+ * @example Single agent
+ * ```ts
+ * import { createAgent, analytics } from "@databricks/appkit";
+ * import { DatabricksAdapter } from "@databricks/appkit/agents/databricks";
+ *
+ * createAgent({
+ * plugins: [analytics()],
+ * adapter: DatabricksAdapter.fromServingEndpoint({
+ * workspaceClient: new WorkspaceClient({}),
+ * endpointName: "databricks-claude-sonnet-4-5",
+ * systemPrompt: "You are a data assistant...",
+ * }),
+ * }).then(agent => {
+ * console.log("Tools:", agent.getTools());
+ * });
+ * ```
+ *
+ * @example Multiple agents
+ * ```ts
+ * createAgent({
+ * plugins: [analytics(), files()],
+ * agents: {
+ * assistant: DatabricksAdapter.fromServingEndpoint({ ... }),
+ * autocomplete: DatabricksAdapter.fromServingEndpoint({ ... }),
+ * },
+ * defaultAgent: "assistant",
+ * });
+ * ```
+ */
+export async function createAgent(
+ config: CreateAgentConfig = {},
+): Promise {
+ if (config.adapter && config.agents) {
+ throw new Error(
+ "createAgent: 'adapter' and 'agents' are mutually exclusive. " +
+ "Use 'adapter' for a single agent or 'agents' for multiple.",
+ );
+ }
+
+ let agents = config.adapter ? { assistant: config.adapter } : config.agents;
+
+ // Default: if no adapter or agents provided, use DatabricksAdapter.fromModelServing()
+ // which reads from DATABRICKS_AGENT_ENDPOINT env var. Config-file agents
+ // (from config/agents/*.md) will also be loaded during agent plugin setup.
+ if (!agents && !config.adapter) {
+ try {
+ const { DatabricksAdapter } = await import("../agents/databricks");
+ agents = { assistant: DatabricksAdapter.fromModelServing() };
+ } catch {
+ // No adapter available — agent plugin will rely on config files
+ }
+ }
+
+ const appkit = await createApp({
+ plugins: [
+ agent({
+ agents,
+ defaultAgent: config.defaultAgent,
+ tools: config.tools,
+ }),
+ ...(config.plugins ?? []),
+ server({
+ autoStart: true,
+ ...(config.port !== undefined && { port: config.port }),
+ ...(config.host !== undefined && { host: config.host }),
+ }),
+ ],
+ telemetry: config.telemetry,
+ cache: config.cache,
+ client: config.client,
+ });
+
+ const agentExports = (appkit as any).agent;
+ const hiddenKeys = new Set(["agent", "server"]);
+
+ const plugins: Record = {};
+ for (const [key, value] of Object.entries(appkit as Record)) {
+ if (!hiddenKeys.has(key)) {
+ plugins[key] = value;
+ }
+ }
+
+ return {
+ registerAgent: agentExports.registerAgent,
+ addTools: agentExports.addTools,
+ getTools: agentExports.getTools,
+ getThreads: agentExports.getThreads,
+ plugins,
+ };
+}
diff --git a/packages/appkit/src/core/tests/create-agent.test.ts b/packages/appkit/src/core/tests/create-agent.test.ts
new file mode 100644
index 00000000..58df743b
--- /dev/null
+++ b/packages/appkit/src/core/tests/create-agent.test.ts
@@ -0,0 +1,243 @@
+import { describe, expect, test, vi } from "vitest";
+
+vi.mock("../../cache", () => ({
+ CacheManager: {
+ getInstance: vi.fn().mockResolvedValue({
+ get: vi.fn(),
+ set: vi.fn(),
+ delete: vi.fn(),
+ getOrExecute: vi.fn(),
+ }),
+ getInstanceSync: vi.fn().mockReturnValue({
+ get: vi.fn(),
+ set: vi.fn(),
+ delete: vi.fn(),
+ getOrExecute: vi.fn(),
+ }),
+ },
+}));
+
+vi.mock("../../telemetry", () => ({
+ TelemetryManager: {
+ initialize: vi.fn(),
+ getProvider: vi.fn(() => ({
+ getTracer: vi.fn(),
+ getMeter: vi.fn(),
+ getLogger: vi.fn(),
+ emit: vi.fn(),
+ startActiveSpan: vi.fn(),
+ registerInstrumentations: vi.fn(),
+ })),
+ },
+ normalizeTelemetryOptions: vi.fn(() => ({
+ traces: false,
+ metrics: false,
+ logs: false,
+ })),
+}));
+
+vi.mock("../../context/service-context", () => {
+ const mockClient = {
+ statementExecution: { executeStatement: vi.fn() },
+ currentUser: { me: vi.fn().mockResolvedValue({ id: "test-user" }) },
+ config: { host: "https://test.databricks.com" },
+ };
+
+ return {
+ ServiceContext: {
+ initialize: vi.fn().mockResolvedValue({
+ client: mockClient,
+ serviceUserId: "test-service-user",
+ workspaceId: Promise.resolve("test-workspace"),
+ }),
+ get: vi.fn().mockReturnValue({
+ client: mockClient,
+ serviceUserId: "test-service-user",
+ workspaceId: Promise.resolve("test-workspace"),
+ }),
+ isInitialized: vi.fn().mockReturnValue(true),
+ createUserContext: vi.fn(),
+ },
+ };
+});
+
+vi.mock("../../registry", () => ({
+ ResourceRegistry: vi.fn().mockImplementation(() => ({
+ collectResources: vi.fn(),
+ getRequired: vi.fn().mockReturnValue([]),
+ enforceValidation: vi.fn(),
+ })),
+ ResourceType: { SQL_WAREHOUSE: "sql_warehouse" },
+ getPluginManifest: vi.fn(),
+ getResourceRequirements: vi.fn(),
+}));
+
+// Mock server plugin to avoid actually starting a server
+vi.mock("../../plugins/server", () => {
+ const manifest = {
+ name: "server",
+ displayName: "Server",
+ description: "Server",
+ resources: { required: [], optional: [] },
+ };
+
+ class MockServerPlugin {
+ static manifest = manifest;
+ static phase = "deferred";
+ static DEFAULT_CONFIG = {};
+ name = "server";
+ config: any;
+ constructor(config: any) {
+ this.config = config;
+ }
+ async setup() {}
+ injectRoutes() {}
+ getEndpoints() {
+ return {};
+ }
+ exports() {
+ return {
+ start: vi.fn(),
+ extend: vi.fn(),
+ getServer: vi.fn(),
+ getConfig: vi.fn(() => this.config),
+ };
+ }
+ }
+
+ return {
+ server: (config: any = {}) => ({
+ plugin: MockServerPlugin,
+ config,
+ name: "server",
+ }),
+ ServerPlugin: MockServerPlugin,
+ };
+});
+
+import type { AgentAdapter, AgentEvent } from "shared";
+import { createAgent } from "../create-agent";
+
+function createMockAdapter(): AgentAdapter {
+ return {
+ async *run(): AsyncGenerator {
+ yield { type: "message_delta", content: "hello" };
+ },
+ };
+}
+
+describe("createAgent", () => {
+ test("returns an AgentHandle with registerAgent, getTools, getThreads", async () => {
+ const handle = await createAgent({
+ adapter: createMockAdapter(),
+ });
+
+ expect(handle.registerAgent).toBeTypeOf("function");
+ expect(handle.getTools).toBeTypeOf("function");
+ expect(handle.getThreads).toBeTypeOf("function");
+ expect(handle.plugins).toBeDefined();
+ });
+
+ test("adapter shorthand registers as 'assistant'", async () => {
+ const handle = await createAgent({
+ adapter: createMockAdapter(),
+ });
+
+ const tools = handle.getTools();
+ expect(tools).toBeInstanceOf(Array);
+ });
+
+ test("agents record is passed through", async () => {
+ const handle = await createAgent({
+ agents: {
+ main: createMockAdapter(),
+ secondary: createMockAdapter(),
+ },
+ defaultAgent: "main",
+ });
+
+ expect(handle.getTools).toBeTypeOf("function");
+ });
+
+ test("throws when both adapter and agents are provided", async () => {
+ await expect(
+ createAgent({
+ adapter: createMockAdapter(),
+ agents: { other: createMockAdapter() },
+ }),
+ ).rejects.toThrow("mutually exclusive");
+ });
+
+ test("plugins namespace excludes agent and server", async () => {
+ const handle = await createAgent({
+ adapter: createMockAdapter(),
+ });
+
+ expect(handle.plugins).not.toHaveProperty("agent");
+ expect(handle.plugins).not.toHaveProperty("server");
+ });
+
+ test("accepts port and host config", async () => {
+ const handle = await createAgent({
+ adapter: createMockAdapter(),
+ port: 9000,
+ host: "127.0.0.1",
+ });
+
+ expect(handle).toBeDefined();
+ });
+
+ test("works with promised adapters", async () => {
+ const handle = await createAgent({
+ adapter: Promise.resolve(createMockAdapter()),
+ });
+
+ expect(handle.registerAgent).toBeTypeOf("function");
+ });
+
+ test("registerAgent allows adding agents after creation", async () => {
+ const handle = await createAgent({
+ adapter: createMockAdapter(),
+ });
+
+ handle.registerAgent("second", createMockAdapter());
+ expect(handle.getTools).toBeTypeOf("function");
+ });
+
+ test("tools config is forwarded to agent plugin", async () => {
+ const handle = await createAgent({
+ adapter: createMockAdapter(),
+ tools: [
+ {
+ type: "function" as const,
+ name: "greet",
+ description: "Say hello",
+ parameters: { type: "object", properties: {} },
+ execute: async () => "hello",
+ },
+ ],
+ });
+
+ const tools = handle.getTools();
+ expect(tools.some((t) => t.name === "greet")).toBe(true);
+ });
+
+ test("addTools is exposed on AgentHandle", async () => {
+ const handle = await createAgent({
+ adapter: createMockAdapter(),
+ });
+
+ expect(handle.addTools).toBeTypeOf("function");
+
+ handle.addTools([
+ {
+ type: "function" as const,
+ name: "farewell",
+ execute: async () => "bye",
+ },
+ ]);
+
+ const tools = handle.getTools();
+ expect(tools.some((t) => t.name === "farewell")).toBe(true);
+ });
+});
diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts
index 033e6d09..2ea33fbb 100644
--- a/packages/appkit/src/index.ts
+++ b/packages/appkit/src/index.ts
@@ -43,6 +43,8 @@ export {
} from "./connectors/lakebase";
export { getExecutionContext } from "./context";
export { createApp } from "./core";
+export type { AgentHandle, CreateAgentConfig } from "./core/create-agent";
+export { createAgent } from "./core/create-agent";
// Errors
export {
AppKitError,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ed083b4f..120147f4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -81,6 +81,76 @@ importers:
specifier: 3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.7.2)(jiti@2.6.1)(jsdom@27.0.0(bufferutil@4.0.9)(postcss@8.5.6))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2)
+ apps/agent-app:
+ dependencies:
+ '@databricks/appkit':
+ specifier: workspace:*
+ version: link:../../packages/appkit
+ '@databricks/appkit-ui':
+ specifier: workspace:*
+ version: link:../../packages/appkit-ui
+ '@databricks/sdk-experimental':
+ specifier: ^0.16.0
+ version: 0.16.0
+ dotenv:
+ specifier: ^16.6.1
+ version: 16.6.1
+ lucide-react:
+ specifier: ^0.511.0
+ version: 0.511.0(react@19.2.0)
+ marked:
+ specifier: ^15.0.0
+ version: 15.0.12
+ react:
+ specifier: 19.2.0
+ version: 19.2.0
+ react-dom:
+ specifier: 19.2.0
+ version: 19.2.0(react@19.2.0)
+ zod:
+ specifier: ^4.0.0
+ version: 4.3.6
+ devDependencies:
+ '@tailwindcss/postcss':
+ specifier: 4.1.17
+ version: 4.1.17
+ '@types/node':
+ specifier: 24.10.1
+ version: 24.10.1
+ '@types/react':
+ specifier: 19.2.7
+ version: 19.2.7
+ '@types/react-dom':
+ specifier: 19.2.3
+ version: 19.2.3(@types/react@19.2.7)
+ '@vitejs/plugin-react':
+ specifier: 5.1.1
+ version: 5.1.1(rolldown-vite@7.1.14(@types/node@24.10.1)(esbuild@0.25.10)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2))
+ autoprefixer:
+ specifier: 10.4.21
+ version: 10.4.21(postcss@8.5.6)
+ postcss:
+ specifier: 8.5.6
+ version: 8.5.6
+ tailwindcss:
+ specifier: 4.1.17
+ version: 4.1.17
+ tailwindcss-animate:
+ specifier: 1.0.7
+ version: 1.0.7(tailwindcss@4.1.17)
+ tsx:
+ specifier: 4.20.6
+ version: 4.20.6
+ tw-animate-css:
+ specifier: 1.4.0
+ version: 1.4.0
+ typescript:
+ specifier: 5.9.3
+ version: 5.9.3
+ vite:
+ specifier: npm:rolldown-vite@7.1.14
+ version: rolldown-vite@7.1.14(@types/node@24.10.1)(esbuild@0.25.10)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2)
+
apps/clean-app:
dependencies:
'@databricks/appkit':
@@ -148,12 +218,18 @@ importers:
specifier: 0.3.28
version: 0.3.28(pg@8.18.0)
devDependencies:
+ '@ai-sdk/openai':
+ specifier: 1.0.0
+ version: 1.0.0(zod@4.3.6)
'@playwright/test':
specifier: 1.58.1
version: 1.58.1
'@types/node':
specifier: 20.19.21
version: 20.19.21
+ ai:
+ specifier: 4.0.0
+ version: 4.0.0(react@19.2.0)(zod@4.3.6)
dotenv:
specifier: 16.6.1
version: 16.6.1
@@ -579,12 +655,27 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4.1.8
+ '@ai-sdk/openai@1.0.0':
+ resolution: {integrity: sha512-EZ2UDxTBb3v3e2eexKTFGXF9MEy7rEcfIrkdD3yo8RCpwIkwRjyxCfs6wzh8KAW6XQZRu3Rp0kqw1S4FQcQgJA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.0.0
+
'@ai-sdk/openai@4.0.0-beta.27':
resolution: {integrity: sha512-7DpXCE4pcc4pVzuEc0whMrQN6Whi14Qsqjx97mLPGjpS6Lff48Zcn2322IFpWuhVJ10hIM1kEZNxUYvXt1O/yg==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.25.76 || ^4.1.8
+ '@ai-sdk/provider-utils@2.0.0':
+ resolution: {integrity: sha512-uITgVJByhtzuQU2ZW+2CidWRmQqTUTp6KADevy+4aRnmILZxY2LCt+UZ/ZtjJqq0MffwkuQPPY21ExmFAQ6kKA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.0.0
+ peerDependenciesMeta:
+ zod:
+ optional: true
+
'@ai-sdk/provider-utils@3.0.19':
resolution: {integrity: sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA==}
engines: {node: '>=18'}
@@ -597,6 +688,10 @@ packages:
peerDependencies:
zod: ^3.25.76 || ^4.1.8
+ '@ai-sdk/provider@1.0.0':
+ resolution: {integrity: sha512-Sj29AzooJ7SYvhPd+AAWt/E7j63E9+AzRnoMHUaJPRYzOd/WDrVNxxv85prF9gDcQ7XPVlSk9j6oAZV9/DXYpA==}
+ engines: {node: '>=18'}
+
'@ai-sdk/provider@2.0.0':
resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==}
engines: {node: '>=18'}
@@ -605,6 +700,18 @@ packages:
resolution: {integrity: sha512-E2O/LCWjqOxAUfpykQR4xLmcGXySIu6L+wYJjav2xiHu38otPq0qIexgH9ZKulBvBWkrtJ3fxz0kzHDlCBkwng==}
engines: {node: '>=18'}
+ '@ai-sdk/react@1.0.0':
+ resolution: {integrity: sha512-BDrZqQA07Btg64JCuhFvBgYV+tt2B8cXINzEqWknGoxqcwgdE8wSLG2gkXoLzyC2Rnj7oj0HHpOhLUxDCmoKZg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ react: ^18 || ^19 || ^19.0.0-rc
+ zod: ^3.0.0
+ peerDependenciesMeta:
+ react:
+ optional: true
+ zod:
+ optional: true
+
'@ai-sdk/react@2.0.115':
resolution: {integrity: sha512-Etu7gWSEi2dmXss1PoR5CAZGwGShXsF9+Pon1eRO6EmatjYaBMhq1CfHPyYhGzWrint8jJIK2VaAhiMef29qZw==}
engines: {node: '>=18'}
@@ -615,6 +722,15 @@ packages:
zod:
optional: true
+ '@ai-sdk/ui-utils@1.0.0':
+ resolution: {integrity: sha512-oXBDIM/0niWeTWyw77RVl505dNxBUDLLple7bTsqo2d3i1UKwGlzBUX8XqZsh7GbY7I6V05nlG0Y8iGlWxv1Aw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.0.0
+ peerDependenciesMeta:
+ zod:
+ optional: true
+
'@algolia/abtesting@1.12.0':
resolution: {integrity: sha512-EfW0bfxjPs+C7ANkJDw2TATntfBKsFiy7APh+KO0pQ8A6HYa5I0NjFuCGCXWfzzzLXNZta3QUl3n5Kmm6aJo9Q==}
engines: {node: '>= 14.0.0'}
@@ -2188,15 +2304,9 @@ packages:
resolution: {integrity: sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==}
engines: {node: '>=20.0'}
- '@emnapi/core@1.7.1':
- resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==}
-
'@emnapi/core@1.8.1':
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
- '@emnapi/runtime@1.7.1':
- resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
-
'@emnapi/runtime@1.8.1':
resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
@@ -2702,9 +2812,6 @@ packages:
'@mermaid-js/parser@0.6.3':
resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==}
- '@napi-rs/wasm-runtime@1.0.7':
- resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==}
-
'@napi-rs/wasm-runtime@1.1.1':
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
@@ -4656,39 +4763,79 @@ packages:
resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
engines: {node: '>=14.16'}
+ '@tailwindcss/node@4.1.17':
+ resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==}
+
'@tailwindcss/node@4.1.18':
resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
+ '@tailwindcss/oxide-android-arm64@4.1.17':
+ resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
'@tailwindcss/oxide-android-arm64@4.1.18':
resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
+ '@tailwindcss/oxide-darwin-arm64@4.1.17':
+ resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
'@tailwindcss/oxide-darwin-arm64@4.1.18':
resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
+ '@tailwindcss/oxide-darwin-x64@4.1.17':
+ resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
'@tailwindcss/oxide-darwin-x64@4.1.18':
resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
+ '@tailwindcss/oxide-freebsd-x64@4.1.17':
+ resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
'@tailwindcss/oxide-freebsd-x64@4.1.18':
resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [freebsd]
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17':
+ resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.17':
+ resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
'@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==}
engines: {node: '>= 10'}
@@ -4696,6 +4843,13 @@ packages:
os: [linux]
libc: [glibc]
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.17':
+ resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
engines: {node: '>= 10'}
@@ -4703,6 +4857,13 @@ packages:
os: [linux]
libc: [musl]
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.17':
+ resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
engines: {node: '>= 10'}
@@ -4710,6 +4871,13 @@ packages:
os: [linux]
libc: [glibc]
+ '@tailwindcss/oxide-linux-x64-musl@4.1.17':
+ resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
engines: {node: '>= 10'}
@@ -4717,6 +4885,18 @@ packages:
os: [linux]
libc: [musl]
+ '@tailwindcss/oxide-wasm32-wasi@4.1.17':
+ resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
engines: {node: '>=14.0.0'}
@@ -4729,22 +4909,41 @@ packages:
- '@emnapi/wasi-threads'
- tslib
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.17':
+ resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
'@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.17':
+ resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
'@tailwindcss/oxide-win32-x64-msvc@4.1.18':
resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
+ '@tailwindcss/oxide@4.1.17':
+ resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==}
+ engines: {node: '>= 10'}
+
'@tailwindcss/oxide@4.1.18':
resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==}
engines: {node: '>= 10'}
+ '@tailwindcss/postcss@4.1.17':
+ resolution: {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==}
+
'@tailwindcss/postcss@4.1.18':
resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==}
@@ -4932,6 +5131,9 @@ packages:
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+ '@types/diff-match-patch@1.0.36':
+ resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
+
'@types/eslint-scope@3.7.7':
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
@@ -5360,6 +5562,18 @@ packages:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
+ ai@4.0.0:
+ resolution: {integrity: sha512-cqf2GCaXnOPhUU+Ccq6i+5I0jDjnFkzfq7t6mc0SUSibSa1wDPn5J4p8+Joh2fDGDYZOJ44rpTW9hSs40rXNAw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ react: ^18 || ^19 || ^19.0.0-rc
+ zod: ^3.0.0
+ peerDependenciesMeta:
+ react:
+ optional: true
+ zod:
+ optional: true
+
ai@5.0.113:
resolution: {integrity: sha512-26vivpSO/mzZj0k1Si2IpsFspp26ttQICHRySQiMrtWcRd5mnJMX2a8sG28vmZ38C+JUn1cWmfZrsLMxkSMw9g==}
engines: {node: '>=18'}
@@ -5527,6 +5741,13 @@ packages:
autocomplete.js@0.37.1:
resolution: {integrity: sha512-PgSe9fHYhZEsm/9jggbjtVsGXJkPLvd+9mC7gZJ662vVL5CRWEtm/mIrrzCx0MrNxHVwxD5d00UOn6NsmL2LUQ==}
+ autoprefixer@10.4.21:
+ resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+
autoprefixer@10.4.23:
resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==}
engines: {node: ^10 || ^12 || >=14}
@@ -6623,6 +6844,9 @@ packages:
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+ diff-match-patch@1.0.5:
+ resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==}
+
dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@@ -7295,6 +7519,9 @@ packages:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
+ fraction.js@4.3.7:
+ resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+
fraction.js@5.3.4:
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
@@ -8241,6 +8468,11 @@ packages:
resolution: {integrity: sha512-OCzaRMK8HobtX8fp37uIVmL8CY1IGc/a6gLsDqz3quExFR09/U78HUzWYr7T31UEB6+Eu0/8dkVD5fFDOl9a8w==}
engines: {node: '>= 8'}
+ jsondiffpatch@0.6.0:
+ resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+
jsonfile@6.2.0:
resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
@@ -8545,6 +8777,11 @@ packages:
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
engines: {node: '>=12'}
+ lucide-react@0.511.0:
+ resolution: {integrity: sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
lucide-react@0.554.0:
resolution: {integrity: sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA==}
peerDependencies:
@@ -8595,6 +8832,11 @@ packages:
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
+ marked@15.0.12:
+ resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
+ engines: {node: '>= 18'}
+ hasBin: true
+
marked@16.4.2:
resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
engines: {node: '>= 20'}
@@ -9001,6 +9243,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ nanoid@5.1.7:
+ resolution: {integrity: sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+
napi-build-utils@2.0.0:
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
@@ -9109,6 +9356,10 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
+ normalize-range@0.1.2:
+ resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+ engines: {node: '>=0.10.0'}
+
normalize-url@8.1.0:
resolution: {integrity: sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==}
engines: {node: '>=14.16'}
@@ -10523,6 +10774,9 @@ packages:
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
engines: {node: '>=4'}
+ secure-json-parse@2.7.0:
+ resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
+
select-hose@2.0.0:
resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==}
@@ -11996,15 +12250,17 @@ packages:
resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
engines: {node: '>=18'}
+ zod-to-json-schema@3.25.2:
+ resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
+ peerDependencies:
+ zod: ^3.25.28 || ^4
+
zod-validation-error@4.0.2:
resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
zod: ^3.25.0 || ^4.0.0
- zod@4.1.13:
- resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==}
-
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
@@ -12033,12 +12289,27 @@ snapshots:
'@vercel/oidc': 3.2.0
zod: 4.3.6
+ '@ai-sdk/openai@1.0.0(zod@4.3.6)':
+ dependencies:
+ '@ai-sdk/provider': 1.0.0
+ '@ai-sdk/provider-utils': 2.0.0(zod@4.3.6)
+ zod: 4.3.6
+
'@ai-sdk/openai@4.0.0-beta.27(zod@4.3.6)':
dependencies:
'@ai-sdk/provider': 4.0.0-beta.10
'@ai-sdk/provider-utils': 5.0.0-beta.16(zod@4.3.6)
zod: 4.3.6
+ '@ai-sdk/provider-utils@2.0.0(zod@4.3.6)':
+ dependencies:
+ '@ai-sdk/provider': 1.0.0
+ eventsource-parser: 3.0.6
+ nanoid: 5.1.7
+ secure-json-parse: 2.7.0
+ optionalDependencies:
+ zod: 4.3.6
+
'@ai-sdk/provider-utils@3.0.19(zod@4.3.6)':
dependencies:
'@ai-sdk/provider': 2.0.0
@@ -12053,6 +12324,10 @@ snapshots:
eventsource-parser: 3.0.6
zod: 4.3.6
+ '@ai-sdk/provider@1.0.0':
+ dependencies:
+ json-schema: 0.4.0
+
'@ai-sdk/provider@2.0.0':
dependencies:
json-schema: 0.4.0
@@ -12061,6 +12336,16 @@ snapshots:
dependencies:
json-schema: 0.4.0
+ '@ai-sdk/react@1.0.0(react@19.2.0)(zod@4.3.6)':
+ dependencies:
+ '@ai-sdk/provider-utils': 2.0.0(zod@4.3.6)
+ '@ai-sdk/ui-utils': 1.0.0(zod@4.3.6)
+ swr: 2.3.8(react@19.2.0)
+ throttleit: 2.1.0
+ optionalDependencies:
+ react: 19.2.0
+ zod: 4.3.6
+
'@ai-sdk/react@2.0.115(react@19.2.0)(zod@4.3.6)':
dependencies:
'@ai-sdk/provider-utils': 3.0.19(zod@4.3.6)
@@ -12071,6 +12356,14 @@ snapshots:
optionalDependencies:
zod: 4.3.6
+ '@ai-sdk/ui-utils@1.0.0(zod@4.3.6)':
+ dependencies:
+ '@ai-sdk/provider': 1.0.0
+ '@ai-sdk/provider-utils': 2.0.0(zod@4.3.6)
+ zod-to-json-schema: 3.25.2(zod@4.3.6)
+ optionalDependencies:
+ zod: 4.3.6
+
'@algolia/abtesting@1.12.0':
dependencies:
'@algolia/client-common': 5.46.0
@@ -14529,23 +14822,12 @@ snapshots:
- uglify-js
- webpack-cli
- '@emnapi/core@1.7.1':
- dependencies:
- '@emnapi/wasi-threads': 1.1.0
- tslib: 2.8.1
- optional: true
-
'@emnapi/core@1.8.1':
dependencies:
'@emnapi/wasi-threads': 1.1.0
tslib: 2.8.1
optional: true
- '@emnapi/runtime@1.7.1':
- dependencies:
- tslib: 2.8.1
- optional: true
-
'@emnapi/runtime@1.8.1':
dependencies:
tslib: 2.8.1
@@ -15035,13 +15317,6 @@ snapshots:
dependencies:
langium: 3.3.1
- '@napi-rs/wasm-runtime@1.0.7':
- dependencies:
- '@emnapi/core': 1.7.1
- '@emnapi/runtime': 1.7.1
- '@tybys/wasm-util': 0.10.1
- optional: true
-
'@napi-rs/wasm-runtime@1.1.1':
dependencies:
'@emnapi/core': 1.8.1
@@ -16768,7 +17043,7 @@ snapshots:
'@rolldown/binding-wasm32-wasi@1.0.0-beta.41':
dependencies:
- '@napi-rs/wasm-runtime': 1.0.7
+ '@napi-rs/wasm-runtime': 1.1.1
optional: true
'@rolldown/binding-wasm32-wasi@1.0.0-rc.3':
@@ -17064,6 +17339,16 @@ snapshots:
dependencies:
defer-to-connect: 2.0.1
+ '@tailwindcss/node@4.1.17':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.18.3
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.17
+
'@tailwindcss/node@4.1.18':
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -17074,42 +17359,93 @@ snapshots:
source-map-js: 1.2.1
tailwindcss: 4.1.18
+ '@tailwindcss/oxide-android-arm64@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-android-arm64@4.1.18':
optional: true
+ '@tailwindcss/oxide-darwin-arm64@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-darwin-arm64@4.1.18':
optional: true
+ '@tailwindcss/oxide-darwin-x64@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-darwin-x64@4.1.18':
optional: true
+ '@tailwindcss/oxide-freebsd-x64@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-freebsd-x64@4.1.18':
optional: true
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18':
optional: true
+ '@tailwindcss/oxide-linux-arm64-gnu@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-linux-arm64-gnu@4.1.18':
optional: true
+ '@tailwindcss/oxide-linux-arm64-musl@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
optional: true
+ '@tailwindcss/oxide-linux-x64-gnu@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
optional: true
+ '@tailwindcss/oxide-linux-x64-musl@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
optional: true
+ '@tailwindcss/oxide-wasm32-wasi@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
optional: true
+ '@tailwindcss/oxide-win32-arm64-msvc@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-win32-arm64-msvc@4.1.18':
optional: true
+ '@tailwindcss/oxide-win32-x64-msvc@4.1.17':
+ optional: true
+
'@tailwindcss/oxide-win32-x64-msvc@4.1.18':
optional: true
+ '@tailwindcss/oxide@4.1.17':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.1.17
+ '@tailwindcss/oxide-darwin-arm64': 4.1.17
+ '@tailwindcss/oxide-darwin-x64': 4.1.17
+ '@tailwindcss/oxide-freebsd-x64': 4.1.17
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.1.17
+ '@tailwindcss/oxide-linux-arm64-musl': 4.1.17
+ '@tailwindcss/oxide-linux-x64-gnu': 4.1.17
+ '@tailwindcss/oxide-linux-x64-musl': 4.1.17
+ '@tailwindcss/oxide-wasm32-wasi': 4.1.17
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17
+ '@tailwindcss/oxide-win32-x64-msvc': 4.1.17
+
'@tailwindcss/oxide@4.1.18':
optionalDependencies:
'@tailwindcss/oxide-android-arm64': 4.1.18
@@ -17125,6 +17461,14 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
'@tailwindcss/oxide-win32-x64-msvc': 4.1.18
+ '@tailwindcss/postcss@4.1.17':
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ '@tailwindcss/node': 4.1.17
+ '@tailwindcss/oxide': 4.1.17
+ postcss: 8.5.6
+ tailwindcss: 4.1.17
+
'@tailwindcss/postcss@4.1.18':
dependencies:
'@alloc/quick-lru': 5.2.0
@@ -17353,6 +17697,8 @@ snapshots:
'@types/deep-eql@4.0.2': {}
+ '@types/diff-match-patch@1.0.36': {}
+
'@types/eslint-scope@3.7.7':
dependencies:
'@types/eslint': 9.6.1
@@ -17704,6 +18050,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@vitejs/plugin-react@5.1.1(rolldown-vite@7.1.14(@types/node@24.10.1)(esbuild@0.25.10)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2))':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5)
+ '@rolldown/pluginutils': 1.0.0-beta.47
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.18.0
+ vite: rolldown-vite@7.1.14(@types/node@24.10.1)(esbuild@0.25.10)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2)
+ transitivePeerDependencies:
+ - supports-color
+
'@vitejs/plugin-react@5.1.1(rolldown-vite@7.1.14(@types/node@25.2.3)(esbuild@0.25.10)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2))':
dependencies:
'@babel/core': 7.28.5
@@ -17908,6 +18266,19 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
+ ai@4.0.0(react@19.2.0)(zod@4.3.6):
+ dependencies:
+ '@ai-sdk/provider': 1.0.0
+ '@ai-sdk/provider-utils': 2.0.0(zod@4.3.6)
+ '@ai-sdk/react': 1.0.0(react@19.2.0)(zod@4.3.6)
+ '@ai-sdk/ui-utils': 1.0.0(zod@4.3.6)
+ '@opentelemetry/api': 1.9.0
+ jsondiffpatch: 0.6.0
+ zod-to-json-schema: 3.25.2(zod@4.3.6)
+ optionalDependencies:
+ react: 19.2.0
+ zod: 4.3.6
+
ai@5.0.113(zod@4.3.6):
dependencies:
'@ai-sdk/gateway': 2.0.21(zod@4.3.6)
@@ -18086,6 +18457,16 @@ snapshots:
dependencies:
immediate: 3.3.0
+ autoprefixer@10.4.21(postcss@8.5.6):
+ dependencies:
+ browserslist: 4.28.1
+ caniuse-lite: 1.0.30001760
+ fraction.js: 4.3.7
+ normalize-range: 0.1.2
+ picocolors: 1.1.1
+ postcss: 8.5.6
+ postcss-value-parser: 4.2.0
+
autoprefixer@10.4.23(postcss@8.5.6):
dependencies:
browserslist: 4.28.1
@@ -18919,7 +19300,7 @@ snapshots:
cssnano-preset-advanced@6.1.2(postcss@8.5.6):
dependencies:
- autoprefixer: 10.4.23(postcss@8.5.6)
+ autoprefixer: 10.4.21(postcss@8.5.6)
browserslist: 4.28.1
cssnano-preset-default: 6.1.2(postcss@8.5.6)
postcss: 8.5.6
@@ -19289,6 +19670,8 @@ snapshots:
dependencies:
dequal: 2.0.3
+ diff-match-patch@1.0.5: {}
+
dir-glob@3.0.1:
dependencies:
path-type: 4.0.0
@@ -19578,8 +19961,8 @@ snapshots:
'@babel/parser': 7.28.5
eslint: 9.39.1(jiti@2.6.1)
hermes-parser: 0.25.1
- zod: 4.1.13
- zod-validation-error: 4.0.2(zod@4.1.13)
+ zod: 4.3.6
+ zod-validation-error: 4.0.2(zod@4.3.6)
transitivePeerDependencies:
- supports-color
@@ -19951,6 +20334,8 @@ snapshots:
forwarded@0.2.0: {}
+ fraction.js@4.3.7: {}
+
fraction.js@5.3.4: {}
fresh@0.5.2: {}
@@ -21105,6 +21490,12 @@ snapshots:
jsonata@2.1.0:
optional: true
+ jsondiffpatch@0.6.0:
+ dependencies:
+ '@types/diff-match-patch': 1.0.36
+ chalk: 5.6.2
+ diff-match-patch: 1.0.5
+
jsonfile@6.2.0:
dependencies:
universalify: 2.0.1
@@ -21381,6 +21772,10 @@ snapshots:
lru-cache@7.18.3: {}
+ lucide-react@0.511.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+
lucide-react@0.554.0(react@19.2.0):
dependencies:
react: 19.2.0
@@ -21444,6 +21839,8 @@ snapshots:
markdown-table@3.0.4: {}
+ marked@15.0.12: {}
+
marked@16.4.2: {}
marked@17.0.3: {}
@@ -22130,6 +22527,8 @@ snapshots:
nanoid@3.3.11: {}
+ nanoid@5.1.7: {}
+
napi-build-utils@2.0.0:
optional: true
@@ -22231,6 +22630,8 @@ snapshots:
normalize-path@3.0.0: {}
+ normalize-range@0.1.2: {}
+
normalize-url@8.1.0: {}
normalize-url@8.1.1: {}
@@ -23750,6 +24151,24 @@ snapshots:
tsx: 4.20.6
yaml: 2.8.2
+ rolldown-vite@7.1.14(@types/node@24.10.1)(esbuild@0.25.10)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2):
+ dependencies:
+ '@oxc-project/runtime': 0.92.0
+ fdir: 6.5.0(picomatch@4.0.3)
+ lightningcss: 1.30.2
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rolldown: 1.0.0-beta.41
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 24.10.1
+ esbuild: 0.25.10
+ fsevents: 2.3.3
+ jiti: 2.6.1
+ terser: 5.44.1
+ tsx: 4.20.6
+ yaml: 2.8.2
+
rolldown-vite@7.1.14(@types/node@25.2.3)(esbuild@0.25.10)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.2):
dependencies:
'@oxc-project/runtime': 0.92.0
@@ -23925,6 +24344,8 @@ snapshots:
extend-shallow: 2.0.1
kind-of: 6.0.3
+ secure-json-parse@2.7.0: {}
+
select-hose@2.0.0: {}
selfsigned@2.4.1:
@@ -25446,11 +25867,13 @@ snapshots:
yoctocolors@2.1.2: {}
- zod-validation-error@4.0.2(zod@4.1.13):
+ zod-to-json-schema@3.25.2(zod@4.3.6):
dependencies:
- zod: 4.1.13
+ zod: 4.3.6
- zod@4.1.13: {}
+ zod-validation-error@4.0.2(zod@4.3.6):
+ dependencies:
+ zod: 4.3.6
zod@4.3.6: {}
diff --git a/template/appkit.plugins.json b/template/appkit.plugins.json
index a9ca281d..1d891256 100644
--- a/template/appkit.plugins.json
+++ b/template/appkit.plugins.json
@@ -159,6 +159,30 @@
"optional": []
},
"requiredByTemplate": true
+ },
+ "serving": {
+ "name": "serving",
+ "displayName": "Model Serving Plugin",
+ "description": "Authenticated proxy to Databricks Model Serving endpoints",
+ "package": "@databricks/appkit",
+ "resources": {
+ "required": [
+ {
+ "type": "serving_endpoint",
+ "alias": "Serving Endpoint",
+ "resourceKey": "serving-endpoint",
+ "description": "Model Serving endpoint for inference",
+ "permission": "CAN_QUERY",
+ "fields": {
+ "name": {
+ "env": "DATABRICKS_SERVING_ENDPOINT_NAME",
+ "description": "Serving endpoint name"
+ }
+ }
+ }
+ ],
+ "optional": []
+ }
}
}
}
diff --git a/tools/deploy-agent-app.ts b/tools/deploy-agent-app.ts
new file mode 100644
index 00000000..42d0d87f
--- /dev/null
+++ b/tools/deploy-agent-app.ts
@@ -0,0 +1,144 @@
+import { exec as execChildProcess, spawn } from "node:child_process";
+import fs from "node:fs";
+import os from "node:os";
+import path from "node:path";
+import { promisify } from "node:util";
+import ora from "ora";
+
+const _exec = promisify(execChildProcess);
+
+const config = {
+ profile: process.env.DATABRICKS_PROFILE,
+ appName: process.env.DATABRICKS_APP_NAME,
+};
+
+const ROOT = process.cwd();
+const AGENT_APP_DIR = path.join(ROOT, "apps", "agent-app");
+const DEPLOY_DIR = path.join(ROOT, "deployable-agent");
+
+const appKitPkg = JSON.parse(
+ fs.readFileSync(
+ path.join(ROOT, "packages", "appkit", "package.json"),
+ "utf-8",
+ ),
+);
+const appKitUiPkg = JSON.parse(
+ fs.readFileSync(
+ path.join(ROOT, "packages", "appkit-ui", "package.json"),
+ "utf-8",
+ ),
+);
+const appKitTarball = path.join(
+ ROOT,
+ "packages",
+ "appkit",
+ "tmp",
+ `databricks-appkit-${appKitPkg.version}.tgz`,
+);
+const appKitUiTarball = path.join(
+ ROOT,
+ "packages",
+ "appkit-ui",
+ "tmp",
+ `databricks-appkit-ui-${appKitUiPkg.version}.tgz`,
+);
+
+async function deploy() {
+ const spinner = ora("Deploying agent-app").start();
+
+ if (!fs.existsSync(appKitTarball) || !fs.existsSync(appKitUiTarball)) {
+ spinner.fail(
+ "Tarballs not found. Run `pnpm pack:sdk` first to build them.",
+ );
+ process.exit(1);
+ }
+
+ if (fs.existsSync(DEPLOY_DIR)) {
+ const databricksState = path.join(DEPLOY_DIR, ".databricks");
+ if (fs.existsSync(databricksState)) {
+ fs.cpSync(databricksState, path.join(AGENT_APP_DIR, ".databricks"), {
+ recursive: true,
+ });
+ }
+ fs.rmSync(DEPLOY_DIR, { recursive: true });
+ }
+
+ spinner.text = "Copying agent-app to deploy folder";
+ fs.cpSync(AGENT_APP_DIR, DEPLOY_DIR, {
+ recursive: true,
+ filter: (src) =>
+ !src.includes("node_modules") && !src.includes(".databricks"),
+ });
+
+ spinner.text = "Patching package.json with tarballs";
+ const pkgPath = path.join(DEPLOY_DIR, "package.json");
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
+
+ pkg.dependencies["@databricks/appkit"] =
+ `file:./databricks-appkit-${appKitPkg.version}.tgz`;
+ pkg.dependencies["@databricks/appkit-ui"] =
+ `file:./databricks-appkit-ui-${appKitUiPkg.version}.tgz`;
+
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
+
+ spinner.text = "Copying tarballs";
+ fs.copyFileSync(
+ appKitTarball,
+ path.join(DEPLOY_DIR, `databricks-appkit-${appKitPkg.version}.tgz`),
+ );
+ fs.copyFileSync(
+ appKitUiTarball,
+ path.join(DEPLOY_DIR, `databricks-appkit-ui-${appKitUiPkg.version}.tgz`),
+ );
+
+ spinner.text = "Patching vite.config.ts (removing monorepo aliases)";
+ const viteConfigPath = path.join(DEPLOY_DIR, "vite.config.ts");
+ if (fs.existsSync(viteConfigPath)) {
+ let viteConfig = fs.readFileSync(viteConfigPath, "utf-8");
+ viteConfig = viteConfig
+ .replace(/import path from "node:path";\n?/, "")
+ .replace(/alias:\s*\{[^}]*\},?\n?/s, "")
+ .replace(/preserveSymlinks:\s*true,?\n?/, "")
+ .replace(/exclude:\s*\[[^\]]*\],?\n?/, "");
+ fs.writeFileSync(viteConfigPath, viteConfig);
+ }
+
+ process.chdir(DEPLOY_DIR);
+
+ const username = os.userInfo().username;
+ const appName =
+ config.appName || `${username.replace(/\./g, "-")}-appkit-agent`;
+ const profileArgs = config.profile ? ["-p", config.profile] : [];
+
+ spinner.info(`Deploying as "${appName}"`);
+ await execWithOutput("databricks", [
+ "apps",
+ "deploy",
+ "--skip-validation",
+ ...profileArgs,
+ ]);
+
+ spinner.succeed(`Agent app "${appName}" deployed`);
+}
+
+function execWithOutput(
+ command: string,
+ args: string[],
+): Promise<{ code: number }> {
+ return new Promise((resolve, reject) => {
+ const child = spawn(command, args, { stdio: "inherit" });
+ child.on("close", (code) => resolve({ code: code ?? 0 }));
+ child.on("error", reject);
+ });
+}
+
+deploy()
+ .catch((err) => {
+ console.error("Deploy failed:", err);
+ process.exit(1);
+ })
+ .finally(() => {
+ if (fs.existsSync(DEPLOY_DIR)) {
+ fs.rmSync(DEPLOY_DIR, { recursive: true });
+ }
+ });