From 8573d3bc3eae9440e105d753f0b45a6128cb50b7 Mon Sep 17 00:00:00 2001 From: Hubert Zub Date: Tue, 10 Mar 2026 10:38:30 +0100 Subject: [PATCH] feat(appkit): add AgentPlugin for LangChain/LangGraph agents Signed-off-by: Hubert Zub --- apps/dev-playground/.env.dist | 1 + .../client/src/routeTree.gen.ts | 21 + .../client/src/routes/__root.tsx | 8 + .../client/src/routes/agent.route.tsx | 31 + .../client/src/routes/index.tsx | 19 + apps/dev-playground/server/agent-tools.ts | 52 ++ apps/dev-playground/server/index.ts | 19 +- .../api/appkit/Interface.AgentInterface.md | 43 + .../api/appkit/Interface.BasePluginConfig.md | 4 + .../docs/api/appkit/Interface.IAgentConfig.md | 138 ++++ .../docs/api/appkit/Interface.InvokeParams.md | 38 + .../api/appkit/Interface.StandardAgent.md | 53 ++ .../appkit/TypeAlias.ResponseStreamEvent.md | 11 + docs/docs/api/appkit/Variable.agent.md | 20 + docs/docs/api/appkit/index.md | 6 + docs/docs/api/appkit/typedoc-sidebar.ts | 30 + .../react/agent-chat/agent-chat-message.tsx | 45 ++ .../src/react/agent-chat/agent-chat-part.tsx | 46 ++ .../src/react/agent-chat/agent-chat.tsx | 85 ++ .../appkit-ui/src/react/agent-chat/index.ts | 6 + .../appkit-ui/src/react/agent-chat/types.ts | 66 ++ .../src/react/agent-chat/use-agent-chat.ts | 174 ++++ .../appkit-ui/src/react/agent-chat/utils.ts | 32 + packages/appkit-ui/src/react/index.ts | 1 + packages/appkit/package.json | 25 + packages/appkit/src/index.ts | 9 +- .../src/plugins/agent/agent-interface.ts | 121 +++ packages/appkit/src/plugins/agent/agent.ts | 278 +++++++ packages/appkit/src/plugins/agent/index.ts | 14 + .../src/plugins/agent/invoke-handler.ts | 160 ++++ .../appkit/src/plugins/agent/manifest.json | 23 + .../src/plugins/agent/standard-agent.ts | 223 ++++++ .../agent/tests/agent.integration.test.ts | 228 ++++++ .../src/plugins/agent/tests/agent.test.ts | 172 ++++ .../agent/tests/invoke-handler.test.ts | 325 ++++++++ .../src/plugins/agent/tests/stub-agent.ts | 71 ++ packages/appkit/src/plugins/agent/types.ts | 43 + packages/appkit/src/plugins/index.ts | 1 + pnpm-lock.yaml | 752 ++++++++++++++++++ template/appkit.plugins.json | 24 + 40 files changed, 3415 insertions(+), 3 deletions(-) create mode 100644 apps/dev-playground/client/src/routes/agent.route.tsx create mode 100644 apps/dev-playground/server/agent-tools.ts create mode 100644 docs/docs/api/appkit/Interface.AgentInterface.md create mode 100644 docs/docs/api/appkit/Interface.IAgentConfig.md create mode 100644 docs/docs/api/appkit/Interface.InvokeParams.md create mode 100644 docs/docs/api/appkit/Interface.StandardAgent.md create mode 100644 docs/docs/api/appkit/TypeAlias.ResponseStreamEvent.md create mode 100644 docs/docs/api/appkit/Variable.agent.md create mode 100644 packages/appkit-ui/src/react/agent-chat/agent-chat-message.tsx create mode 100644 packages/appkit-ui/src/react/agent-chat/agent-chat-part.tsx create mode 100644 packages/appkit-ui/src/react/agent-chat/agent-chat.tsx create mode 100644 packages/appkit-ui/src/react/agent-chat/index.ts create mode 100644 packages/appkit-ui/src/react/agent-chat/types.ts create mode 100644 packages/appkit-ui/src/react/agent-chat/use-agent-chat.ts create mode 100644 packages/appkit-ui/src/react/agent-chat/utils.ts create mode 100644 packages/appkit/src/plugins/agent/agent-interface.ts create mode 100644 packages/appkit/src/plugins/agent/agent.ts create mode 100644 packages/appkit/src/plugins/agent/index.ts create mode 100644 packages/appkit/src/plugins/agent/invoke-handler.ts create mode 100644 packages/appkit/src/plugins/agent/manifest.json create mode 100644 packages/appkit/src/plugins/agent/standard-agent.ts create mode 100644 packages/appkit/src/plugins/agent/tests/agent.integration.test.ts create mode 100644 packages/appkit/src/plugins/agent/tests/agent.test.ts create mode 100644 packages/appkit/src/plugins/agent/tests/invoke-handler.test.ts create mode 100644 packages/appkit/src/plugins/agent/tests/stub-agent.ts create mode 100644 packages/appkit/src/plugins/agent/types.ts diff --git a/apps/dev-playground/.env.dist b/apps/dev-playground/.env.dist index 2d3a28f0..aba5db22 100644 --- a/apps/dev-playground/.env.dist +++ b/apps/dev-playground/.env.dist @@ -7,6 +7,7 @@ OTEL_EXPORTER_OTLP_ENDPOINT='http://localhost:4318' OTEL_RESOURCE_ATTRIBUTES='service.sample_attribute=dev' OTEL_SERVICE_NAME='dev-playground' DATABRICKS_GENIE_SPACE_ID= +DATABRICKS_MODEL= LAKEBASE_ENDPOINT='' # Run: databricks postgres list-endpoints projects/{project-id}/branches/{branch-id} — use the `name` field from the output PGHOST= PGUSER= diff --git a/apps/dev-playground/client/src/routeTree.gen.ts b/apps/dev-playground/client/src/routeTree.gen.ts index 1f2aff3e..392c354d 100644 --- a/apps/dev-playground/client/src/routeTree.gen.ts +++ b/apps/dev-playground/client/src/routeTree.gen.ts @@ -19,6 +19,7 @@ import { Route as DataVisualizationRouteRouteImport } from './routes/data-visual import { Route as ChartInferenceRouteRouteImport } from './routes/chart-inference.route' import { Route as ArrowAnalyticsRouteRouteImport } from './routes/arrow-analytics.route' import { Route as AnalyticsRouteRouteImport } from './routes/analytics.route' +import { Route as AgentRouteRouteImport } from './routes/agent.route' import { Route as IndexRouteImport } from './routes/index' const TypeSafetyRouteRoute = TypeSafetyRouteRouteImport.update({ @@ -71,6 +72,11 @@ const AnalyticsRouteRoute = AnalyticsRouteRouteImport.update({ path: '/analytics', getParentRoute: () => rootRouteImport, } as any) +const AgentRouteRoute = AgentRouteRouteImport.update({ + id: '/agent', + path: '/agent', + getParentRoute: () => rootRouteImport, +} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', @@ -79,6 +85,7 @@ const IndexRoute = IndexRouteImport.update({ export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/agent': typeof AgentRouteRoute '/analytics': typeof AnalyticsRouteRoute '/arrow-analytics': typeof ArrowAnalyticsRouteRoute '/chart-inference': typeof ChartInferenceRouteRoute @@ -92,6 +99,7 @@ export interface FileRoutesByFullPath { } export interface FileRoutesByTo { '/': typeof IndexRoute + '/agent': typeof AgentRouteRoute '/analytics': typeof AnalyticsRouteRoute '/arrow-analytics': typeof ArrowAnalyticsRouteRoute '/chart-inference': typeof ChartInferenceRouteRoute @@ -106,6 +114,7 @@ export interface FileRoutesByTo { export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute + '/agent': typeof AgentRouteRoute '/analytics': typeof AnalyticsRouteRoute '/arrow-analytics': typeof ArrowAnalyticsRouteRoute '/chart-inference': typeof ChartInferenceRouteRoute @@ -121,6 +130,7 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' + | '/agent' | '/analytics' | '/arrow-analytics' | '/chart-inference' @@ -134,6 +144,7 @@ export interface FileRouteTypes { fileRoutesByTo: FileRoutesByTo to: | '/' + | '/agent' | '/analytics' | '/arrow-analytics' | '/chart-inference' @@ -147,6 +158,7 @@ export interface FileRouteTypes { id: | '__root__' | '/' + | '/agent' | '/analytics' | '/arrow-analytics' | '/chart-inference' @@ -161,6 +173,7 @@ export interface FileRouteTypes { } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + AgentRouteRoute: typeof AgentRouteRoute AnalyticsRouteRoute: typeof AnalyticsRouteRoute ArrowAnalyticsRouteRoute: typeof ArrowAnalyticsRouteRoute ChartInferenceRouteRoute: typeof ChartInferenceRouteRoute @@ -245,6 +258,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AnalyticsRouteRouteImport parentRoute: typeof rootRouteImport } + '/agent': { + id: '/agent' + path: '/agent' + fullPath: '/agent' + preLoaderRoute: typeof AgentRouteRouteImport + parentRoute: typeof rootRouteImport + } '/': { id: '/' path: '/' @@ -257,6 +277,7 @@ declare module '@tanstack/react-router' { const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + AgentRouteRoute: AgentRouteRoute, AnalyticsRouteRoute: AnalyticsRouteRoute, ArrowAnalyticsRouteRoute: ArrowAnalyticsRouteRoute, ChartInferenceRouteRoute: ChartInferenceRouteRoute, diff --git a/apps/dev-playground/client/src/routes/__root.tsx b/apps/dev-playground/client/src/routes/__root.tsx index b3ef6233..09364859 100644 --- a/apps/dev-playground/client/src/routes/__root.tsx +++ b/apps/dev-playground/client/src/routes/__root.tsx @@ -96,6 +96,14 @@ function RootComponent() { Chart Inference + + + 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..62b8a580 --- /dev/null +++ b/apps/dev-playground/client/src/routes/agent.route.tsx @@ -0,0 +1,31 @@ +import { AgentChat } from "@databricks/appkit-ui/react"; +import { createFileRoute } from "@tanstack/react-router"; + +export const Route = createFileRoute("/agent")({ + component: AgentChatRoute, +}); + +function AgentChatRoute() { + return ( +
+
+
+

+ Agent Chat +

+

+ Chat with the agent via POST /invocations (Responses + API SSE stream). +

+
+ + +
+
+ ); +} diff --git a/apps/dev-playground/client/src/routes/index.tsx b/apps/dev-playground/client/src/routes/index.tsx index a0aec851..d9d05938 100644 --- a/apps/dev-playground/client/src/routes/index.tsx +++ b/apps/dev-playground/client/src/routes/index.tsx @@ -200,6 +200,25 @@ function IndexRoute() { + + +
+

+ Agent Chat +

+

+ Chat with a LangChain/LangGraph AI agent powered by the AppKit + Agent Plugin. Features Responses API SSE streaming and tool call + rendering. +

+ +
+
diff --git a/apps/dev-playground/server/agent-tools.ts b/apps/dev-playground/server/agent-tools.ts new file mode 100644 index 00000000..e2a1d5ff --- /dev/null +++ b/apps/dev-playground/server/agent-tools.ts @@ -0,0 +1,52 @@ +import { tool } from "@langchain/core/tools"; +import { z } from "zod"; +import { toJSONSchema } from "zod/v4/core"; + +/** + * Workaround: @databricks/langchainjs@0.1.0 calls `schema.toJSONSchema()` + * as a method, but zod v4 only exposes it as a standalone function. + * Patch the schema so ChatDatabricks can convert it. + */ +function patchZodSchema(schema: T): T { + (schema as any).toJSONSchema = () => toJSONSchema(schema); + return schema; +} + +export const weatherTool = tool( + async ({ location }) => { + const conditions = ["sunny", "partly cloudy", "rainy", "windy"]; + const condition = conditions[Math.floor(Math.random() * conditions.length)]; + const temp = Math.floor(Math.random() * 30) + 50; + return `Weather in ${location}: ${condition}, ${temp}°F`; + }, + { + name: "get_weather", + description: "Get the current weather for a location", + schema: patchZodSchema( + z.object({ + location: z.string().describe("City name, e.g. 'San Francisco'"), + }), + ), + }, +); + +export const timeTool = tool( + async ({ timezone }) => { + const tz = timezone ?? "UTC"; + return `Current time in ${tz}: ${new Date().toLocaleString("en-US", { timeZone: tz })}`; + }, + { + name: "get_current_time", + description: "Get the current date and time in a timezone", + schema: patchZodSchema( + z.object({ + timezone: z + .string() + .optional() + .describe("IANA timezone, e.g. 'America/New_York'. Defaults to UTC"), + }), + ), + }, +); + +export const demoTools = [weatherTool, timeTool]; diff --git a/apps/dev-playground/server/index.ts b/apps/dev-playground/server/index.ts index d54781ea..2fedf02b 100644 --- a/apps/dev-playground/server/index.ts +++ b/apps/dev-playground/server/index.ts @@ -1,6 +1,7 @@ import "reflect-metadata"; -import { analytics, createApp, genie, server } from "@databricks/appkit"; +import { agent, analytics, createApp, genie, server } from "@databricks/appkit"; import { WorkspaceClient } from "@databricks/sdk-experimental"; +import { demoTools } from "./agent-tools"; import { lakebaseExamples } from "./lakebase-examples-plugin"; import { reconnect } from "./reconnect-plugin"; import { telemetryExamples } from "./telemetry-example-plugin"; @@ -25,11 +26,25 @@ createApp({ spaces: { demo: process.env.DATABRICKS_GENIE_SPACE_ID ?? "placeholder" }, }), lakebaseExamples(), + agent({ + model: process.env.DATABRICKS_MODEL || "databricks-claude-sonnet-4-5", + systemPrompt: + "You are a helpful assistant. Use tools when appropriate — for example, use get_weather for weather questions, and get_current_time for time queries.", + }), ], ...(process.env.APPKIT_E2E_TEST && { client: createMockClient() }), -}).then((appkit) => { +}).then(async (appkit) => { + // Add tools (and optionally MCP servers) after app creation + await appkit.agent.addTools(demoTools); + appkit.server .extend((app) => { + // Rewrite to use standard Databricks Apps convention: /invocations at root + app.post("/invocations", (req, res) => { + req.url = "/api/agent"; + app(req, res); + }); + app.get("/sp", (_req, res) => { appkit.analytics .query("SELECT * FROM samples.nyctaxi.trips;") diff --git a/docs/docs/api/appkit/Interface.AgentInterface.md b/docs/docs/api/appkit/Interface.AgentInterface.md new file mode 100644 index 00000000..0736fe32 --- /dev/null +++ b/docs/docs/api/appkit/Interface.AgentInterface.md @@ -0,0 +1,43 @@ +# Interface: AgentInterface + +Contract that agent implementations must fulfil. + +The plugin calls `invoke()` for non-streaming requests and `stream()` for +SSE streaming. Implementations are responsible for translating their SDK's +output into Responses API types. + +## Methods + +### invoke() + +```ts +invoke(params: InvokeParams): Promise; +``` + +#### Parameters + +| Parameter | Type | +| ------ | ------ | +| `params` | [`InvokeParams`](Interface.InvokeParams.md) | + +#### Returns + +`Promise`\<`ResponseOutputItem`[]\> + +*** + +### stream() + +```ts +stream(params: InvokeParams): AsyncGenerator; +``` + +#### Parameters + +| Parameter | Type | +| ------ | ------ | +| `params` | [`InvokeParams`](Interface.InvokeParams.md) | + +#### Returns + +`AsyncGenerator`\<[`ResponseStreamEvent`](TypeAlias.ResponseStreamEvent.md)\> diff --git a/docs/docs/api/appkit/Interface.BasePluginConfig.md b/docs/docs/api/appkit/Interface.BasePluginConfig.md index a7faffc6..1cf97ca0 100644 --- a/docs/docs/api/appkit/Interface.BasePluginConfig.md +++ b/docs/docs/api/appkit/Interface.BasePluginConfig.md @@ -2,6 +2,10 @@ Base configuration interface for AppKit plugins +## Extended by + +- [`IAgentConfig`](Interface.IAgentConfig.md) + ## Indexable ```ts diff --git a/docs/docs/api/appkit/Interface.IAgentConfig.md b/docs/docs/api/appkit/Interface.IAgentConfig.md new file mode 100644 index 00000000..2f288c04 --- /dev/null +++ b/docs/docs/api/appkit/Interface.IAgentConfig.md @@ -0,0 +1,138 @@ +# Interface: IAgentConfig + +Base configuration interface for AppKit plugins. + +When you do **not** set `agentInstance`, the agent is built from `model`, `tools`, and `mcpServers`. You can then add more tools or MCP servers after app creation via `appkit.agent.addTools()` and `appkit.agent.addMcpServers()` (see [agent](Variable.agent.md) Plugin API). + +## Extends + +- [`BasePluginConfig`](Interface.BasePluginConfig.md) + +## Indexable + +```ts +[key: string]: unknown +``` + +## Properties + +### agentInstance? + +```ts +optional agentInstance: AgentInterface; +``` + +Pre-built agent implementing AgentInterface. +When provided the plugin skips internal LangGraph setup and delegates +directly to this instance. Use this to bring your own agent +implementation or a different LangChain variant. + +--- + +### host? + +```ts +optional host: string; +``` + +#### Inherited from + +[`BasePluginConfig`](Interface.BasePluginConfig.md).[`host`](Interface.BasePluginConfig.md#host) + +--- + +### maxTokens? + +```ts +optional maxTokens: number; +``` + +Max tokens to generate (default 2000). Ignored when `agentInstance` is provided. + +--- + +### mcpServers? + +```ts +optional mcpServers: DatabricksMCPServer[]; +``` + +MCP servers for Databricks tool integration. Ignored when `agentInstance` is provided. You can add more at runtime with `appkit.agent.addMcpServers()`. + +--- + +### model? + +```ts +optional model: string; +``` + +Databricks model serving endpoint name (e.g. "databricks-claude-sonnet-4-5"). +Falls back to DATABRICKS_MODEL env var. +Ignored when `agentInstance` is provided. + +--- + +### name? + +```ts +optional name: string; +``` + +#### Inherited from + +[`BasePluginConfig`](Interface.BasePluginConfig.md).[`name`](Interface.BasePluginConfig.md#name) + +--- + +### systemPrompt? + +```ts +optional systemPrompt: string; +``` + +System prompt injected at the start of every conversation + +--- + +### telemetry? + +```ts +optional telemetry: TelemetryOptions; +``` + +#### Inherited from + +[`BasePluginConfig`](Interface.BasePluginConfig.md).[`telemetry`](Interface.BasePluginConfig.md#telemetry) + +--- + +### temperature? + +```ts +optional temperature: number; +``` + +Sampling temperature (0.0-1.0, default 0.1). Ignored when `agentInstance` is provided. + +--- + +### tools? + +```ts +optional tools: StructuredTool[]; +``` + +Additional LangChain tools to register alongside MCP tools. Ignored when `agentInstance` is provided. You can add more at runtime with `appkit.agent.addTools()`. + +--- + +### useResponsesApi? + +```ts +optional useResponsesApi: boolean; +``` + +Whether ChatDatabricks calls the upstream model using the Responses API +instead of the Chat Completions API. Default: false. +Ignored when `agentInstance` is provided. diff --git a/docs/docs/api/appkit/Interface.InvokeParams.md b/docs/docs/api/appkit/Interface.InvokeParams.md new file mode 100644 index 00000000..f4f360ea --- /dev/null +++ b/docs/docs/api/appkit/Interface.InvokeParams.md @@ -0,0 +1,38 @@ +# Interface: InvokeParams + +Agent interface types for the AppKit Agent Plugin. + +These types define the contract between the plugin framework and agent +implementations. They mirror the OpenAI Responses API SSE format without +requiring the `openai` package as a dependency. + +## Properties + +### chat\_history? + +```ts +optional chat_history: { + content: string; + role: string; +}[]; +``` + +#### content + +```ts +content: string; +``` + +#### role + +```ts +role: string; +``` + +*** + +### input + +```ts +input: string; +``` diff --git a/docs/docs/api/appkit/Interface.StandardAgent.md b/docs/docs/api/appkit/Interface.StandardAgent.md new file mode 100644 index 00000000..c1d483e1 --- /dev/null +++ b/docs/docs/api/appkit/Interface.StandardAgent.md @@ -0,0 +1,53 @@ +# Interface: StandardAgent + +## Implements + +- [`AgentInterface`](Interface.AgentInterface.md) + +## Methods + +### invoke() + +```ts +invoke(params: InvokeParams): Promise; +``` + +#### Parameters + +| Parameter | Type | +| ------ | ------ | +| `params` | [`InvokeParams`](Interface.InvokeParams.md) | + +#### Returns + +`Promise`\<`ResponseOutputItem`[]\> + +#### Implementation of + +```ts +AgentInterface.invoke +``` + +*** + +### stream() + +```ts +stream(params: InvokeParams): AsyncGenerator; +``` + +#### Parameters + +| Parameter | Type | +| ------ | ------ | +| `params` | [`InvokeParams`](Interface.InvokeParams.md) | + +#### Returns + +`AsyncGenerator`\<[`ResponseStreamEvent`](TypeAlias.ResponseStreamEvent.md)\> + +#### Implementation of + +```ts +AgentInterface.stream +``` diff --git a/docs/docs/api/appkit/TypeAlias.ResponseStreamEvent.md b/docs/docs/api/appkit/TypeAlias.ResponseStreamEvent.md new file mode 100644 index 00000000..4a4fb290 --- /dev/null +++ b/docs/docs/api/appkit/TypeAlias.ResponseStreamEvent.md @@ -0,0 +1,11 @@ +# Type Alias: ResponseStreamEvent + +```ts +type ResponseStreamEvent = + | ResponseOutputItemAddedEvent + | ResponseOutputItemDoneEvent + | ResponseTextDeltaEvent + | ResponseCompletedEvent + | ResponseErrorEvent + | ResponseFailedEvent; +``` diff --git a/docs/docs/api/appkit/Variable.agent.md b/docs/docs/api/appkit/Variable.agent.md new file mode 100644 index 00000000..0a499cf4 --- /dev/null +++ b/docs/docs/api/appkit/Variable.agent.md @@ -0,0 +1,20 @@ +# Variable: agent + +```ts +const agent: ToPlugin; +``` + +Plugin factory for the AppKit agent (LangChain/LangGraph). Use in `createApp({ plugins: [agent({ ... })] })`. Configuration: [`IAgentConfig`](Interface.IAgentConfig.md). + +## Plugin API (runtime) + +After `const appkit = await createApp({ plugins: [..., agent(config)] })`, `appkit.agent` exposes: + +| Method | Description | +| ------ | ------ | +| `invoke(messages)` | Run the agent (non-streaming). Returns the assistant reply text. | +| `stream(messages)` | Run the agent with streaming. Yields [`ResponseStreamEvent`](TypeAlias.ResponseStreamEvent.md)s. | +| `addTools(tools)` | Add LangChain tools after app creation. Rebuilds the agent. **Only when not using `agentInstance`.** | +| `addMcpServers(servers)` | Add MCP servers after app creation. Rebuilds the agent and MCP client. **Only when not using `agentInstance`.** | + +When the plugin is configured with `model` and optional `tools` / `mcpServers` (i.e. without `agentInstance`), you can call `appkit.agent.addTools([...])` and `appkit.agent.addMcpServers([...])` in the `.then()` of `createApp()` to register additional tools or MCP servers at runtime. diff --git a/docs/docs/api/appkit/index.md b/docs/docs/api/appkit/index.md index 4ad80820..5356bda8 100644 --- a/docs/docs/api/appkit/index.md +++ b/docs/docs/api/appkit/index.md @@ -30,10 +30,13 @@ plugin architecture, and React integration. | Interface | Description | | ------ | ------ | +| [AgentInterface](Interface.AgentInterface.md) | Contract that agent implementations must fulfil. | | [BasePluginConfig](Interface.BasePluginConfig.md) | Base configuration interface for AppKit plugins | | [CacheConfig](Interface.CacheConfig.md) | Configuration for caching | | [DatabaseCredential](Interface.DatabaseCredential.md) | Database credentials with OAuth token for Postgres connection | | [GenerateDatabaseCredentialRequest](Interface.GenerateDatabaseCredentialRequest.md) | Request parameters for generating database OAuth credentials | +| [IAgentConfig](Interface.IAgentConfig.md) | Base configuration interface for AppKit plugins | +| [InvokeParams](Interface.InvokeParams.md) | Agent interface types for the AppKit Agent Plugin. | | [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 | | [PluginManifest](Interface.PluginManifest.md) | Plugin manifest that declares metadata and resource requirements. Attached to plugin classes as a static property. | @@ -42,6 +45,7 @@ plugin architecture, and React integration. | [ResourceEntry](Interface.ResourceEntry.md) | Internal representation of a resource in the registry. Extends ResourceRequirement with resolution state and plugin ownership. | | [ResourceFieldEntry](Interface.ResourceFieldEntry.md) | Defines a single field for a resource. Each field has its own environment variable and optional description. Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key). | | [ResourceRequirement](Interface.ResourceRequirement.md) | Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements(). | +| [StandardAgent](Interface.StandardAgent.md) | - | | [StreamExecutionSettings](Interface.StreamExecutionSettings.md) | Configuration for streaming execution with default and user-scoped settings | | [TelemetryConfig](Interface.TelemetryConfig.md) | OpenTelemetry configuration for AppKit applications | | [ValidationResult](Interface.ValidationResult.md) | Result of validating all registered resources against the environment. | @@ -54,12 +58,14 @@ plugin architecture, and React integration. | [IAppRouter](TypeAlias.IAppRouter.md) | Express router type for plugin route registration | | [PluginData](TypeAlias.PluginData.md) | - | | [ResourcePermission](TypeAlias.ResourcePermission.md) | Union of all possible permission levels across all resource types. | +| [ResponseStreamEvent](TypeAlias.ResponseStreamEvent.md) | - | | [ToPlugin](TypeAlias.ToPlugin.md) | - | ## Variables | Variable | Description | | ------ | ------ | +| [agent](Variable.agent.md) | Agent plugin factory; runtime API includes invoke, stream, addTools, addMcpServers. | | [sql](Variable.sql.md) | SQL helper namespace | ## Functions diff --git a/docs/docs/api/appkit/typedoc-sidebar.ts b/docs/docs/api/appkit/typedoc-sidebar.ts index 2f17b1d2..d4718669 100644 --- a/docs/docs/api/appkit/typedoc-sidebar.ts +++ b/docs/docs/api/appkit/typedoc-sidebar.ts @@ -82,6 +82,11 @@ const typedocSidebar: SidebarsConfig = { type: "category", label: "Interfaces", items: [ + { + type: "doc", + id: "api/appkit/Interface.AgentInterface", + label: "AgentInterface" + }, { type: "doc", id: "api/appkit/Interface.BasePluginConfig", @@ -102,6 +107,16 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.GenerateDatabaseCredentialRequest", label: "GenerateDatabaseCredentialRequest" }, + { + type: "doc", + id: "api/appkit/Interface.IAgentConfig", + label: "IAgentConfig" + }, + { + type: "doc", + id: "api/appkit/Interface.InvokeParams", + label: "InvokeParams" + }, { type: "doc", id: "api/appkit/Interface.ITelemetry", @@ -142,6 +157,11 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.ResourceRequirement", label: "ResourceRequirement" }, + { + type: "doc", + id: "api/appkit/Interface.StandardAgent", + label: "StandardAgent" + }, { type: "doc", id: "api/appkit/Interface.StreamExecutionSettings", @@ -183,6 +203,11 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/TypeAlias.ResourcePermission", label: "ResourcePermission" }, + { + type: "doc", + id: "api/appkit/TypeAlias.ResponseStreamEvent", + label: "ResponseStreamEvent" + }, { type: "doc", id: "api/appkit/TypeAlias.ToPlugin", @@ -194,6 +219,11 @@ const typedocSidebar: SidebarsConfig = { type: "category", label: "Variables", items: [ + { + type: "doc", + id: "api/appkit/Variable.agent", + label: "agent" + }, { type: "doc", id: "api/appkit/Variable.sql", diff --git a/packages/appkit-ui/src/react/agent-chat/agent-chat-message.tsx b/packages/appkit-ui/src/react/agent-chat/agent-chat-message.tsx new file mode 100644 index 00000000..a8b3df1f --- /dev/null +++ b/packages/appkit-ui/src/react/agent-chat/agent-chat-message.tsx @@ -0,0 +1,45 @@ +import { AgentChatPart } from "./agent-chat-part"; +import type { ChatMessage } from "./types"; + +export interface AgentChatMessageProps { + message: ChatMessage; + isLast?: boolean; + isStreaming?: boolean; +} + +/** Renders a single chat message bubble (user or assistant with parts). */ +export function AgentChatMessage({ + message, + isLast = false, + isStreaming = false, +}: AgentChatMessageProps) { + if (message.role === "user") { + return ( +
+ + You + +
+ {message.content} +
+
+ ); + } + + return ( +
+ + Agent + +
+ {message.parts.map((part, j) => ( + + ))} +
+
+ ); +} diff --git a/packages/appkit-ui/src/react/agent-chat/agent-chat-part.tsx b/packages/appkit-ui/src/react/agent-chat/agent-chat-part.tsx new file mode 100644 index 00000000..4da5a1fa --- /dev/null +++ b/packages/appkit-ui/src/react/agent-chat/agent-chat-part.tsx @@ -0,0 +1,46 @@ +import type { AssistantPart } from "./types"; +import { tryFormatJson } from "./utils"; + +export interface AgentChatPartProps { + part: AssistantPart; + showCursor?: boolean; +} + +/** Renders a single assistant part: text, function_call, or function_call_output. */ +export function AgentChatPart({ + part, + showCursor = false, +}: AgentChatPartProps) { + if (part.type === "text") { + return ( +
+ {part.content} + {showCursor && |} +
+ ); + } + + if (part.type === "function_call") { + return ( +
+
+ Tool: {part.name} +
+
+          {tryFormatJson(part.arguments)}
+        
+
+ ); + } + + return ( +
+
+ Result +
+
+        {tryFormatJson(part.output)}
+      
+
+ ); +} diff --git a/packages/appkit-ui/src/react/agent-chat/agent-chat.tsx b/packages/appkit-ui/src/react/agent-chat/agent-chat.tsx new file mode 100644 index 00000000..1bda5061 --- /dev/null +++ b/packages/appkit-ui/src/react/agent-chat/agent-chat.tsx @@ -0,0 +1,85 @@ +import { useEffect, useRef } from "react"; +import { cn } from "../lib/utils"; +import { Button, Card } from "../ui"; +import { AgentChatMessage } from "./agent-chat-message"; +import type { AgentChatProps, ChatMessage } from "./types"; +import { useAgentChat } from "./use-agent-chat"; + +/** Agent chat UI: message list + input, wired to POST /invocations SSE streaming. */ +export function AgentChat({ + invokeUrl = "/invocations", + placeholder = "Type a message...", + emptyMessage = "Send a message to start.", + className, +}: AgentChatProps) { + const scrollRef = useRef(null); + const { + displayMessages, + loading, + input, + setInput, + handleSubmit, + isStreamingText, + } = useAgentChat({ invokeUrl }); + + const contentLength = displayMessages.length; + // biome-ignore lint/correctness/useExhaustiveDependencies: deps used as triggers for auto-scroll + useEffect(() => { + scrollRef.current?.scrollTo({ + top: scrollRef.current.scrollHeight, + behavior: "smooth", + }); + }, [contentLength, isStreamingText]); + + return ( +
+ +
+ {displayMessages.length === 0 && ( +

{emptyMessage}

+ )} + {displayMessages.map((msg, i) => ( + + ))} +
+ +
+ setInput(e.target.value)} + placeholder={placeholder} + className="flex-1 rounded-lg border border-input bg-background 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={loading} + /> + +
+
+
+ ); +} + +function MessageItem({ + message, + isLast, + isStreaming, +}: { + message: ChatMessage; + isLast: boolean; + isStreaming: boolean; +}) { + return ( + + ); +} diff --git a/packages/appkit-ui/src/react/agent-chat/index.ts b/packages/appkit-ui/src/react/agent-chat/index.ts new file mode 100644 index 00000000..214204b8 --- /dev/null +++ b/packages/appkit-ui/src/react/agent-chat/index.ts @@ -0,0 +1,6 @@ +export { AgentChat } from "./agent-chat"; +export { AgentChatMessage } from "./agent-chat-message"; +export { AgentChatPart } from "./agent-chat-part"; +export type * from "./types"; +export { useAgentChat } from "./use-agent-chat"; +export { serializeForApi, tryFormatJson } from "./utils"; diff --git a/packages/appkit-ui/src/react/agent-chat/types.ts b/packages/appkit-ui/src/react/agent-chat/types.ts new file mode 100644 index 00000000..519b4fd8 --- /dev/null +++ b/packages/appkit-ui/src/react/agent-chat/types.ts @@ -0,0 +1,66 @@ +export type TextPart = { type: "text"; content: string }; +export type FunctionCallPart = { + type: "function_call"; + id: string; + callId: string; + name: string; + arguments: string; +}; +export type FunctionCallOutputPart = { + type: "function_call_output"; + id: string; + callId: string; + output: string; +}; +export type AssistantPart = + | TextPart + | FunctionCallPart + | FunctionCallOutputPart; + +export type ChatMessage = + | { role: "user"; content: string } + | { role: "assistant"; parts: AssistantPart[] }; + +export interface SSEItem { + type?: string; + id?: string; + call_id?: string; + name?: string; + arguments?: string; + output?: string; +} + +export interface SSEEvent { + type?: string; + delta?: string; + error?: string; + item?: SSEItem; +} + +export interface UseAgentChatOptions { + /** POST URL for invocations (Responses API). Default: "/invocations" */ + invokeUrl?: string; +} + +export interface UseAgentChatReturn { + messages: ChatMessage[]; + loading: boolean; + input: string; + setInput: (value: string) => void; + handleSubmit: (e: React.FormEvent) => void; + /** Messages + current streaming state for display */ + displayMessages: ChatMessage[]; + /** True when the last message is still streaming text */ + isStreamingText: boolean; +} + +export interface AgentChatProps { + /** POST URL for invocations. Default: "/invocations" */ + invokeUrl?: string; + /** Placeholder for the message input */ + placeholder?: string; + /** Empty state text when there are no messages */ + emptyMessage?: string; + /** Additional CSS class for the root container */ + className?: string; +} diff --git a/packages/appkit-ui/src/react/agent-chat/use-agent-chat.ts b/packages/appkit-ui/src/react/agent-chat/use-agent-chat.ts new file mode 100644 index 00000000..0e3ba0f7 --- /dev/null +++ b/packages/appkit-ui/src/react/agent-chat/use-agent-chat.ts @@ -0,0 +1,174 @@ +import { useCallback, useMemo, useState } from "react"; +import type { + AssistantPart, + ChatMessage, + SSEEvent, + UseAgentChatOptions, + UseAgentChatReturn, +} from "./types"; +import { serializeForApi } from "./utils"; + +/** + * Manages agent chat state and streaming via POST /invocations (Responses API SSE). + * Returns messages, loading state, input state, submit handler, and derived display list. + */ +export function useAgentChat( + options: UseAgentChatOptions = {}, +): UseAgentChatReturn { + const { invokeUrl = "/invocations" } = options; + const [messages, setMessages] = useState([]); + const [streamingParts, setStreamingParts] = useState([]); + const [streamingText, setStreamingText] = useState(""); + const [loading, setLoading] = useState(false); + const [input, setInput] = useState(""); + + const handleSubmit = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + const text = input.trim(); + if (!text || loading) return; + + const userMessage: ChatMessage = { role: "user", content: text }; + setInput(""); + setMessages((prev) => [...prev, userMessage]); + setLoading(true); + setStreamingParts([]); + setStreamingText(""); + + try { + const response = await fetch(invokeUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + input: [...messages, userMessage].map(serializeForApi), + stream: true, + }), + }); + + if (!response.ok) { + const err = await response.json().catch(() => ({})); + throw new Error( + (err as { error?: string }).error ?? `HTTP ${response.status}`, + ); + } + + const reader = response.body?.getReader(); + if (!reader) throw new Error("No response body"); + + const decoder = new TextDecoder(); + let buffer = ""; + let fullText = ""; + const parts: AssistantPart[] = []; + const seenItemIds = new Set(); + + for (;;) { + 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 payload = line.slice(6); + if (payload === "[DONE]") continue; + + let data: SSEEvent; + try { + data = JSON.parse(payload); + } catch { + continue; + } + + if (data.type === "response.output_item.added" && data.item) { + const item = data.item; + const id = + item.id ?? `${item.type}_${Date.now()}_${Math.random()}`; + if (seenItemIds.has(id)) continue; + seenItemIds.add(id); + + if (item.type === "function_call" && item.name != null) { + parts.push({ + type: "function_call", + id, + callId: item.call_id ?? id, + name: item.name, + arguments: item.arguments ?? "{}", + }); + } else if ( + item.type === "function_call_output" && + item.call_id != null + ) { + parts.push({ + type: "function_call_output", + id, + callId: item.call_id, + output: item.output ?? "", + }); + } + setStreamingParts([...parts]); + } + + if (data.type === "response.output_text.delta" && data.delta) { + fullText += data.delta; + setStreamingText(fullText); + } + } + } + + const finalParts: AssistantPart[] = [...parts]; + if (fullText) { + finalParts.push({ type: "text", content: fullText }); + } + + setMessages((prev) => [ + ...prev, + { role: "assistant", parts: finalParts }, + ]); + setStreamingParts([]); + setStreamingText(""); + } catch (err) { + const errorText = + err instanceof Error ? err.message : "Something went wrong"; + setMessages((prev) => [ + ...prev, + { + role: "assistant", + parts: [{ type: "text", content: `Error: ${errorText}` }], + }, + ]); + setStreamingParts([]); + setStreamingText(""); + } finally { + setLoading(false); + } + }, + [input, loading, messages, invokeUrl], + ); + + const displayMessages = useMemo(() => { + if (loading && (streamingParts.length > 0 || streamingText)) { + const streamingPartList: AssistantPart[] = [...streamingParts]; + if (streamingText) { + streamingPartList.push({ type: "text", content: streamingText }); + } + return [ + ...messages, + { role: "assistant" as const, parts: streamingPartList }, + ]; + } + return messages; + }, [messages, streamingParts, streamingText, loading]); + + const isStreamingText = Boolean(loading && streamingText); + + return { + messages, + loading, + input, + setInput, + handleSubmit, + displayMessages, + isStreamingText, + }; +} diff --git a/packages/appkit-ui/src/react/agent-chat/utils.ts b/packages/appkit-ui/src/react/agent-chat/utils.ts new file mode 100644 index 00000000..497423f7 --- /dev/null +++ b/packages/appkit-ui/src/react/agent-chat/utils.ts @@ -0,0 +1,32 @@ +import type { ChatMessage } from "./types"; + +/** Serialize chat message for the Responses API request body. */ +export function serializeForApi(msg: ChatMessage): Record { + if (msg.role === "user") { + return { role: "user", content: msg.content }; + } + const content = msg.parts.map((p) => { + if (p.type === "text") + return { type: "output_text" as const, text: p.content }; + if (p.type === "function_call") + return { + type: "function_call" as const, + name: p.name, + arguments: p.arguments, + }; + return { + type: "function_call_output" as const, + call_id: p.callId, + output: p.output, + }; + }); + return { role: "assistant", content }; +} + +export function tryFormatJson(s: string): string { + try { + return JSON.stringify(JSON.parse(s), null, 2); + } catch { + return s; + } +} diff --git a/packages/appkit-ui/src/react/index.ts b/packages/appkit-ui/src/react/index.ts index dc52a2f3..52150a5f 100644 --- a/packages/appkit-ui/src/react/index.ts +++ b/packages/appkit-ui/src/react/index.ts @@ -1,3 +1,4 @@ +export * from "./agent-chat"; export * from "./charts"; export * from "./genie"; export * from "./hooks"; diff --git a/packages/appkit/package.json b/packages/appkit/package.json index 699a1986..1d057033 100644 --- a/packages/appkit/package.json +++ b/packages/appkit/package.json @@ -44,6 +44,30 @@ "tarball": "rm -rf tmp && pnpm dist && npm pack ./tmp --pack-destination ./tmp", "typecheck": "tsc --noEmit" }, + "peerDependencies": { + "@arizeai/openinference-instrumentation-langchain": ">=4.0.0", + "@databricks/langchainjs": ">=0.1.0", + "@langchain/core": ">=1.0.0", + "@langchain/langgraph": ">=1.0.0", + "@langchain/mcp-adapters": ">=1.0.0" + }, + "peerDependenciesMeta": { + "@arizeai/openinference-instrumentation-langchain": { + "optional": true + }, + "@databricks/langchainjs": { + "optional": true + }, + "@langchain/core": { + "optional": true + }, + "@langchain/langgraph": { + "optional": true + }, + "@langchain/mcp-adapters": { + "optional": true + } + }, "dependencies": { "@databricks/lakebase": "workspace:*", "@databricks/sdk-experimental": "^0.16.0", @@ -71,6 +95,7 @@ "shared": "workspace:*", "vite": "npm:rolldown-vite@7.1.14", "ws": "^8.18.3", + "zod": "^4.1.13", "zod-to-ts": "^2.0.0" }, "devDependencies": { diff --git a/packages/appkit/src/index.ts b/packages/appkit/src/index.ts index 0d762fde..64201c04 100644 --- a/packages/appkit/src/index.ts +++ b/packages/appkit/src/index.ts @@ -48,7 +48,14 @@ export { } from "./errors"; // Plugin authoring export { Plugin, type ToPlugin, toPlugin } from "./plugin"; -export { analytics, genie, lakebase, server } from "./plugins"; +export { agent, analytics, genie, lakebase, server } from "./plugins"; +export type { + AgentInterface, + IAgentConfig, + InvokeParams, + ResponseStreamEvent, + StandardAgent, +} from "./plugins/agent"; // Registry types and utilities for plugin manifests export type { ConfigSchema, diff --git a/packages/appkit/src/plugins/agent/agent-interface.ts b/packages/appkit/src/plugins/agent/agent-interface.ts new file mode 100644 index 00000000..2e8a65bc --- /dev/null +++ b/packages/appkit/src/plugins/agent/agent-interface.ts @@ -0,0 +1,121 @@ +/** + * Agent interface types for the AppKit Agent Plugin. + * + * These types define the contract between the plugin framework and agent + * implementations. They mirror the OpenAI Responses API SSE format without + * requiring the `openai` package as a dependency. + */ + +// --------------------------------------------------------------------------- +// Invoke params +// --------------------------------------------------------------------------- + +export interface InvokeParams { + input: string; + chat_history?: Array<{ role: string; content: string }>; +} + +// --------------------------------------------------------------------------- +// Responses API output types (minimal subset) +// --------------------------------------------------------------------------- + +export interface ResponseOutputTextContent { + type: "output_text"; + text: string; + annotations: unknown[]; +} + +export interface ResponseOutputMessage { + id: string; + type: "message"; + role: "assistant"; + status: "in_progress" | "completed"; + content: ResponseOutputTextContent[]; +} + +export interface ResponseFunctionToolCall { + id: string; + type: "function_call"; + call_id: string; + name: string; + arguments: string; + status: "completed"; +} + +export interface ResponseFunctionCallOutput { + id: string; + type: "function_call_output"; + call_id: string; + output: string; +} + +export type ResponseOutputItem = + | ResponseOutputMessage + | ResponseFunctionToolCall + | ResponseFunctionCallOutput; + +// --------------------------------------------------------------------------- +// Responses API SSE event types +// --------------------------------------------------------------------------- + +export interface ResponseOutputItemAddedEvent { + type: "response.output_item.added"; + item: ResponseOutputItem; + output_index: number; + sequence_number: number; +} + +export interface ResponseOutputItemDoneEvent { + type: "response.output_item.done"; + item: ResponseOutputItem; + output_index: number; + sequence_number: number; +} + +export interface ResponseTextDeltaEvent { + type: "response.output_text.delta"; + item_id: string; + output_index: number; + content_index: number; + delta: string; + sequence_number: number; +} + +export interface ResponseCompletedEvent { + type: "response.completed"; + sequence_number: number; + response: Record; +} + +export interface ResponseErrorEvent { + type: "error"; + error: string; +} + +export interface ResponseFailedEvent { + type: "response.failed"; +} + +export type ResponseStreamEvent = + | ResponseOutputItemAddedEvent + | ResponseOutputItemDoneEvent + | ResponseTextDeltaEvent + | ResponseCompletedEvent + | ResponseErrorEvent + | ResponseFailedEvent; + +// --------------------------------------------------------------------------- +// Agent interface +// --------------------------------------------------------------------------- + +/** + * Contract that agent implementations must fulfil. + * + * The plugin calls `invoke()` for non-streaming requests and `stream()` for + * SSE streaming. Implementations are responsible for translating their SDK's + * output into Responses API types. + */ +export interface AgentInterface { + invoke(params: InvokeParams): Promise; + stream(params: InvokeParams): AsyncGenerator; +} diff --git a/packages/appkit/src/plugins/agent/agent.ts b/packages/appkit/src/plugins/agent/agent.ts new file mode 100644 index 00000000..750e64e9 --- /dev/null +++ b/packages/appkit/src/plugins/agent/agent.ts @@ -0,0 +1,278 @@ +/** + * AgentPlugin — first-class AppKit plugin for LangChain/LangGraph agents. + * + * Provides: + * - POST /api/agent (standard AppKit namespaced route) + * + * Supports two modes: + * 1. Bring-your-own agent via `config.agentInstance` + * 2. Auto-build a LangGraph ReAct agent from config (model, tools, MCP servers) + * + * When using config (not agentInstance), you can add tools and MCP servers + * after app creation via appkit.agent.addTools() and appkit.agent.addMcpServers(). + */ + +import type { DatabricksMCPServer } from "@databricks/langchainjs"; +import type { StructuredToolInterface } from "@langchain/core/tools"; +import type express from "express"; +import { createLogger } from "../../logging/logger"; +import { Plugin, toPlugin } from "../../plugin"; +import type { AgentInterface } from "./agent-interface"; +import { createInvokeHandler } from "./invoke-handler"; +import manifest from "./manifest.json"; +import { StandardAgent } from "./standard-agent"; +import type { IAgentConfig } from "./types"; + +const logger = createLogger("agent"); + +const DEFAULT_SYSTEM_PROMPT = + "You are a helpful AI assistant with access to various tools."; + +type ChatDatabricksInstance = InstanceType< + Awaited["ChatDatabricks"] +>; + +export class AgentPlugin extends Plugin { + public name = "agent" as const; + + static manifest = manifest; + + protected declare config: IAgentConfig; + + private agentImpl: AgentInterface | null = null; + private systemPrompt = DEFAULT_SYSTEM_PROMPT; + private mcpClient: { + getTools(): Promise; + close(): Promise; + } | null = null; + + /** Only set when building from config (not agentInstance). Used when rebuilding after addTools/addMcpServers. */ + private model: ChatDatabricksInstance | null = null; + /** Mutable list of tools (config + added). Only used when building from config. */ + private toolsList: StructuredToolInterface[] = []; + /** Mutable list of MCP servers (config + added). Only used when building from config. */ + private mcpServersList: DatabricksMCPServer[] = []; + + async setup() { + this.systemPrompt = this.config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT; + + // If a pre-built agent is provided, use it directly + if (this.config.agentInstance) { + this.agentImpl = this.config.agentInstance; + logger.info("AgentPlugin initialized with provided agentInstance"); + return; + } + + // Otherwise build a LangGraph ReAct agent from config + const modelName = this.config.model ?? process.env.DATABRICKS_MODEL; + + if (!modelName) { + throw new Error( + "AgentPlugin: model name is required. Set config.model or DATABRICKS_MODEL env var.", + ); + } + + const { ChatDatabricks } = await import("@databricks/langchainjs"); + + this.model = new ChatDatabricks({ + model: modelName, + useResponsesApi: this.config.useResponsesApi ?? false, + temperature: this.config.temperature ?? 0.1, + maxTokens: this.config.maxTokens ?? 2000, + maxRetries: 3, + }); + + this.toolsList = [...(this.config.tools ?? [])]; + this.mcpServersList = [...(this.config.mcpServers ?? [])]; + + await this.buildStandardAgent(); + + logger.info( + "AgentPlugin initialized: model=%s tools=%d mcpServers=%d", + modelName, + this.toolsList.length, + this.mcpServersList.length, + ); + } + + /** + * Builds or rebuilds the LangGraph ReAct agent from current model, toolsList, and mcpServersList. + * Call this after changing toolsList or mcpServersList (e.g. via addTools/addMcpServers). + */ + private async buildStandardAgent(): Promise { + if (!this.model) return; + + // Close existing MCP client before creating a new one + if (this.mcpClient) { + try { + await this.mcpClient.close(); + } catch (err) { + logger.warn("Error closing MCP client during rebuild: %O", err); + } + this.mcpClient = null; + } + + const tools: StructuredToolInterface[] = []; + + if (this.mcpServersList.length > 0) { + try { + const { buildMCPServerConfig } = await import( + "@databricks/langchainjs" + ); + const mcpServerConfigs = await buildMCPServerConfig( + this.mcpServersList, + ); + const { MultiServerMCPClient } = await import( + "@langchain/mcp-adapters" + ); + this.mcpClient = new MultiServerMCPClient({ + mcpServers: mcpServerConfigs, + throwOnLoadError: false, + prefixToolNameWithServerName: true, + }); + const mcpTools = await this.mcpClient.getTools(); + tools.push(...mcpTools); + logger.info( + "Loaded %d MCP tools from %d server(s)", + mcpTools.length, + this.mcpServersList.length, + ); + } catch (err) { + logger.warn( + "Failed to load MCP tools — continuing without them: %O", + err, + ); + } + } + + tools.push(...this.toolsList); + + const { createReactAgent } = await import("@langchain/langgraph/prebuilt"); + const langGraphAgent = createReactAgent({ + llm: this.model, + tools, + }); + + this.agentImpl = new StandardAgent( + langGraphAgent as any, + this.systemPrompt, + ); + } + + /** + * Add tools to the agent after app creation. Only supported when the plugin + * was initialized from config (not when using agentInstance). Rebuilds the + * underlying LangGraph agent with the new tool set. + */ + async addTools(tools: StructuredToolInterface[]): Promise { + if (this.config.agentInstance) { + throw new Error( + "addTools() is not supported when using a custom agentInstance", + ); + } + if (!this.model) { + throw new Error("AgentPlugin not initialized — call setup() first"); + } + this.toolsList.push(...tools); + await this.buildStandardAgent(); + logger.info( + "Added %d tool(s); total tools=%d", + tools.length, + this.toolsList.length, + ); + } + + /** + * Add MCP servers to the agent after app creation. Only supported when the + * plugin was initialized from config (not when using agentInstance). Rebuilds + * the underlying LangGraph agent so new MCP tools are available. + */ + async addMcpServers(servers: DatabricksMCPServer[]): Promise { + if (this.config.agentInstance) { + throw new Error( + "addMcpServers() is not supported when using a custom agentInstance", + ); + } + if (!this.model) { + throw new Error("AgentPlugin not initialized — call setup() first"); + } + this.mcpServersList.push(...servers); + await this.buildStandardAgent(); + logger.info( + "Added %d MCP server(s); total servers=%d", + servers.length, + this.mcpServersList.length, + ); + } + + private getAgentImpl(): AgentInterface { + if (!this.agentImpl) { + throw new Error("AgentPlugin not initialized — call setup() first"); + } + return this.agentImpl; + } + + injectRoutes(router: express.Router) { + const handler = createInvokeHandler(() => this.getAgentImpl()); + router.post("/", handler); + this.registerEndpoint("invoke", `/api/${this.name}`); + } + + async abortActiveOperations() { + await super.abortActiveOperations(); + if (this.mcpClient) { + try { + await this.mcpClient.close(); + } catch (err) { + logger.warn("Error closing MCP client: %O", err); + } + } + } + + exports() { + return { + invoke: async ( + messages: { role: string; content: string }[], + ): Promise => { + if (!this.agentImpl) { + throw new Error("AgentPlugin not initialized"); + } + const lastUser = [...messages].reverse().find((m) => m.role === "user"); + const input = lastUser?.content ?? ""; + const chatHistory = messages.slice(0, -1); + const items = await this.agentImpl.invoke({ + input, + chat_history: chatHistory, + }); + const msg = items.find((i) => i.type === "message") as any; + const text = msg?.content?.[0]?.text ?? ""; + return text; + }, + + stream: async function* ( + this: AgentPlugin, + messages: { role: string; content: string }[], + ) { + if (!this.agentImpl) { + throw new Error("AgentPlugin not initialized"); + } + const lastUser = [...messages].reverse().find((m) => m.role === "user"); + const input = lastUser?.content ?? ""; + const chatHistory = messages.slice(0, -1); + yield* this.agentImpl.stream({ + input, + chat_history: chatHistory, + }); + }.bind(this), + + addTools: (tools: StructuredToolInterface[]) => this.addTools(tools), + addMcpServers: (servers: DatabricksMCPServer[]) => + this.addMcpServers(servers), + }; + } +} + +export const agent = toPlugin( + AgentPlugin, + "agent", +); diff --git a/packages/appkit/src/plugins/agent/index.ts b/packages/appkit/src/plugins/agent/index.ts new file mode 100644 index 00000000..616e7f8c --- /dev/null +++ b/packages/appkit/src/plugins/agent/index.ts @@ -0,0 +1,14 @@ +export { AgentPlugin, agent } from "./agent"; +export type { + AgentInterface, + InvokeParams, + ResponseFunctionCallOutput, + ResponseFunctionToolCall, + ResponseOutputItem, + ResponseOutputMessage, + ResponseStreamEvent, +} from "./agent-interface"; +export { createInvokeHandler } from "./invoke-handler"; +export type { LangGraphAgent } from "./standard-agent"; +export { StandardAgent } from "./standard-agent"; +export type { IAgentConfig } from "./types"; diff --git a/packages/appkit/src/plugins/agent/invoke-handler.ts b/packages/appkit/src/plugins/agent/invoke-handler.ts new file mode 100644 index 00000000..20af5798 --- /dev/null +++ b/packages/appkit/src/plugins/agent/invoke-handler.ts @@ -0,0 +1,160 @@ +/** + * Responses API invoke handler for the agent plugin. + * + * Accepts Responses API request format, parses it into InvokeParams, then + * delegates to AgentInterface.stream() / AgentInterface.invoke(). The handler + * is a pure pass-through — all SSE event shaping happens inside the agent. + */ + +import type express from "express"; +import { z } from "zod"; +import type { AgentInterface } from "./agent-interface"; + +const responsesRequestSchema = z.object({ + input: z.union([ + z.string(), + z.array( + z.union([ + z.object({ + role: z.enum(["user", "assistant", "system"]), + content: z.union([ + z.string(), + z.array( + z.union([ + z.object({ type: z.string(), text: z.string() }).passthrough(), + z.object({ type: z.string() }).passthrough(), + ]), + ), + ]), + }), + z.object({ type: z.string() }).passthrough(), + ]), + ), + ]), + stream: z.boolean().optional().default(true), + model: z.string().optional(), +}); + +/** + * Flatten a Responses API message to a plain `{ role, content }` object. + * Handles function_call / function_call_output items and array content. + */ +function flattenHistoryItem(item: any): { role: string; content: string } { + if (item.type === "function_call") { + return { + role: "assistant", + content: `[Tool Call: ${item.name}(${item.arguments})]`, + }; + } + if (item.type === "function_call_output") { + return { role: "assistant", content: `[Tool Result: ${item.output}]` }; + } + + if (Array.isArray(item.content)) { + const textParts = item.content + .filter( + (p: any) => + p.type === "input_text" || + p.type === "output_text" || + p.type === "text", + ) + .map((p: any) => p.text); + + const toolParts = item.content + .filter( + (p: any) => + p.type === "function_call" || p.type === "function_call_output", + ) + .map((p: any) => + p.type === "function_call" + ? `[Tool Call: ${p.name}(${JSON.stringify(p.arguments)})]` + : `[Tool Result: ${p.output}]`, + ); + + const allParts = [...textParts, ...toolParts].filter((p) => p.length > 0); + return { ...item, content: allParts.join("\n") }; + } + + return { role: item.role ?? "user", content: item.content ?? "" }; +} + +/** + * Create an Express handler that invokes the agent via the AgentInterface + * and streams/returns the response in Responses API format. + */ +export function createInvokeHandler( + getAgent: () => AgentInterface, +): express.RequestHandler { + return async (req: express.Request, res: express.Response) => { + try { + const parsed = responsesRequestSchema.safeParse(req.body); + if (!parsed.success) { + res.status(400).json({ + error: "Invalid request format", + details: parsed.error.format(), + }); + return; + } + + const { stream } = parsed.data; + + const input = + typeof parsed.data.input === "string" + ? [{ role: "user" as const, content: parsed.data.input }] + : parsed.data.input; + + const userMessages = input.filter((msg: any) => msg.role === "user"); + if (userMessages.length === 0) { + res.status(400).json({ error: "No user message found in input" }); + return; + } + + const lastUserMessage = userMessages[userMessages.length - 1]; + + let userInput: string; + if (Array.isArray(lastUserMessage.content)) { + userInput = lastUserMessage.content + .filter( + (part: any) => part.type === "input_text" || part.type === "text", + ) + .map((part: any) => part.text) + .join("\n"); + } else { + userInput = lastUserMessage.content as string; + } + + const chatHistory = input.slice(0, -1).map(flattenHistoryItem); + + const agentParams = { input: userInput, chat_history: chatHistory }; + const agent = getAgent(); + + if (stream) { + res.setHeader("Content-Type", "text/event-stream"); + res.setHeader("Cache-Control", "no-cache"); + res.setHeader("Connection", "keep-alive"); + + try { + for await (const event of agent.stream(agentParams)) { + res.write(`data: ${JSON.stringify(event)}\n\n`); + } + res.write("data: [DONE]\n\n"); + res.end(); + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + res.write( + `data: ${JSON.stringify({ type: "error", error: message })}\n\n`, + ); + res.write(`data: ${JSON.stringify({ type: "response.failed" })}\n\n`); + res.write("data: [DONE]\n\n"); + res.end(); + } + } else { + const items = await agent.invoke(agentParams); + res.json({ output: items }); + } + } catch (err: unknown) { + const message = err instanceof Error ? err.message : String(err); + res.status(500).json({ error: "Internal server error", message }); + } + }; +} diff --git a/packages/appkit/src/plugins/agent/manifest.json b/packages/appkit/src/plugins/agent/manifest.json new file mode 100644 index 00000000..ee10116e --- /dev/null +++ b/packages/appkit/src/plugins/agent/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "agent", + "displayName": "Agent Plugin", + "description": "LangChain/LangGraph AI agent with streaming Responses API and MCP tool support", + "resources": { + "required": [ + { + "type": "serving_endpoint", + "alias": "Model Endpoint", + "resourceKey": "agent-model-endpoint", + "description": "Databricks model serving endpoint for the agent LLM", + "permission": "CAN_QUERY", + "fields": { + "name": { + "env": "DATABRICKS_MODEL", + "description": "Model serving endpoint name" + } + } + } + ], + "optional": [] + } +} diff --git a/packages/appkit/src/plugins/agent/standard-agent.ts b/packages/appkit/src/plugins/agent/standard-agent.ts new file mode 100644 index 00000000..39ad9c13 --- /dev/null +++ b/packages/appkit/src/plugins/agent/standard-agent.ts @@ -0,0 +1,223 @@ +/** + * StandardAgent — LangGraph wrapper implementing AgentInterface. + * + * Wraps a LangGraph `createReactAgent` instance and translates its stream + * events into Responses API SSE format. If you swap LangGraph for another + * SDK, provide your own AgentInterface implementation instead. + */ + +import { randomUUID } from "node:crypto"; +import type { BaseMessage } from "@langchain/core/messages"; +import { HumanMessage, SystemMessage } from "@langchain/core/messages"; +import type { + AgentInterface, + InvokeParams, + ResponseFunctionToolCall, + ResponseOutputItem, + ResponseOutputMessage, + ResponseStreamEvent, +} from "./agent-interface"; + +/** + * Minimal interface for the LangGraph agent returned by createReactAgent. + */ +export interface LangGraphAgent { + invoke(input: { + messages: BaseMessage[]; + }): Promise<{ messages: BaseMessage[] }>; + streamEvents( + input: { messages: BaseMessage[] }, + options: { version: "v1" | "v2" }, + ): AsyncIterable<{ + event: string; + name: string; + run_id: string; + data?: any; + }>; +} + +function convertToBaseMessages(messages: any[]): BaseMessage[] { + return messages.map((msg) => { + if (msg instanceof HumanMessage || msg instanceof SystemMessage) { + return msg; + } + const content = msg.content || ""; + switch (msg.role) { + case "user": + return new HumanMessage(content); + case "system": + return new SystemMessage(content); + default: + return new HumanMessage(content); + } + }); +} + +export class StandardAgent implements AgentInterface { + constructor( + private agent: LangGraphAgent, + private systemPrompt: string, + ) {} + + async invoke(params: InvokeParams): Promise { + const { input, chat_history = [] } = params; + + const messages: BaseMessage[] = [ + new SystemMessage(this.systemPrompt), + ...convertToBaseMessages(chat_history), + new HumanMessage(input), + ]; + + const result = await this.agent.invoke({ messages }); + const finalMessages = result.messages || []; + const lastMessage = finalMessages[finalMessages.length - 1]; + const text = + typeof lastMessage?.content === "string" ? lastMessage.content : ""; + + const outputMessage: ResponseOutputMessage = { + id: `msg_${randomUUID()}`, + type: "message", + role: "assistant", + status: "completed", + content: [{ type: "output_text", text, annotations: [] }], + }; + + return [outputMessage]; + } + + async *stream(params: InvokeParams): AsyncGenerator { + const { input, chat_history = [] } = params; + + const messages: BaseMessage[] = [ + new SystemMessage(this.systemPrompt), + ...convertToBaseMessages(chat_history), + new HumanMessage(input), + ]; + + const toolCallIds = new Map(); + let seqNum = 0; + let outputIndex = 0; + const textItemId = `msg_${randomUUID()}`; + let textOutputIndex = -1; + + const eventStream = this.agent.streamEvents( + { messages }, + { version: "v2" }, + ); + + for await (const event of eventStream) { + if (event.event === "on_tool_start") { + const callId = `call_${randomUUID()}`; + toolCallIds.set(`${event.name}_${event.run_id}`, callId); + + const fcItem: ResponseFunctionToolCall = { + id: `fc_${randomUUID()}`, + call_id: callId, + name: event.name, + arguments: JSON.stringify(event.data?.input || {}), + type: "function_call", + status: "completed", + }; + + const currentIndex = outputIndex++; + + yield { + type: "response.output_item.added", + item: fcItem, + output_index: currentIndex, + sequence_number: seqNum++, + }; + + yield { + type: "response.output_item.done", + item: fcItem, + output_index: currentIndex, + sequence_number: seqNum++, + }; + } + + if (event.event === "on_tool_end") { + const toolKey = `${event.name}_${event.run_id}`; + const callId = toolCallIds.get(toolKey) || `call_${randomUUID()}`; + toolCallIds.delete(toolKey); + + const outputItem = { + id: `fco_${randomUUID()}`, + call_id: callId, + output: JSON.stringify(event.data?.output || ""), + type: "function_call_output" as const, + }; + + const currentIndex = outputIndex++; + + yield { + type: "response.output_item.added", + item: outputItem, + output_index: currentIndex, + sequence_number: seqNum++, + }; + + yield { + type: "response.output_item.done", + item: outputItem, + output_index: currentIndex, + sequence_number: seqNum++, + }; + } + + if (event.event === "on_chat_model_stream") { + const content = event.data?.chunk?.content; + if (content && typeof content === "string") { + if (textOutputIndex === -1) { + textOutputIndex = outputIndex++; + + const msgItem: ResponseOutputMessage = { + id: textItemId, + type: "message", + role: "assistant", + status: "in_progress", + content: [], + }; + yield { + type: "response.output_item.added", + item: msgItem, + output_index: textOutputIndex, + sequence_number: seqNum++, + }; + } + + yield { + type: "response.output_text.delta", + item_id: textItemId, + output_index: textOutputIndex, + content_index: 0, + delta: content, + sequence_number: seqNum++, + }; + } + } + } + + if (textOutputIndex !== -1) { + const msgItem: ResponseOutputMessage = { + id: textItemId, + type: "message", + role: "assistant", + status: "completed", + content: [], + }; + yield { + type: "response.output_item.done", + item: msgItem, + output_index: textOutputIndex, + sequence_number: seqNum++, + }; + } + + yield { + type: "response.completed", + sequence_number: seqNum++, + response: {}, + }; + } +} diff --git a/packages/appkit/src/plugins/agent/tests/agent.integration.test.ts b/packages/appkit/src/plugins/agent/tests/agent.integration.test.ts new file mode 100644 index 00000000..d4c2fdb6 --- /dev/null +++ b/packages/appkit/src/plugins/agent/tests/agent.integration.test.ts @@ -0,0 +1,228 @@ +import type { Server } from "node:http"; +import { mockServiceContext, setupDatabricksEnv } from "@tools/test-helpers"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; + +process.env.DATABRICKS_APP_PORT = "8000"; +process.env.FLASK_RUN_HOST = "0.0.0.0"; +process.env.DATABRICKS_MODEL = "test-model"; + +import { ServiceContext } from "../../../context/service-context"; +import { createApp } from "../../../core"; +import { server as serverPlugin } from "../../server/index"; +import { agent } from "../agent"; +import { StubAgent } from "./stub-agent"; + +function parseSSEStream(text: string) { + const events: any[] = []; + let fullOutput = ""; + const lines = text.split("\n"); + for (const line of lines) { + if (line.startsWith("data: ") && line !== "data: [DONE]") { + try { + const data = JSON.parse(line.slice(6)); + events.push(data); + if (data.type === "response.output_text.delta") { + fullOutput += data.delta; + } + } catch {} + } + } + return { events, fullOutput }; +} + +describe("AgentPlugin Integration", () => { + let server: Server; + let baseUrl: string; + let serviceContextMock: Awaited>; + const TEST_PORT = 9885; + + beforeAll(async () => { + setupDatabricksEnv(); + ServiceContext.reset(); + serviceContextMock = await mockServiceContext(); + + const app = await createApp({ + plugins: [ + agent({ agentInstance: new StubAgent() }), + serverPlugin({ + port: TEST_PORT, + host: "127.0.0.1", + autoStart: false, + }), + ], + }); + + await app.server.start(); + server = app.server.getServer(); + baseUrl = `http://127.0.0.1:${TEST_PORT}`; + + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + afterAll(async () => { + serviceContextMock?.restore(); + if (server) { + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) reject(err); + else resolve(); + }); + }); + } + }); + + describe("POST /api/agent (streaming)", () => { + test("streams SSE events and completes", async () => { + const response = await fetch(`${baseUrl}/api/agent`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + input: [{ role: "user", content: "Hello agent" }], + stream: true, + }), + }); + + expect(response.ok).toBe(true); + expect(response.headers.get("content-type")).toContain( + "text/event-stream", + ); + + const text = await response.text(); + const { events, fullOutput } = parseSSEStream(text); + + expect(fullOutput).toContain("Echo: Hello agent"); + + const hasCompleted = events.some((e) => e.type === "response.completed"); + expect(hasCompleted).toBe(true); + + expect(text).toContain("data: [DONE]"); + }); + }); + + describe("non-streaming mode", () => { + test("returns JSON response", async () => { + const response = await fetch(`${baseUrl}/api/agent`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + input: [{ role: "user", content: "No stream" }], + stream: false, + }), + }); + + expect(response.ok).toBe(true); + const data = (await response.json()) as { + output: { type: string; content: { text: string }[] }[]; + }; + + expect(data.output).toBeDefined(); + expect(data.output).toHaveLength(1); + expect(data.output[0].type).toBe("message"); + expect(data.output[0].content[0].text).toContain("Echo: No stream"); + }); + }); + + describe("string input", () => { + test("accepts plain string as input", async () => { + const response = await fetch(`${baseUrl}/api/agent`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + input: "Plain string input", + stream: true, + }), + }); + + expect(response.ok).toBe(true); + const text = await response.text(); + const { fullOutput } = parseSSEStream(text); + expect(fullOutput).toContain("Echo: Plain string input"); + }); + }); + + describe("multi-turn conversations", () => { + test("handles chat history", async () => { + const response = await fetch(`${baseUrl}/api/agent`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + input: [ + { role: "user", content: "My name is Alice" }, + { + role: "assistant", + content: "Nice to meet you, Alice", + }, + { + role: "user", + content: "What is my name?", + }, + ], + stream: true, + }), + }); + + expect(response.ok).toBe(true); + const text = await response.text(); + const { fullOutput } = parseSSEStream(text); + expect(fullOutput).toContain("Echo: What is my name?"); + }); + + test("handles function_call items in history", async () => { + const response = await fetch(`${baseUrl}/api/agent`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + input: [ + { role: "user", content: "Look up the answer" }, + { + type: "function_call", + name: "search", + arguments: '{"q":"test"}', + }, + { + type: "function_call_output", + output: '"42"', + }, + { + role: "user", + content: "What did you find?", + }, + ], + stream: true, + }), + }); + + expect(response.ok).toBe(true); + const text = await response.text(); + const { fullOutput } = parseSSEStream(text); + expect(fullOutput.length).toBeGreaterThan(0); + }); + }); + + describe("error responses", () => { + test("returns 400 for malformed input", async () => { + const response = await fetch(`${baseUrl}/api/agent`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ stream: true }), + }); + + expect(response.ok).toBe(false); + expect(response.status).toBe(400); + }); + + test("returns 400 when no user message", async () => { + const response = await fetch(`${baseUrl}/api/agent`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + input: [{ role: "assistant", content: "Only assistant" }], + stream: true, + }), + }); + + expect(response.ok).toBe(false); + expect(response.status).toBe(400); + }); + }); +}); diff --git a/packages/appkit/src/plugins/agent/tests/agent.test.ts b/packages/appkit/src/plugins/agent/tests/agent.test.ts new file mode 100644 index 00000000..4816b0cb --- /dev/null +++ b/packages/appkit/src/plugins/agent/tests/agent.test.ts @@ -0,0 +1,172 @@ +import { + createMockRouter, + mockServiceContext, + setupDatabricksEnv, +} from "@tools/test-helpers"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { ServiceContext } from "../../../context/service-context"; +import { AgentPlugin, agent } from "../agent"; +import type { IAgentConfig } from "../types"; +import { StubAgent } from "./stub-agent"; + +// Mock CacheManager singleton +vi.mock("../../../cache", () => ({ + CacheManager: { + getInstanceSync: vi.fn(() => ({ + get: vi.fn(), + set: vi.fn(), + delete: vi.fn(), + getOrExecute: vi.fn(async (_key: unknown[], fn: () => Promise) => + fn(), + ), + generateKey: vi.fn(() => "test-key"), + })), + }, +})); + +describe("AgentPlugin", () => { + let serviceContextMock: Awaited>; + + beforeEach(async () => { + setupDatabricksEnv(); + ServiceContext.reset(); + serviceContextMock = await mockServiceContext(); + }); + + afterEach(() => { + serviceContextMock?.restore(); + }); + + test("agent factory produces correct plugin data", () => { + const pluginData = agent({ agentInstance: new StubAgent() }); + expect(pluginData.name).toBe("agent"); + }); + + test("plugin has correct manifest", () => { + expect(AgentPlugin.manifest).toBeDefined(); + expect(AgentPlugin.manifest.name).toBe("agent"); + expect(AgentPlugin.manifest.resources.required).toHaveLength(1); + expect(AgentPlugin.manifest.resources.required[0].type).toBe( + "serving_endpoint", + ); + }); + + test("plugin instance has correct name", () => { + const config: IAgentConfig = { agentInstance: new StubAgent() }; + const plugin = new AgentPlugin(config); + expect(plugin.name).toBe("agent"); + }); + + describe("setup()", () => { + test("uses provided agentInstance", async () => { + const stub = new StubAgent(); + const config: IAgentConfig = { agentInstance: stub }; + const plugin = new AgentPlugin(config); + + await plugin.setup(); + + const exported = plugin.exports(); + const result = await exported.invoke([{ role: "user", content: "hi" }]); + expect(result).toContain("Echo: hi"); + }); + + test("throws when no model and no agentInstance", async () => { + const config: IAgentConfig = {}; + const plugin = new AgentPlugin(config); + + await expect(plugin.setup()).rejects.toThrow("model name is required"); + }); + + test("resolves model from env var when not in config", async () => { + process.env.DATABRICKS_MODEL = "test-model"; + + const config: IAgentConfig = {}; + const plugin = new AgentPlugin(config); + + // Will fail because ChatDatabricks isn't available, but it + // should get past the model name check + try { + await plugin.setup(); + } catch (e: any) { + expect(e.message).not.toContain("model name is required"); + } + + delete process.env.DATABRICKS_MODEL; + }); + }); + + describe("injectRoutes()", () => { + test("registers POST handler on router", () => { + const stub = new StubAgent(); + const config: IAgentConfig = { agentInstance: stub }; + const plugin = new AgentPlugin(config); + + const { router } = createMockRouter(); + plugin.injectRoutes(router as any); + + expect(router.post).toHaveBeenCalledWith("/", expect.any(Function)); + }); + }); + + describe("exports()", () => { + test("returns invoke and stream methods", async () => { + const stub = new StubAgent(); + const config: IAgentConfig = { agentInstance: stub }; + const plugin = new AgentPlugin(config); + await plugin.setup(); + + const exported = plugin.exports(); + + expect(typeof exported.invoke).toBe("function"); + expect(typeof exported.stream).toBe("function"); + }); + + test("invoke returns text from agent response", async () => { + const stub = new StubAgent(); + const config: IAgentConfig = { agentInstance: stub }; + const plugin = new AgentPlugin(config); + await plugin.setup(); + + const result = await plugin + .exports() + .invoke([{ role: "user", content: "test message" }]); + + expect(result).toBe("Echo: test message"); + }); + + test("stream yields ResponseStreamEvents", async () => { + const stub = new StubAgent(); + const config: IAgentConfig = { agentInstance: stub }; + const plugin = new AgentPlugin(config); + await plugin.setup(); + + const events: any[] = []; + for await (const event of plugin + .exports() + .stream([{ role: "user", content: "hello" }])) { + events.push(event); + } + + expect(events.length).toBeGreaterThan(0); + const deltaEvent = events.find( + (e) => e.type === "response.output_text.delta", + ); + expect(deltaEvent).toBeDefined(); + expect(deltaEvent.delta).toContain("Echo: hello"); + + const completedEvent = events.find( + (e) => e.type === "response.completed", + ); + expect(completedEvent).toBeDefined(); + }); + + test("throws when not initialized", async () => { + const config: IAgentConfig = { agentInstance: new StubAgent() }; + const plugin = new AgentPlugin(config); + + await expect( + plugin.exports().invoke([{ role: "user", content: "hi" }]), + ).rejects.toThrow("not initialized"); + }); + }); +}); diff --git a/packages/appkit/src/plugins/agent/tests/invoke-handler.test.ts b/packages/appkit/src/plugins/agent/tests/invoke-handler.test.ts new file mode 100644 index 00000000..8bd1cec6 --- /dev/null +++ b/packages/appkit/src/plugins/agent/tests/invoke-handler.test.ts @@ -0,0 +1,325 @@ +import { createMockRequest, createMockResponse } from "@tools/test-helpers"; +import { describe, expect, test, vi } from "vitest"; +import { createInvokeHandler } from "../invoke-handler"; +import { StubAgent } from "./stub-agent"; + +function makeReq(body: any) { + return createMockRequest({ body }) as any; +} + +function makeRes() { + const res = createMockResponse() as any; + // Collect all writes for SSE assertions + const chunks: string[] = []; + res.write.mockImplementation((chunk: string) => { + chunks.push(chunk); + return true; + }); + (res as any).__chunks = chunks; + return res; +} + +function parseSSE(res: any): { events: any[]; fullOutput: string } { + const chunks: string[] = res.__chunks; + const events: any[] = []; + let fullOutput = ""; + + for (const chunk of chunks) { + const lines = chunk.split("\n"); + for (const line of lines) { + if (line.startsWith("data: ") && line !== "data: [DONE]") { + try { + const data = JSON.parse(line.slice(6)); + events.push(data); + if (data.type === "response.output_text.delta") { + fullOutput += data.delta; + } + } catch {} + } + } + } + return { events, fullOutput }; +} + +describe("createInvokeHandler", () => { + const stubAgent = new StubAgent(); + const handler = createInvokeHandler(() => stubAgent); + + describe("streaming mode", () => { + test("streams SSE events with correct format", async () => { + const req = makeReq({ + input: [{ role: "user", content: "Hello" }], + stream: true, + }); + const res = makeRes(); + + await handler(req, res, vi.fn()); + + expect(res.setHeader).toHaveBeenCalledWith( + "Content-Type", + "text/event-stream", + ); + + const { events, fullOutput } = parseSSE(res); + + expect(fullOutput).toContain("Echo: Hello"); + + const hasCompleted = events.some((e) => e.type === "response.completed"); + expect(hasCompleted).toBe(true); + + // Last write should be [DONE] + const lastChunk = res.__chunks[res.__chunks.length - 1]; + expect(lastChunk).toContain("[DONE]"); + + expect(res.end).toHaveBeenCalled(); + }); + + test("emits output_item.added and output_item.done events", async () => { + const req = makeReq({ + input: [{ role: "user", content: "Test" }], + stream: true, + }); + const res = makeRes(); + + await handler(req, res, vi.fn()); + + const { events } = parseSSE(res); + const addedEvent = events.find( + (e) => e.type === "response.output_item.added", + ); + const doneEvent = events.find( + (e) => e.type === "response.output_item.done", + ); + + expect(addedEvent).toBeDefined(); + expect(addedEvent.item.type).toBe("message"); + expect(doneEvent).toBeDefined(); + }); + }); + + describe("non-streaming mode", () => { + test("returns JSON with output items", async () => { + const req = makeReq({ + input: [{ role: "user", content: "Hello" }], + stream: false, + }); + const res = makeRes(); + + await handler(req, res, vi.fn()); + + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ + output: expect.arrayContaining([ + expect.objectContaining({ + type: "message", + content: expect.arrayContaining([ + expect.objectContaining({ + type: "output_text", + text: "Echo: Hello", + }), + ]), + }), + ]), + }), + ); + }); + }); + + describe("input parsing", () => { + test("accepts string input", async () => { + const req = makeReq({ + input: "Hello string", + stream: true, + }); + const res = makeRes(); + + await handler(req, res, vi.fn()); + + const { fullOutput } = parseSSE(res); + expect(fullOutput).toContain("Echo: Hello string"); + }); + + test("accepts array input with multipart content", async () => { + const req = makeReq({ + input: [ + { + role: "user", + content: [ + { type: "input_text", text: "Part one" }, + { type: "input_text", text: "Part two" }, + ], + }, + ], + stream: true, + }); + const res = makeRes(); + + await handler(req, res, vi.fn()); + + const { fullOutput } = parseSSE(res); + expect(fullOutput).toContain("Echo: Part one\nPart two"); + }); + }); + + describe("chat history", () => { + test("passes chat history to agent", async () => { + const spyAgent = { + invoke: vi.fn().mockResolvedValue([ + { + id: "msg_1", + type: "message", + role: "assistant", + status: "completed", + content: [ + { type: "output_text", text: "response", annotations: [] }, + ], + }, + ]), + stream: vi.fn(), + }; + const historyHandler = createInvokeHandler(() => spyAgent as any); + + const req = makeReq({ + input: [ + { role: "user", content: "First message" }, + { role: "assistant", content: "First reply" }, + { role: "user", content: "Second message" }, + ], + stream: false, + }); + const res = makeRes(); + + await historyHandler(req, res, vi.fn()); + + expect(spyAgent.invoke).toHaveBeenCalledWith( + expect.objectContaining({ + input: "Second message", + chat_history: expect.arrayContaining([ + expect.objectContaining({ + role: "user", + content: "First message", + }), + expect.objectContaining({ + role: "assistant", + content: "First reply", + }), + ]), + }), + ); + }); + + test("handles function_call items in history", async () => { + const spyAgent = { + invoke: vi.fn().mockResolvedValue([ + { + id: "msg_1", + type: "message", + role: "assistant", + status: "completed", + content: [{ type: "output_text", text: "done", annotations: [] }], + }, + ]), + stream: vi.fn(), + }; + const historyHandler = createInvokeHandler(() => spyAgent as any); + + const req = makeReq({ + input: [ + { role: "user", content: "Look up the answer" }, + { + type: "function_call", + name: "search", + arguments: '{"q":"test"}', + }, + { + type: "function_call_output", + output: '"42"', + }, + { role: "user", content: "What did you find?" }, + ], + stream: false, + }); + const res = makeRes(); + + await historyHandler(req, res, vi.fn()); + + const calledHistory = spyAgent.invoke.mock.calls[0][0].chat_history; + expect(calledHistory).toHaveLength(3); + expect(calledHistory[1].content).toContain("[Tool Call:"); + expect(calledHistory[2].content).toContain("[Tool Result:"); + }); + }); + + describe("error handling", () => { + test("returns 400 for missing input", async () => { + const req = makeReq({ stream: true }); + const res = makeRes(); + + await handler(req, res, vi.fn()); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ error: "Invalid request format" }), + ); + }); + + test("returns 400 when no user message is present", async () => { + const req = makeReq({ + input: [{ role: "assistant", content: "I am assistant" }], + stream: true, + }); + const res = makeRes(); + + await handler(req, res, vi.fn()); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ + error: "No user message found in input", + }), + ); + }); + + test("handles agent errors gracefully in streaming mode", async () => { + const errorAgent = { + invoke: vi.fn(), + stream: (_params: any) => { + // Return an async iterable that throws on first next() + return { + async next() { + throw new Error("Agent exploded"); + }, + async return() { + return { done: true, value: undefined }; + }, + async throw(e: unknown) { + throw e; + }, + [Symbol.asyncIterator]() { + return this; + }, + [Symbol.asyncDispose]: undefined, + } as unknown as AsyncGenerator; + }, + }; + const errorHandler = createInvokeHandler(() => errorAgent as any); + + const req = makeReq({ + input: [{ role: "user", content: "boom" }], + stream: true, + }); + const res = makeRes(); + + await errorHandler(req, res, vi.fn()); + + const { events } = parseSSE(res); + const errorEvent = events.find((e) => e.type === "error"); + const failedEvent = events.find((e) => e.type === "response.failed"); + + expect(errorEvent).toBeDefined(); + expect(errorEvent.error).toContain("Agent exploded"); + expect(failedEvent).toBeDefined(); + expect(res.end).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/appkit/src/plugins/agent/tests/stub-agent.ts b/packages/appkit/src/plugins/agent/tests/stub-agent.ts new file mode 100644 index 00000000..540a2fca --- /dev/null +++ b/packages/appkit/src/plugins/agent/tests/stub-agent.ts @@ -0,0 +1,71 @@ +/** + * Deterministic stub AgentInterface for framework tests. + * + * Echoes user input as "Echo: {input}" — no LLM or network required. + */ + +import { randomUUID } from "node:crypto"; +import type { + AgentInterface, + InvokeParams, + ResponseOutputItem, + ResponseOutputMessage, + ResponseStreamEvent, +} from "../agent-interface"; + +export class StubAgent implements AgentInterface { + async invoke(params: InvokeParams): Promise { + const text = `Echo: ${params.input}`; + const message: ResponseOutputMessage = { + id: `msg_${randomUUID()}`, + type: "message", + role: "assistant", + status: "completed", + content: [{ type: "output_text", text, annotations: [] }], + }; + return [message]; + } + + async *stream(params: InvokeParams): AsyncGenerator { + const text = `Echo: ${params.input}`; + const itemId = `msg_${randomUUID()}`; + let seqNum = 0; + + const msgItem: ResponseOutputMessage = { + id: itemId, + type: "message", + role: "assistant", + status: "in_progress", + content: [], + }; + + yield { + type: "response.output_item.added", + item: msgItem, + output_index: 0, + sequence_number: seqNum++, + }; + + yield { + type: "response.output_text.delta", + item_id: itemId, + output_index: 0, + content_index: 0, + delta: text, + sequence_number: seqNum++, + }; + + yield { + type: "response.output_item.done", + item: { ...msgItem, status: "completed" }, + output_index: 0, + sequence_number: seqNum++, + }; + + yield { + type: "response.completed", + sequence_number: seqNum++, + response: {}, + }; + } +} diff --git a/packages/appkit/src/plugins/agent/types.ts b/packages/appkit/src/plugins/agent/types.ts new file mode 100644 index 00000000..0f6fa02c --- /dev/null +++ b/packages/appkit/src/plugins/agent/types.ts @@ -0,0 +1,43 @@ +import type { DatabricksMCPServer } from "@databricks/langchainjs"; +import type { StructuredTool } from "@langchain/core/tools"; +import type { BasePluginConfig } from "shared"; +import type { AgentInterface } from "./agent-interface"; + +export interface IAgentConfig extends BasePluginConfig { + /** + * Pre-built agent implementing AgentInterface. + * When provided the plugin skips internal LangGraph setup and delegates + * directly to this instance. Use this to bring your own agent + * implementation or a different LangChain variant. + */ + agentInstance?: AgentInterface; + + /** + * Databricks model serving endpoint name (e.g. "databricks-claude-sonnet-4-5"). + * Falls back to DATABRICKS_MODEL env var. + * Ignored when `agentInstance` is provided. + */ + model?: string; + + /** + * Whether ChatDatabricks calls the upstream model using the Responses API + * instead of the Chat Completions API. Default: false. + * Ignored when `agentInstance` is provided. + */ + useResponsesApi?: boolean; + + /** System prompt injected at the start of every conversation */ + systemPrompt?: string; + + /** Sampling temperature (0.0-1.0, default 0.1). Ignored when `agentInstance` is provided. */ + temperature?: number; + + /** Max tokens to generate (default 2000). Ignored when `agentInstance` is provided. */ + maxTokens?: number; + + /** MCP servers for Databricks tool integration. Ignored when `agentInstance` is provided. */ + mcpServers?: DatabricksMCPServer[]; + + /** Additional LangChain tools to register alongside MCP tools. Ignored when `agentInstance` is provided. */ + tools?: StructuredTool[]; +} diff --git a/packages/appkit/src/plugins/index.ts b/packages/appkit/src/plugins/index.ts index fafd11eb..23fc1cb5 100644 --- a/packages/appkit/src/plugins/index.ts +++ b/packages/appkit/src/plugins/index.ts @@ -1,3 +1,4 @@ +export * from "./agent"; export * from "./analytics"; export * from "./genie"; export * from "./lakebase"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05a67f18..99e1dcaa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -248,12 +248,27 @@ importers: packages/appkit: dependencies: + '@arizeai/openinference-instrumentation-langchain': + specifier: '>=4.0.0' + version: 4.0.6(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))) '@databricks/lakebase': specifier: workspace:* version: link:../lakebase + '@databricks/langchainjs': + specifier: '>=0.1.0' + version: 0.1.0(@cfworker/json-schema@4.1.1)(@langchain/langgraph@1.2.1(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(zod-to-json-schema@3.25.1(zod@4.1.13))(zod@4.1.13))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)) '@databricks/sdk-experimental': specifier: ^0.16.0 version: 0.16.0 + '@langchain/core': + specifier: '>=1.0.0' + version: 1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)) + '@langchain/langgraph': + specifier: '>=1.0.0' + version: 1.2.1(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(zod-to-json-schema@3.25.1(zod@4.1.13))(zod@4.1.13) + '@langchain/mcp-adapters': + specifier: '>=1.0.0' + version: 1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(@langchain/langgraph@1.2.1(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(zod-to-json-schema@3.25.1(zod@4.1.13))(zod@4.1.13)) '@opentelemetry/api': specifier: ^1.9.0 version: 1.9.0 @@ -326,6 +341,9 @@ importers: ws: specifier: ^8.18.3 version: 8.18.3(bufferutil@4.0.9) + zod: + specifier: ^4.1.13 + version: 4.1.13 zod-to-ts: specifier: ^2.0.0 version: 2.0.0(typescript@5.9.3)(zod@4.1.13) @@ -576,16 +594,32 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/gateway@3.0.66': + resolution: {integrity: sha512-SIQ0YY0iMuv+07HLsZ+bB990zUJ6S4ujORAh+Jv1V2KGNn73qQKnGO0JBk+w+Res8YqOFSycwDoWcFlQrVxS4A==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider-utils@3.0.19': resolution: {integrity: sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider-utils@4.0.19': + resolution: {integrity: sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider@2.0.0': resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} engines: {node: '>=18'} + '@ai-sdk/provider@3.0.8': + resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==} + engines: {node: '>=18'} + '@ai-sdk/react@2.0.115': resolution: {integrity: sha512-Etu7gWSEi2dmXss1PoR5CAZGwGShXsF9+Pon1eRO6EmatjYaBMhq1CfHPyYhGzWrint8jJIK2VaAhiMef29qZw==} engines: {node: '>=18'} @@ -676,6 +710,17 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@arizeai/openinference-core@2.0.5': + resolution: {integrity: sha512-BnufYaFqmG9twkz/9DHX9WTcOs7YvVAYaufau5tdjOT1c0Y8niJwmNWzV36phNPg3c7SmdD5OYLuzeAUN0T3pQ==} + + '@arizeai/openinference-instrumentation-langchain@4.0.6': + resolution: {integrity: sha512-yvA7ObrNUjhUN8y37lO+Cr8Ef7Bq6NKKoChXPOaKG/IufwAAcXUowdEC40gipUelS3k3AOgxcIU2rfP+7f+YyQ==} + peerDependencies: + '@langchain/core': ^1.0.0 || ^0.3.0 || ^0.2.0 + + '@arizeai/openinference-semantic-conventions@2.1.7': + resolution: {integrity: sha512-KyBfwxkSusPvxHBaW/TJ0japEbXCNziW9o6/IRKiPu+gp5TMKIagV2NKvt47rWYa4Jc0Nl+SvAPm+yxkdJqVbg==} + '@asamuzakjp/css-color@4.0.5': resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==} @@ -1431,6 +1476,9 @@ packages: '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + '@cfworker/json-schema@4.1.1': + resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} + '@chevrotain/cst-dts-gen@11.0.3': resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} @@ -1831,6 +1879,21 @@ packages: peerDependencies: postcss: ^8.4 + '@databricks/ai-sdk-provider@0.3.0': + resolution: {integrity: sha512-KKSeF/vvTeN/YEIzbpPl0tC0uWqXbCU3bjzAlX90aIUdyLjhD+8PviEXuh2g7YYpsDsBdWClu33Z7K+ooudfCA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@ai-sdk/provider': ^3.0.5 + '@ai-sdk/provider-utils': ^4.0.10 + + '@databricks/langchainjs@0.1.0': + resolution: {integrity: sha512-pCAsmoqBxoBOrHP9pxAxWj+jNbqqaD2WfYtnk61xpBpCbgfak1NA5MOZrc56TokidT8kam/f2RNKlFHjsok9aA==} + engines: {node: '>=18.0.0'} + + '@databricks/sdk-experimental@0.15.0': + resolution: {integrity: sha512-HkoMiF7dNDt6WRW0xhi7oPlBJQfxJ9suJhEZRFt08VwLMaWcw2PiF8monfHlkD4lkufEYV6CTxi5njQkciqiHA==} + engines: {node: '>=22.0', npm: '>=10.0.0'} + '@databricks/sdk-experimental@0.16.0': resolution: {integrity: sha512-9c2RxWYoRDFupdt4ZnBc1IPE1XaXgN+/wyV4DVcEqOnIa31ep51OnwAD/3014BImfKdyXg32nmgrB9dwvB6+lg==} engines: {node: '>=22.0', npm: '>=10.0.0'} @@ -2303,6 +2366,12 @@ packages: '@hapi/topo@5.1.0': resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + '@hono/node-server@1.19.11': + resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@hookform/resolvers@5.2.2': resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} peerDependencies: @@ -2551,6 +2620,48 @@ packages: peerDependencies: tslib: '2' + '@langchain/core@1.1.31': + resolution: {integrity: sha512-FxsgIUONjKaRpjx59sISgmb0OMCbAetPGyhzjGa2kX0y1f8LZ5xm9VB2db7W9HYWyLvzRWcMA51Uu4OSTJmtZQ==} + engines: {node: '>=20'} + + '@langchain/langgraph-checkpoint@1.0.0': + resolution: {integrity: sha512-xrclBGvNCXDmi0Nz28t3vjpxSH6UYx6w5XAXSiiB1WEdc2xD2iY/a913I3x3a31XpInUW/GGfXXfePfaghV54A==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': ^1.0.1 + + '@langchain/langgraph-sdk@1.6.5': + resolution: {integrity: sha512-JjprmbhgCnoNJ9DUKcvrEU+C9FfKsNGyT3ooqWxAY5Cx2qofhXmDJOpTCqqbxfDHPKG0RjTs5HgVK3WW5M6Big==} + peerDependencies: + '@langchain/core': ^1.1.16 + react: ^18 || ^19 + react-dom: ^18 || ^19 + peerDependenciesMeta: + '@langchain/core': + optional: true + react: + optional: true + react-dom: + optional: true + + '@langchain/langgraph@1.2.1': + resolution: {integrity: sha512-OeLMejye1DZeZBPnurus2bqvjRi+pyrqfAXX77hYdUqKeQ3hAG7pLG04xdrvMs0pl/F57ZtwywqoE2oVqcI6JA==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': ^1.1.16 + zod: ^3.25.32 || ^4.2.0 + zod-to-json-schema: ^3.x + peerDependenciesMeta: + zod-to-json-schema: + optional: true + + '@langchain/mcp-adapters@1.1.3': + resolution: {integrity: sha512-OPHIQNkTUJjnRj1pr+cp2nguMBZeF3Q1pVT1hCbgU7BrHgV7lov99wbU8po8Cm4zZzmeRtVO/T9X1SrDD1ogtQ==} + engines: {node: '>=20.10.0'} + peerDependencies: + '@langchain/core': ^1.0.0 + '@langchain/langgraph': ^1.0.0 + '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} @@ -2566,6 +2677,16 @@ packages: '@mermaid-js/parser@0.6.3': resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + '@modelcontextprotocol/sdk@1.27.1': + resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + '@napi-rs/wasm-runtime@1.0.7': resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} @@ -2660,6 +2781,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@1.30.1': + resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.2.0': resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} engines: {node: ^18.19.0 || >=20.6.0} @@ -2984,6 +3111,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation@0.46.0': + resolution: {integrity: sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-exporter-base@0.208.0': resolution: {integrity: sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==} engines: {node: ^18.19.0 || >=20.6.0} @@ -3084,6 +3217,10 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/semantic-conventions@1.28.0': + resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} + engines: {node: '>=14'} + '@opentelemetry/semantic-conventions@1.38.0': resolution: {integrity: sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==} engines: {node: '>=14'} @@ -4820,6 +4957,9 @@ packages: '@types/serve-static@1.15.9': resolution: {integrity: sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==} + '@types/shimmer@1.2.0': + resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + '@types/sockjs@0.3.36': resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} @@ -4838,6 +4978,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/validator@13.15.10': resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} @@ -4916,6 +5059,10 @@ packages: resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==} engines: {node: '>= 20'} + '@vercel/oidc@3.1.0': + resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} + engines: {node: '>= 20'} + '@vitejs/plugin-react@5.0.4': resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5039,6 +5186,16 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-import-assertions@1.9.0: + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + deprecated: package has been renamed to acorn-import-attributes + peerDependencies: + acorn: ^8 + acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -5082,6 +5239,12 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 + ai@6.0.116: + resolution: {integrity: sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -5340,6 +5503,10 @@ packages: resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + bonjour-service@1.3.0: resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==} @@ -5727,6 +5894,9 @@ packages: console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + console-table-printer@2.15.0: + resolution: {integrity: sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==} + content-disposition@0.5.2: resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} engines: {node: '>= 0.6'} @@ -5735,6 +5905,10 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -5790,6 +5964,10 @@ packages: cookie-signature@1.0.7: resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -5812,6 +5990,10 @@ packages: core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} @@ -6188,6 +6370,10 @@ packages: supports-color: optional: true + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} @@ -6822,6 +7008,10 @@ packages: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -6838,10 +7028,20 @@ packages: resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} engines: {node: '>=12.0.0'} + express-rate-limit@8.3.0: + resolution: {integrity: sha512-KJzBawY6fB9FiZGdE/0aftepZ91YlaGIrV8vgblRM3J8X+dHx/aiowJWwkx6LIGyuqGiANsjSwwrbb8mifOJ4Q==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + express@4.22.0: resolution: {integrity: sha512-c2iPh3xp5vvCLgaHK03+mWLFPhox7j1LwyxcZwFVApEv5i0X+IjPpbT50SJJwwLpdBVfp45AkK/v+AFgv/XlfQ==} engines: {node: '>= 0.10.0'} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -6852,6 +7052,9 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extended-eventsource@1.7.0: + resolution: {integrity: sha512-s8rtvZuYcKBpzytHb5g95cHbZ1J99WeMnV18oKc5wKoxkHzlzpPc/bNAm7Da2Db0BDw0CAu1z3LpH+7UsyzIpw==} + fast-content-type-parse@3.0.0: resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} @@ -6932,6 +7135,10 @@ packages: resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-cache-dir@4.0.0: resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==} engines: {node: '>=14.16'} @@ -7034,6 +7241,10 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fs-extra@11.3.2: resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} engines: {node: '>=14.14'} @@ -7399,6 +7610,10 @@ packages: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} + hono@4.12.5: + resolution: {integrity: sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==} + engines: {node: '>=16.9.0'} + hookable@6.0.1: resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} @@ -7555,6 +7770,9 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-in-the-middle@1.7.1: + resolution: {integrity: sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg==} + import-in-the-middle@2.0.0: resolution: {integrity: sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A==} @@ -7795,6 +8013,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-regexp@1.0.0: resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} engines: {node: '>=0.10.0'} @@ -7930,6 +8151,12 @@ packages: joi@17.13.3: resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + jose@6.2.1: + resolution: {integrity: sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==} + + js-tiktoken@1.0.21: + resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -7977,6 +8204,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -8023,6 +8253,23 @@ packages: resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} engines: {node: '>=16.0.0'} + langsmith@0.5.8: + resolution: {integrity: sha512-AsdwxazXXLwbEzVTXB5uo7Fva5MhGhSvIJ9FjBbkWOkgwqC28E9Gmah5SGbPM3CPjN0FdxB6nKzX5GRkkkXDjQ==} + peerDependencies: + '@opentelemetry/api': '*' + '@opentelemetry/exporter-trace-otlp-proto': '*' + '@opentelemetry/sdk-trace-base': '*' + openai: '*' + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@opentelemetry/exporter-trace-otlp-proto': + optional: true + '@opentelemetry/sdk-trace-base': + optional: true + openai: + optional: true + latest-version@7.0.0: resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} engines: {node: '>=14.16'} @@ -8390,6 +8637,10 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + memfs@4.51.1: resolution: {integrity: sha512-Eyt3XrufitN2ZL9c/uIRMyDwXanLI88h/L3MoWqNY747ha3dMR9dWqp8cRT5ntjZ0U1TNuq4U91ZXK0sMBjYOQ==} @@ -8404,6 +8655,10 @@ packages: merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -8661,6 +8916,10 @@ packages: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true + mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + mute-stream@1.0.0: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -8688,6 +8947,10 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -8916,14 +9179,26 @@ packages: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} + p-queue@9.1.0: + resolution: {integrity: sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==} + engines: {node: '>=20'} + p-retry@6.2.1: resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==} engines: {node: '>=16.17'} + p-retry@7.1.1: + resolution: {integrity: sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==} + engines: {node: '>=20'} + p-timeout@3.2.0: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} + p-timeout@7.0.1: + resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==} + engines: {node: '>=20'} + pac-proxy-agent@7.2.0: resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} engines: {node: '>= 14'} @@ -9040,6 +9315,9 @@ packages: path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -9107,6 +9385,10 @@ packages: engines: {node: '>=0.10'} hasBin: true + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + pkg-dir@7.0.0: resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} engines: {node: '>=14.16'} @@ -9642,6 +9924,10 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} @@ -9670,6 +9956,10 @@ packages: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -9939,6 +10229,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@7.5.2: + resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} + engines: {node: '>=8.6.0'} + require-in-the-middle@8.0.1: resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} @@ -10103,6 +10397,10 @@ packages: roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} @@ -10206,6 +10504,10 @@ packages: resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==} engines: {node: '>= 0.8.0'} + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + sequelize-pool@7.1.0: resolution: {integrity: sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==} engines: {node: '>= 10.0.0'} @@ -10257,6 +10559,10 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -10291,6 +10597,9 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} + shimmer@1.2.1: + resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -10317,6 +10626,9 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-wcswidth@1.1.2: + resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==} + sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} @@ -10857,6 +11169,10 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -11155,10 +11471,18 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + uuid@11.1.0: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -11581,6 +11905,11 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + zod-to-ts@2.0.0: resolution: {integrity: sha512-aHsUgIl+CQutKAxtRNeZslLCLXoeuSq+j5HU7q3kvi/c2KIAo6q4YjT7/lwFfACxLB923ELHYMkHmlxiqFy4lw==} peerDependencies: @@ -11596,6 +11925,9 @@ packages: zod@4.1.13: resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zrender@6.0.0: resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==} @@ -11614,6 +11946,13 @@ snapshots: '@vercel/oidc': 3.0.5 zod: 4.1.13 + '@ai-sdk/gateway@3.0.66(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.19(zod@4.3.6) + '@vercel/oidc': 3.1.0 + zod: 4.3.6 + '@ai-sdk/provider-utils@3.0.19(zod@4.1.13)': dependencies: '@ai-sdk/provider': 2.0.0 @@ -11621,10 +11960,21 @@ snapshots: eventsource-parser: 3.0.6 zod: 4.1.13 + '@ai-sdk/provider-utils@4.0.19(zod@4.3.6)': + dependencies: + '@ai-sdk/provider': 3.0.8 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.6 + zod: 4.3.6 + '@ai-sdk/provider@2.0.0': dependencies: json-schema: 0.4.0 + '@ai-sdk/provider@3.0.8': + dependencies: + json-schema: 0.4.0 + '@ai-sdk/react@2.0.115(react@19.2.0)(zod@4.1.13)': dependencies: '@ai-sdk/provider-utils': 3.0.19(zod@4.1.13) @@ -11750,6 +12100,25 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.0.1 + '@arizeai/openinference-core@2.0.5': + dependencies: + '@arizeai/openinference-semantic-conventions': 2.1.7 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + + '@arizeai/openinference-instrumentation-langchain@4.0.6(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))': + dependencies: + '@arizeai/openinference-core': 2.0.5 + '@arizeai/openinference-semantic-conventions': 2.1.7 + '@langchain/core': 1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)) + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.46.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@arizeai/openinference-semantic-conventions@2.1.7': {} + '@asamuzakjp/css-color@4.0.5': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -12701,6 +13070,8 @@ snapshots: '@braintree/sanitize-url@7.1.1': {} + '@cfworker/json-schema@4.1.1': {} + '@chevrotain/cst-dts-gen@11.0.3': dependencies: '@chevrotain/gast': 11.0.3 @@ -13153,6 +13524,40 @@ snapshots: dependencies: postcss: 8.5.6 + '@databricks/ai-sdk-provider@0.3.0(@ai-sdk/provider-utils@4.0.19(zod@4.3.6))(@ai-sdk/provider@3.0.8)': + dependencies: + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.19(zod@4.3.6) + zod: 4.3.6 + + '@databricks/langchainjs@0.1.0(@cfworker/json-schema@4.1.1)(@langchain/langgraph@1.2.1(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(zod-to-json-schema@3.25.1(zod@4.1.13))(zod@4.1.13))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))': + dependencies: + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.19(zod@4.3.6) + '@databricks/ai-sdk-provider': 0.3.0(@ai-sdk/provider-utils@4.0.19(zod@4.3.6))(@ai-sdk/provider@3.0.8) + '@databricks/sdk-experimental': 0.15.0 + '@langchain/core': 1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)) + '@langchain/mcp-adapters': 1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(@langchain/langgraph@1.2.1(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(zod-to-json-schema@3.25.1(zod@4.1.13))(zod@4.1.13)) + ai: 6.0.116(zod@4.3.6) + zod: 4.3.6 + transitivePeerDependencies: + - '@cfworker/json-schema' + - '@langchain/langgraph' + - '@opentelemetry/api' + - '@opentelemetry/exporter-trace-otlp-proto' + - '@opentelemetry/sdk-trace-base' + - openai + - supports-color + + '@databricks/sdk-experimental@0.15.0': + dependencies: + google-auth-library: 10.5.0 + ini: 6.0.0 + reflect-metadata: 0.2.2 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + '@databricks/sdk-experimental@0.16.0': dependencies: google-auth-library: 10.5.0 @@ -14172,6 +14577,10 @@ snapshots: dependencies: '@hapi/hoek': 9.3.0 + '@hono/node-server@1.19.11(hono@4.12.5)': + dependencies: + hono: 4.12.5 + '@hookform/resolvers@5.2.2(react-hook-form@7.68.0(react@19.2.0))': dependencies: '@standard-schema/utils': 0.3.0 @@ -14416,6 +14825,68 @@ snapshots: '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) tslib: 2.8.1 + '@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))': + dependencies: + '@cfworker/json-schema': 4.1.1 + '@standard-schema/spec': 1.1.0 + ansi-styles: 5.2.0 + camelcase: 6.3.0 + decamelize: 1.2.0 + js-tiktoken: 1.0.21 + langsmith: 0.5.8(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)) + mustache: 4.2.0 + p-queue: 6.6.2 + uuid: 11.1.0 + zod: 4.1.13 + transitivePeerDependencies: + - '@opentelemetry/api' + - '@opentelemetry/exporter-trace-otlp-proto' + - '@opentelemetry/sdk-trace-base' + - openai + + '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))': + dependencies: + '@langchain/core': 1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)) + uuid: 10.0.0 + + '@langchain/langgraph-sdk@1.6.5(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@types/json-schema': 7.0.15 + p-queue: 9.1.0 + p-retry: 7.1.1 + uuid: 13.0.0 + optionalDependencies: + '@langchain/core': 1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@langchain/langgraph@1.2.1(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(zod-to-json-schema@3.25.1(zod@4.1.13))(zod@4.1.13)': + dependencies: + '@langchain/core': 1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)) + '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))) + '@langchain/langgraph-sdk': 1.6.5(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@standard-schema/spec': 1.1.0 + uuid: 10.0.0 + zod: 4.1.13 + optionalDependencies: + zod-to-json-schema: 3.25.1(zod@4.1.13) + transitivePeerDependencies: + - react + - react-dom + + '@langchain/mcp-adapters@1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(@langchain/langgraph@1.2.1(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(zod-to-json-schema@3.25.1(zod@4.1.13))(zod@4.1.13))': + dependencies: + '@langchain/core': 1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)) + '@langchain/langgraph': 1.2.1(@langchain/core@1.1.31(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(zod-to-json-schema@3.25.1(zod@4.1.13))(zod@4.1.13) + '@modelcontextprotocol/sdk': 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.1.13) + debug: 4.4.3 + zod: 4.1.13 + optionalDependencies: + extended-eventsource: 1.7.0 + transitivePeerDependencies: + - '@cfworker/json-schema' + - supports-color + '@leichtgewicht/ip-codec@2.0.5': {} '@mdx-js/mdx@3.1.1': @@ -14458,6 +14929,30 @@ snapshots: dependencies: langium: 3.3.1 + '@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.1.13)': + dependencies: + '@hono/node-server': 1.19.11(hono@4.12.5) + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.3.0(express@5.2.1) + hono: 4.12.5 + jose: 6.2.1 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.1.13 + zod-to-json-schema: 3.25.1(zod@4.1.13) + optionalDependencies: + '@cfworker/json-schema': 4.1.1 + transitivePeerDependencies: + - supports-color + '@napi-rs/wasm-runtime@1.0.7': dependencies: '@emnapi/core': 1.7.1 @@ -14617,6 +15112,11 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.28.0 + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -15077,6 +15577,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/instrumentation@0.46.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.7.1 + require-in-the-middle: 7.5.2 + semver: 7.7.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + '@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -15211,6 +15722,8 @@ snapshots: '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions@1.28.0': {} + '@opentelemetry/semantic-conventions@1.38.0': {} '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': @@ -16898,6 +17411,8 @@ snapshots: '@types/node': 24.10.1 '@types/send': 0.17.5 + '@types/shimmer@1.2.0': {} + '@types/sockjs@0.3.36': dependencies: '@types/node': 24.10.1 @@ -16917,6 +17432,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/uuid@10.0.0': {} + '@types/validator@13.15.10': {} '@types/ws@8.18.1': @@ -17024,6 +17541,8 @@ snapshots: '@vercel/oidc@3.0.5': {} + '@vercel/oidc@3.1.0': {} + '@vitejs/plugin-react@5.0.4(rolldown-vite@7.1.14(@types/node@20.19.21)(esbuild@0.25.10)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.4 @@ -17254,6 +17773,15 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-import-assertions@1.9.0(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-import-attributes@1.9.5(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -17289,6 +17817,14 @@ snapshots: '@opentelemetry/api': 1.9.0 zod: 4.1.13 + ai@6.0.116(zod@4.3.6): + dependencies: + '@ai-sdk/gateway': 3.0.66(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.19(zod@4.3.6) + '@opentelemetry/api': 1.9.0 + zod: 4.3.6 + ajv-formats@2.1.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -17552,6 +18088,20 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.0 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + bonjour-service@1.3.0: dependencies: fast-deep-equal: 3.1.3 @@ -17975,12 +18525,18 @@ snapshots: console-control-strings@1.1.0: {} + console-table-printer@2.15.0: + dependencies: + simple-wcswidth: 1.1.2 + content-disposition@0.5.2: {} content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 + content-disposition@1.0.1: {} + content-type@1.0.5: {} conventional-changelog-angular@7.0.0: @@ -18042,6 +18598,8 @@ snapshots: cookie-signature@1.0.7: {} + cookie-signature@1.2.2: {} + cookie@0.7.2: {} copy-webpack-plugin@11.0.0(webpack@5.103.0): @@ -18064,6 +18622,11 @@ snapshots: core-util-is@1.0.3: {} + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -18476,6 +19039,8 @@ snapshots: dependencies: ms: 2.1.3 + decamelize@1.2.0: {} + decimal.js-light@2.5.1: {} decimal.js@10.6.0: {} @@ -19052,6 +19617,10 @@ snapshots: eventsource-parser@3.0.6: {} + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -19082,6 +19651,11 @@ snapshots: expect-type@1.2.2: {} + express-rate-limit@8.3.0(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.1.0 + express@4.22.0: dependencies: accepts: 1.3.8 @@ -19118,6 +19692,39 @@ snapshots: transitivePeerDependencies: - supports-color + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + exsolve@1.0.8: {} extend-shallow@2.0.1: @@ -19126,6 +19733,9 @@ snapshots: extend@3.0.2: {} + extended-eventsource@1.7.0: + optional: true + fast-content-type-parse@3.0.0: {} fast-deep-equal@3.1.3: {} @@ -19219,6 +19829,17 @@ snapshots: transitivePeerDependencies: - supports-color + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-cache-dir@4.0.0: dependencies: common-path-prefix: 3.0.0 @@ -19303,6 +19924,8 @@ snapshots: fresh@0.5.2: {} + fresh@2.0.0: {} + fs-extra@11.3.2: dependencies: graceful-fs: 4.2.11 @@ -19900,6 +20523,8 @@ snapshots: dependencies: parse-passwd: 1.0.0 + hono@4.12.5: {} + hookable@6.0.1: {} hosted-git-info@8.1.0: @@ -20074,6 +20699,13 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-in-the-middle@1.7.1: + dependencies: + acorn: 8.15.0 + acorn-import-assertions: 1.9.0(acorn@8.15.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 + import-in-the-middle@2.0.0: dependencies: acorn: 8.15.0 @@ -20257,6 +20889,8 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} + is-regexp@1.0.0: {} is-relative@1.0.0: @@ -20396,6 +21030,12 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 + jose@6.2.1: {} + + js-tiktoken@1.0.21: + dependencies: + base64-js: 1.5.1 + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -20453,6 +21093,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-schema@0.4.0: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -20500,6 +21142,19 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.0.8 + langsmith@0.5.8(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)): + dependencies: + '@types/uuid': 10.0.0 + chalk: 5.6.2 + console-table-printer: 2.15.0 + p-queue: 6.6.2 + semver: 7.7.3 + uuid: 10.0.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/exporter-trace-otlp-proto': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) + latest-version@7.0.0: dependencies: package-json: 8.1.1 @@ -20962,6 +21617,8 @@ snapshots: media-typer@0.3.0: {} + media-typer@1.1.0: {} + memfs@4.51.1: dependencies: '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) @@ -20977,6 +21634,8 @@ snapshots: merge-descriptors@1.0.3: {} + merge-descriptors@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -21398,6 +22057,8 @@ snapshots: dns-packet: 5.6.1 thunky: 1.1.0 + mustache@4.2.0: {} + mute-stream@1.0.0: {} mute-stream@2.0.0: {} @@ -21414,6 +22075,8 @@ snapshots: negotiator@0.6.4: {} + negotiator@1.0.0: {} + neo-async@2.6.2: {} netmask@2.0.2: {} @@ -21661,16 +22324,27 @@ snapshots: eventemitter3: 4.0.7 p-timeout: 3.2.0 + p-queue@9.1.0: + dependencies: + eventemitter3: 5.0.1 + p-timeout: 7.0.1 + p-retry@6.2.1: dependencies: '@types/retry': 0.12.2 is-network-error: 1.3.0 retry: 0.13.1 + p-retry@7.1.1: + dependencies: + is-network-error: 1.3.0 + p-timeout@3.2.0: dependencies: p-finally: 1.0.0 + p-timeout@7.0.1: {} + pac-proxy-agent@7.2.0: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 @@ -21798,6 +22472,8 @@ snapshots: path-to-regexp@3.3.0: {} + path-to-regexp@8.3.0: {} + path-type@4.0.0: {} pathe@2.0.3: {} @@ -21851,6 +22527,8 @@ snapshots: pidtree@0.6.0: {} + pkce-challenge@5.0.1: {} + pkg-dir@7.0.0: dependencies: find-up: 6.3.0 @@ -22473,6 +23151,10 @@ snapshots: dependencies: side-channel: 1.1.0 + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + quansync@1.0.0: {} queue-microtask@1.2.3: {} @@ -22496,6 +23178,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.0 + unpipe: 1.0.0 + rc9@2.1.2: dependencies: defu: 6.1.4 @@ -22885,6 +23574,14 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@7.5.2: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + require-in-the-middle@8.0.1: dependencies: debug: 4.4.3 @@ -23101,6 +23798,16 @@ snapshots: points-on-curve: 0.2.0 points-on-path: 0.2.1 + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + rrweb-cssom@0.8.0: {} rtlcss@4.3.0: @@ -23222,6 +23929,22 @@ snapshots: transitivePeerDependencies: - supports-color + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + sequelize-pool@7.1.0: {} sequelize@6.37.7(pg@8.18.0): @@ -23282,6 +24005,15 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -23315,6 +24047,8 @@ snapshots: shell-quote@1.8.3: {} + shimmer@1.2.1: {} + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -23349,6 +24083,8 @@ snapshots: signal-exit@4.1.0: {} + simple-wcswidth@1.1.2: {} + sirv@2.0.4: dependencies: '@polka/url': 1.0.0-next.29 @@ -23831,6 +24567,12 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -24095,8 +24837,12 @@ snapshots: utils-merge@1.0.1: {} + uuid@10.0.0: {} + uuid@11.1.0: {} + uuid@13.0.0: {} + uuid@8.3.2: {} uuid@9.0.1: {} @@ -24605,6 +25351,10 @@ snapshots: yoctocolors@2.1.2: {} + zod-to-json-schema@3.25.1(zod@4.1.13): + dependencies: + zod: 4.1.13 + zod-to-ts@2.0.0(typescript@5.9.3)(zod@4.1.13): dependencies: typescript: 5.9.3 @@ -24616,6 +25366,8 @@ snapshots: zod@4.1.13: {} + zod@4.3.6: {} + zrender@6.0.0: dependencies: tslib: 2.3.0 diff --git a/template/appkit.plugins.json b/template/appkit.plugins.json index 03bcabc2..d93fe2d3 100644 --- a/template/appkit.plugins.json +++ b/template/appkit.plugins.json @@ -2,6 +2,30 @@ "$schema": "https://databricks.github.io/appkit/schemas/template-plugins.schema.json", "version": "1.0", "plugins": { + "agent": { + "name": "agent", + "displayName": "Agent Plugin", + "description": "LangChain/LangGraph AI agent with streaming Responses API and MCP tool support", + "package": "@databricks/appkit", + "resources": { + "required": [ + { + "type": "serving_endpoint", + "alias": "Model Endpoint", + "resourceKey": "agent-model-endpoint", + "description": "Databricks model serving endpoint for the agent LLM", + "permission": "CAN_QUERY", + "fields": { + "name": { + "env": "DATABRICKS_MODEL", + "description": "Model serving endpoint name" + } + } + } + ], + "optional": [] + } + }, "analytics": { "name": "analytics", "displayName": "Analytics Plugin",