@databricks/appkit 0.31.0 → 0.32.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 +1 -0
- package/NOTICE.md +1 -0
- package/dist/appkit/package.js +1 -1
- package/dist/beta.d.ts +14 -1
- package/dist/beta.js +12 -1
- package/dist/connectors/index.js +3 -0
- package/dist/connectors/mcp/client.d.ts +60 -0
- package/dist/connectors/mcp/client.d.ts.map +1 -0
- package/dist/connectors/mcp/client.js +197 -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 +50 -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 +67 -0
- package/dist/core/agent/load-agents.d.ts.map +1 -0
- package/dist/core/agent/load-agents.js +228 -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/run-agent.d.ts +34 -0
- package/dist/core/agent/run-agent.d.ts.map +1 -0
- package/dist/core/agent/run-agent.js +146 -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/tools/define-tool.d.ts +54 -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 +27 -0
- package/dist/core/agent/tools/function-tool.d.ts.map +1 -0
- package/dist/core/agent/tools/function-tool.js +21 -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 +34 -0
- package/dist/core/agent/tools/tool.d.ts.map +1 -0
- package/dist/core/agent/tools/tool.js +41 -0
- package/dist/core/agent/tools/tool.js.map +1 -0
- package/dist/core/agent/types.d.ts +214 -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/index.d.ts +1 -1
- 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 +15 -4
- package/dist/plugin/to-plugin.d.ts.map +1 -1
- package/dist/plugin/to-plugin.js +14 -4
- package/dist/plugin/to-plugin.js.map +1 -1
- package/dist/plugins/agents/agents.d.ts +4 -0
- package/dist/plugins/agents/agents.js +882 -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 +27 -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 +17 -2
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js +33 -0
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/files/plugin.d.ts +22 -3
- package/dist/plugins/files/plugin.d.ts.map +1 -1
- package/dist/plugins/files/plugin.js +102 -2
- package/dist/plugins/files/plugin.js.map +1 -1
- package/dist/plugins/genie/genie.d.ts +15 -2
- package/dist/plugins/genie/genie.d.ts.map +1 -1
- package/dist/plugins/genie/genie.js +45 -0
- package/dist/plugins/genie/genie.js.map +1 -1
- package/dist/plugins/jobs/plugin.d.ts +2 -1
- package/dist/plugins/jobs/plugin.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 +33 -4
- 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 +38 -1
- package/dist/plugins/lakebase/types.d.ts.map +1 -1
- package/dist/plugins/server/index.d.ts +12 -1
- package/dist/plugins/server/index.d.ts.map +1 -1
- package/dist/plugins/server/index.js +39 -5
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/types.d.ts +0 -3
- package/dist/plugins/server/types.d.ts.map +1 -1
- package/dist/plugins/serving/serving.d.ts +2 -1
- package/dist/plugins/serving/serving.d.ts.map +1 -1
- package/dist/shared/src/agent.d.ts +63 -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.Plugin.md +65 -23
- package/docs/api/appkit/Function.createApp.md +10 -8
- package/docs/privacy.md +41 -0
- package/llms.txt +1 -0
- package/package.json +4 -2
- package/sbom.cdx.json +1 -1
package/dist/core/appkit.js
CHANGED
|
@@ -2,11 +2,16 @@ import { createLogger } from "../logging/logger.js";
|
|
|
2
2
|
import { TelemetryManager } from "../telemetry/telemetry-manager.js";
|
|
3
3
|
import "../telemetry/index.js";
|
|
4
4
|
import { CacheManager } from "../cache/index.js";
|
|
5
|
+
import { version } from "../appkit/package.js";
|
|
5
6
|
import { ServiceContext } from "../context/service-context.js";
|
|
6
7
|
import { init_context } from "../context/index.js";
|
|
8
|
+
import { isInternalTelemetryEnabled } from "../internal-telemetry/config.js";
|
|
9
|
+
import { TelemetryReporter } from "../internal-telemetry/reporter.js";
|
|
10
|
+
import "../internal-telemetry/index.js";
|
|
7
11
|
import { ResourceType } from "../registry/types.generated.js";
|
|
8
12
|
import { ResourceRegistry } from "../registry/resource-registry.js";
|
|
9
13
|
import "../registry/index.js";
|
|
14
|
+
import { PluginContext, isToolProvider } from "./plugin-context.js";
|
|
10
15
|
|
|
11
16
|
//#region src/core/appkit.ts
|
|
12
17
|
init_context();
|
|
@@ -14,28 +19,37 @@ const logger = createLogger("appkit");
|
|
|
14
19
|
var AppKit = class AppKit {
|
|
15
20
|
#pluginInstances = {};
|
|
16
21
|
#setupPromises = [];
|
|
22
|
+
#context;
|
|
17
23
|
constructor(config) {
|
|
18
24
|
const { plugins, ...globalConfig } = config;
|
|
25
|
+
this.#context = new PluginContext();
|
|
19
26
|
const pluginEntries = Object.entries(plugins);
|
|
20
27
|
const corePlugins = pluginEntries.filter(([_, p]) => {
|
|
21
28
|
return (p?.plugin?.phase ?? "normal") === "core";
|
|
22
29
|
});
|
|
23
30
|
const normalPlugins = pluginEntries.filter(([_, p]) => (p?.plugin?.phase ?? "normal") === "normal");
|
|
24
31
|
const deferredPlugins = pluginEntries.filter(([_, p]) => (p?.plugin?.phase ?? "normal") === "deferred");
|
|
25
|
-
for (const [name, pluginData] of corePlugins) if (pluginData) this.createAndRegisterPlugin(globalConfig, name, pluginData);
|
|
26
|
-
for (const [name, pluginData] of normalPlugins) if (pluginData) this.createAndRegisterPlugin(globalConfig, name, pluginData);
|
|
27
|
-
for (const [name, pluginData] of deferredPlugins) if (pluginData) this.createAndRegisterPlugin(globalConfig, name, pluginData, {
|
|
32
|
+
for (const [name, pluginData] of corePlugins) if (pluginData) this.createAndRegisterPlugin(globalConfig, name, pluginData, { context: this.#context });
|
|
33
|
+
for (const [name, pluginData] of normalPlugins) if (pluginData) this.createAndRegisterPlugin(globalConfig, name, pluginData, { context: this.#context });
|
|
34
|
+
for (const [name, pluginData] of deferredPlugins) if (pluginData) this.createAndRegisterPlugin(globalConfig, name, pluginData, { context: this.#context });
|
|
28
35
|
}
|
|
29
36
|
createAndRegisterPlugin(config, name, pluginData, extraData) {
|
|
30
37
|
const { plugin: Plugin, config: pluginConfig } = pluginData;
|
|
31
|
-
const
|
|
38
|
+
const baseConfig = {
|
|
32
39
|
...config,
|
|
33
40
|
...Plugin.DEFAULT_CONFIG,
|
|
34
41
|
...pluginConfig,
|
|
35
42
|
name,
|
|
36
43
|
...extraData
|
|
44
|
+
};
|
|
45
|
+
const pluginInstance = new Plugin(baseConfig);
|
|
46
|
+
if (typeof pluginInstance.attachContext === "function") pluginInstance.attachContext({
|
|
47
|
+
context: this.#context,
|
|
48
|
+
telemetryConfig: baseConfig.telemetry
|
|
37
49
|
});
|
|
38
50
|
this.#pluginInstances[name] = pluginInstance;
|
|
51
|
+
this.#context.registerPlugin(name, pluginInstance);
|
|
52
|
+
if (isToolProvider(pluginInstance)) this.#context.registerToolProvider(name, pluginInstance);
|
|
39
53
|
this.#setupPromises.push(pluginInstance.setup());
|
|
40
54
|
const self = this;
|
|
41
55
|
Object.defineProperty(this, name, {
|
|
@@ -101,16 +115,29 @@ var AppKit = class AppKit {
|
|
|
101
115
|
registry.enforceValidation();
|
|
102
116
|
const instance = new AppKit({ plugins: AppKit.preparePlugins(rawPlugins) });
|
|
103
117
|
await Promise.all(instance.#setupPromises);
|
|
118
|
+
await instance.#context.emitLifecycle("setup:complete");
|
|
104
119
|
const handle = instance;
|
|
105
120
|
if (config.onPluginsReady) {
|
|
106
121
|
logger.debug("Running onPluginsReady hook");
|
|
107
122
|
await config.onPluginsReady(handle);
|
|
108
123
|
logger.debug("onPluginsReady hook completed");
|
|
109
124
|
}
|
|
125
|
+
if (isInternalTelemetryEnabled(config)) AppKit.bootstrapInternalTelemetry();
|
|
110
126
|
const serverPlugin = instance.#pluginInstances.server;
|
|
111
127
|
if (serverPlugin && typeof serverPlugin.start === "function") await serverPlugin.start();
|
|
112
128
|
return handle;
|
|
113
129
|
}
|
|
130
|
+
static bootstrapInternalTelemetry() {
|
|
131
|
+
const serviceCtx = ServiceContext.get();
|
|
132
|
+
const reporter = TelemetryReporter.initialize({
|
|
133
|
+
workspaceId: serviceCtx.workspaceId,
|
|
134
|
+
client: serviceCtx.client,
|
|
135
|
+
appId: process.env.DATABRICKS_CLIENT_ID || "",
|
|
136
|
+
appkitVersion: version
|
|
137
|
+
});
|
|
138
|
+
reporter.start();
|
|
139
|
+
reporter.sendStartup().catch(() => {});
|
|
140
|
+
}
|
|
114
141
|
static preparePlugins(plugins) {
|
|
115
142
|
const result = {};
|
|
116
143
|
for (const currentPlugin of plugins) result[currentPlugin.name] = {
|
package/dist/core/appkit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appkit.js","names":["#pluginInstances","#setupPromises"],"sources":["../../src/core/appkit.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type {\n BasePlugin,\n CacheConfig,\n InputPluginMap,\n OptionalConfigPluginDef,\n PluginConstructor,\n PluginData,\n PluginMap,\n} from \"shared\";\nimport { CacheManager } from \"../cache\";\nimport { ServiceContext } from \"../context\";\nimport { createLogger } from \"../logging/logger\";\nimport { ResourceRegistry, ResourceType } from \"../registry\";\nimport type { TelemetryConfig } from \"../telemetry\";\nimport { TelemetryManager } from \"../telemetry\";\n\nconst logger = createLogger(\"appkit\");\n\nexport class AppKit<TPlugins extends InputPluginMap> {\n #pluginInstances: Record<string, BasePlugin> = {};\n #setupPromises: Promise<void>[] = [];\n\n private constructor(config: { plugins: TPlugins }) {\n const { plugins, ...globalConfig } = config;\n\n const pluginEntries = Object.entries(plugins);\n\n const corePlugins = pluginEntries.filter(([_, p]) => {\n return (p?.plugin?.phase ?? \"normal\") === \"core\";\n });\n const normalPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"normal\",\n );\n const deferredPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"deferred\",\n );\n\n for (const [name, pluginData] of corePlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData);\n }\n }\n\n for (const [name, pluginData] of normalPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData);\n }\n }\n\n for (const [name, pluginData] of deferredPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n plugins: this.#pluginInstances,\n });\n }\n }\n }\n\n private createAndRegisterPlugin<T extends PluginConstructor>(\n config: Omit<{ plugins: TPlugins }, \"plugins\">,\n name: string,\n pluginData: OptionalConfigPluginDef<T>,\n extraData?: Record<string, unknown>,\n ) {\n const { plugin: Plugin, config: pluginConfig } = pluginData;\n const baseConfig = {\n ...config,\n ...Plugin.DEFAULT_CONFIG,\n ...pluginConfig,\n name,\n ...extraData,\n };\n const pluginInstance = new Plugin(baseConfig);\n\n this.#pluginInstances[name] = pluginInstance;\n\n this.#setupPromises.push(pluginInstance.setup());\n\n const self = this;\n\n Object.defineProperty(this, name, {\n get() {\n const plugin = self.#pluginInstances[name];\n return self.wrapWithAsUser(plugin);\n },\n enumerable: true,\n });\n }\n\n /**\n * Binds all function properties in an exports object to the given context.\n * Recurses into plain objects to handle nested APIs (e.g., volume APIs).\n */\n private bindExportMethods(\n exports: Record<string, unknown>,\n context: BasePlugin,\n ) {\n for (const key in exports) {\n if (!Object.hasOwn(exports, key)) continue;\n const val = exports[key];\n if (typeof val === \"function\") {\n exports[key] = (val as (...args: unknown[]) => unknown).bind(context);\n } else if (AppKit.isPlainObject(val)) {\n this.bindExportMethods(val as Record<string, unknown>, context);\n }\n }\n }\n\n /**\n * Wraps a plugin's exports with an `asUser` method that returns\n * a user-scoped version of the exports.\n *\n * When `exports()` returns a callable (function), it is returned as-is\n * since the plugin manages its own `asUser` per-call (e.g. files plugin).\n * When it returns a plain object, the standard `asUser` wrapper is added.\n */\n private wrapWithAsUser<T extends BasePlugin>(plugin: T) {\n // If plugin doesn't implement exports(), return empty object\n const pluginExports = plugin.exports?.() ?? {};\n\n // If exports is a function, the plugin manages its own asUser pattern\n if (typeof pluginExports === \"function\") {\n return pluginExports;\n }\n\n const objExports = pluginExports as Record<string, unknown>;\n this.bindExportMethods(objExports, plugin);\n\n // If plugin doesn't support asUser (no asUser method), return exports as-is\n if (typeof (plugin as any).asUser !== \"function\") {\n return objExports;\n }\n\n return {\n ...objExports,\n /**\n * Execute operations using the user's identity from the request.\n * Returns user-scoped exports where all methods execute with the\n * user's Databricks credentials instead of the service principal.\n */\n asUser: (req: import(\"express\").Request) => {\n const userPlugin = (plugin as any).asUser(req);\n const userExports = (userPlugin.exports?.() ?? {}) as Record<\n string,\n unknown\n >;\n this.bindExportMethods(userExports, userPlugin);\n return userExports;\n },\n };\n }\n\n /**\n * Returns true if the value is a plain object (not an array, Date, etc.).\n */\n private static isPlainObject(\n value: unknown,\n ): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n }\n\n static async _createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n } = {},\n ): Promise<PluginMap<T>> {\n // Initialize core services\n TelemetryManager.initialize(config?.telemetry);\n await CacheManager.getInstance(config?.cache);\n\n const rawPlugins = config.plugins as T;\n\n // Collect manifest resources via registry\n const registry = new ResourceRegistry();\n registry.collectResources(rawPlugins);\n\n // Derive ServiceContext needs from what manifests declared\n const needsWarehouse = registry\n .getRequired()\n .some((r) => r.type === ResourceType.SQL_WAREHOUSE);\n await ServiceContext.initialize(\n { warehouseId: needsWarehouse },\n config?.client,\n );\n\n // Validate env vars\n registry.enforceValidation();\n\n const preparedPlugins = AppKit.preparePlugins(rawPlugins);\n const mergedConfig = {\n plugins: preparedPlugins,\n };\n\n const instance = new AppKit(mergedConfig);\n\n await Promise.all(instance.#setupPromises);\n\n const handle = instance as unknown as PluginMap<T>;\n\n if (config.onPluginsReady) {\n logger.debug(\"Running onPluginsReady hook\");\n await config.onPluginsReady(handle);\n logger.debug(\"onPluginsReady hook completed\");\n }\n\n const serverPlugin = instance.#pluginInstances.server;\n if (serverPlugin && typeof (serverPlugin as any).start === \"function\") {\n await (serverPlugin as any).start();\n }\n\n return handle;\n }\n\n private static preparePlugins(\n plugins: PluginData<PluginConstructor, unknown, string>[],\n ) {\n const result: InputPluginMap = {};\n for (const currentPlugin of plugins) {\n result[currentPlugin.name] = {\n plugin: currentPlugin.plugin,\n config: currentPlugin.config as Record<string, unknown>,\n };\n }\n return result;\n }\n}\n\n/**\n * Bootstraps AppKit with the provided configuration.\n *\n * Initializes telemetry, cache, and service context, then registers plugins\n * in phase order (core, normal, deferred) and awaits their setup.\n * If a `onPluginsReady` callback is provided it runs after plugin setup but\n * before the server starts, giving you access to the full appkit handle\n * for registering custom routes or performing async setup.\n * The returned object maps each plugin name to its `exports()` API,\n * with an `asUser(req)` method for user-scoped execution.\n *\n * @returns A `PluginMap` keyed by plugin name with typed exports\n *\n * @example Minimal server\n * ```ts\n * import { createApp, server } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server()],\n * });\n * ```\n *\n * @example Server with custom routes via onPluginsReady\n * ```ts\n * import { createApp, server, analytics } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server(), analytics({})],\n * onPluginsReady(appkit) {\n * appkit.server.extend((app) => {\n * app.get(\"/custom\", (_req, res) => res.json({ ok: true }));\n * });\n * },\n * });\n * ```\n */\nexport async function createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n>(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n } = {},\n): Promise<PluginMap<T>> {\n return AppKit._createApp(config);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"appkit.js","names":["#context","#pluginInstances","#setupPromises","productVersion"],"sources":["../../src/core/appkit.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type {\n BasePlugin,\n CacheConfig,\n InputPluginMap,\n OptionalConfigPluginDef,\n PluginConstructor,\n PluginData,\n PluginMap,\n} from \"shared\";\nimport { version as productVersion } from \"../../package.json\";\nimport { CacheManager } from \"../cache\";\nimport { ServiceContext } from \"../context\";\nimport {\n isInternalTelemetryEnabled,\n TelemetryReporter,\n} from \"../internal-telemetry\";\nimport { createLogger } from \"../logging/logger\";\nimport { ResourceRegistry, ResourceType } from \"../registry\";\nimport type { TelemetryConfig } from \"../telemetry\";\nimport { TelemetryManager } from \"../telemetry\";\nimport { isToolProvider, PluginContext } from \"./plugin-context\";\n\nconst logger = createLogger(\"appkit\");\n\nexport class AppKit<TPlugins extends InputPluginMap> {\n #pluginInstances: Record<string, BasePlugin> = {};\n #setupPromises: Promise<void>[] = [];\n #context: PluginContext;\n\n private constructor(config: { plugins: TPlugins }) {\n const { plugins, ...globalConfig } = config;\n\n this.#context = new PluginContext();\n\n const pluginEntries = Object.entries(plugins);\n\n const corePlugins = pluginEntries.filter(([_, p]) => {\n return (p?.plugin?.phase ?? \"normal\") === \"core\";\n });\n const normalPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"normal\",\n );\n const deferredPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"deferred\",\n );\n\n for (const [name, pluginData] of corePlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of normalPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of deferredPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n }\n\n private createAndRegisterPlugin<T extends PluginConstructor>(\n config: Omit<{ plugins: TPlugins }, \"plugins\">,\n name: string,\n pluginData: OptionalConfigPluginDef<T>,\n extraData?: Record<string, unknown>,\n ) {\n const { plugin: Plugin, config: pluginConfig } = pluginData;\n const baseConfig = {\n ...config,\n ...Plugin.DEFAULT_CONFIG,\n ...pluginConfig,\n name,\n ...extraData,\n };\n const pluginInstance = new Plugin(baseConfig);\n\n if (typeof pluginInstance.attachContext === \"function\") {\n pluginInstance.attachContext({\n context: this.#context,\n telemetryConfig: baseConfig.telemetry,\n });\n }\n\n this.#pluginInstances[name] = pluginInstance;\n\n this.#context.registerPlugin(name, pluginInstance);\n if (isToolProvider(pluginInstance)) {\n this.#context.registerToolProvider(name, pluginInstance);\n }\n\n this.#setupPromises.push(pluginInstance.setup());\n\n const self = this;\n\n Object.defineProperty(this, name, {\n get() {\n const plugin = self.#pluginInstances[name];\n return self.wrapWithAsUser(plugin);\n },\n enumerable: true,\n });\n }\n\n /**\n * Binds all function properties in an exports object to the given context.\n * Recurses into plain objects to handle nested APIs (e.g., volume APIs).\n */\n private bindExportMethods(\n exports: Record<string, unknown>,\n context: BasePlugin,\n ) {\n for (const key in exports) {\n if (!Object.hasOwn(exports, key)) continue;\n const val = exports[key];\n if (typeof val === \"function\") {\n exports[key] = (val as (...args: unknown[]) => unknown).bind(context);\n } else if (AppKit.isPlainObject(val)) {\n this.bindExportMethods(val as Record<string, unknown>, context);\n }\n }\n }\n\n /**\n * Wraps a plugin's exports with an `asUser` method that returns\n * a user-scoped version of the exports.\n *\n * When `exports()` returns a callable (function), it is returned as-is\n * since the plugin manages its own `asUser` per-call (e.g. files plugin).\n * When it returns a plain object, the standard `asUser` wrapper is added.\n */\n private wrapWithAsUser<T extends BasePlugin>(plugin: T) {\n // If plugin doesn't implement exports(), return empty object\n const pluginExports = plugin.exports?.() ?? {};\n\n // If exports is a function, the plugin manages its own asUser pattern\n if (typeof pluginExports === \"function\") {\n return pluginExports;\n }\n\n const objExports = pluginExports as Record<string, unknown>;\n this.bindExportMethods(objExports, plugin);\n\n // If plugin doesn't support asUser (no asUser method), return exports as-is\n if (typeof (plugin as any).asUser !== \"function\") {\n return objExports;\n }\n\n return {\n ...objExports,\n /**\n * Execute operations using the user's identity from the request.\n * Returns user-scoped exports where all methods execute with the\n * user's Databricks credentials instead of the service principal.\n */\n asUser: (req: import(\"express\").Request) => {\n const userPlugin = (plugin as any).asUser(req);\n const userExports = (userPlugin.exports?.() ?? {}) as Record<\n string,\n unknown\n >;\n this.bindExportMethods(userExports, userPlugin);\n return userExports;\n },\n };\n }\n\n /**\n * Returns true if the value is a plain object (not an array, Date, etc.).\n */\n private static isPlainObject(\n value: unknown,\n ): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n }\n\n static async _createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n ): Promise<PluginMap<T>> {\n // Initialize core services\n TelemetryManager.initialize(config?.telemetry);\n await CacheManager.getInstance(config?.cache);\n\n const rawPlugins = config.plugins as T;\n\n // Collect manifest resources via registry\n const registry = new ResourceRegistry();\n registry.collectResources(rawPlugins);\n\n // Derive ServiceContext needs from what manifests declared\n const needsWarehouse = registry\n .getRequired()\n .some((r) => r.type === ResourceType.SQL_WAREHOUSE);\n await ServiceContext.initialize(\n { warehouseId: needsWarehouse },\n config?.client,\n );\n\n // Validate env vars\n registry.enforceValidation();\n\n const preparedPlugins = AppKit.preparePlugins(rawPlugins);\n const mergedConfig = {\n plugins: preparedPlugins,\n };\n\n const instance = new AppKit(mergedConfig);\n\n await Promise.all(instance.#setupPromises);\n await instance.#context.emitLifecycle(\"setup:complete\");\n\n const handle = instance as unknown as PluginMap<T>;\n\n if (config.onPluginsReady) {\n logger.debug(\"Running onPluginsReady hook\");\n await config.onPluginsReady(handle);\n logger.debug(\"onPluginsReady hook completed\");\n }\n\n if (isInternalTelemetryEnabled(config)) {\n AppKit.bootstrapInternalTelemetry();\n }\n\n const serverPlugin = instance.#pluginInstances.server;\n if (serverPlugin && typeof (serverPlugin as any).start === \"function\") {\n await (serverPlugin as any).start();\n }\n\n return handle;\n }\n\n private static bootstrapInternalTelemetry(): void {\n const serviceCtx = ServiceContext.get();\n const reporter = TelemetryReporter.initialize({\n workspaceId: serviceCtx.workspaceId,\n client: serviceCtx.client,\n appId: process.env.DATABRICKS_CLIENT_ID || \"\",\n appkitVersion: productVersion,\n });\n reporter.start();\n reporter.sendStartup().catch(() => {});\n }\n\n private static preparePlugins(\n plugins: PluginData<PluginConstructor, unknown, string>[],\n ) {\n const result: InputPluginMap = {};\n for (const currentPlugin of plugins) {\n result[currentPlugin.name] = {\n plugin: currentPlugin.plugin,\n config: currentPlugin.config as Record<string, unknown>,\n };\n }\n return result;\n }\n}\n\n/**\n * Bootstraps AppKit with the provided configuration.\n *\n * Initializes telemetry, cache, and service context, then registers plugins\n * in phase order (core, normal, deferred) and awaits their setup.\n * If a `onPluginsReady` callback is provided it runs after plugin setup but\n * before the server starts, giving you access to the full appkit handle\n * for registering custom routes or performing async setup.\n * The returned object maps each plugin name to its `exports()` API,\n * with an `asUser(req)` method for user-scoped execution.\n *\n * @returns A `PluginMap` keyed by plugin name with typed exports\n *\n * @example Minimal server\n * ```ts\n * import { createApp, server } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server()],\n * });\n * ```\n *\n * @example Server with custom routes via onPluginsReady\n * ```ts\n * import { createApp, server, analytics } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server(), analytics({})],\n * onPluginsReady(appkit) {\n * appkit.server.extend((app) => {\n * app.get(\"/custom\", (_req, res) => res.json({ ok: true }));\n * });\n * },\n * });\n * ```\n */\nexport async function createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n>(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n): Promise<PluginMap<T>> {\n return AppKit._createApp(config);\n}\n"],"mappings":"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"}
|
|
@@ -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"}
|