@databricks/appkit 0.31.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +54 -1
- package/NOTICE.md +2 -0
- package/dist/agents/databricks.d.ts.map +1 -1
- package/dist/agents/databricks.js +8 -3
- package/dist/agents/databricks.js.map +1 -1
- package/dist/appkit/package.js +1 -1
- package/dist/beta.d.ts +16 -1
- package/dist/beta.js +14 -1
- package/dist/connectors/index.js +3 -0
- package/dist/connectors/mcp/client.d.ts +85 -0
- package/dist/connectors/mcp/client.d.ts.map +1 -0
- package/dist/connectors/mcp/client.js +296 -0
- package/dist/connectors/mcp/client.js.map +1 -0
- package/dist/connectors/mcp/host-policy.d.ts +51 -0
- package/dist/connectors/mcp/host-policy.d.ts.map +1 -0
- package/dist/connectors/mcp/host-policy.js +168 -0
- package/dist/connectors/mcp/host-policy.js.map +1 -0
- package/dist/connectors/mcp/index.d.ts +3 -0
- package/dist/connectors/mcp/index.js +4 -0
- package/dist/connectors/mcp/types.d.ts +16 -0
- package/dist/connectors/mcp/types.d.ts.map +1 -0
- package/dist/context/index.js +1 -1
- package/dist/core/agent/build-toolkit.d.ts +2 -0
- package/dist/core/agent/build-toolkit.js +45 -0
- package/dist/core/agent/build-toolkit.js.map +1 -0
- package/dist/core/agent/consume-adapter-stream.js +33 -0
- package/dist/core/agent/consume-adapter-stream.js.map +1 -0
- package/dist/core/agent/create-agent.d.ts +27 -0
- package/dist/core/agent/create-agent.d.ts.map +1 -0
- package/dist/core/agent/create-agent.js +50 -0
- package/dist/core/agent/create-agent.js.map +1 -0
- package/dist/core/agent/load-agents.d.ts +72 -0
- package/dist/core/agent/load-agents.d.ts.map +1 -0
- package/dist/core/agent/load-agents.js +268 -0
- package/dist/core/agent/load-agents.js.map +1 -0
- package/dist/core/agent/normalize-result.js +39 -0
- package/dist/core/agent/normalize-result.js.map +1 -0
- package/dist/core/agent/plugins-map.js +44 -0
- package/dist/core/agent/plugins-map.js.map +1 -0
- package/dist/core/agent/run-agent.d.ts +58 -0
- package/dist/core/agent/run-agent.d.ts.map +1 -0
- package/dist/core/agent/run-agent.js +257 -0
- package/dist/core/agent/run-agent.js.map +1 -0
- package/dist/core/agent/system-prompt.js +38 -0
- package/dist/core/agent/system-prompt.js.map +1 -0
- package/dist/core/agent/toolkit-options.js +28 -0
- package/dist/core/agent/toolkit-options.js.map +1 -0
- package/dist/core/agent/toolkit-resolver.js +44 -0
- package/dist/core/agent/toolkit-resolver.js.map +1 -0
- package/dist/core/agent/tools/define-tool.d.ts +66 -0
- package/dist/core/agent/tools/define-tool.d.ts.map +1 -0
- package/dist/core/agent/tools/define-tool.js +50 -0
- package/dist/core/agent/tools/define-tool.js.map +1 -0
- package/dist/core/agent/tools/function-tool.d.ts +38 -0
- package/dist/core/agent/tools/function-tool.d.ts.map +1 -0
- package/dist/core/agent/tools/function-tool.js +22 -0
- package/dist/core/agent/tools/function-tool.js.map +1 -0
- package/dist/core/agent/tools/hosted-tools.d.ts +47 -0
- package/dist/core/agent/tools/hosted-tools.d.ts.map +1 -0
- package/dist/core/agent/tools/hosted-tools.js +67 -0
- package/dist/core/agent/tools/hosted-tools.js.map +1 -0
- package/dist/core/agent/tools/index.d.ts +5 -0
- package/dist/core/agent/tools/index.js +7 -0
- package/dist/core/agent/tools/json-schema.js +24 -0
- package/dist/core/agent/tools/json-schema.js.map +1 -0
- package/dist/core/agent/tools/sql-policy.js +256 -0
- package/dist/core/agent/tools/sql-policy.js.map +1 -0
- package/dist/core/agent/tools/tool.d.ts +63 -0
- package/dist/core/agent/tools/tool.d.ts.map +1 -0
- package/dist/core/agent/tools/tool.js +42 -0
- package/dist/core/agent/tools/tool.js.map +1 -0
- package/dist/core/agent/types.d.ts +299 -0
- package/dist/core/agent/types.d.ts.map +1 -0
- package/dist/core/agent/types.js +12 -0
- package/dist/core/agent/types.js.map +1 -0
- package/dist/core/appkit.d.ts +1 -0
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js +31 -4
- package/dist/core/appkit.js.map +1 -1
- package/dist/core/plugin-context.d.ts +133 -0
- package/dist/core/plugin-context.d.ts.map +1 -0
- package/dist/core/plugin-context.js +220 -0
- package/dist/core/plugin-context.js.map +1 -0
- package/dist/index.d.ts +11 -11
- package/dist/internal-telemetry/appkit-log.js +19 -0
- package/dist/internal-telemetry/appkit-log.js.map +1 -0
- package/dist/internal-telemetry/config.js +15 -0
- package/dist/internal-telemetry/config.js.map +1 -0
- package/dist/internal-telemetry/index.js +4 -0
- package/dist/internal-telemetry/reporter.js +132 -0
- package/dist/internal-telemetry/reporter.js.map +1 -0
- package/dist/plugin/plugin.d.ts +18 -3
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +26 -2
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugin/to-plugin.d.ts +3 -2
- package/dist/plugin/to-plugin.d.ts.map +1 -1
- package/dist/plugin/to-plugin.js +7 -4
- package/dist/plugin/to-plugin.js.map +1 -1
- package/dist/plugins/agents/agents.d.ts +186 -0
- package/dist/plugins/agents/agents.d.ts.map +1 -0
- package/dist/plugins/agents/agents.js +979 -0
- package/dist/plugins/agents/agents.js.map +1 -0
- package/dist/plugins/agents/defaults.js +13 -0
- package/dist/plugins/agents/defaults.js.map +1 -0
- package/dist/plugins/agents/event-channel.js +64 -0
- package/dist/plugins/agents/event-channel.js.map +1 -0
- package/dist/plugins/agents/event-translator.js +224 -0
- package/dist/plugins/agents/event-translator.js.map +1 -0
- package/dist/plugins/agents/index.d.ts +4 -0
- package/dist/plugins/agents/index.js +6 -0
- package/dist/plugins/agents/manifest.js +26 -0
- package/dist/plugins/agents/manifest.js.map +1 -0
- package/dist/plugins/agents/schemas.js +51 -0
- package/dist/plugins/agents/schemas.js.map +1 -0
- package/dist/plugins/agents/thread-store.js +58 -0
- package/dist/plugins/agents/thread-store.js.map +1 -0
- package/dist/plugins/agents/tool-approval-gate.js +75 -0
- package/dist/plugins/agents/tool-approval-gate.js.map +1 -0
- package/dist/plugins/analytics/analytics.d.ts +15 -1
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js +37 -2
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/analytics/index.js +1 -0
- package/dist/plugins/analytics/types.js +15 -0
- package/dist/plugins/analytics/types.js.map +1 -0
- package/dist/plugins/beta-exports.generated.d.ts +2 -0
- package/dist/plugins/beta-exports.generated.js +4 -0
- package/dist/plugins/files/plugin.d.ts +20 -2
- package/dist/plugins/files/plugin.d.ts.map +1 -1
- package/dist/plugins/files/plugin.js +120 -2
- package/dist/plugins/files/plugin.js.map +1 -1
- package/dist/plugins/genie/genie.d.ts +17 -3
- package/dist/plugins/genie/genie.d.ts.map +1 -1
- package/dist/plugins/genie/genie.js +61 -2
- package/dist/plugins/genie/genie.js.map +1 -1
- package/dist/plugins/genie/types.d.ts +10 -2
- package/dist/plugins/genie/types.d.ts.map +1 -1
- package/dist/plugins/jobs/plugin.js +1 -1
- package/dist/plugins/lakebase/index.d.ts +2 -2
- package/dist/plugins/lakebase/index.js +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts +31 -3
- package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
- package/dist/plugins/lakebase/lakebase.js +77 -5
- package/dist/plugins/lakebase/lakebase.js.map +1 -1
- package/dist/plugins/lakebase/types.d.ts +39 -1
- package/dist/plugins/lakebase/types.d.ts.map +1 -1
- package/dist/plugins/server/index.d.ts +12 -0
- package/dist/plugins/server/index.d.ts.map +1 -1
- package/dist/plugins/server/index.js +47 -10
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/types.d.ts +11 -3
- package/dist/plugins/server/types.d.ts.map +1 -1
- package/dist/shared/src/agent.d.ts +75 -1
- package/dist/shared/src/agent.d.ts.map +1 -1
- package/dist/shared/src/index.d.ts +1 -1
- package/dist/shared/src/plugin.d.ts +8 -0
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/docs/api/appkit/Class.AppKitMcpClient.md +157 -0
- package/docs/api/appkit/Class.DatabricksAdapter.md +151 -0
- package/docs/api/appkit/Class.Plugin.md +65 -23
- package/docs/api/appkit/Function.agentIdFromMarkdownPath.md +18 -0
- package/docs/api/appkit/Function.createAgent.md +33 -0
- package/docs/api/appkit/Function.createApp.md +10 -8
- package/docs/api/appkit/Function.defineTool.md +26 -0
- package/docs/api/appkit/Function.executeFromRegistry.md +25 -0
- package/docs/api/appkit/Function.functionToolToDefinition.md +16 -0
- package/docs/api/appkit/Function.isFunctionTool.md +16 -0
- package/docs/api/appkit/Function.isHostedTool.md +16 -0
- package/docs/api/appkit/Function.isToolkitEntry.md +18 -0
- package/docs/api/appkit/Function.loadAgentFromFile.md +21 -0
- package/docs/api/appkit/Function.loadAgentsFromDir.md +26 -0
- package/docs/api/appkit/Function.mcpServer.md +28 -0
- package/docs/api/appkit/Function.parseTextToolCalls.md +26 -0
- package/docs/api/appkit/Function.resolveHostedTools.md +16 -0
- package/docs/api/appkit/Function.runAgent.md +26 -0
- package/docs/api/appkit/Function.tool.md +28 -0
- package/docs/api/appkit/Function.toolsFromRegistry.md +20 -0
- package/docs/api/appkit/Interface.AgentAdapter.md +21 -0
- package/docs/api/appkit/Interface.AgentDefinition.md +112 -0
- package/docs/api/appkit/Interface.AgentInput.md +37 -0
- package/docs/api/appkit/Interface.AgentRunContext.md +32 -0
- package/docs/api/appkit/Interface.AgentToolDefinition.md +37 -0
- package/docs/api/appkit/Interface.AgentsPluginConfig.md +241 -0
- package/docs/api/appkit/Interface.AutoInheritToolsConfig.md +27 -0
- package/docs/api/appkit/Interface.BasePluginConfig.md +1 -0
- package/docs/api/appkit/Interface.FunctionTool.md +80 -0
- package/docs/api/appkit/Interface.McpConnectAllResult.md +38 -0
- package/docs/api/appkit/Interface.Message.md +55 -0
- package/docs/api/appkit/Interface.PluginToolkitProvider.md +22 -0
- package/docs/api/appkit/Interface.PromptContext.md +30 -0
- package/docs/api/appkit/Interface.RegisteredAgent.md +75 -0
- package/docs/api/appkit/Interface.RunAgentInput.md +34 -0
- package/docs/api/appkit/Interface.RunAgentResult.md +23 -0
- package/docs/api/appkit/Interface.Thread.md +46 -0
- package/docs/api/appkit/Interface.ThreadStore.md +103 -0
- package/docs/api/appkit/Interface.ToolAnnotations.md +56 -0
- package/docs/api/appkit/Interface.ToolConfig.md +72 -0
- package/docs/api/appkit/Interface.ToolEntry.md +73 -0
- package/docs/api/appkit/Interface.ToolProvider.md +38 -0
- package/docs/api/appkit/Interface.ToolkitEntry.md +59 -0
- package/docs/api/appkit/Interface.ToolkitOptions.md +45 -0
- package/docs/api/appkit/TypeAlias.AgentEvent.md +299 -0
- package/docs/api/appkit/TypeAlias.AgentTool.md +11 -0
- package/docs/api/appkit/TypeAlias.AgentTools.md +8 -0
- package/docs/api/appkit/TypeAlias.AgentToolsFn.md +20 -0
- package/docs/api/appkit/TypeAlias.BaseSystemPromptOption.md +9 -0
- package/docs/api/appkit/TypeAlias.HostedTool.md +10 -0
- package/docs/api/appkit/TypeAlias.Plugins.md +26 -0
- package/docs/api/appkit/TypeAlias.ResolvedToolEntry.md +29 -0
- package/docs/api/appkit/TypeAlias.ToolRegistry.md +6 -0
- package/docs/api/appkit/Variable.agents.md +19 -0
- package/docs/api/appkit.md +113 -62
- package/docs/plugins/agents.md +441 -0
- package/docs/privacy.md +41 -0
- package/llms.txt +54 -1
- package/package.json +4 -2
- package/sbom.cdx.json +1 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { ToolProvider } from "../shared/src/agent.js";
|
|
2
|
+
import { BasePlugin, IAppRequest } from "../shared/src/plugin.js";
|
|
3
|
+
import "../shared/src/index.js";
|
|
4
|
+
import express from "express";
|
|
5
|
+
|
|
6
|
+
//#region src/core/plugin-context.d.ts
|
|
7
|
+
interface RouteTarget {
|
|
8
|
+
addExtension(fn: (app: express.Application) => void): void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A tool-provider plugin that also exposes user-scoped execution. Plugins
|
|
12
|
+
* derived from {@link Plugin} satisfy this implicitly because `asUser` lives
|
|
13
|
+
* on the base class. {@link isToolProvider} narrows to this shape so
|
|
14
|
+
* `executeTool` can call `asUser` without an unsafe cast.
|
|
15
|
+
*/
|
|
16
|
+
type ToolProviderPlugin = BasePlugin & ToolProvider & {
|
|
17
|
+
asUser: (req: IAppRequest) => ToolProvider;
|
|
18
|
+
};
|
|
19
|
+
type LifecycleEvent = "setup:complete" | "server:ready" | "shutdown";
|
|
20
|
+
/**
|
|
21
|
+
* Mediator for inter-plugin communication.
|
|
22
|
+
*
|
|
23
|
+
* Created by AppKit core and passed to every plugin. Plugins request
|
|
24
|
+
* capabilities from the context instead of holding direct references
|
|
25
|
+
* to sibling plugin instances.
|
|
26
|
+
*
|
|
27
|
+
* Capabilities:
|
|
28
|
+
* - Route mounting with buffering (order-independent)
|
|
29
|
+
* - Typed ToolProvider registry (live, not snapshot-based)
|
|
30
|
+
* - User-scoped tool execution with automatic telemetry
|
|
31
|
+
* - Lifecycle hooks for plugin coordination
|
|
32
|
+
*/
|
|
33
|
+
declare class PluginContext {
|
|
34
|
+
private routeBuffer;
|
|
35
|
+
private routeTarget;
|
|
36
|
+
private toolProviders;
|
|
37
|
+
private plugins;
|
|
38
|
+
private lifecycleHooks;
|
|
39
|
+
private telemetry;
|
|
40
|
+
/**
|
|
41
|
+
* Register a route on the root Express application.
|
|
42
|
+
*
|
|
43
|
+
* If a route target (server plugin) has registered, the route is applied
|
|
44
|
+
* immediately. Otherwise it is buffered and flushed when a route target
|
|
45
|
+
* becomes available.
|
|
46
|
+
*/
|
|
47
|
+
addRoute(method: string, path: string, ...handlers: express.RequestHandler[]): void;
|
|
48
|
+
/**
|
|
49
|
+
* Register middleware on the root Express application.
|
|
50
|
+
*
|
|
51
|
+
* Same buffering semantics as `addRoute`.
|
|
52
|
+
*/
|
|
53
|
+
addMiddleware(path: string, ...handlers: express.RequestHandler[]): void;
|
|
54
|
+
/**
|
|
55
|
+
* Called by the server plugin to opt in as the route target.
|
|
56
|
+
* Flushes all buffered routes via the server's `addExtension`.
|
|
57
|
+
*
|
|
58
|
+
* Only the first caller wins — subsequent calls are ignored with a warning.
|
|
59
|
+
* In practice only the server plugin registers, but a misconfigured app
|
|
60
|
+
* (two server plugins, or duplicate AppKit setup in tests) would otherwise
|
|
61
|
+
* silently drop the first target's later extensions.
|
|
62
|
+
*/
|
|
63
|
+
registerAsRouteTarget(target: RouteTarget): void;
|
|
64
|
+
/**
|
|
65
|
+
* Register a plugin that implements the ToolProvider interface.
|
|
66
|
+
* Called by AppKit core after constructing each plugin.
|
|
67
|
+
*
|
|
68
|
+
* Plugin names should be unique (they are derived from `manifest.name`).
|
|
69
|
+
* A duplicate registration overwrites the previous entry and emits a
|
|
70
|
+
* warning so the misconfiguration is visible in startup logs.
|
|
71
|
+
*/
|
|
72
|
+
registerToolProvider(name: string, plugin: ToolProviderPlugin): void;
|
|
73
|
+
/**
|
|
74
|
+
* Register a plugin instance.
|
|
75
|
+
* Called by AppKit core after constructing each plugin.
|
|
76
|
+
*/
|
|
77
|
+
registerPlugin(name: string, instance: BasePlugin): void;
|
|
78
|
+
/**
|
|
79
|
+
* Returns all registered plugin instances keyed by name.
|
|
80
|
+
* Used by the server plugin for route injection, client config,
|
|
81
|
+
* and shutdown coordination. The returned map is read-only at the
|
|
82
|
+
* type level — callers must not mutate the live registry.
|
|
83
|
+
*/
|
|
84
|
+
getPlugins(): ReadonlyMap<string, BasePlugin>;
|
|
85
|
+
/**
|
|
86
|
+
* Returns all registered ToolProvider plugins.
|
|
87
|
+
* Always returns the current set — not a frozen snapshot.
|
|
88
|
+
*/
|
|
89
|
+
getToolProviders(): Array<{
|
|
90
|
+
name: string;
|
|
91
|
+
provider: ToolProvider;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Execute a tool on a ToolProvider plugin with automatic user scoping
|
|
95
|
+
* and telemetry.
|
|
96
|
+
*
|
|
97
|
+
* The context:
|
|
98
|
+
* 1. Resolves the plugin by name
|
|
99
|
+
* 2. Calls `asUser(req)` for user-scoped execution
|
|
100
|
+
* 3. Wraps the call in a telemetry span with a configurable timeout
|
|
101
|
+
*
|
|
102
|
+
* @param timeoutMs Per-call timeout. Defaults to 5 minutes — the floor
|
|
103
|
+
* for cold SQL Warehouse round-trips, long Genie conversations, and
|
|
104
|
+
* busy serverless Lakebase queries. The agents plugin overrides this
|
|
105
|
+
* per-app via `agents({ limits: { toolCallTimeoutMs } })`.
|
|
106
|
+
*/
|
|
107
|
+
executeTool(req: express.Request, pluginName: string, toolName: string, args: unknown, signal?: AbortSignal, timeoutMs?: number): Promise<unknown>;
|
|
108
|
+
/**
|
|
109
|
+
* Register a lifecycle hook callback.
|
|
110
|
+
*/
|
|
111
|
+
onLifecycle(event: LifecycleEvent, fn: () => void | Promise<void>): void;
|
|
112
|
+
/**
|
|
113
|
+
* Emit a lifecycle event, calling all registered callbacks.
|
|
114
|
+
* Errors in individual callbacks are logged but do not prevent
|
|
115
|
+
* other callbacks from running.
|
|
116
|
+
*
|
|
117
|
+
* @internal Called by AppKit core only.
|
|
118
|
+
*/
|
|
119
|
+
emitLifecycle(event: LifecycleEvent): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Returns all registered plugin names.
|
|
122
|
+
*/
|
|
123
|
+
getPluginNames(): string[];
|
|
124
|
+
/**
|
|
125
|
+
* Check if a plugin with the given name is registered.
|
|
126
|
+
*/
|
|
127
|
+
hasPlugin(name: string): boolean;
|
|
128
|
+
private applyRoute;
|
|
129
|
+
private applyMiddleware;
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
export { PluginContext };
|
|
133
|
+
//# sourceMappingURL=plugin-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-context.d.ts","names":[],"sources":["../../src/core/plugin-context.ts"],"mappings":";;;;;;UAaU,WAAA;EACR,YAAA,CAAa,EAAA,GAAK,GAAA,EAAK,OAAA,CAAQ,WAAA;AAAA;;AAbmC;;;;;KAsB/D,kBAAA,GAAqB,UAAA,GACxB,YAAA;EAAiB,MAAA,GAAS,GAAA,EAAK,WAAA,KAAgB,YAAA;AAAA;AAAA,KAE5C,cAAA;;;AAZgD;;;;;;;;;;;cA2BxC,aAAA;EAAA,QACH,WAAA;EAAA,QACA,WAAA;EAAA,QACA,aAAA;EAAA,QACA,OAAA;EAAA,QACA,cAAA;EAAA,QAIA,SAAA;EAxBS;;;;AAenB;;;EAkBE,QAAA,CACE,MAAA,UACA,IAAA,aACG,QAAA,EAAU,OAAA,CAAQ,cAAA;EAckB;;;;;EAAzC,aAAA,CAAc,IAAA,aAAiB,QAAA,EAAU,OAAA,CAAQ,cAAA;EA4EG;;;;;;;;;EA3DpD,qBAAA,CAAsB,MAAA,EAAQ,WAAA;EAoJqB;;;;;;;;EAzHnD,oBAAA,CAAqB,IAAA,UAAc,MAAA,EAAQ,kBAAA;EA3DzC;;;;EAyEF,cAAA,CAAe,IAAA,UAAc,QAAA,EAAU,UAAA;EA1DzB;;;;;;EAoEd,UAAA,CAAA,GAAc,WAAA,SAAoB,UAAA;EAxBlC;;;;EAgCA,gBAAA,CAAA,GAAoB,KAAA;IAAQ,IAAA;IAAc,QAAA,EAAU,YAAA;EAAA;EARpD;;;;;;;;;;;;;;EA6BM,WAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,UAAA,UACA,QAAA,UACA,IAAA,WACA,MAAA,GAAS,WAAA,EACT,SAAA,YACC,OAAA;EAFQ;;;EA+CX,WAAA,CAAY,KAAA,EAAO,cAAA,EAAgB,EAAA,eAAiB,OAAA;EAApD;;;;;;;EAgBM,aAAA,CAAc,KAAA,EAAO,cAAA,GAAiB,OAAA;EAAA;;;EA8B5C,cAAA,CAAA;EAWQ;;;EAJR,SAAA,CAAU,IAAA;EAAA,QAIF,UAAA;EAAA,QAaA,eAAA;AAAA"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { createLogger } from "../logging/logger.js";
|
|
2
|
+
import { TelemetryManager } from "../telemetry/telemetry-manager.js";
|
|
3
|
+
import { SpanStatusCode } from "../telemetry/index.js";
|
|
4
|
+
|
|
5
|
+
//#region src/core/plugin-context.ts
|
|
6
|
+
const logger = createLogger("plugin-context");
|
|
7
|
+
/**
|
|
8
|
+
* Mediator for inter-plugin communication.
|
|
9
|
+
*
|
|
10
|
+
* Created by AppKit core and passed to every plugin. Plugins request
|
|
11
|
+
* capabilities from the context instead of holding direct references
|
|
12
|
+
* to sibling plugin instances.
|
|
13
|
+
*
|
|
14
|
+
* Capabilities:
|
|
15
|
+
* - Route mounting with buffering (order-independent)
|
|
16
|
+
* - Typed ToolProvider registry (live, not snapshot-based)
|
|
17
|
+
* - User-scoped tool execution with automatic telemetry
|
|
18
|
+
* - Lifecycle hooks for plugin coordination
|
|
19
|
+
*/
|
|
20
|
+
var PluginContext = class {
|
|
21
|
+
routeBuffer = [];
|
|
22
|
+
routeTarget = null;
|
|
23
|
+
toolProviders = /* @__PURE__ */ new Map();
|
|
24
|
+
plugins = /* @__PURE__ */ new Map();
|
|
25
|
+
lifecycleHooks = /* @__PURE__ */ new Map();
|
|
26
|
+
telemetry = TelemetryManager.getProvider("plugin-context");
|
|
27
|
+
/**
|
|
28
|
+
* Register a route on the root Express application.
|
|
29
|
+
*
|
|
30
|
+
* If a route target (server plugin) has registered, the route is applied
|
|
31
|
+
* immediately. Otherwise it is buffered and flushed when a route target
|
|
32
|
+
* becomes available.
|
|
33
|
+
*/
|
|
34
|
+
addRoute(method, path, ...handlers) {
|
|
35
|
+
if (this.routeTarget) this.applyRoute({
|
|
36
|
+
method,
|
|
37
|
+
path,
|
|
38
|
+
handlers
|
|
39
|
+
});
|
|
40
|
+
else this.routeBuffer.push({
|
|
41
|
+
method,
|
|
42
|
+
path,
|
|
43
|
+
handlers
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Register middleware on the root Express application.
|
|
48
|
+
*
|
|
49
|
+
* Same buffering semantics as `addRoute`.
|
|
50
|
+
*/
|
|
51
|
+
addMiddleware(path, ...handlers) {
|
|
52
|
+
if (this.routeTarget) this.applyMiddleware(path, handlers);
|
|
53
|
+
else this.routeBuffer.push({
|
|
54
|
+
method: "use",
|
|
55
|
+
path,
|
|
56
|
+
handlers
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Called by the server plugin to opt in as the route target.
|
|
61
|
+
* Flushes all buffered routes via the server's `addExtension`.
|
|
62
|
+
*
|
|
63
|
+
* Only the first caller wins — subsequent calls are ignored with a warning.
|
|
64
|
+
* In practice only the server plugin registers, but a misconfigured app
|
|
65
|
+
* (two server plugins, or duplicate AppKit setup in tests) would otherwise
|
|
66
|
+
* silently drop the first target's later extensions.
|
|
67
|
+
*/
|
|
68
|
+
registerAsRouteTarget(target) {
|
|
69
|
+
if (this.routeTarget) {
|
|
70
|
+
logger.warn("registerAsRouteTarget called more than once; ignoring duplicate registration");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.routeTarget = target;
|
|
74
|
+
for (const route of this.routeBuffer) if (route.method === "use") this.applyMiddleware(route.path, route.handlers);
|
|
75
|
+
else this.applyRoute(route);
|
|
76
|
+
this.routeBuffer = [];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Register a plugin that implements the ToolProvider interface.
|
|
80
|
+
* Called by AppKit core after constructing each plugin.
|
|
81
|
+
*
|
|
82
|
+
* Plugin names should be unique (they are derived from `manifest.name`).
|
|
83
|
+
* A duplicate registration overwrites the previous entry and emits a
|
|
84
|
+
* warning so the misconfiguration is visible in startup logs.
|
|
85
|
+
*/
|
|
86
|
+
registerToolProvider(name, plugin) {
|
|
87
|
+
if (this.toolProviders.has(name)) logger.warn("Tool provider \"%s\" registered more than once; the previous registration is being overwritten", name);
|
|
88
|
+
this.toolProviders.set(name, plugin);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Register a plugin instance.
|
|
92
|
+
* Called by AppKit core after constructing each plugin.
|
|
93
|
+
*/
|
|
94
|
+
registerPlugin(name, instance) {
|
|
95
|
+
this.plugins.set(name, instance);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns all registered plugin instances keyed by name.
|
|
99
|
+
* Used by the server plugin for route injection, client config,
|
|
100
|
+
* and shutdown coordination. The returned map is read-only at the
|
|
101
|
+
* type level — callers must not mutate the live registry.
|
|
102
|
+
*/
|
|
103
|
+
getPlugins() {
|
|
104
|
+
return this.plugins;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Returns all registered ToolProvider plugins.
|
|
108
|
+
* Always returns the current set — not a frozen snapshot.
|
|
109
|
+
*/
|
|
110
|
+
getToolProviders() {
|
|
111
|
+
return Array.from(this.toolProviders.entries()).map(([name, provider]) => ({
|
|
112
|
+
name,
|
|
113
|
+
provider
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Execute a tool on a ToolProvider plugin with automatic user scoping
|
|
118
|
+
* and telemetry.
|
|
119
|
+
*
|
|
120
|
+
* The context:
|
|
121
|
+
* 1. Resolves the plugin by name
|
|
122
|
+
* 2. Calls `asUser(req)` for user-scoped execution
|
|
123
|
+
* 3. Wraps the call in a telemetry span with a configurable timeout
|
|
124
|
+
*
|
|
125
|
+
* @param timeoutMs Per-call timeout. Defaults to 5 minutes — the floor
|
|
126
|
+
* for cold SQL Warehouse round-trips, long Genie conversations, and
|
|
127
|
+
* busy serverless Lakebase queries. The agents plugin overrides this
|
|
128
|
+
* per-app via `agents({ limits: { toolCallTimeoutMs } })`.
|
|
129
|
+
*/
|
|
130
|
+
async executeTool(req, pluginName, toolName, args, signal, timeoutMs = 3e5) {
|
|
131
|
+
const provider = this.toolProviders.get(pluginName);
|
|
132
|
+
if (!provider) throw new Error(`PluginContext: unknown plugin "${pluginName}". Available: ${Array.from(this.toolProviders.keys()).join(", ")}`);
|
|
133
|
+
const tracer = this.telemetry.getTracer();
|
|
134
|
+
const operationName = `executeTool:${pluginName}.${toolName}`;
|
|
135
|
+
return tracer.startActiveSpan(operationName, async (span) => {
|
|
136
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
137
|
+
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
138
|
+
try {
|
|
139
|
+
const result = await provider.asUser(req).executeAgentTool(toolName, args, combinedSignal);
|
|
140
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
141
|
+
return result;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
span.setStatus({
|
|
144
|
+
code: SpanStatusCode.ERROR,
|
|
145
|
+
message: error instanceof Error ? error.message : "Tool execution failed"
|
|
146
|
+
});
|
|
147
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
148
|
+
throw error;
|
|
149
|
+
} finally {
|
|
150
|
+
span.end();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Register a lifecycle hook callback.
|
|
156
|
+
*/
|
|
157
|
+
onLifecycle(event, fn) {
|
|
158
|
+
let hooks = this.lifecycleHooks.get(event);
|
|
159
|
+
if (!hooks) {
|
|
160
|
+
hooks = /* @__PURE__ */ new Set();
|
|
161
|
+
this.lifecycleHooks.set(event, hooks);
|
|
162
|
+
}
|
|
163
|
+
hooks.add(fn);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Emit a lifecycle event, calling all registered callbacks.
|
|
167
|
+
* Errors in individual callbacks are logged but do not prevent
|
|
168
|
+
* other callbacks from running.
|
|
169
|
+
*
|
|
170
|
+
* @internal Called by AppKit core only.
|
|
171
|
+
*/
|
|
172
|
+
async emitLifecycle(event) {
|
|
173
|
+
const hooks = this.lifecycleHooks.get(event);
|
|
174
|
+
if (!hooks) return;
|
|
175
|
+
if (event === "setup:complete" && this.routeBuffer.length > 0 && !this.routeTarget) logger.warn("%d buffered routes were never applied — no server plugin registered as route target", this.routeBuffer.length);
|
|
176
|
+
for (const fn of [...hooks]) try {
|
|
177
|
+
await fn();
|
|
178
|
+
} catch (error) {
|
|
179
|
+
logger.error("Lifecycle hook '%s' failed: %O", event, error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Returns all registered plugin names.
|
|
184
|
+
*/
|
|
185
|
+
getPluginNames() {
|
|
186
|
+
return Array.from(this.plugins.keys());
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Check if a plugin with the given name is registered.
|
|
190
|
+
*/
|
|
191
|
+
hasPlugin(name) {
|
|
192
|
+
return this.plugins.has(name);
|
|
193
|
+
}
|
|
194
|
+
applyRoute(route) {
|
|
195
|
+
if (!this.routeTarget) return;
|
|
196
|
+
this.routeTarget.addExtension((app) => {
|
|
197
|
+
const method = route.method.toLowerCase();
|
|
198
|
+
if (typeof app[method] === "function") app[method](route.path, ...route.handlers);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
applyMiddleware(path, handlers) {
|
|
202
|
+
if (!this.routeTarget) return;
|
|
203
|
+
this.routeTarget.addExtension((app) => {
|
|
204
|
+
app.use(path, ...handlers);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* Type guard: checks whether a plugin implements the ToolProvider interface
|
|
210
|
+
* and exposes the user-scoped `asUser` helper that the {@link Plugin} base
|
|
211
|
+
* class provides. Narrowing to {@link ToolProviderPlugin} lets `executeTool`
|
|
212
|
+
* call `asUser` without an unsafe cast.
|
|
213
|
+
*/
|
|
214
|
+
function isToolProvider(plugin) {
|
|
215
|
+
return typeof plugin === "object" && plugin !== null && "getAgentTools" in plugin && typeof plugin.getAgentTools === "function" && "executeAgentTool" in plugin && typeof plugin.executeAgentTool === "function" && "asUser" in plugin && typeof plugin.asUser === "function";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
//#endregion
|
|
219
|
+
export { PluginContext, isToolProvider };
|
|
220
|
+
//# sourceMappingURL=plugin-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-context.js","names":[],"sources":["../../src/core/plugin-context.ts"],"sourcesContent":["import type express from \"express\";\nimport type { BasePlugin, IAppRequest, ToolProvider } from \"shared\";\nimport { createLogger } from \"../logging/logger\";\nimport { SpanStatusCode, TelemetryManager } from \"../telemetry\";\n\nconst logger = createLogger(\"plugin-context\");\n\ninterface BufferedRoute {\n method: string;\n path: string;\n handlers: express.RequestHandler[];\n}\n\ninterface RouteTarget {\n addExtension(fn: (app: express.Application) => void): void;\n}\n\n/**\n * A tool-provider plugin that also exposes user-scoped execution. Plugins\n * derived from {@link Plugin} satisfy this implicitly because `asUser` lives\n * on the base class. {@link isToolProvider} narrows to this shape so\n * `executeTool` can call `asUser` without an unsafe cast.\n */\ntype ToolProviderPlugin = BasePlugin &\n ToolProvider & { asUser: (req: IAppRequest) => ToolProvider };\n\ntype LifecycleEvent = \"setup:complete\" | \"server:ready\" | \"shutdown\";\n\n/**\n * Mediator for inter-plugin communication.\n *\n * Created by AppKit core and passed to every plugin. Plugins request\n * capabilities from the context instead of holding direct references\n * to sibling plugin instances.\n *\n * Capabilities:\n * - Route mounting with buffering (order-independent)\n * - Typed ToolProvider registry (live, not snapshot-based)\n * - User-scoped tool execution with automatic telemetry\n * - Lifecycle hooks for plugin coordination\n */\nexport class PluginContext {\n private routeBuffer: BufferedRoute[] = [];\n private routeTarget: RouteTarget | null = null;\n private toolProviders = new Map<string, ToolProviderPlugin>();\n private plugins = new Map<string, BasePlugin>();\n private lifecycleHooks = new Map<\n LifecycleEvent,\n Set<() => void | Promise<void>>\n >();\n private telemetry = TelemetryManager.getProvider(\"plugin-context\");\n\n /**\n * Register a route on the root Express application.\n *\n * If a route target (server plugin) has registered, the route is applied\n * immediately. Otherwise it is buffered and flushed when a route target\n * becomes available.\n */\n addRoute(\n method: string,\n path: string,\n ...handlers: express.RequestHandler[]\n ): void {\n if (this.routeTarget) {\n this.applyRoute({ method, path, handlers });\n } else {\n this.routeBuffer.push({ method, path, handlers });\n }\n }\n\n /**\n * Register middleware on the root Express application.\n *\n * Same buffering semantics as `addRoute`.\n */\n addMiddleware(path: string, ...handlers: express.RequestHandler[]): void {\n if (this.routeTarget) {\n this.applyMiddleware(path, handlers);\n } else {\n this.routeBuffer.push({ method: \"use\", path, handlers });\n }\n }\n\n /**\n * Called by the server plugin to opt in as the route target.\n * Flushes all buffered routes via the server's `addExtension`.\n *\n * Only the first caller wins — subsequent calls are ignored with a warning.\n * In practice only the server plugin registers, but a misconfigured app\n * (two server plugins, or duplicate AppKit setup in tests) would otherwise\n * silently drop the first target's later extensions.\n */\n registerAsRouteTarget(target: RouteTarget): void {\n if (this.routeTarget) {\n logger.warn(\n \"registerAsRouteTarget called more than once; ignoring duplicate registration\",\n );\n return;\n }\n this.routeTarget = target;\n\n for (const route of this.routeBuffer) {\n if (route.method === \"use\") {\n this.applyMiddleware(route.path, route.handlers);\n } else {\n this.applyRoute(route);\n }\n }\n this.routeBuffer = [];\n }\n\n /**\n * Register a plugin that implements the ToolProvider interface.\n * Called by AppKit core after constructing each plugin.\n *\n * Plugin names should be unique (they are derived from `manifest.name`).\n * A duplicate registration overwrites the previous entry and emits a\n * warning so the misconfiguration is visible in startup logs.\n */\n registerToolProvider(name: string, plugin: ToolProviderPlugin): void {\n if (this.toolProviders.has(name)) {\n logger.warn(\n 'Tool provider \"%s\" registered more than once; the previous registration is being overwritten',\n name,\n );\n }\n this.toolProviders.set(name, plugin);\n }\n\n /**\n * Register a plugin instance.\n * Called by AppKit core after constructing each plugin.\n */\n registerPlugin(name: string, instance: BasePlugin): void {\n this.plugins.set(name, instance);\n }\n\n /**\n * Returns all registered plugin instances keyed by name.\n * Used by the server plugin for route injection, client config,\n * and shutdown coordination. The returned map is read-only at the\n * type level — callers must not mutate the live registry.\n */\n getPlugins(): ReadonlyMap<string, BasePlugin> {\n return this.plugins;\n }\n\n /**\n * Returns all registered ToolProvider plugins.\n * Always returns the current set — not a frozen snapshot.\n */\n getToolProviders(): Array<{ name: string; provider: ToolProvider }> {\n return Array.from(this.toolProviders.entries()).map(([name, provider]) => ({\n name,\n provider,\n }));\n }\n\n /**\n * Execute a tool on a ToolProvider plugin with automatic user scoping\n * and telemetry.\n *\n * The context:\n * 1. Resolves the plugin by name\n * 2. Calls `asUser(req)` for user-scoped execution\n * 3. Wraps the call in a telemetry span with a configurable timeout\n *\n * @param timeoutMs Per-call timeout. Defaults to 5 minutes — the floor\n * for cold SQL Warehouse round-trips, long Genie conversations, and\n * busy serverless Lakebase queries. The agents plugin overrides this\n * per-app via `agents({ limits: { toolCallTimeoutMs } })`.\n */\n async executeTool(\n req: express.Request,\n pluginName: string,\n toolName: string,\n args: unknown,\n signal?: AbortSignal,\n timeoutMs: number = 300_000,\n ): Promise<unknown> {\n const provider = this.toolProviders.get(pluginName);\n if (!provider) {\n throw new Error(\n `PluginContext: unknown plugin \"${pluginName}\". Available: ${Array.from(this.toolProviders.keys()).join(\", \")}`,\n );\n }\n\n const tracer = this.telemetry.getTracer();\n const operationName = `executeTool:${pluginName}.${toolName}`;\n\n return tracer.startActiveSpan(operationName, async (span) => {\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const combinedSignal = signal\n ? AbortSignal.any([signal, timeoutSignal])\n : timeoutSignal;\n\n try {\n const userScoped = provider.asUser(req);\n const result = await userScoped.executeAgentTool(\n toolName,\n args,\n combinedSignal,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message:\n error instanceof Error ? error.message : \"Tool execution failed\",\n });\n span.recordException(\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n } finally {\n span.end();\n }\n });\n }\n\n /**\n * Register a lifecycle hook callback.\n */\n onLifecycle(event: LifecycleEvent, fn: () => void | Promise<void>): void {\n let hooks = this.lifecycleHooks.get(event);\n if (!hooks) {\n hooks = new Set();\n this.lifecycleHooks.set(event, hooks);\n }\n hooks.add(fn);\n }\n\n /**\n * Emit a lifecycle event, calling all registered callbacks.\n * Errors in individual callbacks are logged but do not prevent\n * other callbacks from running.\n *\n * @internal Called by AppKit core only.\n */\n async emitLifecycle(event: LifecycleEvent): Promise<void> {\n const hooks = this.lifecycleHooks.get(event);\n if (!hooks) return;\n\n if (\n event === \"setup:complete\" &&\n this.routeBuffer.length > 0 &&\n !this.routeTarget\n ) {\n logger.warn(\n \"%d buffered routes were never applied — no server plugin registered as route target\",\n this.routeBuffer.length,\n );\n }\n\n // Snapshot before iterating so a callback that registers a new hook for\n // the same event does not mutate the loop. ECMAScript Set iteration would\n // otherwise visit late-added entries, risking unexpected re-entry.\n for (const fn of [...hooks]) {\n try {\n await fn();\n } catch (error) {\n logger.error(\"Lifecycle hook '%s' failed: %O\", event, error);\n }\n }\n }\n\n /**\n * Returns all registered plugin names.\n */\n getPluginNames(): string[] {\n return Array.from(this.plugins.keys());\n }\n\n /**\n * Check if a plugin with the given name is registered.\n */\n hasPlugin(name: string): boolean {\n return this.plugins.has(name);\n }\n\n private applyRoute(route: BufferedRoute): void {\n if (!this.routeTarget) return;\n this.routeTarget.addExtension((app) => {\n const method = route.method.toLowerCase() as keyof express.Application;\n if (typeof app[method] === \"function\") {\n (app[method] as (...a: unknown[]) => void)(\n route.path,\n ...route.handlers,\n );\n }\n });\n }\n\n private applyMiddleware(\n path: string,\n handlers: express.RequestHandler[],\n ): void {\n if (!this.routeTarget) return;\n this.routeTarget.addExtension((app) => {\n app.use(path, ...handlers);\n });\n }\n}\n\n/**\n * Type guard: checks whether a plugin implements the ToolProvider interface\n * and exposes the user-scoped `asUser` helper that the {@link Plugin} base\n * class provides. Narrowing to {@link ToolProviderPlugin} lets `executeTool`\n * call `asUser` without an unsafe cast.\n */\nexport function isToolProvider(plugin: unknown): plugin is ToolProviderPlugin {\n return (\n typeof plugin === \"object\" &&\n plugin !== null &&\n \"getAgentTools\" in plugin &&\n typeof (plugin as ToolProvider).getAgentTools === \"function\" &&\n \"executeAgentTool\" in plugin &&\n typeof (plugin as ToolProvider).executeAgentTool === \"function\" &&\n \"asUser\" in plugin &&\n typeof (plugin as { asUser?: unknown }).asUser === \"function\"\n );\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,14 +4,23 @@ import { CacheConfig } from "./shared/src/cache.js";
|
|
|
4
4
|
import { StreamExecutionSettings } from "./shared/src/execute.js";
|
|
5
5
|
import { isSQLTypeMarker, sql } from "./shared/src/sql/helpers.js";
|
|
6
6
|
import "./shared/src/index.js";
|
|
7
|
+
import { ExecutionResult } from "./plugin/execution-result.js";
|
|
7
8
|
import { CacheManager } from "./cache/index.js";
|
|
9
|
+
import { ITelemetry, TelemetryConfig } from "./telemetry/types.js";
|
|
10
|
+
import { Counter, Histogram, SeverityNumber, Span, SpanStatusCode } from "./telemetry/index.js";
|
|
11
|
+
import { Plugin } from "./plugin/plugin.js";
|
|
12
|
+
import { toPlugin } from "./plugin/to-plugin.js";
|
|
13
|
+
import "./plugin/index.js";
|
|
14
|
+
import { ResourcePermission, ResourceType } from "./registry/types.generated.js";
|
|
15
|
+
import { ConfigSchema, PluginManifest, ResourceEntry, ResourceRequirement, ValidationResult } from "./registry/types.js";
|
|
16
|
+
import { getPluginManifest, getResourceRequirements } from "./registry/manifest-loader.js";
|
|
17
|
+
import { ResourceRegistry } from "./registry/resource-registry.js";
|
|
18
|
+
import "./registry/index.js";
|
|
8
19
|
import { JobsConnectorConfig } from "./connectors/jobs/types.js";
|
|
9
20
|
import "./connectors/jobs/index.js";
|
|
10
21
|
import { DatabaseCredential, GenerateDatabaseCredentialRequest, LakebasePoolConfig, RequestedClaims, RequestedClaimsPermissionSet, RequestedResource, createLakebasePool, generateDatabaseCredential, getLakebaseOrmConfig, getLakebasePgConfig, getUsernameWithApiLookup, getWorkspaceClient } from "./connectors/lakebase/index.js";
|
|
11
22
|
import { getExecutionContext } from "./context/execution-context.js";
|
|
12
23
|
import "./context/index.js";
|
|
13
|
-
import { ITelemetry, TelemetryConfig } from "./telemetry/types.js";
|
|
14
|
-
import { Counter, Histogram, SeverityNumber, Span, SpanStatusCode } from "./telemetry/index.js";
|
|
15
24
|
import { createApp } from "./core/appkit.js";
|
|
16
25
|
import "./core/index.js";
|
|
17
26
|
import { AppKitError } from "./errors/base.js";
|
|
@@ -23,16 +32,7 @@ import { InitializationError } from "./errors/initialization.js";
|
|
|
23
32
|
import { ServerError } from "./errors/server.js";
|
|
24
33
|
import { TunnelError } from "./errors/tunnel.js";
|
|
25
34
|
import { ValidationError } from "./errors/validation.js";
|
|
26
|
-
import { ExecutionResult } from "./plugin/execution-result.js";
|
|
27
|
-
import { Plugin } from "./plugin/plugin.js";
|
|
28
|
-
import { toPlugin } from "./plugin/to-plugin.js";
|
|
29
|
-
import "./plugin/index.js";
|
|
30
35
|
import { FileAction, FilePolicy, FilePolicyUser, FileResource, PolicyDeniedError, READ_ACTIONS, WRITE_ACTIONS } from "./plugins/files/policy.js";
|
|
31
|
-
import { ResourcePermission, ResourceType } from "./registry/types.generated.js";
|
|
32
|
-
import { ConfigSchema, PluginManifest, ResourceEntry, ResourceRequirement, ValidationResult } from "./registry/types.js";
|
|
33
|
-
import { getPluginManifest, getResourceRequirements } from "./registry/manifest-loader.js";
|
|
34
|
-
import { ResourceRegistry } from "./registry/resource-registry.js";
|
|
35
|
-
import "./registry/index.js";
|
|
36
36
|
import { analytics } from "./plugins/analytics/analytics.js";
|
|
37
37
|
import { files } from "./plugins/files/plugin.js";
|
|
38
38
|
import { genie } from "./plugins/genie/genie.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/internal-telemetry/appkit-log.ts
|
|
2
|
+
function wrapAppkitLog(log) {
|
|
3
|
+
return {
|
|
4
|
+
frontend_log_event_id: `appkit-${log.event_name.toLowerCase()}-${crypto.randomUUID()}`,
|
|
5
|
+
inferred_timestamp_millis: Date.now(),
|
|
6
|
+
entry: { appkit_log: log }
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function buildAppkitPayload(logs) {
|
|
10
|
+
return {
|
|
11
|
+
uploadTime: Date.now(),
|
|
12
|
+
items: [],
|
|
13
|
+
protoLogs: logs.map((log) => JSON.stringify(wrapAppkitLog(log)))
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { buildAppkitPayload };
|
|
19
|
+
//# sourceMappingURL=appkit-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appkit-log.js","names":[],"sources":["../../src/internal-telemetry/appkit-log.ts"],"sourcesContent":["// IMPORTANT: keep this file in sync with the AppkitLog proto schema served by\n// the Databricks client telemetry endpoint. Field names use proto JSON\n// conventions (snake_case) so the wire format matches the backend.\n\nexport type AppkitEventName =\n | \"APPKIT_EVENT_NAME_UNSPECIFIED\"\n | \"APP_STARTUP\"\n | \"HEARTBEAT\"\n | \"REQUEST_METRICS\";\n\nexport type AppStartupEvent = Record<string, never>;\n\nexport type HeartbeatEvent = Record<string, never>;\n\nexport interface RequestMetricsEvent {\n endpoint?: string;\n request_count?: number;\n request_latency_ms_avg?: number;\n response_count_http4xx?: number;\n response_count_http5xx?: number;\n}\n\nexport interface AppkitLog {\n event_name: AppkitEventName;\n app_id?: string;\n appkit_version?: string;\n app_startup_event?: AppStartupEvent;\n heartbeat_event?: HeartbeatEvent;\n request_metrics_event?: RequestMetricsEvent;\n}\n\ninterface AppkitLogEnvelope {\n frontend_log_event_id: string;\n inferred_timestamp_millis: number;\n entry: { appkit_log: AppkitLog };\n}\n\ninterface TelemetryPayload {\n uploadTime: number;\n items: never[];\n protoLogs: string[];\n}\n\nexport function wrapAppkitLog(log: AppkitLog): AppkitLogEnvelope {\n return {\n frontend_log_event_id: `appkit-${log.event_name.toLowerCase()}-${crypto.randomUUID()}`,\n inferred_timestamp_millis: Date.now(),\n entry: { appkit_log: log },\n };\n}\n\nexport function buildAppkitPayload(logs: AppkitLog[]): TelemetryPayload {\n return {\n uploadTime: Date.now(),\n items: [],\n protoLogs: logs.map((log) => JSON.stringify(wrapAppkitLog(log))),\n };\n}\n"],"mappings":";AA2CA,SAAgB,cAAc,KAAmC;AAC/D,QAAO;EACL,uBAAuB,UAAU,IAAI,WAAW,aAAa,CAAC,GAAG,OAAO,YAAY;EACpF,2BAA2B,KAAK,KAAK;EACrC,OAAO,EAAE,YAAY,KAAK;EAC3B;;AAGH,SAAgB,mBAAmB,MAAqC;AACtE,QAAO;EACL,YAAY,KAAK,KAAK;EACtB,OAAO,EAAE;EACT,WAAW,KAAK,KAAK,QAAQ,KAAK,UAAU,cAAc,IAAI,CAAC,CAAC;EACjE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/internal-telemetry/config.ts
|
|
2
|
+
/**
|
|
3
|
+
* Checks whether internal telemetry is enabled.
|
|
4
|
+
* Shared across all telemetry event types (startup, heartbeat, metrics, etc.).
|
|
5
|
+
*/
|
|
6
|
+
function isInternalTelemetryEnabled(opts) {
|
|
7
|
+
if (opts?.disableInternalTelemetry) return false;
|
|
8
|
+
if (process.env.DISABLE_APPKIT_INTERNAL_TELEMETRY === "true") return false;
|
|
9
|
+
if (process.env.DO_NOT_TRACK === "1") return false;
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { isInternalTelemetryEnabled };
|
|
15
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","names":[],"sources":["../../src/internal-telemetry/config.ts"],"sourcesContent":["/**\n * Checks whether internal telemetry is enabled.\n * Shared across all telemetry event types (startup, heartbeat, metrics, etc.).\n */\nexport function isInternalTelemetryEnabled(opts?: {\n disableInternalTelemetry?: boolean;\n}): boolean {\n if (opts?.disableInternalTelemetry) return false;\n if (process.env.DISABLE_APPKIT_INTERNAL_TELEMETRY === \"true\") return false;\n if (process.env.DO_NOT_TRACK === \"1\") return false;\n return true;\n}\n"],"mappings":";;;;;AAIA,SAAgB,2BAA2B,MAE/B;AACV,KAAI,MAAM,yBAA0B,QAAO;AAC3C,KAAI,QAAQ,IAAI,sCAAsC,OAAQ,QAAO;AACrE,KAAI,QAAQ,IAAI,iBAAiB,IAAK,QAAO;AAC7C,QAAO"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { buildAppkitPayload } from "./appkit-log.js";
|
|
2
|
+
|
|
3
|
+
//#region src/internal-telemetry/reporter.ts
|
|
4
|
+
const DEFAULT_HEARTBEAT_INTERVAL_MS = 300 * 1e3;
|
|
5
|
+
const DEFAULT_METRICS_FLUSH_INTERVAL_MS = 60 * 1e3;
|
|
6
|
+
function envIntervalMs(name, fallback) {
|
|
7
|
+
const raw = process.env[name];
|
|
8
|
+
if (!raw) return fallback;
|
|
9
|
+
const n = Number(raw);
|
|
10
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
11
|
+
}
|
|
12
|
+
var TelemetryReporter = class TelemetryReporter {
|
|
13
|
+
static #instance = null;
|
|
14
|
+
#workspaceIdPromise;
|
|
15
|
+
#client;
|
|
16
|
+
#appId;
|
|
17
|
+
#appkitVersion;
|
|
18
|
+
#heartbeatIntervalMs;
|
|
19
|
+
#metricsFlushIntervalMs;
|
|
20
|
+
#heartbeatTimer = null;
|
|
21
|
+
#metricsTimer = null;
|
|
22
|
+
#buckets = /* @__PURE__ */ new Map();
|
|
23
|
+
constructor(opts) {
|
|
24
|
+
this.#workspaceIdPromise = Promise.resolve(opts.workspaceId);
|
|
25
|
+
this.#workspaceIdPromise.catch(() => {});
|
|
26
|
+
this.#client = opts.client;
|
|
27
|
+
this.#appId = opts.appId;
|
|
28
|
+
this.#appkitVersion = opts.appkitVersion;
|
|
29
|
+
this.#heartbeatIntervalMs = opts.heartbeatIntervalMs ?? envIntervalMs("APPKIT_TELEMETRY_HEARTBEAT_INTERVAL_MS", DEFAULT_HEARTBEAT_INTERVAL_MS);
|
|
30
|
+
this.#metricsFlushIntervalMs = opts.metricsFlushIntervalMs ?? envIntervalMs("APPKIT_TELEMETRY_METRICS_FLUSH_INTERVAL_MS", DEFAULT_METRICS_FLUSH_INTERVAL_MS);
|
|
31
|
+
}
|
|
32
|
+
static initialize(opts) {
|
|
33
|
+
TelemetryReporter.#instance?.stop();
|
|
34
|
+
TelemetryReporter.#instance = new TelemetryReporter(opts);
|
|
35
|
+
return TelemetryReporter.#instance;
|
|
36
|
+
}
|
|
37
|
+
static getInstance() {
|
|
38
|
+
return TelemetryReporter.#instance;
|
|
39
|
+
}
|
|
40
|
+
/** @internal Test-only reset. */
|
|
41
|
+
static _reset() {
|
|
42
|
+
TelemetryReporter.#instance?.stop();
|
|
43
|
+
TelemetryReporter.#instance = null;
|
|
44
|
+
}
|
|
45
|
+
start() {
|
|
46
|
+
if (this.#heartbeatTimer || this.#metricsTimer) return;
|
|
47
|
+
this.#heartbeatTimer = setInterval(() => {
|
|
48
|
+
this.sendHeartbeat().catch(() => {});
|
|
49
|
+
}, this.#heartbeatIntervalMs);
|
|
50
|
+
this.#heartbeatTimer.unref?.();
|
|
51
|
+
this.#metricsTimer = setInterval(() => {
|
|
52
|
+
this.flushRequestMetrics().catch(() => {});
|
|
53
|
+
}, this.#metricsFlushIntervalMs);
|
|
54
|
+
this.#metricsTimer.unref?.();
|
|
55
|
+
}
|
|
56
|
+
stop() {
|
|
57
|
+
if (this.#heartbeatTimer) clearInterval(this.#heartbeatTimer);
|
|
58
|
+
if (this.#metricsTimer) clearInterval(this.#metricsTimer);
|
|
59
|
+
this.#heartbeatTimer = null;
|
|
60
|
+
this.#metricsTimer = null;
|
|
61
|
+
}
|
|
62
|
+
recordRequest(method, routeTemplate, statusCode, latencyMs) {
|
|
63
|
+
if (!routeTemplate) return;
|
|
64
|
+
const key = `${method.toUpperCase()} ${routeTemplate}`;
|
|
65
|
+
const bucket = this.#buckets.get(key) ?? {
|
|
66
|
+
count: 0,
|
|
67
|
+
latencyMsTotal: 0,
|
|
68
|
+
http4xx: 0,
|
|
69
|
+
http5xx: 0
|
|
70
|
+
};
|
|
71
|
+
bucket.count += 1;
|
|
72
|
+
bucket.latencyMsTotal += Math.max(0, latencyMs);
|
|
73
|
+
if (statusCode >= 400 && statusCode < 500) bucket.http4xx += 1;
|
|
74
|
+
if (statusCode >= 500 && statusCode < 600) bucket.http5xx += 1;
|
|
75
|
+
this.#buckets.set(key, bucket);
|
|
76
|
+
}
|
|
77
|
+
async sendStartup() {
|
|
78
|
+
await this.#send([this.#wrap({
|
|
79
|
+
event_name: "APP_STARTUP",
|
|
80
|
+
app_startup_event: {}
|
|
81
|
+
})]);
|
|
82
|
+
}
|
|
83
|
+
async sendHeartbeat() {
|
|
84
|
+
await this.#send([this.#wrap({
|
|
85
|
+
event_name: "HEARTBEAT",
|
|
86
|
+
heartbeat_event: {}
|
|
87
|
+
})]);
|
|
88
|
+
}
|
|
89
|
+
async flushRequestMetrics() {
|
|
90
|
+
if (this.#buckets.size === 0) return;
|
|
91
|
+
const drained = this.#buckets;
|
|
92
|
+
this.#buckets = /* @__PURE__ */ new Map();
|
|
93
|
+
const logs = [];
|
|
94
|
+
for (const [endpoint, bucket] of drained) {
|
|
95
|
+
const event = {
|
|
96
|
+
endpoint,
|
|
97
|
+
request_count: bucket.count,
|
|
98
|
+
request_latency_ms_avg: Math.round(bucket.latencyMsTotal / bucket.count),
|
|
99
|
+
response_count_http4xx: bucket.http4xx,
|
|
100
|
+
response_count_http5xx: bucket.http5xx
|
|
101
|
+
};
|
|
102
|
+
logs.push(this.#wrap({
|
|
103
|
+
event_name: "REQUEST_METRICS",
|
|
104
|
+
request_metrics_event: event
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
await this.#send(logs);
|
|
108
|
+
}
|
|
109
|
+
#wrap(partial) {
|
|
110
|
+
return {
|
|
111
|
+
...partial,
|
|
112
|
+
app_id: this.#appId,
|
|
113
|
+
appkit_version: this.#appkitVersion
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async #send(logs) {
|
|
117
|
+
if (logs.length === 0) return;
|
|
118
|
+
const workspaceId = await this.#workspaceIdPromise;
|
|
119
|
+
await this.#client.apiClient.request({
|
|
120
|
+
path: "/telemetry-ext",
|
|
121
|
+
method: "POST",
|
|
122
|
+
query: { o: workspaceId },
|
|
123
|
+
headers: new Headers(),
|
|
124
|
+
payload: buildAppkitPayload(logs),
|
|
125
|
+
raw: false
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
131
|
+
export { TelemetryReporter };
|
|
132
|
+
//# sourceMappingURL=reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.js","names":["#instance","#workspaceIdPromise","#client","#appId","#appkitVersion","#heartbeatIntervalMs","#metricsFlushIntervalMs","#heartbeatTimer","#metricsTimer","#buckets","#send","#wrap"],"sources":["../../src/internal-telemetry/reporter.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport {\n type AppkitLog,\n buildAppkitPayload,\n type RequestMetricsEvent,\n} from \"./appkit-log\";\n\nconst DEFAULT_HEARTBEAT_INTERVAL_MS = 5 * 60 * 1000;\nconst DEFAULT_METRICS_FLUSH_INTERVAL_MS = 60 * 1000;\n\ninterface ReporterOptions {\n workspaceId: Promise<string> | string;\n client: WorkspaceClient;\n appId: string;\n appkitVersion: string;\n heartbeatIntervalMs?: number;\n metricsFlushIntervalMs?: number;\n}\n\ninterface RequestBucket {\n count: number;\n latencyMsTotal: number;\n http4xx: number;\n http5xx: number;\n}\n\nfunction envIntervalMs(name: string, fallback: number): number {\n const raw = process.env[name];\n if (!raw) return fallback;\n const n = Number(raw);\n return Number.isFinite(n) && n > 0 ? n : fallback;\n}\n\nexport class TelemetryReporter {\n static #instance: TelemetryReporter | null = null;\n\n readonly #workspaceIdPromise: Promise<string>;\n readonly #client: WorkspaceClient;\n readonly #appId: string;\n readonly #appkitVersion: string;\n readonly #heartbeatIntervalMs: number;\n readonly #metricsFlushIntervalMs: number;\n\n #heartbeatTimer: NodeJS.Timeout | null = null;\n #metricsTimer: NodeJS.Timeout | null = null;\n #buckets: Map<string, RequestBucket> = new Map();\n\n private constructor(opts: ReporterOptions) {\n this.#workspaceIdPromise = Promise.resolve(opts.workspaceId);\n // Mark the rejection (if any) as handled so a misconfigured workspaceId\n // doesn't trigger an unhandled-rejection warning before the first #send\n // awaits it. The original promise still rejects when awaited.\n this.#workspaceIdPromise.catch(() => {});\n this.#client = opts.client;\n this.#appId = opts.appId;\n this.#appkitVersion = opts.appkitVersion;\n this.#heartbeatIntervalMs =\n opts.heartbeatIntervalMs ??\n envIntervalMs(\n \"APPKIT_TELEMETRY_HEARTBEAT_INTERVAL_MS\",\n DEFAULT_HEARTBEAT_INTERVAL_MS,\n );\n this.#metricsFlushIntervalMs =\n opts.metricsFlushIntervalMs ??\n envIntervalMs(\n \"APPKIT_TELEMETRY_METRICS_FLUSH_INTERVAL_MS\",\n DEFAULT_METRICS_FLUSH_INTERVAL_MS,\n );\n }\n\n static initialize(opts: ReporterOptions): TelemetryReporter {\n TelemetryReporter.#instance?.stop();\n TelemetryReporter.#instance = new TelemetryReporter(opts);\n return TelemetryReporter.#instance;\n }\n\n static getInstance(): TelemetryReporter | null {\n return TelemetryReporter.#instance;\n }\n\n /** @internal Test-only reset. */\n static _reset(): void {\n TelemetryReporter.#instance?.stop();\n TelemetryReporter.#instance = null;\n }\n\n start(): void {\n if (this.#heartbeatTimer || this.#metricsTimer) return;\n this.#heartbeatTimer = setInterval(() => {\n this.sendHeartbeat().catch(() => {});\n }, this.#heartbeatIntervalMs);\n this.#heartbeatTimer.unref?.();\n\n this.#metricsTimer = setInterval(() => {\n this.flushRequestMetrics().catch(() => {});\n }, this.#metricsFlushIntervalMs);\n this.#metricsTimer.unref?.();\n }\n\n stop(): void {\n if (this.#heartbeatTimer) clearInterval(this.#heartbeatTimer);\n if (this.#metricsTimer) clearInterval(this.#metricsTimer);\n this.#heartbeatTimer = null;\n this.#metricsTimer = null;\n }\n\n recordRequest(\n method: string,\n routeTemplate: string,\n statusCode: number,\n latencyMs: number,\n ): void {\n if (!routeTemplate) return;\n const key = `${method.toUpperCase()} ${routeTemplate}`;\n const bucket = this.#buckets.get(key) ?? {\n count: 0,\n latencyMsTotal: 0,\n http4xx: 0,\n http5xx: 0,\n };\n bucket.count += 1;\n bucket.latencyMsTotal += Math.max(0, latencyMs);\n if (statusCode >= 400 && statusCode < 500) bucket.http4xx += 1;\n if (statusCode >= 500 && statusCode < 600) bucket.http5xx += 1;\n this.#buckets.set(key, bucket);\n }\n\n async sendStartup(): Promise<void> {\n await this.#send([\n this.#wrap({ event_name: \"APP_STARTUP\", app_startup_event: {} }),\n ]);\n }\n\n async sendHeartbeat(): Promise<void> {\n await this.#send([\n this.#wrap({ event_name: \"HEARTBEAT\", heartbeat_event: {} }),\n ]);\n }\n\n async flushRequestMetrics(): Promise<void> {\n if (this.#buckets.size === 0) return;\n const drained = this.#buckets;\n this.#buckets = new Map();\n\n const logs: AppkitLog[] = [];\n for (const [endpoint, bucket] of drained) {\n const event: RequestMetricsEvent = {\n endpoint,\n request_count: bucket.count,\n request_latency_ms_avg: Math.round(\n bucket.latencyMsTotal / bucket.count,\n ),\n response_count_http4xx: bucket.http4xx,\n response_count_http5xx: bucket.http5xx,\n };\n logs.push(\n this.#wrap({\n event_name: \"REQUEST_METRICS\",\n request_metrics_event: event,\n }),\n );\n }\n await this.#send(logs);\n }\n\n #wrap(partial: AppkitLog): AppkitLog {\n return {\n ...partial,\n app_id: this.#appId,\n appkit_version: this.#appkitVersion,\n };\n }\n\n async #send(logs: AppkitLog[]): Promise<void> {\n if (logs.length === 0) return;\n const workspaceId = await this.#workspaceIdPromise;\n await this.#client.apiClient.request({\n path: \"/telemetry-ext\",\n method: \"POST\",\n query: { o: workspaceId },\n headers: new Headers(),\n payload: buildAppkitPayload(logs),\n raw: false,\n });\n }\n}\n"],"mappings":"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"}
|