diff --git a/apps/dev-playground/config/agents/assistant.md b/apps/dev-playground/config/agents/assistant.md index 2a116b28..ea99d47b 100644 --- a/apps/dev-playground/config/agents/assistant.md +++ b/apps/dev-playground/config/agents/assistant.md @@ -1,7 +1,6 @@ --- - -## endpoint: databricks-claude-sonnet-4-5 - +endpoint: databricks-claude-sonnet-4-5 default: true +--- -You are a helpful data assistant. Use the available tools to query data and help users with their analysis. \ No newline at end of file +You are a helpful data assistant. Use the available tools to query data and help users with their analysis. diff --git a/docs/docs/api/appkit/Class.Plugin.md b/docs/docs/api/appkit/Class.Plugin.md index dcb963f7..34034537 100644 --- a/docs/docs/api/appkit/Class.Plugin.md +++ b/docs/docs/api/appkit/Class.Plugin.md @@ -252,6 +252,42 @@ AuthenticationError if user token is not available in request headers (productio *** +### attachContext() + +```ts +attachContext(deps: { + context?: unknown; + telemetryConfig?: TelemetryOptions; +}): void; +``` + +Binds runtime dependencies (telemetry provider, cache, plugin context) to +this plugin. Called by `AppKit._createApp` after construction and before +`setup()`. Idempotent: safe to call if the constructor already bound them +eagerly. Kept separate so factories can eagerly construct plugin instances +without running this before `TelemetryManager.initialize()` / +`CacheManager.getInstance()` have run. + +#### Parameters + +| Parameter | Type | +| ------ | ------ | +| `deps` | \{ `context?`: `unknown`; `telemetryConfig?`: `TelemetryOptions`; \} | +| `deps.context?` | `unknown` | +| `deps.telemetryConfig?` | `TelemetryOptions` | + +#### Returns + +`void` + +#### Implementation of + +```ts +BasePlugin.attachContext +``` + +*** + ### clientConfig() ```ts diff --git a/docs/docs/api/appkit/Function.createAgent.md b/docs/docs/api/appkit/Function.createAgent.md index 6981a315..61064e51 100644 --- a/docs/docs/api/appkit/Function.createAgent.md +++ b/docs/docs/api/appkit/Function.createAgent.md @@ -1,52 +1,35 @@ # Function: createAgent() ```ts -function createAgent(config: CreateAgentConfig): Promise; +function createAgent(def: AgentDefinition): AgentDefinition; ``` -Creates an agent-powered app with batteries included. +Pure factory for agent definitions. Returns the passed-in definition after +cycle-detecting the sub-agent graph. Accepts the full `AgentDefinition` shape +and is safe to call at module top-level. -Wraps `createApp` with `server()` and `agent()` pre-configured. -Automatically starts an HTTP server with agent chat routes. - -For apps that need custom routes or manual server control, -use `createApp` with `server()` and `agent()` directly. +The returned value is a plain `AgentDefinition` — no adapter construction, +no side effects. Register it with `agents({ agents: { name: def } })` or run +it standalone via `runAgent(def, input)`. ## Parameters | Parameter | Type | | ------ | ------ | -| `config` | [`CreateAgentConfig`](Interface.CreateAgentConfig.md) | +| `def` | [`AgentDefinition`](Interface.AgentDefinition.md) | ## Returns -`Promise`\<[`AgentHandle`](Interface.AgentHandle.md)\> - -## Examples +[`AgentDefinition`](Interface.AgentDefinition.md) -```ts -import { createAgent, analytics } from "@databricks/appkit"; -import { DatabricksAdapter } from "@databricks/appkit/agents/databricks"; - -createAgent({ - plugins: [analytics()], - adapter: DatabricksAdapter.fromServingEndpoint({ - workspaceClient: new WorkspaceClient({}), - endpointName: "databricks-claude-sonnet-4-5", - systemPrompt: "You are a data assistant...", - }), -}).then(agent => { - console.log("Tools:", agent.getTools()); -}); -``` +## Example ```ts -createAgent({ - plugins: [analytics(), files()], - agents: { - assistant: DatabricksAdapter.fromServingEndpoint({ ... }), - autocomplete: DatabricksAdapter.fromServingEndpoint({ ... }), +const support = createAgent({ + instructions: "You help customers.", + model: "databricks-claude-sonnet-4-5", + tools: { + get_weather: tool({ ... }), }, - defaultAgent: "assistant", }); ``` diff --git a/docs/docs/api/appkit/Function.fromPlugin.md b/docs/docs/api/appkit/Function.fromPlugin.md new file mode 100644 index 00000000..5262ef54 --- /dev/null +++ b/docs/docs/api/appkit/Function.fromPlugin.md @@ -0,0 +1,50 @@ +# Function: fromPlugin() + +```ts +function fromPlugin(factory: F, opts?: ToolkitOptions): FromPluginSpread; +``` + +Reference a plugin's tools inside an `AgentDefinition.tools` record without +naming the plugin instance. The returned spread-friendly object carries a +symbol-keyed marker that the agents plugin resolves against registered +`ToolProvider`s at setup time. + +The factory argument must come from `toPlugin` (or any function that +carries a `pluginName` field). `fromPlugin` reads `factory.pluginName` +synchronously — it does not construct an instance. + +If the referenced plugin is also registered in `createApp({ plugins })`, the +same runtime instance is used for dispatch. If the plugin is missing, +`AgentsPlugin.setup()` throws with a clear `Available: …` listing. + +## Type Parameters + +| Type Parameter | +| ------ | +| `F` *extends* `NamedPluginFactory` | + +## Parameters + +| Parameter | Type | Description | +| ------ | ------ | ------ | +| `factory` | `F` | A plugin factory produced by `toPlugin`. Must expose a `pluginName` field. | +| `opts?` | [`ToolkitOptions`](Interface.ToolkitOptions.md) | Optional toolkit scoping — `prefix`, `only`, `except`, `rename`. Same shape as the `.toolkit()` method. | + +## Returns + +`FromPluginSpread` + +## Example + +```ts +import { analytics, createAgent, files, fromPlugin, tool } from "@databricks/appkit"; + +const support = createAgent({ + instructions: "You help customers.", + tools: { + ...fromPlugin(analytics), + ...fromPlugin(files, { only: ["uploads.read"] }), + get_weather: tool({ ... }), + }, +}); +``` diff --git a/docs/docs/api/appkit/Function.isFromPluginMarker.md b/docs/docs/api/appkit/Function.isFromPluginMarker.md new file mode 100644 index 00000000..2ba9c752 --- /dev/null +++ b/docs/docs/api/appkit/Function.isFromPluginMarker.md @@ -0,0 +1,17 @@ +# Function: isFromPluginMarker() + +```ts +function isFromPluginMarker(value: unknown): value is FromPluginMarker; +``` + +Type guard for [FromPluginMarker](Interface.FromPluginMarker.md). + +## Parameters + +| Parameter | Type | +| ------ | ------ | +| `value` | `unknown` | + +## Returns + +`value is FromPluginMarker` diff --git a/docs/docs/api/appkit/Function.isToolkitEntry.md b/docs/docs/api/appkit/Function.isToolkitEntry.md new file mode 100644 index 00000000..892907a4 --- /dev/null +++ b/docs/docs/api/appkit/Function.isToolkitEntry.md @@ -0,0 +1,18 @@ +# Function: isToolkitEntry() + +```ts +function isToolkitEntry(value: unknown): value is ToolkitEntry; +``` + +Type guard for `ToolkitEntry` — used by the agents plugin to differentiate +toolkit references from inline tools in a mixed `tools` record. + +## Parameters + +| Parameter | Type | +| ------ | ------ | +| `value` | `unknown` | + +## Returns + +`value is ToolkitEntry` diff --git a/docs/docs/api/appkit/Function.loadAgentFromFile.md b/docs/docs/api/appkit/Function.loadAgentFromFile.md new file mode 100644 index 00000000..3eab5346 --- /dev/null +++ b/docs/docs/api/appkit/Function.loadAgentFromFile.md @@ -0,0 +1,19 @@ +# Function: loadAgentFromFile() + +```ts +function loadAgentFromFile(filePath: string, ctx: LoadContext): Promise; +``` + +Loads a single markdown agent file and resolves its frontmatter against +registered plugin toolkits + ambient tool library. + +## Parameters + +| Parameter | Type | +| ------ | ------ | +| `filePath` | `string` | +| `ctx` | `LoadContext` | + +## Returns + +`Promise`\<[`AgentDefinition`](Interface.AgentDefinition.md)\> diff --git a/docs/docs/api/appkit/Function.loadAgentsFromDir.md b/docs/docs/api/appkit/Function.loadAgentsFromDir.md new file mode 100644 index 00000000..86665e17 --- /dev/null +++ b/docs/docs/api/appkit/Function.loadAgentsFromDir.md @@ -0,0 +1,20 @@ +# Function: loadAgentsFromDir() + +```ts +function loadAgentsFromDir(dir: string, ctx: LoadContext): Promise; +``` + +Scans a directory for `*.md` files and produces an `AgentDefinition` record +keyed by file-stem. Throws on frontmatter errors or unresolved references. +Returns an empty map if the directory does not exist. + +## Parameters + +| Parameter | Type | +| ------ | ------ | +| `dir` | `string` | +| `ctx` | `LoadContext` | + +## Returns + +`Promise`\<`LoadResult`\> diff --git a/docs/docs/api/appkit/Function.runAgent.md b/docs/docs/api/appkit/Function.runAgent.md new file mode 100644 index 00000000..4e1f8608 --- /dev/null +++ b/docs/docs/api/appkit/Function.runAgent.md @@ -0,0 +1,29 @@ +# Function: runAgent() + +```ts +function runAgent(def: AgentDefinition, input: RunAgentInput): Promise; +``` + +Standalone agent execution without `createApp`. Resolves the adapter, binds +inline tools, and drives the adapter's `run()` loop to completion. + +Limitations vs. running through the agents() plugin: +- No OBO: there is no HTTP request, so plugin tools run as the service + principal (when they work at all). +- Hosted tools (MCP) are not supported — they require a live MCP client + that only exists inside the agents plugin. +- Sub-agents (`agents: { ... }` on the def) are executed as nested + `runAgent` calls with no shared thread state. +- Plugin tools (`fromPlugin` markers or `ToolkitEntry` spreads) require + passing `plugins: [...]` via `RunAgentInput`. + +## Parameters + +| Parameter | Type | +| ------ | ------ | +| `def` | [`AgentDefinition`](Interface.AgentDefinition.md) | +| `input` | [`RunAgentInput`](Interface.RunAgentInput.md) | + +## Returns + +`Promise`\<[`RunAgentResult`](Interface.RunAgentResult.md)\> diff --git a/docs/docs/api/appkit/Interface.AgentDefinition.md b/docs/docs/api/appkit/Interface.AgentDefinition.md new file mode 100644 index 00000000..a3d7dc77 --- /dev/null +++ b/docs/docs/api/appkit/Interface.AgentDefinition.md @@ -0,0 +1,82 @@ +# Interface: AgentDefinition + +## Properties + +### agents? + +```ts +optional agents: Record; +``` + +Sub-agents, exposed as `agent-` tools on this agent. + +*** + +### baseSystemPrompt? + +```ts +optional baseSystemPrompt: BaseSystemPromptOption; +``` + +Override the plugin's baseSystemPrompt for this agent only. + +*** + +### instructions + +```ts +instructions: string; +``` + +System prompt body. For markdown-loaded agents this is the file body. + +*** + +### maxSteps? + +```ts +optional maxSteps: number; +``` + +*** + +### maxTokens? + +```ts +optional maxTokens: number; +``` + +*** + +### model? + +```ts +optional model: + | string + | AgentAdapter +| Promise; +``` + +Model adapter (or endpoint-name string sugar for +`DatabricksAdapter.fromServingEndpoint({ endpointName })`). Optional — +falls back to the plugin's `defaultModel`. + +*** + +### name? + +```ts +optional name: string; +``` + +Filled in from the enclosing key when used in `agents: { foo: def }`. + +*** + +### tools? + +```ts +optional tools: AgentTools; +``` + +Per-agent tool record. Key is the LLM-visible tool-call name. diff --git a/docs/docs/api/appkit/Interface.AgentHandle.md b/docs/docs/api/appkit/Interface.AgentHandle.md deleted file mode 100644 index d630567d..00000000 --- a/docs/docs/api/appkit/Interface.AgentHandle.md +++ /dev/null @@ -1,86 +0,0 @@ -# Interface: AgentHandle - -## Properties - -### addTools() - -```ts -addTools: (tools: FunctionTool[]) => void; -``` - -Add function tools at runtime (HostedTools must be configured at setup). - -#### Parameters - -| Parameter | Type | -| ------ | ------ | -| `tools` | [`FunctionTool`](Interface.FunctionTool.md)[] | - -#### Returns - -`void` - -*** - -### getThreads() - -```ts -getThreads: (userId: string) => Promise; -``` - -List threads for a user. - -#### Parameters - -| Parameter | Type | -| ------ | ------ | -| `userId` | `string` | - -#### Returns - -`Promise`\<`unknown`\> - -*** - -### getTools() - -```ts -getTools: () => AgentToolDefinition[]; -``` - -Get all tool definitions available to agents. - -#### Returns - -[`AgentToolDefinition`](Interface.AgentToolDefinition.md)[] - -*** - -### plugins - -```ts -plugins: Record; -``` - -Access to user-provided plugin APIs. - -*** - -### registerAgent() - -```ts -registerAgent: (name: string, adapter: AgentAdapter) => void; -``` - -Register an additional agent at runtime. - -#### Parameters - -| Parameter | Type | -| ------ | ------ | -| `name` | `string` | -| `adapter` | [`AgentAdapter`](Interface.AgentAdapter.md) | - -#### Returns - -`void` diff --git a/docs/docs/api/appkit/Interface.AgentsPluginConfig.md b/docs/docs/api/appkit/Interface.AgentsPluginConfig.md new file mode 100644 index 00000000..79af0562 --- /dev/null +++ b/docs/docs/api/appkit/Interface.AgentsPluginConfig.md @@ -0,0 +1,132 @@ +# Interface: AgentsPluginConfig + +Base configuration interface for AppKit plugins + +## Extends + +- [`BasePluginConfig`](Interface.BasePluginConfig.md) + +## Indexable + +```ts +[key: string]: unknown +``` + +## Properties + +### agents? + +```ts +optional agents: Record; +``` + +Code-defined agents, merged with file-loaded ones (code wins on key collision). + +*** + +### autoInheritTools? + +```ts +optional autoInheritTools: boolean | AutoInheritToolsConfig; +``` + +Whether to auto-inherit every ToolProvider plugin's toolkit. Accepts a boolean shorthand. + +*** + +### baseSystemPrompt? + +```ts +optional baseSystemPrompt: BaseSystemPromptOption; +``` + +Customize or disable the AppKit base system prompt. + +*** + +### defaultAgent? + +```ts +optional defaultAgent: string; +``` + +Agent used when clients don't specify one. Defaults to the first-registered agent or the file with `default: true` frontmatter. + +*** + +### defaultModel? + +```ts +optional defaultModel: + | string + | AgentAdapter +| Promise; +``` + +Default model for agents that don't specify their own (in code or frontmatter). + +*** + +### dir? + +```ts +optional dir: string | false; +``` + +Directory to scan for markdown agent files. Default `./config/agents`. Set to `false` to disable. + +*** + +### host? + +```ts +optional host: string; +``` + +#### Inherited from + +[`BasePluginConfig`](Interface.BasePluginConfig.md).[`host`](Interface.BasePluginConfig.md#host) + +*** + +### name? + +```ts +optional name: string; +``` + +#### Inherited from + +[`BasePluginConfig`](Interface.BasePluginConfig.md).[`name`](Interface.BasePluginConfig.md#name) + +*** + +### telemetry? + +```ts +optional telemetry: TelemetryOptions; +``` + +#### Inherited from + +[`BasePluginConfig`](Interface.BasePluginConfig.md).[`telemetry`](Interface.BasePluginConfig.md#telemetry) + +*** + +### threadStore? + +```ts +optional threadStore: ThreadStore; +``` + +Persistent thread store. Default: in-memory. + +*** + +### tools? + +```ts +optional tools: Record; +``` + +Ambient tool library. Keys may be referenced by markdown frontmatter via `tools: [key1, key2]`. diff --git a/docs/docs/api/appkit/Interface.BasePluginConfig.md b/docs/docs/api/appkit/Interface.BasePluginConfig.md index a7faffc6..130a61c1 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 + +- [`AgentsPluginConfig`](Interface.AgentsPluginConfig.md) + ## Indexable ```ts diff --git a/docs/docs/api/appkit/Interface.CreateAgentConfig.md b/docs/docs/api/appkit/Interface.CreateAgentConfig.md deleted file mode 100644 index 67b6c2a2..00000000 --- a/docs/docs/api/appkit/Interface.CreateAgentConfig.md +++ /dev/null @@ -1,105 +0,0 @@ -# Interface: CreateAgentConfig - -## Properties - -### adapter? - -```ts -optional adapter: - | AgentAdapter -| Promise; -``` - -Single agent adapter (mutually exclusive with `agents`). Registered as "assistant". - -*** - -### agents? - -```ts -optional agents: Record>; -``` - -Multiple named agents (mutually exclusive with `adapter`). - -*** - -### cache? - -```ts -optional cache: CacheConfig; -``` - -Cache configuration. - -*** - -### client? - -```ts -optional client: WorkspaceClient; -``` - -Pre-configured WorkspaceClient. - -*** - -### defaultAgent? - -```ts -optional defaultAgent: string; -``` - -Which agent to use when the client doesn't specify one. - -*** - -### host? - -```ts -optional host: string; -``` - -Server host. Defaults to FLASK_RUN_HOST or 0.0.0.0. - -*** - -### plugins? - -```ts -optional plugins: PluginData[]; -``` - -Tool-providing plugins (analytics, files, genie, lakebase, etc.) - -*** - -### port? - -```ts -optional port: number; -``` - -Server port. Defaults to DATABRICKS_APP_PORT or 8000. - -*** - -### telemetry? - -```ts -optional telemetry: TelemetryConfig; -``` - -Telemetry configuration. - -*** - -### tools? - -```ts -optional tools: AgentTool[]; -``` - -Explicit tools (FunctionTool, HostedTool) alongside auto-discovered ToolProvider tools. diff --git a/docs/docs/api/appkit/Interface.FromPluginMarker.md b/docs/docs/api/appkit/Interface.FromPluginMarker.md new file mode 100644 index 00000000..1a1fedd3 --- /dev/null +++ b/docs/docs/api/appkit/Interface.FromPluginMarker.md @@ -0,0 +1,32 @@ +# Interface: FromPluginMarker + +A lazy reference to a plugin's tools, produced by [fromPlugin](Function.fromPlugin.md) and +resolved to concrete `ToolkitEntry`s at `AgentsPlugin.setup()` time. + +The marker is spread under a unique symbol key so multiple calls to +`fromPlugin` (even for the same plugin) coexist in an `AgentDefinition.tools` +record without colliding. + +## Properties + +### \[FROM\_PLUGIN\_MARKER\] + +```ts +readonly [FROM_PLUGIN_MARKER]: true; +``` + +*** + +### opts + +```ts +readonly opts: ToolkitOptions | undefined; +``` + +*** + +### pluginName + +```ts +readonly pluginName: string; +``` diff --git a/docs/docs/api/appkit/Interface.PromptContext.md b/docs/docs/api/appkit/Interface.PromptContext.md new file mode 100644 index 00000000..e26ea167 --- /dev/null +++ b/docs/docs/api/appkit/Interface.PromptContext.md @@ -0,0 +1,27 @@ +# Interface: PromptContext + +Context passed to `baseSystemPrompt` callbacks. + +## Properties + +### agentName + +```ts +agentName: string; +``` + +*** + +### pluginNames + +```ts +pluginNames: string[]; +``` + +*** + +### toolNames + +```ts +toolNames: string[]; +``` diff --git a/docs/docs/api/appkit/Interface.RunAgentInput.md b/docs/docs/api/appkit/Interface.RunAgentInput.md new file mode 100644 index 00000000..c7fa4b02 --- /dev/null +++ b/docs/docs/api/appkit/Interface.RunAgentInput.md @@ -0,0 +1,35 @@ +# Interface: RunAgentInput + +## Properties + +### messages + +```ts +messages: string | Message[]; +``` + +Seed messages for the run. Either a single user string or a full message list. + +*** + +### plugins? + +```ts +optional plugins: PluginData[]; +``` + +Optional plugin list used to resolve `fromPlugin` markers in `def.tools`. +Required when the def contains any `...fromPlugin(factory)` spreads; +ignored otherwise. `runAgent` constructs a fresh instance per plugin +and dispatches tool calls against it as the service principal (no +OBO — there is no HTTP request in standalone mode). + +*** + +### signal? + +```ts +optional signal: AbortSignal; +``` + +Abort signal for cancellation. diff --git a/docs/docs/api/appkit/Interface.RunAgentResult.md b/docs/docs/api/appkit/Interface.RunAgentResult.md new file mode 100644 index 00000000..a9ba258d --- /dev/null +++ b/docs/docs/api/appkit/Interface.RunAgentResult.md @@ -0,0 +1,21 @@ +# Interface: RunAgentResult + +## Properties + +### events + +```ts +events: AgentEvent[]; +``` + +Every event the adapter yielded, in order. Useful for inspection/tests. + +*** + +### text + +```ts +text: string; +``` + +Aggregated text output from all `message_delta` events. diff --git a/docs/docs/api/appkit/Interface.ToolkitEntry.md b/docs/docs/api/appkit/Interface.ToolkitEntry.md new file mode 100644 index 00000000..699c07b0 --- /dev/null +++ b/docs/docs/api/appkit/Interface.ToolkitEntry.md @@ -0,0 +1,46 @@ +# Interface: ToolkitEntry + +A tool reference produced by a plugin's `.toolkit()` call. The agents plugin +recognizes the `__toolkitRef` brand and dispatches tool invocations through +`PluginContext.executeTool(req, pluginName, localName, ...)`, preserving +OBO (asUser) and telemetry spans. + +## Properties + +### \_\_toolkitRef + +```ts +readonly __toolkitRef: true; +``` + +*** + +### annotations? + +```ts +optional annotations: ToolAnnotations; +``` + +*** + +### def + +```ts +def: AgentToolDefinition; +``` + +*** + +### localName + +```ts +localName: string; +``` + +*** + +### pluginName + +```ts +pluginName: string; +``` diff --git a/docs/docs/api/appkit/Interface.ToolkitOptions.md b/docs/docs/api/appkit/Interface.ToolkitOptions.md new file mode 100644 index 00000000..1beb22b0 --- /dev/null +++ b/docs/docs/api/appkit/Interface.ToolkitOptions.md @@ -0,0 +1,41 @@ +# Interface: ToolkitOptions + +## Properties + +### except? + +```ts +optional except: string[]; +``` + +Exclude tools whose local name matches one of these. + +*** + +### only? + +```ts +optional only: string[]; +``` + +Only include tools whose local name matches one of these. + +*** + +### prefix? + +```ts +optional prefix: string; +``` + +Key prefix to prepend to each tool's local name. Defaults to `${pluginName}.`. + +*** + +### rename? + +```ts +optional rename: Record; +``` + +Remap specific local names to different keys (applied after prefix). diff --git a/docs/docs/api/appkit/TypeAlias.AgentTool.md b/docs/docs/api/appkit/TypeAlias.AgentTool.md index 8a9da8f0..e165cec6 100644 --- a/docs/docs/api/appkit/TypeAlias.AgentTool.md +++ b/docs/docs/api/appkit/TypeAlias.AgentTool.md @@ -3,5 +3,10 @@ ```ts type AgentTool = | FunctionTool - | HostedTool; + | HostedTool + | ToolkitEntry; ``` + +Any tool an agent can invoke: inline function tools (`tool()`), hosted MCP +tools (`mcpServer()` / raw hosted), or toolkit references from plugins +(`analytics().toolkit()`). diff --git a/docs/docs/api/appkit/TypeAlias.AgentTools.md b/docs/docs/api/appkit/TypeAlias.AgentTools.md new file mode 100644 index 00000000..05b9ce61 --- /dev/null +++ b/docs/docs/api/appkit/TypeAlias.AgentTools.md @@ -0,0 +1,14 @@ +# Type Alias: AgentTools + +```ts +type AgentTools = { +[key: string]: AgentTool; +} & { +[key: symbol]: FromPluginMarker; +}; +``` + +Per-agent tool record. String keys map to inline tools, toolkit entries, +hosted tools, etc. Symbol keys hold `FromPluginMarker` references produced +by `fromPlugin(factory)` spreads — these are resolved at +`AgentsPlugin.setup()` time against registered `ToolProvider` plugins. diff --git a/docs/docs/api/appkit/TypeAlias.BaseSystemPromptOption.md b/docs/docs/api/appkit/TypeAlias.BaseSystemPromptOption.md new file mode 100644 index 00000000..c5922661 --- /dev/null +++ b/docs/docs/api/appkit/TypeAlias.BaseSystemPromptOption.md @@ -0,0 +1,8 @@ +# Type Alias: BaseSystemPromptOption + +```ts +type BaseSystemPromptOption = + | false + | string + | (ctx: PromptContext) => string; +``` diff --git a/docs/docs/api/appkit/Variable.agents.md b/docs/docs/api/appkit/Variable.agents.md new file mode 100644 index 00000000..d5bc7a09 --- /dev/null +++ b/docs/docs/api/appkit/Variable.agents.md @@ -0,0 +1,19 @@ +# Variable: agents + +```ts +const agents: ToPlugin & NamedPluginFactory; +``` + +Plugin factory for the agents plugin. Reads `config/agents/*.md` by default, +resolves toolkits/tools from registered plugins, exposes `appkit.agents.*` +runtime API and mounts `/invocations`. + +## Example + +```ts +import { agents, analytics, createApp, server } from "@databricks/appkit"; + +await createApp({ + plugins: [server(), analytics(), agents()], +}); +``` diff --git a/docs/docs/api/appkit/index.md b/docs/docs/api/appkit/index.md index 31c79fe8..4abfedcb 100644 --- a/docs/docs/api/appkit/index.md +++ b/docs/docs/api/appkit/index.md @@ -31,26 +31,30 @@ plugin architecture, and React integration. | Interface | Description | | ------ | ------ | | [AgentAdapter](Interface.AgentAdapter.md) | - | -| [AgentHandle](Interface.AgentHandle.md) | - | +| [AgentDefinition](Interface.AgentDefinition.md) | - | | [AgentInput](Interface.AgentInput.md) | - | | [AgentRunContext](Interface.AgentRunContext.md) | - | +| [AgentsPluginConfig](Interface.AgentsPluginConfig.md) | Base configuration interface for AppKit plugins | | [AgentToolDefinition](Interface.AgentToolDefinition.md) | - | | [BasePluginConfig](Interface.BasePluginConfig.md) | Base configuration interface for AppKit plugins | | [CacheConfig](Interface.CacheConfig.md) | Configuration for the CacheInterceptor. Controls TTL, size limits, storage backend, and probabilistic cleanup. | -| [CreateAgentConfig](Interface.CreateAgentConfig.md) | - | | [DatabaseCredential](Interface.DatabaseCredential.md) | Database credentials with OAuth token for Postgres connection | | [EndpointConfig](Interface.EndpointConfig.md) | - | +| [FromPluginMarker](Interface.FromPluginMarker.md) | A lazy reference to a plugin's tools, produced by [fromPlugin](Function.fromPlugin.md) and resolved to concrete `ToolkitEntry`s at `AgentsPlugin.setup()` time. | | [FunctionTool](Interface.FunctionTool.md) | - | | [GenerateDatabaseCredentialRequest](Interface.GenerateDatabaseCredentialRequest.md) | Request parameters for generating database OAuth credentials | | [ITelemetry](Interface.ITelemetry.md) | Plugin-facing interface for OpenTelemetry instrumentation. Provides a thin abstraction over OpenTelemetry APIs for plugins. | | [LakebasePoolConfig](Interface.LakebasePoolConfig.md) | Configuration for creating a Lakebase connection pool | | [Message](Interface.Message.md) | - | | [PluginManifest](Interface.PluginManifest.md) | Plugin manifest that declares metadata and resource requirements. Attached to plugin classes as a static property. Extends the shared PluginManifest with strict resource types. | +| [PromptContext](Interface.PromptContext.md) | Context passed to `baseSystemPrompt` callbacks. | | [RequestedClaims](Interface.RequestedClaims.md) | Optional claims for fine-grained Unity Catalog table permissions When specified, the returned token will be scoped to only the requested tables | | [RequestedResource](Interface.RequestedResource.md) | Resource to request permissions for in Unity Catalog | | [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(). Narrows the generated base: type → ResourceType enum, permission → ResourcePermission union. | +| [RunAgentInput](Interface.RunAgentInput.md) | - | +| [RunAgentResult](Interface.RunAgentResult.md) | - | | [ServingEndpointEntry](Interface.ServingEndpointEntry.md) | Shape of a single registry entry. | | [ServingEndpointRegistry](Interface.ServingEndpointRegistry.md) | Registry interface for serving endpoint type generation. Empty by default — augmented by the Vite type generator's `.d.ts` output via module augmentation. When populated, provides autocomplete for alias names and typed request/response/chunk per endpoint. | | [StreamExecutionSettings](Interface.StreamExecutionSettings.md) | Execution settings for streaming endpoints. Extends PluginExecutionSettings with SSE stream configuration. | @@ -58,6 +62,8 @@ plugin architecture, and React integration. | [Thread](Interface.Thread.md) | - | | [ThreadStore](Interface.ThreadStore.md) | - | | [ToolConfig](Interface.ToolConfig.md) | - | +| [ToolkitEntry](Interface.ToolkitEntry.md) | A tool reference produced by a plugin's `.toolkit()` call. The agents plugin recognizes the `__toolkitRef` brand and dispatches tool invocations through `PluginContext.executeTool(req, pluginName, localName, ...)`, preserving OBO (asUser) and telemetry spans. | +| [ToolkitOptions](Interface.ToolkitOptions.md) | - | | [ToolProvider](Interface.ToolProvider.md) | - | | [ValidationResult](Interface.ValidationResult.md) | Result of validating all registered resources against the environment. | @@ -66,7 +72,9 @@ plugin architecture, and React integration. | Type Alias | Description | | ------ | ------ | | [AgentEvent](TypeAlias.AgentEvent.md) | - | -| [AgentTool](TypeAlias.AgentTool.md) | - | +| [AgentTool](TypeAlias.AgentTool.md) | Any tool an agent can invoke: inline function tools (`tool()`), hosted MCP tools (`mcpServer()` / raw hosted), or toolkit references from plugins (`analytics().toolkit()`). | +| [AgentTools](TypeAlias.AgentTools.md) | Per-agent tool record. String keys map to inline tools, toolkit entries, hosted tools, etc. Symbol keys hold `FromPluginMarker` references produced by `fromPlugin(factory)` spreads — these are resolved at `AgentsPlugin.setup()` time against registered `ToolProvider` plugins. | +| [BaseSystemPromptOption](TypeAlias.BaseSystemPromptOption.md) | - | | [ConfigSchema](TypeAlias.ConfigSchema.md) | Configuration schema definition for plugin config. Re-exported from the standard JSON Schema Draft 7 types. | | [ExecutionResult](TypeAlias.ExecutionResult.md) | Discriminated union for plugin execution results. | | [HostedTool](TypeAlias.HostedTool.md) | - | @@ -80,6 +88,7 @@ plugin architecture, and React integration. | Variable | Description | | ------ | ------ | +| [agents](Variable.agents.md) | Plugin factory for the agents plugin. Reads `config/agents/*.md` by default, resolves toolkits/tools from registered plugins, exposes `appkit.agents.*` runtime API and mounts `/invocations`. | | [sql](Variable.sql.md) | SQL helper namespace | ## Functions @@ -88,11 +97,12 @@ plugin architecture, and React integration. | ------ | ------ | | [appKitServingTypesPlugin](Function.appKitServingTypesPlugin.md) | Vite plugin to generate TypeScript types for AppKit serving endpoints. Fetches OpenAPI schemas from Databricks and generates a .d.ts with ServingEndpointRegistry module augmentation. | | [appKitTypesPlugin](Function.appKitTypesPlugin.md) | Vite plugin to generate types for AppKit queries. Calls generateFromEntryPoint under the hood. | -| [createAgent](Function.createAgent.md) | Creates an agent-powered app with batteries included. | +| [createAgent](Function.createAgent.md) | Pure factory for agent definitions. Returns the passed-in definition after cycle-detecting the sub-agent graph. Accepts the full `AgentDefinition` shape and is safe to call at module top-level. | | [createApp](Function.createApp.md) | Bootstraps AppKit with the provided configuration. | | [createLakebasePool](Function.createLakebasePool.md) | Create a Lakebase pool with appkit's logger integration. Telemetry automatically uses appkit's OpenTelemetry configuration via global registry. | | [extractServingEndpoints](Function.extractServingEndpoints.md) | Extract serving endpoint config from a server file by AST-parsing it. Looks for `serving({ endpoints: { alias: { env: "..." }, ... } })` calls and extracts the endpoint alias names and their environment variable mappings. | | [findServerFile](Function.findServerFile.md) | Find the server entry file by checking candidate paths in order. | +| [fromPlugin](Function.fromPlugin.md) | Reference a plugin's tools inside an `AgentDefinition.tools` record without naming the plugin instance. The returned spread-friendly object carries a symbol-keyed marker that the agents plugin resolves against registered `ToolProvider`s at setup time. | | [generateDatabaseCredential](Function.generateDatabaseCredential.md) | Generate OAuth credentials for Postgres database connection using the proper Postgres API. | | [getExecutionContext](Function.getExecutionContext.md) | Get the current execution context. | | [getLakebaseOrmConfig](Function.getLakebaseOrmConfig.md) | Get Lakebase connection configuration for ORMs that don't accept pg.Pool directly. | @@ -101,8 +111,13 @@ plugin architecture, and React integration. | [getResourceRequirements](Function.getResourceRequirements.md) | Gets the resource requirements from a plugin's manifest. | | [getUsernameWithApiLookup](Function.getUsernameWithApiLookup.md) | Resolves the PostgreSQL username for a Lakebase connection. | | [getWorkspaceClient](Function.getWorkspaceClient.md) | Get workspace client from config or SDK default auth chain | +| [isFromPluginMarker](Function.isFromPluginMarker.md) | Type guard for [FromPluginMarker](Interface.FromPluginMarker.md). | | [isFunctionTool](Function.isFunctionTool.md) | - | | [isHostedTool](Function.isHostedTool.md) | - | | [isSQLTypeMarker](Function.isSQLTypeMarker.md) | Type guard to check if a value is a SQL type marker | +| [isToolkitEntry](Function.isToolkitEntry.md) | Type guard for `ToolkitEntry` — used by the agents plugin to differentiate toolkit references from inline tools in a mixed `tools` record. | +| [loadAgentFromFile](Function.loadAgentFromFile.md) | Loads a single markdown agent file and resolves its frontmatter against registered plugin toolkits + ambient tool library. | +| [loadAgentsFromDir](Function.loadAgentsFromDir.md) | Scans a directory for `*.md` files and produces an `AgentDefinition` record keyed by file-stem. Throws on frontmatter errors or unresolved references. Returns an empty map if the directory does not exist. | | [mcpServer](Function.mcpServer.md) | Factory for declaring a custom MCP server tool. | +| [runAgent](Function.runAgent.md) | Standalone agent execution without `createApp`. Resolves the adapter, binds inline tools, and drives the adapter's `run()` loop to completion. | | [tool](Function.tool.md) | Factory for defining function tools with Zod schemas. | diff --git a/docs/docs/api/appkit/typedoc-sidebar.ts b/docs/docs/api/appkit/typedoc-sidebar.ts index b44d2318..cf001fcc 100644 --- a/docs/docs/api/appkit/typedoc-sidebar.ts +++ b/docs/docs/api/appkit/typedoc-sidebar.ts @@ -89,8 +89,8 @@ const typedocSidebar: SidebarsConfig = { }, { type: "doc", - id: "api/appkit/Interface.AgentHandle", - label: "AgentHandle" + id: "api/appkit/Interface.AgentDefinition", + label: "AgentDefinition" }, { type: "doc", @@ -102,6 +102,11 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.AgentRunContext", label: "AgentRunContext" }, + { + type: "doc", + id: "api/appkit/Interface.AgentsPluginConfig", + label: "AgentsPluginConfig" + }, { type: "doc", id: "api/appkit/Interface.AgentToolDefinition", @@ -117,11 +122,6 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.CacheConfig", label: "CacheConfig" }, - { - type: "doc", - id: "api/appkit/Interface.CreateAgentConfig", - label: "CreateAgentConfig" - }, { type: "doc", id: "api/appkit/Interface.DatabaseCredential", @@ -132,6 +132,11 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.EndpointConfig", label: "EndpointConfig" }, + { + type: "doc", + id: "api/appkit/Interface.FromPluginMarker", + label: "FromPluginMarker" + }, { type: "doc", id: "api/appkit/Interface.FunctionTool", @@ -162,6 +167,11 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.PluginManifest", label: "PluginManifest" }, + { + type: "doc", + id: "api/appkit/Interface.PromptContext", + label: "PromptContext" + }, { type: "doc", id: "api/appkit/Interface.RequestedClaims", @@ -187,6 +197,16 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.ResourceRequirement", label: "ResourceRequirement" }, + { + type: "doc", + id: "api/appkit/Interface.RunAgentInput", + label: "RunAgentInput" + }, + { + type: "doc", + id: "api/appkit/Interface.RunAgentResult", + label: "RunAgentResult" + }, { type: "doc", id: "api/appkit/Interface.ServingEndpointEntry", @@ -222,6 +242,16 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Interface.ToolConfig", label: "ToolConfig" }, + { + type: "doc", + id: "api/appkit/Interface.ToolkitEntry", + label: "ToolkitEntry" + }, + { + type: "doc", + id: "api/appkit/Interface.ToolkitOptions", + label: "ToolkitOptions" + }, { type: "doc", id: "api/appkit/Interface.ToolProvider", @@ -248,6 +278,16 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/TypeAlias.AgentTool", label: "AgentTool" }, + { + type: "doc", + id: "api/appkit/TypeAlias.AgentTools", + label: "AgentTools" + }, + { + type: "doc", + id: "api/appkit/TypeAlias.BaseSystemPromptOption", + label: "BaseSystemPromptOption" + }, { type: "doc", id: "api/appkit/TypeAlias.ConfigSchema", @@ -294,6 +334,11 @@ const typedocSidebar: SidebarsConfig = { type: "category", label: "Variables", items: [ + { + type: "doc", + id: "api/appkit/Variable.agents", + label: "agents" + }, { type: "doc", id: "api/appkit/Variable.sql", @@ -340,6 +385,11 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Function.findServerFile", label: "findServerFile" }, + { + type: "doc", + id: "api/appkit/Function.fromPlugin", + label: "fromPlugin" + }, { type: "doc", id: "api/appkit/Function.generateDatabaseCredential", @@ -380,6 +430,11 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Function.getWorkspaceClient", label: "getWorkspaceClient" }, + { + type: "doc", + id: "api/appkit/Function.isFromPluginMarker", + label: "isFromPluginMarker" + }, { type: "doc", id: "api/appkit/Function.isFunctionTool", @@ -395,11 +450,31 @@ const typedocSidebar: SidebarsConfig = { id: "api/appkit/Function.isSQLTypeMarker", label: "isSQLTypeMarker" }, + { + type: "doc", + id: "api/appkit/Function.isToolkitEntry", + label: "isToolkitEntry" + }, + { + type: "doc", + id: "api/appkit/Function.loadAgentFromFile", + label: "loadAgentFromFile" + }, + { + type: "doc", + id: "api/appkit/Function.loadAgentsFromDir", + label: "loadAgentsFromDir" + }, { type: "doc", id: "api/appkit/Function.mcpServer", label: "mcpServer" }, + { + type: "doc", + id: "api/appkit/Function.runAgent", + label: "runAgent" + }, { type: "doc", id: "api/appkit/Function.tool", diff --git a/docs/docs/guides/migrating-to-agents-plugin.md b/docs/docs/guides/migrating-to-agents-plugin.md index bdea44f3..1b87dad9 100644 --- a/docs/docs/guides/migrating-to-agents-plugin.md +++ b/docs/docs/guides/migrating-to-agents-plugin.md @@ -136,27 +136,7 @@ Engineers declare tools in code; prompt authors pick from a menu in frontmatter. ## Scoping tools in code with `fromPlugin` -Earlier alphas of the new API required a three-touch dance for every plugin whose tools you wanted on a code-defined agent: - -```ts -// Before: intermediate variable + .toolkit() + a second entry in plugins[] -const analyticsP = analytics(); -const filesP = files(); - -const support = createAgent({ - instructions: "…", - tools: { - ...analyticsP.toolkit(), - ...filesP.toolkit({ only: ["uploads.read"] }), - }, -}); - -await createApp({ - plugins: [server(), analyticsP, filesP, agents({ agents: { support } })], -}); -``` - -`fromPlugin(factory, opts?)` collapses this to a single reference per plugin: +Code-defined agents pull plugin tools in via `fromPlugin(factory, opts?)`. The marker is resolved against registered plugins at `agents()` setup time — you write each plugin factory twice (once inside `fromPlugin`, once in `plugins: [...]`), and nothing more. ```ts import { agents, analytics, createAgent, createApp, files, fromPlugin, server } from "@databricks/appkit"; @@ -174,9 +154,7 @@ await createApp({ }); ``` -`fromPlugin` returns a spread-friendly, symbol-keyed marker. The agents plugin resolves it at setup against registered `ToolProvider`s and throws a clear `Available: …` error if the referenced plugin is missing from `plugins: [...]`. - -`.toolkit()` is **not deprecated** — use it when you need to rename individual tools or combine fine-grained scoping that `fromPlugin`'s options can't express. For the 90% case where you want "all tools from this plugin", prefer `fromPlugin`. +`fromPlugin` accepts the same scoping options as markdown frontmatter toolkits (`only` / `except` / `prefix` / `rename`). If a referenced plugin isn't in `plugins: [...]`, `agents()` throws at setup with an `Available: …` listing. ## Standalone runs diff --git a/docs/docs/plugins/agents.md b/docs/docs/plugins/agents.md index b1c47878..38566112 100644 --- a/docs/docs/plugins/agents.md +++ b/docs/docs/plugins/agents.md @@ -120,10 +120,6 @@ For plugins that don't expose a `.toolkit()` method (e.g., third-party `ToolProv If a referenced plugin is not registered in `createApp({ plugins })`, the agents plugin throws at setup with an `Available: …` listing so you can fix the wiring before the first request. -### Using `.toolkit()` directly (advanced) - -`.toolkit()` is still available on `analytics()`, `files()`, `genie()`, and `lakebase()` handles. Use it when you need to rename tools individually or bind them under a custom record key — anything `fromPlugin` can't express. In the common case, prefer `fromPlugin`. - ## Level 4: sub-agents ```ts diff --git a/packages/appkit/src/core/appkit.ts b/packages/appkit/src/core/appkit.ts index 4866cfd5..a0c2e566 100644 --- a/packages/appkit/src/core/appkit.ts +++ b/packages/appkit/src/core/appkit.ts @@ -76,9 +76,7 @@ export class AppKit { name, ...extraData, }; - // If the factory eagerly constructed an instance (via - // `toPluginWithInstance`), reuse it; otherwise construct now. - const pluginInstance = pluginData.instance ?? new Plugin(baseConfig); + const pluginInstance = new Plugin(baseConfig); if (typeof pluginInstance.attachContext === "function") { pluginInstance.attachContext({ @@ -231,11 +229,9 @@ export class AppKit { ) { const result: InputPluginMap = {}; for (const currentPlugin of plugins) { - const instance = (currentPlugin as { instance?: BasePlugin }).instance; result[currentPlugin.name] = { plugin: currentPlugin.plugin, config: currentPlugin.config as Record, - instance, }; } return result; diff --git a/packages/appkit/src/core/run-agent.ts b/packages/appkit/src/core/run-agent.ts index 491a8ea2..6bbed55f 100644 --- a/packages/appkit/src/core/run-agent.ts +++ b/packages/appkit/src/core/run-agent.ts @@ -3,13 +3,13 @@ import type { AgentAdapter, AgentEvent, AgentToolDefinition, - BasePlugin, Message, PluginConstructor, PluginData, ToolProvider, } from "shared"; import { isFromPluginMarker } from "../plugins/agents/from-plugin"; +import { resolveToolkitFromProvider } from "../plugins/agents/toolkit-resolver"; import { type FunctionTool, functionToolToDefinition, @@ -20,7 +20,6 @@ import type { AgentDefinition, AgentTool, ToolkitEntry, - ToolkitOptions, } from "../plugins/agents/types"; import { isToolkitEntry } from "../plugins/agents/types"; @@ -32,9 +31,9 @@ export interface RunAgentInput { /** * Optional plugin list used to resolve `fromPlugin` markers in `def.tools`. * Required when the def contains any `...fromPlugin(factory)` spreads; - * ignored otherwise. `runAgent` reuses eagerly-constructed instances - * (from `toPluginWithInstance`) and constructs fresh ones for plain - * `toPlugin` factories. + * ignored otherwise. `runAgent` constructs a fresh instance per plugin + * and dispatches tool calls against it as the service principal (no + * OBO — there is no HTTP request in standalone mode). */ plugins?: PluginData[]; } @@ -215,7 +214,7 @@ function buildStandaloneToolIndex( plugins, providerCache, ); - const entries = synthesizeToolkit( + const entries = resolveToolkitFromProvider( marker.pluginName, provider, marker.opts, @@ -332,9 +331,10 @@ function resolveStandaloneProvider( ); } - const preBuilt = (match as { instance?: BasePlugin }).instance; - const instance = - preBuilt ?? new match.plugin({ ...(match.config ?? {}), name: pluginName }); + const instance = new match.plugin({ + ...(match.config ?? {}), + name: pluginName, + }); const provider = instance as unknown as ToolProvider; if ( typeof (provider as { getAgentTools?: unknown }).getAgentTools !== @@ -351,38 +351,3 @@ function resolveStandaloneProvider( cache.set(pluginName, provider); return provider; } - -function synthesizeToolkit( - pluginName: string, - provider: ToolProvider, - opts?: ToolkitOptions, -): Record { - const withToolkit = provider as ToolProvider & { - toolkit?: (opts?: ToolkitOptions) => Record; - }; - if (typeof withToolkit.toolkit === "function") { - return withToolkit.toolkit(opts); - } - - const only = opts?.only ? new Set(opts.only) : null; - const except = opts?.except ? new Set(opts.except) : null; - const rename = opts?.rename ?? {}; - const prefix = opts?.prefix ?? `${pluginName}.`; - - const out: Record = {}; - for (const tool of provider.getAgentTools()) { - if (only && !only.has(tool.name)) continue; - if (except?.has(tool.name)) continue; - - const keyAfterPrefix = `${prefix}${tool.name}`; - const key = rename[tool.name] ?? keyAfterPrefix; - - out[key] = { - __toolkitRef: true, - pluginName, - localName: tool.name, - def: { ...tool, name: key }, - }; - } - return out; -} diff --git a/packages/appkit/src/core/tests/appkit.test.ts b/packages/appkit/src/core/tests/appkit.test.ts deleted file mode 100644 index 900d5f56..00000000 --- a/packages/appkit/src/core/tests/appkit.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -import type { AgentToolDefinition, ToolProvider } from "shared"; -import { describe, expect, test, vi } from "vitest"; - -vi.mock("../../cache", () => ({ - CacheManager: { - getInstance: vi.fn().mockResolvedValue({ - get: vi.fn(), - set: vi.fn(), - delete: vi.fn(), - getOrExecute: vi.fn(), - }), - getInstanceSync: vi.fn().mockReturnValue({ - get: vi.fn(), - set: vi.fn(), - delete: vi.fn(), - getOrExecute: vi.fn(), - }), - }, -})); - -vi.mock("../../telemetry", () => ({ - TelemetryManager: { - initialize: vi.fn(), - getProvider: vi.fn(() => ({ - getTracer: vi.fn(), - getMeter: vi.fn(), - getLogger: vi.fn(), - emit: vi.fn(), - startActiveSpan: vi.fn(), - registerInstrumentations: vi.fn(), - })), - }, - normalizeTelemetryOptions: vi.fn(() => ({ - traces: false, - metrics: false, - logs: false, - })), -})); - -vi.mock("../../context/service-context", () => { - const mockClient = { - statementExecution: { executeStatement: vi.fn() }, - currentUser: { me: vi.fn().mockResolvedValue({ id: "test-user" }) }, - config: { host: "https://test.databricks.com" }, - }; - - return { - ServiceContext: { - initialize: vi.fn().mockResolvedValue({ - client: mockClient, - serviceUserId: "test-service-user", - workspaceId: Promise.resolve("test-workspace"), - }), - get: vi.fn().mockReturnValue({ - client: mockClient, - serviceUserId: "test-service-user", - workspaceId: Promise.resolve("test-workspace"), - }), - isInitialized: vi.fn().mockReturnValue(true), - createUserContext: vi.fn(), - }, - }; -}); - -vi.mock("../../registry", () => ({ - ResourceRegistry: vi.fn().mockImplementation(() => ({ - collectResources: vi.fn(), - getRequired: vi.fn().mockReturnValue([]), - enforceValidation: vi.fn(), - })), - ResourceType: { SQL_WAREHOUSE: "sql_warehouse" }, - getPluginManifest: vi.fn(), - getResourceRequirements: vi.fn(), -})); - -import { toPlugin, toPluginWithInstance } from "../../plugin/to-plugin"; -import { createApp } from "../appkit"; - -const manifest = { - name: "counter", - displayName: "Counter", - description: "Counter", - resources: { required: [], optional: [] }, -}; - -function makeCounterPlugin() { - let constructions = 0; - class CounterPlugin implements ToolProvider { - static manifest = manifest; - static DEFAULT_CONFIG = {}; - name = "counter"; - id: number; - flag = false; - constructor(public config: unknown) { - constructions += 1; - this.id = constructions; - } - async setup() {} - injectRoutes() {} - getEndpoints() { - return {}; - } - getAgentTools(): AgentToolDefinition[] { - return []; - } - async executeAgentTool() { - return "ok"; - } - exports() { - return { - getFlag: () => this.flag, - getId: () => this.id, - }; - } - } - return { - CounterPlugin, - getConstructionCount: () => constructions, - }; -} - -describe("AppKit.preparePlugins instance forwarding", () => { - test("toPluginWithInstance: reuses the eagerly constructed instance", async () => { - const { CounterPlugin, getConstructionCount } = makeCounterPlugin(); - - const counter = toPluginWithInstance( - CounterPlugin as never, - ["exports"] as const, - ); - const pluginData = counter(); - - expect(getConstructionCount()).toBe(1); - expect(pluginData.instance).toBeDefined(); - - (pluginData.instance as unknown as { flag: boolean }).flag = true; - const factoryId = (pluginData.instance as unknown as { id: number }).id; - - const app = await createApp({ plugins: [pluginData] }); - - expect(getConstructionCount()).toBe(1); - - const handle = ( - app as unknown as Record< - string, - { getFlag: () => boolean; getId: () => number } - > - ).counter; - expect(handle.getFlag()).toBe(true); - expect(handle.getId()).toBe(factoryId); - }); - - test("toPlugin: constructs a fresh instance inside createApp (unchanged behavior)", async () => { - const { CounterPlugin, getConstructionCount } = makeCounterPlugin(); - - const counter = toPlugin(CounterPlugin as never); - const pluginData = counter(); - - expect(getConstructionCount()).toBe(0); - expect((pluginData as { instance?: unknown }).instance).toBeUndefined(); - - const app = await createApp({ plugins: [pluginData] }); - - expect(getConstructionCount()).toBe(1); - - const handle = ( - app as unknown as Record< - string, - { getFlag: () => boolean; getId: () => number } - > - ).counter; - expect(handle.getFlag()).toBe(false); - expect(handle.getId()).toBe(1); - }); -}); diff --git a/packages/appkit/src/plugin/index.ts b/packages/appkit/src/plugin/index.ts index 59a87b36..46a4eb94 100644 --- a/packages/appkit/src/plugin/index.ts +++ b/packages/appkit/src/plugin/index.ts @@ -1,8 +1,4 @@ export type { ToPlugin } from "shared"; export type { ExecutionResult } from "./execution-result"; export { Plugin } from "./plugin"; -export { - type NamedPluginFactory, - toPlugin, - toPluginWithInstance, -} from "./to-plugin"; +export { type NamedPluginFactory, toPlugin } from "./to-plugin"; diff --git a/packages/appkit/src/plugin/to-plugin.ts b/packages/appkit/src/plugin/to-plugin.ts index 56ea3e03..c882f300 100644 --- a/packages/appkit/src/plugin/to-plugin.ts +++ b/packages/appkit/src/plugin/to-plugin.ts @@ -1,13 +1,8 @@ -import type { - BasePlugin, - PluginConstructor, - PluginData, - ToPlugin, -} from "shared"; +import type { PluginConstructor, PluginData, ToPlugin } from "shared"; /** - * Factory function produced by `toPlugin` / `toPluginWithInstance`. Carries a - * static `pluginName` field so tooling (e.g. `fromPlugin`) can identify which + * Factory function produced by {@link toPlugin}. Carries a static + * `pluginName` field so tooling (e.g. `fromPlugin`) can identify which * plugin a factory references without constructing an instance. */ export type NamedPluginFactory = { @@ -15,8 +10,11 @@ export type NamedPluginFactory = { }; /** - * Wraps a plugin class so it can be passed to createApp with optional config. - * Infers config type from the constructor and plugin name from the static `name` property. + * Wraps a plugin class so it can be passed to `createApp` with optional + * config. Infers the config type from the constructor and the plugin name + * from the static `manifest.name` property, and stamps `pluginName` onto + * the returned factory function so `fromPlugin` can identify the plugin + * without needing to construct it. * * @internal */ @@ -27,7 +25,9 @@ export function toPlugin( type Config = ConstructorParameters[0]; type Name = T["manifest"]["name"]; const pluginName = plugin.manifest.name as Name; - const factory = (config: Config = {} as Config): PluginData => ({ + const factory = ( + config: Config = {} as Config, + ): PluginData => ({ plugin: plugin as T, config: config as Config, name: pluginName, @@ -39,57 +39,3 @@ export function toPlugin( }); return factory as ToPlugin & NamedPluginFactory; } - -/** - * Variant of `toPlugin` that eagerly constructs the plugin instance and - * exposes it (plus any instance methods specified in `expose`) on the - * returned `PluginData`. Lets users call plugin-level helpers like - * `analytics().toolkit()` at module top-level. `AppKit._createApp` reuses the - * eagerly constructed instance instead of constructing a new one. - * - * @internal - */ -export function toPluginWithInstance< - T extends PluginConstructor, - Methods extends readonly (keyof InstanceType)[], ->(plugin: T, expose: Methods) { - type Config = ConstructorParameters[0]; - type Name = T["manifest"]["name"]; - type Instance = InstanceType; - type Exposed = Pick; - - const pluginName = plugin.manifest.name as Name; - - const factory = ( - config: Config = {} as Config, - ): PluginData & { - instance: BasePlugin; - } & Exposed => { - const instance = new plugin({ ...(config ?? {}), name: pluginName }) as Instance; - - const exposed: Record = {}; - for (const methodName of expose) { - const bound = instance[methodName]; - if (typeof bound === "function") { - exposed[methodName as string] = (bound as Function).bind(instance); - } else { - exposed[methodName as string] = bound; - } - } - - return { - plugin: plugin as T, - config: config as Config, - name: pluginName, - instance: instance as unknown as BasePlugin, - ...(exposed as Exposed), - }; - }; - - Object.defineProperty(factory, "pluginName", { - value: pluginName, - writable: false, - enumerable: true, - }); - return factory as typeof factory & NamedPluginFactory; -} diff --git a/packages/appkit/src/plugins/agents/agents.ts b/packages/appkit/src/plugins/agents/agents.ts index 076fe9c7..c8758ac8 100644 --- a/packages/appkit/src/plugins/agents/agents.ts +++ b/packages/appkit/src/plugins/agents/agents.ts @@ -25,6 +25,7 @@ import manifest from "./manifest.json"; import { chatRequestSchema, invocationsRequestSchema } from "./schemas"; import { buildBaseSystemPrompt, composeSystemPrompt } from "./system-prompt"; import { InMemoryThreadStore } from "./thread-store"; +import { resolveToolkitFromProvider } from "./toolkit-resolver"; import { AppKitMcpClient, type FunctionTool, @@ -40,8 +41,6 @@ import type { PromptContext, RegisteredAgent, ResolvedToolEntry, - ToolkitEntry, - ToolkitOptions, } from "./types"; import { isToolkitEntry } from "./types"; @@ -390,8 +389,7 @@ export class AgentsPlugin extends Plugin implements ToolProvider { const providerEntry = providers.find((p) => p.name === marker.pluginName); if (!providerEntry) { - const available = - providers.map((p) => p.name).join(", ") || "(none)"; + const available = providers.map((p) => p.name).join(", ") || "(none)"; throw new Error( `Agent '${agentName}' references plugin '${marker.pluginName}' via ` + `fromPlugin(), but that plugin is not registered in createApp. ` + @@ -991,49 +989,6 @@ function normalizeAutoInherit(value: AgentsPluginConfig["autoInheritTools"]): { return { file: value.file ?? true, code: value.code ?? false }; } -/** - * Extract a plugin's toolkit as a keyed record of `ToolkitEntry`s. Prefers the - * plugin's own `.toolkit(opts)` method; falls back to walking `getAgentTools()` - * and synthesizing namespaced keys (`${pluginName}.${localName}`) with - * `only` / `except` filtering. Shared between `applyAutoInherit` and - * `resolveFromPluginMarkers` so both paths treat third-party ToolProviders - * without `.toolkit()` the same way. - */ -function resolveToolkitFromProvider( - pluginName: string, - provider: ToolProvider, - opts?: ToolkitOptions, -): Record { - const withToolkit = provider as ToolProvider & { - toolkit?: (opts?: ToolkitOptions) => Record; - }; - if (typeof withToolkit.toolkit === "function") { - return withToolkit.toolkit(opts); - } - - const only = opts?.only ? new Set(opts.only) : null; - const except = opts?.except ? new Set(opts.except) : null; - const rename = opts?.rename ?? {}; - const prefix = opts?.prefix ?? `${pluginName}.`; - - const out: Record = {}; - for (const tool of provider.getAgentTools()) { - if (only && !only.has(tool.name)) continue; - if (except?.has(tool.name)) continue; - - const keyAfterPrefix = `${prefix}${tool.name}`; - const key = rename[tool.name] ?? keyAfterPrefix; - - out[key] = { - __toolkitRef: true, - pluginName, - localName: tool.name, - def: { ...tool, name: key }, - }; - } - return out; -} - function composePromptForAgent( registered: RegisteredAgent, pluginLevel: BaseSystemPromptOption | undefined, diff --git a/packages/appkit/src/plugins/agents/build-toolkit.ts b/packages/appkit/src/plugins/agents/build-toolkit.ts index aa1a0ff2..540fec25 100644 --- a/packages/appkit/src/plugins/agents/build-toolkit.ts +++ b/packages/appkit/src/plugins/agents/build-toolkit.ts @@ -1,6 +1,6 @@ import type { AgentToolDefinition } from "shared"; -import { toJSONSchema } from "zod"; import type { ToolRegistry } from "./tools/define-tool"; +import { toToolJSONSchema } from "./tools/json-schema"; import type { ToolkitEntry, ToolkitOptions } from "./types"; /** @@ -36,7 +36,7 @@ export function buildToolkitEntries( const keyAfterPrefix = `${prefix}${localName}`; const key = rename[localName] ?? keyAfterPrefix; - const parameters = toJSONSchema( + const parameters = toToolJSONSchema( entry.schema, ) as unknown as AgentToolDefinition["parameters"]; diff --git a/packages/appkit/src/plugins/agents/from-plugin.ts b/packages/appkit/src/plugins/agents/from-plugin.ts index 041a9fa4..b1128594 100644 --- a/packages/appkit/src/plugins/agents/from-plugin.ts +++ b/packages/appkit/src/plugins/agents/from-plugin.ts @@ -36,9 +36,9 @@ export type FromPluginSpread = { readonly [key: symbol]: FromPluginMarker }; * symbol-keyed marker that the agents plugin resolves against registered * `ToolProvider`s at setup time. * - * The factory argument must come from `toPlugin` / `toPluginWithInstance` (or - * any function that carries a `pluginName` field). `fromPlugin` reads - * `factory.pluginName` synchronously — it does not construct an instance. + * The factory argument must come from `toPlugin` (or any function that + * carries a `pluginName` field). `fromPlugin` reads `factory.pluginName` + * synchronously — it does not construct an instance. * * If the referenced plugin is also registered in `createApp({ plugins })`, the * same runtime instance is used for dispatch. If the plugin is missing, @@ -58,8 +58,8 @@ export type FromPluginSpread = { readonly [key: symbol]: FromPluginMarker }; * }); * ``` * - * @param factory A plugin factory produced by `toPlugin` or - * `toPluginWithInstance`. Must expose a `pluginName` field. + * @param factory A plugin factory produced by `toPlugin`. Must expose a + * `pluginName` field. * @param opts Optional toolkit scoping — `prefix`, `only`, `except`, `rename`. * Same shape as the `.toolkit()` method. */ @@ -67,9 +67,13 @@ export function fromPlugin( factory: F, opts?: ToolkitOptions, ): FromPluginSpread { - if (!factory || typeof factory.pluginName !== "string" || !factory.pluginName) { + if ( + !factory || + typeof factory.pluginName !== "string" || + !factory.pluginName + ) { throw new Error( - "fromPlugin(): factory is missing pluginName. Pass a factory created by toPlugin() or toPluginWithInstance().", + "fromPlugin(): factory is missing pluginName. Pass a factory created by toPlugin().", ); } const pluginName = factory.pluginName; diff --git a/packages/appkit/src/plugins/agents/tests/run-agent.test.ts b/packages/appkit/src/plugins/agents/tests/run-agent.test.ts index 213dd386..da626f49 100644 --- a/packages/appkit/src/plugins/agents/tests/run-agent.test.ts +++ b/packages/appkit/src/plugins/agents/tests/run-agent.test.ts @@ -139,7 +139,11 @@ describe("runAgent", () => { }, }); - const pluginData = factory() as PluginData; + const pluginData = factory() as PluginData< + PluginConstructor, + unknown, + string + >; await runAgent(def, { messages: "hi", plugins: [pluginData] }); expect(capturedCtx).not.toBeNull(); @@ -170,9 +174,7 @@ describe("runAgent", () => { }, }); - await expect(runAgent(def, { messages: "hi" })).rejects.toThrow( - /absent/, - ); + await expect(runAgent(def, { messages: "hi" })).rejects.toThrow(/absent/); await expect(runAgent(def, { messages: "hi" })).rejects.toThrow( /Available:/, ); diff --git a/packages/appkit/src/plugins/agents/toolkit-resolver.ts b/packages/appkit/src/plugins/agents/toolkit-resolver.ts new file mode 100644 index 00000000..8ec8cf1f --- /dev/null +++ b/packages/appkit/src/plugins/agents/toolkit-resolver.ts @@ -0,0 +1,62 @@ +import type { ToolProvider } from "shared"; +import type { ToolkitEntry, ToolkitOptions } from "./types"; + +/** + * Internal interface: a `ToolProvider` that optionally exposes a typed + * `.toolkit(opts)` method. Core plugins (analytics, files, genie, lakebase) + * implement this; third-party `ToolProvider`s may not. + */ +type MaybeToolkitProvider = ToolProvider & { + toolkit?: (opts?: ToolkitOptions) => Record; +}; + +/** + * Resolve a plugin's tools into a keyed record of {@link ToolkitEntry} markers + * ready to be merged into an agent's tool index. + * + * Preferred path: call the plugin's own `.toolkit(opts)` method, which + * typically delegates to `buildToolkitEntries` with full `ToolkitOptions` + * support (prefix, only, except, rename). + * + * Fallback path: when the plugin doesn't expose `.toolkit()` (e.g. a + * third-party `ToolProvider` built with plain `toPlugin`), walk + * `getAgentTools()` and synthesize namespaced keys (`${pluginName}.${name}`) + * while still honoring `only` / `except` / `rename` / `prefix`. + * + * This helper is the single source of truth for "turn a provider into a + * toolkit entry record" and is used by `AgentsPlugin.buildToolIndex` + * (both the `fromPlugin` resolution pass and auto-inherit) and by the + * standalone `runAgent` executor. + */ +export function resolveToolkitFromProvider( + pluginName: string, + provider: ToolProvider, + opts?: ToolkitOptions, +): Record { + const withToolkit = provider as MaybeToolkitProvider; + if (typeof withToolkit.toolkit === "function") { + return withToolkit.toolkit(opts); + } + + const only = opts?.only ? new Set(opts.only) : null; + const except = opts?.except ? new Set(opts.except) : null; + const rename = opts?.rename ?? {}; + const prefix = opts?.prefix ?? `${pluginName}.`; + + const out: Record = {}; + for (const tool of provider.getAgentTools()) { + if (only && !only.has(tool.name)) continue; + if (except?.has(tool.name)) continue; + + const keyAfterPrefix = `${prefix}${tool.name}`; + const key = rename[tool.name] ?? keyAfterPrefix; + + out[key] = { + __toolkitRef: true, + pluginName, + localName: tool.name, + def: { ...tool, name: key }, + }; + } + return out; +} diff --git a/packages/appkit/src/plugins/agents/tools/define-tool.ts b/packages/appkit/src/plugins/agents/tools/define-tool.ts index 7c1f49e4..bcefceef 100644 --- a/packages/appkit/src/plugins/agents/tools/define-tool.ts +++ b/packages/appkit/src/plugins/agents/tools/define-tool.ts @@ -1,5 +1,6 @@ import type { AgentToolDefinition, ToolAnnotations } from "shared"; -import { toJSONSchema, type z } from "zod"; +import type { z } from "zod"; +import { toToolJSONSchema } from "./json-schema"; import { formatZodError } from "./tool"; /** @@ -67,7 +68,7 @@ export function toolsFromRegistry( registry: ToolRegistry, ): AgentToolDefinition[] { return Object.entries(registry).map(([name, entry]) => { - const parameters = toJSONSchema( + const parameters = toToolJSONSchema( entry.schema, ) as unknown as AgentToolDefinition["parameters"]; const def: AgentToolDefinition = { diff --git a/packages/appkit/src/plugins/agents/tools/json-schema.ts b/packages/appkit/src/plugins/agents/tools/json-schema.ts new file mode 100644 index 00000000..805fd48f --- /dev/null +++ b/packages/appkit/src/plugins/agents/tools/json-schema.ts @@ -0,0 +1,22 @@ +import { toJSONSchema, type z } from "zod"; + +/** + * Converts a Zod schema to JSON Schema suitable for an LLM tool-call + * `parameters` field. + * + * Wraps `zod`'s `toJSONSchema()` and strips the top-level `$schema` annotation + * that Zod v4 emits by default (e.g. `"https://json-schema.org/draft/..."`). + * The Databricks Mosaic serving endpoint forwards tool schemas to Google's + * Gemini `function_declarations` format, which rejects any top-level key it + * doesn't explicitly recognize — including `$schema` — with a 400 + * `Invalid JSON payload received. Unknown name "$schema"` error. Other LLM + * providers either ignore the field or also trip on it, so stripping here is + * safe across backends. + */ +export function toToolJSONSchema( + schema: z.ZodType, +): Record { + const raw = toJSONSchema(schema) as Record; + const { $schema: _ignored, ...rest } = raw; + return rest; +} diff --git a/packages/appkit/src/plugins/agents/tools/tool.ts b/packages/appkit/src/plugins/agents/tools/tool.ts index 18b04485..b5d4db65 100644 --- a/packages/appkit/src/plugins/agents/tools/tool.ts +++ b/packages/appkit/src/plugins/agents/tools/tool.ts @@ -1,5 +1,6 @@ -import { toJSONSchema, type z } from "zod"; +import type { z } from "zod"; import type { FunctionTool } from "./function-tool"; +import { toToolJSONSchema } from "./json-schema"; export interface ToolConfig { name: string; @@ -18,7 +19,7 @@ export interface ToolConfig { * can self-correct on its next turn. */ export function tool(config: ToolConfig): FunctionTool { - const parameters = toJSONSchema(config.schema) as unknown as Record< + const parameters = toToolJSONSchema(config.schema) as unknown as Record< string, unknown >; diff --git a/packages/appkit/src/plugins/analytics/analytics.ts b/packages/appkit/src/plugins/analytics/analytics.ts index f175121a..26f326cc 100644 --- a/packages/appkit/src/plugins/analytics/analytics.ts +++ b/packages/appkit/src/plugins/analytics/analytics.ts @@ -12,7 +12,7 @@ import { z } from "zod"; import { SQLWarehouseConnector } from "../../connectors"; import { getWarehouseId, getWorkspaceClient } from "../../context"; import { createLogger } from "../../logging/logger"; -import { Plugin, toPluginWithInstance } from "../../plugin"; +import { Plugin, toPlugin } from "../../plugin"; import type { PluginManifest } from "../../registry"; import { buildToolkitEntries } from "../agents/build-toolkit"; import { @@ -300,17 +300,11 @@ export class AnalyticsPlugin extends Plugin implements ToolProvider { } /** - * Returns the plugin's tools as a keyed record of `ToolkitEntry` markers, - * suitable for spreading into an `AgentDefinition.tools` record. - * - * @example - * ```ts - * const analyticsP = analytics(); - * createAgent({ - * instructions: "...", - * tools: { ...analyticsP.toolkit({ only: ["query"] }) }, - * }); - * ``` + * Returns the plugin's tools as a keyed record of `ToolkitEntry` markers. + * Called by the agents plugin (via `resolveToolkitFromProvider`) to spread + * a filtered, renamed view of the plugin's tools into an agent's tool + * index. Most callers should go through `fromPlugin(analytics, opts)` at + * module scope instead of reaching for this directly. */ toolkit(opts?: import("../agents/types").ToolkitOptions) { return buildToolkitEntries(this.name, this.tools, opts); @@ -333,6 +327,4 @@ export class AnalyticsPlugin extends Plugin implements ToolProvider { /** * @internal */ -export const analytics = toPluginWithInstance(AnalyticsPlugin, [ - "toolkit", -] as const); +export const analytics = toPlugin(AnalyticsPlugin); diff --git a/packages/appkit/src/plugins/analytics/tests/analytics.test.ts b/packages/appkit/src/plugins/analytics/tests/analytics.test.ts index 22f53e9f..29157fff 100644 --- a/packages/appkit/src/plugins/analytics/tests/analytics.test.ts +++ b/packages/appkit/src/plugins/analytics/tests/analytics.test.ts @@ -610,10 +610,9 @@ describe("Analytics Plugin", () => { }); describe("toolkit()", () => { - test("factory exposes a toolkit() method producing ToolkitEntry records", () => { - const pluginData = analytics({}); - expect(typeof pluginData.toolkit).toBe("function"); - const entries = pluginData.toolkit(); + test("produces ToolkitEntry records keyed by the plugin name", () => { + const plugin = new AnalyticsPlugin({ name: "analytics" }); + const entries = plugin.toolkit(); expect(Object.keys(entries)).toContain("analytics.query"); const entry = entries["analytics.query"]; expect(entry.__toolkitRef).toBe(true); @@ -621,9 +620,9 @@ describe("Analytics Plugin", () => { expect(entry.localName).toBe("query"); }); - test("toolkit() respects prefix and only options", () => { - const pluginData = analytics({}); - const entries = pluginData.toolkit({ prefix: "", only: ["query"] }); + test("respects prefix and only options", () => { + const plugin = new AnalyticsPlugin({ name: "analytics" }); + const entries = plugin.toolkit({ prefix: "", only: ["query"] }); expect(Object.keys(entries)).toEqual(["query"]); }); }); diff --git a/packages/appkit/src/plugins/files/plugin.ts b/packages/appkit/src/plugins/files/plugin.ts index 7f538e45..cb588352 100644 --- a/packages/appkit/src/plugins/files/plugin.ts +++ b/packages/appkit/src/plugins/files/plugin.ts @@ -18,7 +18,7 @@ import { import { getWorkspaceClient, isInUserContext } from "../../context"; import { AuthenticationError } from "../../errors"; import { createLogger } from "../../logging/logger"; -import { Plugin, toPluginWithInstance } from "../../plugin"; +import { Plugin, toPlugin } from "../../plugin"; import type { PluginManifest, ResourceRequirement } from "../../registry"; import { ResourceType } from "../../registry"; import { buildToolkitEntries } from "../agents/build-toolkit"; @@ -1089,4 +1089,4 @@ export class FilesPlugin extends Plugin implements ToolProvider { /** * @internal */ -export const files = toPluginWithInstance(FilesPlugin, ["toolkit"] as const); +export const files = toPlugin(FilesPlugin); diff --git a/packages/appkit/src/plugins/genie/genie.ts b/packages/appkit/src/plugins/genie/genie.ts index 18b339d6..0c251994 100644 --- a/packages/appkit/src/plugins/genie/genie.ts +++ b/packages/appkit/src/plugins/genie/genie.ts @@ -10,7 +10,7 @@ import { z } from "zod"; import { GenieConnector } from "../../connectors"; import { getWorkspaceClient } from "../../context"; import { createLogger } from "../../logging"; -import { Plugin, toPluginWithInstance } from "../../plugin"; +import { Plugin, toPlugin } from "../../plugin"; import type { PluginManifest } from "../../registry"; import { buildToolkitEntries } from "../agents/build-toolkit"; import { @@ -375,4 +375,4 @@ export class GeniePlugin extends Plugin implements ToolProvider { /** * @internal */ -export const genie = toPluginWithInstance(GeniePlugin, ["toolkit"] as const); +export const genie = toPlugin(GeniePlugin); diff --git a/packages/appkit/src/plugins/lakebase/lakebase.ts b/packages/appkit/src/plugins/lakebase/lakebase.ts index c940c6e8..f1866d39 100644 --- a/packages/appkit/src/plugins/lakebase/lakebase.ts +++ b/packages/appkit/src/plugins/lakebase/lakebase.ts @@ -8,7 +8,7 @@ import { getUsernameWithApiLookup, } from "../../connectors/lakebase"; import { createLogger } from "../../logging/logger"; -import { Plugin, toPluginWithInstance } from "../../plugin"; +import { Plugin, toPlugin } from "../../plugin"; import type { PluginManifest } from "../../registry"; import { buildToolkitEntries } from "../agents/build-toolkit"; import { @@ -168,6 +168,4 @@ class LakebasePlugin extends Plugin implements ToolProvider { /** * @internal */ -export const lakebase = toPluginWithInstance(LakebasePlugin, [ - "toolkit", -] as const); +export const lakebase = toPlugin(LakebasePlugin); diff --git a/packages/appkit/src/registry/types.generated.ts b/packages/appkit/src/registry/types.generated.ts index 7e38af9b..348e7455 100644 --- a/packages/appkit/src/registry/types.generated.ts +++ b/packages/appkit/src/registry/types.generated.ts @@ -52,11 +52,7 @@ export type DatabasePermission = "CAN_CONNECT_AND_CREATE"; export type PostgresPermission = "CAN_CONNECT_AND_CREATE"; /** Permissions for GENIE_SPACE resources */ -export type GenieSpacePermission = - | "CAN_VIEW" - | "CAN_RUN" - | "CAN_EDIT" - | "CAN_MANAGE"; +export type GenieSpacePermission = "CAN_VIEW" | "CAN_RUN" | "CAN_EDIT" | "CAN_MANAGE"; /** Permissions for EXPERIMENT resources */ export type ExperimentPermission = "CAN_READ" | "CAN_EDIT" | "CAN_MANAGE"; @@ -81,10 +77,7 @@ export type ResourcePermission = | AppPermission; /** Permission hierarchy per resource type (weakest to strongest). Schema enum order. */ -export const PERMISSION_HIERARCHY_BY_TYPE: Record< - ResourceType, - readonly ResourcePermission[] -> = { +export const PERMISSION_HIERARCHY_BY_TYPE: Record = { [ResourceType.SECRET]: ["READ", "WRITE", "MANAGE"], [ResourceType.JOB]: ["CAN_VIEW", "CAN_MANAGE_RUN", "CAN_MANAGE"], [ResourceType.SQL_WAREHOUSE]: ["CAN_USE", "CAN_MANAGE"], @@ -101,7 +94,4 @@ export const PERMISSION_HIERARCHY_BY_TYPE: Record< } as const; /** Set of valid permissions per type (for validation). */ -export const PERMISSIONS_BY_TYPE: Record< - ResourceType, - readonly ResourcePermission[] -> = PERMISSION_HIERARCHY_BY_TYPE; +export const PERMISSIONS_BY_TYPE: Record = PERMISSION_HIERARCHY_BY_TYPE; diff --git a/packages/shared/src/plugin.ts b/packages/shared/src/plugin.ts index b8929112..651840c7 100644 --- a/packages/shared/src/plugin.ts +++ b/packages/shared/src/plugin.ts @@ -139,14 +139,6 @@ export type ConfigFor = T extends { DEFAULT_CONFIG: infer D } export type OptionalConfigPluginDef

= { plugin: P; config?: Partial>; - /** - * Pre-built plugin instance, populated by factories like - * `toPluginWithInstance` that eagerly construct the plugin. When present, - * AppKit reuses this instance instead of constructing a new one so that - * the handle a user holds at module scope is the same object that answers - * runtime requests. - */ - instance?: BasePlugin; }; // Input plugin map type (used internally by AppKit) diff --git a/packages/shared/src/schemas/plugin-manifest.generated.ts b/packages/shared/src/schemas/plugin-manifest.generated.ts index 5d2e5d4a..e40d3c60 100644 --- a/packages/shared/src/schemas/plugin-manifest.generated.ts +++ b/packages/shared/src/schemas/plugin-manifest.generated.ts @@ -2,284 +2,267 @@ // Run: pnpm exec tsx tools/generate-schema-types.ts /** * Declares a resource requirement for a plugin. Can be defined statically in a manifest or dynamically via getResourceRequirements(). - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "resourceRequirement". */ -export type ResourceRequirement = { - type: ResourceType; - /** - * Human-readable label for UI/display only. Deduplication uses resourceKey, not alias. - */ - alias: string; - /** - * Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup. - */ - resourceKey: string; - /** - * Human-readable description of why this resource is needed - */ - description: string; - /** - * Required permission level. Validated per resource type by the allOf/if-then rules below. - */ - permission: string; - /** - * Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key). - */ - fields?: { - [k: string]: ResourceFieldEntry; - }; -}; +export type ResourceRequirement = ({ +type: ResourceType +/** + * Human-readable label for UI/display only. Deduplication uses resourceKey, not alias. + */ +alias: string +/** + * Stable key for machine use: deduplication, env naming, composite keys, app.yaml. Required for registry lookup. + */ +resourceKey: string +/** + * Human-readable description of why this resource is needed + */ +description: string +/** + * Required permission level. Validated per resource type by the allOf/if-then rules below. + */ +permission: string +/** + * Map of field name to env and optional description. Single-value types use one key (e.g. id); multi-value (database, secret) use multiple (e.g. instance_name, database_name or scope, key). + */ +fields?: { +[k: string]: ResourceFieldEntry +} +}) /** * Type of Databricks resource - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "resourceType". */ -export type ResourceType = - | "secret" - | "job" - | "sql_warehouse" - | "serving_endpoint" - | "volume" - | "vector_search_index" - | "uc_function" - | "uc_connection" - | "database" - | "postgres" - | "genie_space" - | "experiment" - | "app"; +export type ResourceType = ("secret" | "job" | "sql_warehouse" | "serving_endpoint" | "volume" | "vector_search_index" | "uc_function" | "uc_connection" | "database" | "postgres" | "genie_space" | "experiment" | "app") /** * Permission for secret resources (order: weakest to strongest) - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "secretPermission". */ -export type SecretPermission = "READ" | "WRITE" | "MANAGE"; +export type SecretPermission = ("READ" | "WRITE" | "MANAGE") /** * Permission for job resources (order: weakest to strongest) - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "jobPermission". */ -export type JobPermission = "CAN_VIEW" | "CAN_MANAGE_RUN" | "CAN_MANAGE"; +export type JobPermission = ("CAN_VIEW" | "CAN_MANAGE_RUN" | "CAN_MANAGE") /** * Permission for SQL warehouse resources (order: weakest to strongest) - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "sqlWarehousePermission". */ -export type SqlWarehousePermission = "CAN_USE" | "CAN_MANAGE"; +export type SqlWarehousePermission = ("CAN_USE" | "CAN_MANAGE") /** * Permission for serving endpoint resources (order: weakest to strongest) - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "servingEndpointPermission". */ -export type ServingEndpointPermission = "CAN_VIEW" | "CAN_QUERY" | "CAN_MANAGE"; +export type ServingEndpointPermission = ("CAN_VIEW" | "CAN_QUERY" | "CAN_MANAGE") /** * Permission for Unity Catalog volume resources - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "volumePermission". */ -export type VolumePermission = "READ_VOLUME" | "WRITE_VOLUME"; +export type VolumePermission = ("READ_VOLUME" | "WRITE_VOLUME") /** * Permission for vector search index resources - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "vectorSearchIndexPermission". */ -export type VectorSearchIndexPermission = "SELECT"; +export type VectorSearchIndexPermission = "SELECT" /** * Permission for Unity Catalog function resources - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "ucFunctionPermission". */ -export type UcFunctionPermission = "EXECUTE"; +export type UcFunctionPermission = "EXECUTE" /** * Permission for Unity Catalog connection resources - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "ucConnectionPermission". */ -export type UcConnectionPermission = "USE_CONNECTION"; +export type UcConnectionPermission = "USE_CONNECTION" /** * Permission for database resources - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "databasePermission". */ -export type DatabasePermission = "CAN_CONNECT_AND_CREATE"; +export type DatabasePermission = "CAN_CONNECT_AND_CREATE" /** * Permission for Postgres resources - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "postgresPermission". */ -export type PostgresPermission = "CAN_CONNECT_AND_CREATE"; +export type PostgresPermission = "CAN_CONNECT_AND_CREATE" /** * Permission for Genie Space resources (order: weakest to strongest) - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "genieSpacePermission". */ -export type GenieSpacePermission = - | "CAN_VIEW" - | "CAN_RUN" - | "CAN_EDIT" - | "CAN_MANAGE"; +export type GenieSpacePermission = ("CAN_VIEW" | "CAN_RUN" | "CAN_EDIT" | "CAN_MANAGE") /** * Permission for MLflow experiment resources (order: weakest to strongest) - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "experimentPermission". */ -export type ExperimentPermission = "CAN_READ" | "CAN_EDIT" | "CAN_MANAGE"; +export type ExperimentPermission = ("CAN_READ" | "CAN_EDIT" | "CAN_MANAGE") /** * Permission for Databricks App resources - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "appPermission". */ -export type AppPermission = "CAN_USE"; +export type AppPermission = "CAN_USE" /** * Schema for Databricks AppKit plugin manifest files. Defines plugin metadata, resource requirements, and configuration options. */ export interface PluginManifest { - /** - * Reference to the JSON Schema for validation - */ - $schema?: string; - /** - * Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens. - */ - name: string; - /** - * Human-readable display name for UI and CLI - */ - displayName: string; - /** - * Brief description of what the plugin does - */ - description: string; - /** - * Databricks resource requirements for this plugin - */ - resources: { - /** - * Resources that must be available for the plugin to function - */ - required: ResourceRequirement[]; - /** - * Resources that enhance functionality but are not mandatory - */ - optional: ResourceRequirement[]; - }; - /** - * Configuration schema for the plugin - */ - config?: { - schema?: ConfigSchema; - }; - /** - * Author name or organization - */ - author?: string; - /** - * Plugin version (semver format) - */ - version?: string; - /** - * URL to the plugin's source repository - */ - repository?: string; - /** - * Keywords for plugin discovery - */ - keywords?: string[]; - /** - * SPDX license identifier - */ - license?: string; - /** - * Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning). - */ - onSetupMessage?: string; - /** - * When true, this plugin is excluded from the template plugins manifest (appkit.plugins.json) during sync. - */ - hidden?: boolean; +/** + * Reference to the JSON Schema for validation + */ +$schema?: string +/** + * Plugin identifier. Must be lowercase, start with a letter, and contain only letters, numbers, and hyphens. + */ +name: string +/** + * Human-readable display name for UI and CLI + */ +displayName: string +/** + * Brief description of what the plugin does + */ +description: string +/** + * Databricks resource requirements for this plugin + */ +resources: { +/** + * Resources that must be available for the plugin to function + */ +required: ResourceRequirement[] +/** + * Resources that enhance functionality but are not mandatory + */ +optional: ResourceRequirement[] +} +/** + * Configuration schema for the plugin + */ +config?: { +schema?: ConfigSchema +} +/** + * Author name or organization + */ +author?: string +/** + * Plugin version (semver format) + */ +version?: string +/** + * URL to the plugin's source repository + */ +repository?: string +/** + * Keywords for plugin discovery + */ +keywords?: string[] +/** + * SPDX license identifier + */ +license?: string +/** + * Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning). + */ +onSetupMessage?: string +/** + * When true, this plugin is excluded from the template plugins manifest (appkit.plugins.json) during sync. + */ +hidden?: boolean } /** * 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). - * + * * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "resourceFieldEntry". */ export interface ResourceFieldEntry { - /** - * Environment variable name for this field - */ - env?: string; - /** - * Human-readable description for this field - */ - description?: string; - /** - * When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation. - */ - bundleIgnore?: boolean; - /** - * Example values showing the expected format for this field - */ - examples?: string[]; - /** - * When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time. - */ - localOnly?: boolean; - /** - * Static value for this field. Used when no prompted or resolved value exists. - */ - value?: string; - /** - * Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow. - */ - resolve?: string; +/** + * Environment variable name for this field + */ +env?: string +/** + * Human-readable description for this field + */ +description?: string +/** + * When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation. + */ +bundleIgnore?: boolean +/** + * Example values showing the expected format for this field + */ +examples?: string[] +/** + * When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time. + */ +localOnly?: boolean +/** + * Static value for this field. Used when no prompted or resolved value exists. + */ +value?: string +/** + * Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow. + */ +resolve?: string } /** * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "configSchema". */ export interface ConfigSchema { - type: "object" | "array" | "string" | "number" | "boolean"; - properties?: { - [k: string]: ConfigSchemaProperty; - }; - items?: ConfigSchema; - required?: string[]; - additionalProperties?: boolean; +type: ("object" | "array" | "string" | "number" | "boolean") +properties?: { +[k: string]: ConfigSchemaProperty +} +items?: ConfigSchema +required?: string[] +additionalProperties?: boolean } /** * This interface was referenced by `PluginManifest`'s JSON-Schema * via the `definition` "configSchemaProperty". */ export interface ConfigSchemaProperty { - type: "object" | "array" | "string" | "number" | "boolean" | "integer"; - description?: string; - default?: unknown; - enum?: unknown[]; - properties?: { - [k: string]: ConfigSchemaProperty; - }; - items?: ConfigSchemaProperty; - minimum?: number; - maximum?: number; - minLength?: number; - maxLength?: number; - required?: string[]; +type: ("object" | "array" | "string" | "number" | "boolean" | "integer") +description?: string +default?: unknown +enum?: unknown[] +properties?: { +[k: string]: ConfigSchemaProperty +} +items?: ConfigSchemaProperty +minimum?: number +maximum?: number +minLength?: number +maxLength?: number +required?: string[] } diff --git a/template/appkit.plugins.json b/template/appkit.plugins.json index 9d113b0d..8a1461a8 100644 --- a/template/appkit.plugins.json +++ b/template/appkit.plugins.json @@ -2,8 +2,8 @@ "$schema": "https://databricks.github.io/appkit/schemas/template-plugins.schema.json", "version": "1.0", "plugins": { - "agents": { - "name": "agents", + "agent": { + "name": "agent", "displayName": "Agents Plugin", "description": "AI agents driven by markdown configs or code, with auto-tool-discovery from registered plugins", "package": "@databricks/appkit",