@databricks/appkit 0.83.0 → 0.84.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.
Files changed (210) hide show
  1. package/CLAUDE.md +15 -1
  2. package/dist/agents/databricks.d.ts +15 -3
  3. package/dist/agents/databricks.d.ts.map +1 -1
  4. package/dist/agents/databricks.js +25 -8
  5. package/dist/agents/databricks.js.map +1 -1
  6. package/dist/appkit/package.js +1 -1
  7. package/dist/beta.d.ts +2 -2
  8. package/dist/cache/index.d.ts.map +1 -1
  9. package/dist/cache/index.js +6 -1
  10. package/dist/cache/index.js.map +1 -1
  11. package/dist/cli/commands/agent/eval.js +1 -1
  12. package/dist/cli/commands/generate-types.js +1 -1
  13. package/dist/cli/commands/plugin/sync/sync.js +28 -15
  14. package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
  15. package/dist/cli/commands/registry/add.js +3 -11
  16. package/dist/cli/commands/registry/add.js.map +1 -1
  17. package/dist/cli/commands/registry/config-writer.js +1 -1
  18. package/dist/connectors/lakebase/routing-pool.d.ts.map +1 -1
  19. package/dist/connectors/lakebase/routing-pool.js +4 -10
  20. package/dist/connectors/lakebase/routing-pool.js.map +1 -1
  21. package/dist/context/caller-context.d.ts +4 -1
  22. package/dist/context/caller-context.d.ts.map +1 -1
  23. package/dist/context/caller-context.js.map +1 -1
  24. package/dist/context/execution-context.d.ts +29 -1
  25. package/dist/context/execution-context.d.ts.map +1 -1
  26. package/dist/context/execution-context.js +60 -8
  27. package/dist/context/execution-context.js.map +1 -1
  28. package/dist/context/index.d.ts +2 -2
  29. package/dist/context/index.js +2 -1
  30. package/dist/context/request-scope.d.ts +2 -0
  31. package/dist/context/request-scope.js +39 -0
  32. package/dist/context/request-scope.js.map +1 -0
  33. package/dist/context/resource-capabilities.js +59 -0
  34. package/dist/context/resource-capabilities.js.map +1 -0
  35. package/dist/context/scoped-api.js +104 -0
  36. package/dist/context/scoped-api.js.map +1 -0
  37. package/dist/context/service-context.d.ts.map +1 -1
  38. package/dist/context/service-context.js +1 -1
  39. package/dist/context/service-context.js.map +1 -1
  40. package/dist/context/user-context.d.ts +16 -19
  41. package/dist/context/user-context.d.ts.map +1 -1
  42. package/dist/context/user-context.js +30 -3
  43. package/dist/context/user-context.js.map +1 -1
  44. package/dist/core/agent/load-agents.d.ts.map +1 -1
  45. package/dist/core/agent/load-agents.js +5 -2
  46. package/dist/core/agent/load-agents.js.map +1 -1
  47. package/dist/core/agent/run-agent.d.ts +19 -6
  48. package/dist/core/agent/run-agent.d.ts.map +1 -1
  49. package/dist/core/agent/run-agent.js +53 -17
  50. package/dist/core/agent/run-agent.js.map +1 -1
  51. package/dist/core/agent/types.d.ts +18 -1
  52. package/dist/core/agent/types.d.ts.map +1 -1
  53. package/dist/core/agent/types.js.map +1 -1
  54. package/dist/core/appkit.d.ts +4 -3
  55. package/dist/core/appkit.d.ts.map +1 -1
  56. package/dist/core/appkit.js +30 -9
  57. package/dist/core/appkit.js.map +1 -1
  58. package/dist/core/plugin-context.d.ts +14 -15
  59. package/dist/core/plugin-context.d.ts.map +1 -1
  60. package/dist/core/plugin-context.js +24 -14
  61. package/dist/core/plugin-context.js.map +1 -1
  62. package/dist/errors/base.d.ts +2 -2
  63. package/dist/errors/base.js +2 -2
  64. package/dist/errors/base.js.map +1 -1
  65. package/dist/errors/identity-expired.d.ts +14 -0
  66. package/dist/errors/identity-expired.d.ts.map +1 -0
  67. package/dist/errors/identity-expired.js +33 -0
  68. package/dist/errors/identity-expired.js.map +1 -0
  69. package/dist/errors/index.js +1 -0
  70. package/dist/index.d.ts +8 -5
  71. package/dist/index.js +5 -2
  72. package/dist/logging/logger.js +1 -1
  73. package/dist/plugin/execution-result.d.ts +4 -1
  74. package/dist/plugin/execution-result.d.ts.map +1 -1
  75. package/dist/plugin/interceptors/telemetry.js +6 -4
  76. package/dist/plugin/interceptors/telemetry.js.map +1 -1
  77. package/dist/plugin/plugin.d.ts +13 -21
  78. package/dist/plugin/plugin.d.ts.map +1 -1
  79. package/dist/plugin/plugin.js +41 -122
  80. package/dist/plugin/plugin.js.map +1 -1
  81. package/dist/plugins/agents/agents.d.ts +12 -0
  82. package/dist/plugins/agents/agents.d.ts.map +1 -1
  83. package/dist/plugins/agents/agents.js +65 -15
  84. package/dist/plugins/agents/agents.js.map +1 -1
  85. package/dist/plugins/agents/auth-mode.js +42 -0
  86. package/dist/plugins/agents/auth-mode.js.map +1 -0
  87. package/dist/plugins/agents/index.d.ts +1 -1
  88. package/dist/plugins/agents/mlflow.js +11 -3
  89. package/dist/plugins/agents/mlflow.js.map +1 -1
  90. package/dist/plugins/agents/tool-dispatch.js +9 -1
  91. package/dist/plugins/agents/tool-dispatch.js.map +1 -1
  92. package/dist/plugins/ai-search/ai-search.d.ts.map +1 -1
  93. package/dist/plugins/ai-search/ai-search.js +4 -4
  94. package/dist/plugins/ai-search/ai-search.js.map +1 -1
  95. package/dist/plugins/analytics/analytics.d.ts +2 -2
  96. package/dist/plugins/analytics/analytics.js +6 -6
  97. package/dist/plugins/analytics/analytics.js.map +1 -1
  98. package/dist/plugins/database/crud/contract.js +2 -2
  99. package/dist/plugins/database/crud/contract.js.map +1 -1
  100. package/dist/plugins/files/plugin.d.ts +10 -4
  101. package/dist/plugins/files/plugin.d.ts.map +1 -1
  102. package/dist/plugins/files/plugin.js +34 -13
  103. package/dist/plugins/files/plugin.js.map +1 -1
  104. package/dist/plugins/genie/genie.d.ts +9 -0
  105. package/dist/plugins/genie/genie.d.ts.map +1 -1
  106. package/dist/plugins/genie/genie.js +12 -3
  107. package/dist/plugins/genie/genie.js.map +1 -1
  108. package/dist/plugins/genie/manifest.js +1 -0
  109. package/dist/plugins/jobs/plugin.js +2 -2
  110. package/dist/plugins/jobs/plugin.js.map +1 -1
  111. package/dist/plugins/lakebase/lakebase.d.ts +10 -17
  112. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  113. package/dist/plugins/lakebase/lakebase.js +12 -17
  114. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  115. package/dist/plugins/server/client-config-sanitizer.js +1 -4
  116. package/dist/plugins/server/client-config-sanitizer.js.map +1 -1
  117. package/dist/plugins/server/dev-obo-middleware.js +60 -0
  118. package/dist/plugins/server/dev-obo-middleware.js.map +1 -0
  119. package/dist/plugins/server/index.d.ts.map +1 -1
  120. package/dist/plugins/server/index.js +5 -2
  121. package/dist/plugins/server/index.js.map +1 -1
  122. package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js +3 -3
  123. package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
  124. package/dist/plugins/server/static-server.js +3 -3
  125. package/dist/plugins/server/static-server.js.map +1 -1
  126. package/dist/plugins/server/utils.js +3 -3
  127. package/dist/plugins/server/utils.js.map +1 -1
  128. package/dist/plugins/server/vite-dev-server.js +4 -4
  129. package/dist/plugins/server/vite-dev-server.js.map +1 -1
  130. package/dist/plugins/serving/manifest.js +1 -0
  131. package/dist/plugins/serving/serving.js +6 -6
  132. package/dist/plugins/serving/serving.js.map +1 -1
  133. package/dist/schemas/manifest.d.ts +35 -1
  134. package/dist/schemas/manifest.d.ts.map +1 -1
  135. package/dist/schemas/manifest.js +112 -4
  136. package/dist/schemas/manifest.js.map +1 -1
  137. package/dist/shared/src/dev-obo.js +86 -0
  138. package/dist/shared/src/dev-obo.js.map +1 -0
  139. package/dist/shared/src/index.d.ts +1 -1
  140. package/dist/shared/src/plugin.d.ts +12 -1
  141. package/dist/shared/src/plugin.d.ts.map +1 -1
  142. package/dist/shared/src/schemas/manifest.d.ts +41 -33
  143. package/dist/shared/src/schemas/manifest.d.ts.map +1 -1
  144. package/dist/shared/src/schemas/manifest.js +43 -4
  145. package/dist/shared/src/schemas/manifest.js.map +1 -1
  146. package/dist/stream/stream-manager.d.ts.map +1 -1
  147. package/dist/stream/stream-manager.js +5 -2
  148. package/dist/stream/stream-manager.js.map +1 -1
  149. package/dist/telemetry/execution-span-processor.js +21 -0
  150. package/dist/telemetry/execution-span-processor.js.map +1 -0
  151. package/dist/telemetry/telemetry-manager.js +2 -1
  152. package/dist/telemetry/telemetry-manager.js.map +1 -1
  153. package/dist/testing/create-test-app.d.ts +2 -2
  154. package/dist/testing/create-test-app.js.map +1 -1
  155. package/dist/testing/test-plugin-context.d.ts +3 -12
  156. package/dist/testing/test-plugin-context.d.ts.map +1 -1
  157. package/dist/testing/test-plugin-context.js +24 -16
  158. package/dist/testing/test-plugin-context.js.map +1 -1
  159. package/dist/type-generator/database/generate.js +3 -3
  160. package/dist/type-generator/database/generate.js.map +1 -1
  161. package/dist/type-generator/migration.js +2 -2
  162. package/dist/type-generator/migration.js.map +1 -1
  163. package/dist/type-generator/serving/server-file-extractor.js +3 -3
  164. package/dist/type-generator/serving/server-file-extractor.js.map +1 -1
  165. package/dist/utils/is-plain-object.js +11 -0
  166. package/dist/utils/is-plain-object.js.map +1 -0
  167. package/docs/api/appkit/Class.AppKitError.md +2 -1
  168. package/docs/api/appkit/Class.AuthenticationError.md +1 -1
  169. package/docs/api/appkit/Class.ConfigurationError.md +1 -1
  170. package/docs/api/appkit/Class.ConnectionError.md +1 -1
  171. package/docs/api/appkit/Class.DatabaseValidationError.md +1 -1
  172. package/docs/api/appkit/Class.ExecutionError.md +1 -1
  173. package/docs/api/appkit/Class.IdentityExpiredError.md +190 -0
  174. package/docs/api/appkit/Class.InitializationError.md +1 -1
  175. package/docs/api/appkit/Class.Plugin.md +8 -12
  176. package/docs/api/appkit/Class.ServerError.md +1 -1
  177. package/docs/api/appkit/Class.ServiceContext.md +169 -0
  178. package/docs/api/appkit/Class.TunnelError.md +1 -1
  179. package/docs/api/appkit/Class.ValidationError.md +1 -1
  180. package/docs/api/appkit/Function.createApp.md +12 -12
  181. package/docs/api/appkit/Function.getCallerContext.md +12 -0
  182. package/docs/api/appkit/Function.getCurrentUserId.md +14 -0
  183. package/docs/api/appkit/Function.getExecutionContext.md +1 -1
  184. package/docs/api/appkit/Function.getUserContext.md +16 -0
  185. package/docs/api/appkit/Function.isInUserContext.md +12 -0
  186. package/docs/api/appkit/Function.isUserContext.md +20 -0
  187. package/docs/api/appkit/Function.runAgent.md +1 -1
  188. package/docs/api/appkit/Function.runInCallerContext.md +27 -0
  189. package/docs/api/appkit/Function.runInUserContext.md +29 -0
  190. package/docs/api/appkit/Interface.AgentDefinition.md +11 -0
  191. package/docs/api/appkit/Interface.AgentsPluginConfig.md +11 -0
  192. package/docs/api/appkit/Interface.CallerContext.md +13 -0
  193. package/docs/api/appkit/Interface.IndexConfig.md +1 -1
  194. package/docs/api/appkit/Interface.PluginManifest.md +9 -1
  195. package/docs/api/appkit/Interface.RegisteredAgent.md +11 -0
  196. package/docs/api/appkit/Interface.RunAgentInput.md +45 -1
  197. package/docs/api/appkit/TypeAlias.AgentAuth.md +8 -0
  198. package/docs/api/appkit/TypeAlias.AppKitApi.md +35 -0
  199. package/docs/api/appkit/TypeAlias.ExecutionContext.md +4 -1
  200. package/docs/api/appkit/TypeAlias.ExecutionResult.md +65 -0
  201. package/docs/api/appkit/TypeAlias.ScopedPluginMap.md +12 -0
  202. package/docs/api/appkit/TypeAlias.UserContext.md +109 -0
  203. package/docs/api/appkit/TypeAlias.UserScopedApp.md +39 -0
  204. package/docs/api/appkit.md +14 -0
  205. package/docs/plugins/agents.md +45 -0
  206. package/docs/plugins/execution-context.md +101 -50
  207. package/docs/plugins/lakebase.md +6 -82
  208. package/llms.txt +15 -1
  209. package/package.json +1 -1
  210. package/sbom.cdx.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"appkit.js","names":["#context","#pluginInstances","#setupPromises","#lifecycle","productVersion"],"sources":["../../src/core/appkit.ts"],"sourcesContent":["import type {\n BasePlugin,\n CacheConfig,\n InputPluginMap,\n OptionalConfigPluginDef,\n PluginConstructor,\n PluginData,\n PluginMap,\n} from \"shared\";\n\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 { isPlainObject } from \"../plugin/plugin\";\nimport { uiVariants } from \"../plugins/ui-variants\";\nimport { ResourceRegistry, ResourceType } from \"../registry\";\nimport { getConfiguredWarehouseId } from \"../resources/warehouse\";\nimport type { TelemetryConfig } from \"../telemetry\";\nimport { TelemetryManager } from \"../telemetry\";\nimport type { WorkspaceClient } from \"../workspace-client\";\nimport { LifecycleManager } from \"./lifecycle-manager\";\nimport { isToolProvider, PluginContext } from \"./plugin-context\";\n\nconst logger = createLogger(\"appkit\");\n\n/**\n * Internal teardown entry for the test harness (see `createTestApp`).\n * Symbol-keyed so it cannot collide with — or be shadowed by — a plugin\n * manifest name, and so it stays off the public `PluginMap` surface.\n * @internal\n */\nexport const disposeApp = Symbol(\"appkit.internal.dispose\");\n\nexport class AppKit<TPlugins extends InputPluginMap> {\n #pluginInstances: Record<string, BasePlugin> = {};\n #setupPromises: Promise<void>[] = [];\n #context: PluginContext;\n /** Owns the shutdown sequence; assigned once every plugin has started. */\n #lifecycle: LifecycleManager | undefined;\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 // The manifest `name` is camelCase, so it doubles as the public handle key\n // (`appkit.aiSearch`). The kebab HTTP route is derived separately.\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 (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 * The OBO-side wrapping lives inside `Plugin.asUser` — calling\n * `plugin.asUser(req).exports()` returns exports whose functions already\n * run inside the user's AsyncLocalStorage scope. AppKit only adapts the\n * shape; it does not own the user-context concept.\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 (plugin as any).asUser(req).exports() as Record<string, unknown>,\n };\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 * Skip installing the SIGTERM/SIGINT handlers. Internal, and not exposed\n * on {@link createApp}: only the test harness sets it, because it boots\n * repeatedly in one process and manages its own teardown, so\n * accumulating signal handlers would be a leak.\n */\n installSignalHandlers?: boolean;\n } = {},\n ): Promise<PluginMap<T>> {\n // Initialize core services\n TelemetryManager.initialize(config?.telemetry);\n await CacheManager.getInstance(config?.cache);\n\n const withDefaults = AppKit.withDefaultPlugins(config.plugins as T);\n const rawPlugins = AppKit.filterDevOnlyPlugins(withDefaults);\n\n // Collect manifest resources via registry\n const registry = new ResourceRegistry();\n registry.collectResources(rawPlugins);\n registry.validate();\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 {\n warehouseId:\n getConfiguredWarehouseId(registry.getRequired()) ?? needsWarehouse,\n },\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 // Build the global tracer provider now that every plugin's setup() has run\n // and contributed any span processors. Deferred to here so a single provider\n // carries all processors (OTLP + plugin-contributed); see TelemetryManager.\n TelemetryManager.start();\n\n await instance.#context.emitLifecycle(\"setup:complete\");\n\n const app = instance as unknown as PluginMap<T>;\n\n if (config.onPluginsReady) {\n logger.debug(\"Running onPluginsReady hook\");\n await config.onPluginsReady(app);\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 // Core owns graceful shutdown: install the signal handlers once every\n // plugin has started. Applies uniformly whether or not a server plugin\n // is present — server-less apps still get their telemetry flushed and\n // plugin shutdown() hooks run.\n instance.#lifecycle = new LifecycleManager(instance.#context);\n if (config.installSignalHandlers !== false) {\n instance.#lifecycle.installSignalHandlers();\n }\n\n return app;\n }\n\n /**\n * Internal teardown entry point for the test harness: delegates to the\n * lifecycle's non-exiting `shutdown({ exit: false })` (the phases are\n * canonical there). Not public API — AppKit does not support re-booting or\n * embedding, so real apps tear down only through the signal path. The harness\n * drops the core singletons and restores env after this resolves.\n * @internal\n */\n async [disposeApp](): Promise<void> {\n await this.#lifecycle?.shutdown({ exit: false });\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 /**\n * Injects framework-owned default plugins the app author shouldn't have to\n * register by hand. Runs before {@link filterDevOnlyPlugins}, so a default\n * that is itself `devOnly` (like `ui-variants`) is present in dev and stripped\n * in prod through the exact same guard as any user plugin — never alive in a\n * deployed app.\n *\n * Currently injects the dev-only `ui-variants` recorder that backs the\n * `<Variants>` UI picker, so neither the developer nor their coding agent has\n * to remember to add it. If the app already registered it explicitly, that\n * entry wins and no duplicate is added.\n */\n private static withDefaultPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n const list = (plugins ?? []) as PluginData<\n PluginConstructor,\n unknown,\n string\n >[];\n\n const defaults: PluginData<PluginConstructor, unknown, string>[] = [\n uiVariants(),\n ];\n\n const missing = defaults.filter(\n (def) => !list.some((p) => p?.name === def.name),\n );\n\n return [...list, ...missing] as T;\n }\n\n /**\n * Drops plugins whose manifest declares `devOnly: true` unless\n * `NODE_ENV === \"development\"`. Runs before resource collection so a skipped\n * plugin is never constructed, never injects routes, and has its resource\n * requirements ignored — the framework, not the app author, enforces that\n * dev-only tooling can never run in a deployed app.\n *\n * This is the primary guard; plugins that expose a mutating dev endpoint\n * should still keep an in-handler `NODE_ENV` check as fail-safe defense\n * against being mounted through a path that bypasses this filter.\n */\n private static filterDevOnlyPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n if (!plugins || process.env.NODE_ENV === \"development\") return plugins;\n\n return plugins.filter((pluginData) => {\n const isDevOnly = pluginData?.plugin?.manifest?.devOnly === true;\n if (isDevOnly) {\n logger.debug(\n \"Skipping dev-only plugin %s (NODE_ENV=%s)\",\n pluginData?.name ?? \"unknown\",\n process.env.NODE_ENV ?? \"<unset>\",\n );\n }\n return !isDevOnly;\n }) as T;\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 /** Runs after plugin setup but **before** the server starts. */\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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"}
1
+ {"version":3,"file":"appkit.js","names":["#pluginInstances","#context","#setupPromises","#lifecycle","productVersion"],"sources":["../../src/core/appkit.ts"],"sourcesContent":["import type {\n AppKitApi,\n BasePlugin,\n CacheConfig,\n InputPluginMap,\n OptionalConfigPluginDef,\n PluginConstructor,\n PluginData,\n} from \"shared\";\n\nimport { version as productVersion } from \"../../package.json\";\nimport { CacheManager } from \"../cache\";\nimport { ServiceContext } from \"../context\";\nimport { createRequestScope } from \"../context/request-scope\";\nimport {\n assertPluginExecution,\n getPluginResourceTypes,\n guardPluginApi,\n} from \"../context/resource-capabilities\";\nimport { scopeApi } from \"../context/scoped-api\";\nimport {\n isInternalTelemetryEnabled,\n TelemetryReporter,\n} from \"../internal-telemetry\";\nimport { createLogger } from \"../logging/logger\";\nimport { uiVariants } from \"../plugins/ui-variants\";\nimport { ResourceRegistry, ResourceType } from \"../registry\";\nimport { getConfiguredWarehouseId } from \"../resources/warehouse\";\nimport type { TelemetryConfig } from \"../telemetry\";\nimport { TelemetryManager } from \"../telemetry\";\nimport { isPlainObject } from \"../utils/is-plain-object\";\nimport type { WorkspaceClient } from \"../workspace-client\";\nimport { LifecycleManager } from \"./lifecycle-manager\";\nimport { isToolProvider, PluginContext } from \"./plugin-context\";\n\nconst logger = createLogger(\"appkit\");\n\n/**\n * Internal teardown entry for the test harness (see `createTestApp`).\n * Symbol-keyed so it cannot collide with — or be shadowed by — a plugin\n * manifest name, and so it stays off the public `PluginMap` surface.\n * @internal\n */\nexport const disposeApp = Symbol(\"appkit.internal.dispose\");\n\nexport class AppKit<TPlugins extends InputPluginMap> {\n #pluginInstances: Record<string, BasePlugin> = {};\n #setupPromises: Promise<void>[] = [];\n #context: PluginContext;\n /** Owns the shutdown sequence; assigned once every plugin has started. */\n #lifecycle: LifecycleManager | undefined;\n\n /** Execute a block or a single plugin call as the requesting user. */\n asUser(req: import(\"express\").Request) {\n const scope = createRequestScope(\n req,\n Object.values(this.#pluginInstances).flatMap(getPluginResourceTypes),\n );\n const kit: Record<string, unknown> = Object.create(null);\n // Memoize each plugin's scoped export for the life of THIS handle only.\n // The map is created per asUser(req) call and captured solely by this\n // handle's getters, so it dies with the request and never crosses\n // principals. Repeated access (e.g. kit.analytics twice) reuses the same\n // wrapper instead of rebuilding the proxy on every read.\n const scopedExports = new Map<string, unknown>();\n for (const [name, plugin] of Object.entries(this.#pluginInstances)) {\n Object.defineProperty(kit, name, {\n enumerable: true,\n get: () => {\n if (!scopedExports.has(name)) {\n // The app-only guard runs on first access; it is deterministic per\n // (plugin, scope), so caching the result after it passes is safe,\n // and if it throws nothing is cached (stays fail-closed).\n scopedExports.set(\n name,\n scopeApi(\n scope.run(() => {\n assertPluginExecution(plugin, true);\n return plugin.exports?.() ?? {};\n }),\n scope,\n plugin,\n ),\n );\n }\n return scopedExports.get(name);\n },\n });\n }\n Object.defineProperty(kit, \"run\", {\n value: async <T>(\n fn: (kit: Record<string, unknown>) => T | Promise<T>,\n ): Promise<T> => scope.run(() => fn(kit)),\n });\n return Object.freeze(kit);\n }\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 if (name === \"run\" || /^as[A-Z]/.test(name)) {\n throw new Error(\n `Plugin name \"${name}\" is reserved for execution scoping`,\n );\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 // The manifest `name` is camelCase, so it doubles as the public handle key\n // (`appkit.aiSearch`). The kebab HTTP route is derived separately.\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 (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 * This preserves the deprecated per-plugin API. Both entry points delegate\n * to the shared request scope; new code should use the app-level API.\n */\n private wrapWithAsUser<T extends BasePlugin>(plugin: T) {\n assertPluginExecution(plugin);\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 guardPluginApi(plugin, 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 guardPluginApi(plugin, objExports);\n }\n\n return guardPluginApi(plugin, {\n ...objExports,\n /**\n * @deprecated Use appkit.asUser(req) instead.\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 (plugin as any).asUser(req).exports() as Record<string, unknown>,\n });\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: AppKitApi<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n /**\n * Skip installing the SIGTERM/SIGINT handlers. Internal, and not exposed\n * on {@link createApp}: only the test harness sets it, because it boots\n * repeatedly in one process and manages its own teardown, so\n * accumulating signal handlers would be a leak.\n */\n installSignalHandlers?: boolean;\n } = {},\n ): Promise<AppKitApi<T>> {\n // Initialize core services\n TelemetryManager.initialize(config?.telemetry);\n await CacheManager.getInstance(config?.cache);\n\n const withDefaults = AppKit.withDefaultPlugins(config.plugins as T);\n const rawPlugins = AppKit.filterDevOnlyPlugins(withDefaults);\n\n // Collect manifest resources via registry\n const registry = new ResourceRegistry();\n registry.collectResources(rawPlugins);\n registry.validate();\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 {\n warehouseId:\n getConfiguredWarehouseId(registry.getRequired()) ?? needsWarehouse,\n },\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 // Build the global tracer provider now that every plugin's setup() has run\n // and contributed any span processors. Deferred to here so a single provider\n // carries all processors (OTLP + plugin-contributed); see TelemetryManager.\n TelemetryManager.start();\n\n await instance.#context.emitLifecycle(\"setup:complete\");\n\n const app = instance as typeof instance & AppKitApi<T>;\n\n if (config.onPluginsReady) {\n logger.debug(\"Running onPluginsReady hook\");\n await config.onPluginsReady(app);\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 // Core owns graceful shutdown: install the signal handlers once every\n // plugin has started. Applies uniformly whether or not a server plugin\n // is present — server-less apps still get their telemetry flushed and\n // plugin shutdown() hooks run.\n instance.#lifecycle = new LifecycleManager(instance.#context);\n if (config.installSignalHandlers !== false) {\n instance.#lifecycle.installSignalHandlers();\n }\n\n return app;\n }\n\n /**\n * Internal teardown entry point for the test harness: delegates to the\n * lifecycle's non-exiting `shutdown({ exit: false })` (the phases are\n * canonical there). Not public API — AppKit does not support re-booting or\n * embedding, so real apps tear down only through the signal path. The harness\n * drops the core singletons and restores env after this resolves.\n * @internal\n */\n async [disposeApp](): Promise<void> {\n await this.#lifecycle?.shutdown({ exit: false });\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 /**\n * Injects framework-owned default plugins the app author shouldn't have to\n * register by hand. Runs before {@link filterDevOnlyPlugins}, so a default\n * that is itself `devOnly` (like `ui-variants`) is present in dev and stripped\n * in prod through the exact same guard as any user plugin — never alive in a\n * deployed app.\n *\n * Currently injects the dev-only `ui-variants` recorder that backs the\n * `<Variants>` UI picker, so neither the developer nor their coding agent has\n * to remember to add it. If the app already registered it explicitly, that\n * entry wins and no duplicate is added.\n */\n private static withDefaultPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n const list = (plugins ?? []) as PluginData<\n PluginConstructor,\n unknown,\n string\n >[];\n\n const defaults: PluginData<PluginConstructor, unknown, string>[] = [\n uiVariants(),\n ];\n\n const missing = defaults.filter(\n (def) => !list.some((p) => p?.name === def.name),\n );\n\n return [...list, ...missing] as T;\n }\n\n /**\n * Drops plugins whose manifest declares `devOnly: true` unless\n * `NODE_ENV === \"development\"`. Runs before resource collection so a skipped\n * plugin is never constructed, never injects routes, and has its resource\n * requirements ignored — the framework, not the app author, enforces that\n * dev-only tooling can never run in a deployed app.\n *\n * This is the primary guard; plugins that expose a mutating dev endpoint\n * should still keep an in-handler `NODE_ENV` check as fail-safe defense\n * against being mounted through a path that bypasses this filter.\n */\n private static filterDevOnlyPlugins<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(plugins: T): T {\n if (!plugins || process.env.NODE_ENV === \"development\") return plugins;\n\n return plugins.filter((pluginData) => {\n const isDevOnly = pluginData?.plugin?.manifest?.devOnly === true;\n if (isDevOnly) {\n logger.debug(\n \"Skipping dev-only plugin %s (NODE_ENV=%s)\",\n pluginData?.name ?? \"unknown\",\n process.env.NODE_ENV ?? \"<unset>\",\n );\n }\n return !isDevOnly;\n }) as T;\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 /** Runs after plugin setup but **before** the server starts. */\n onPluginsReady?: (appkit: AppKitApi<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n): Promise<AppKitApi<T>> {\n return AppKit._createApp(config);\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { ToolProvider } from "../shared/src/agent.js";
2
- import { BasePlugin, IAppRequest } from "../shared/src/plugin.js";
2
+ import { BasePlugin } from "../shared/src/plugin.js";
3
3
  import "../shared/src/index.js";
4
+ import { ServiceContext } from "../context/service-context.js";
4
5
  import { ITelemetry } from "../telemetry/types.js";
5
6
  import "../telemetry/index.js";
6
7
  import express from "express";
@@ -10,14 +11,9 @@ interface RouteTarget {
10
11
  addExtension(fn: (app: express.Application) => void): void;
11
12
  }
12
13
  /**
13
- * A tool-provider plugin that also exposes user-scoped execution. Plugins
14
- * derived from {@link Plugin} satisfy this implicitly because `asUser` lives
15
- * on the base class. {@link isToolProvider} narrows to this shape so
16
- * `executeTool` can call `asUser` without an unsafe cast.
14
+ * Tool execution inherits the caller scope established by the entry point.
17
15
  */
18
- type ToolProviderPlugin = BasePlugin & ToolProvider & {
19
- asUser: (req: IAppRequest) => ToolProvider;
20
- };
16
+ type ToolProviderPlugin = BasePlugin & ToolProvider;
21
17
  /**
22
18
  * Lifecycle events emitted through {@link PluginContext.emitLifecycle}.
23
19
  *
@@ -52,16 +48,19 @@ declare class PluginContext {
52
48
  private plugins;
53
49
  private lifecycleHooks;
54
50
  private telemetry;
51
+ private createCallerContext;
55
52
  /**
56
53
  * @param deps.telemetry - Telemetry provider used for `executeTool` spans.
57
- * Defaults to the shared `"plugin-context"` provider — the production
54
+ * Defaults to the shared `"plugin-context"` provider, the production
58
55
  * path. Injectable so the testing kit can pass a mock provider and run
59
- * `executeTool` without a live OpenTelemetry pipeline. This is the only
60
- * seam the mock context needs; route buffering and the tool registry are
56
+ * `executeTool` without a live OpenTelemetry pipeline.
57
+ * @param deps.createCallerContext - Offline credential factory for tests.
58
+ * Header validation and ALS remain real; route buffering and the registry are
61
59
  * exercised through the existing public API.
62
60
  */
63
61
  constructor(deps?: {
64
- telemetry?: ITelemetry;
62
+ telemetry?: ITelemetry; /** @internal Credential factory seam for offline testing. */
63
+ createCallerContext?: typeof ServiceContext.createCallerContext;
65
64
  });
66
65
  /**
67
66
  * Register a route on the root Express application.
@@ -117,15 +116,15 @@ declare class PluginContext {
117
116
  provider: ToolProvider;
118
117
  }>;
119
118
  /**
120
- * Execute a tool on a ToolProvider plugin with automatic user scoping
119
+ * Execute a tool on a ToolProvider plugin with the ambient principal
121
120
  * and telemetry.
122
121
  *
123
122
  * The context:
124
123
  * 1. Resolves the plugin by name
125
- * 2. Calls `asUser(req)` for user-scoped execution
124
+ * 2. Inherits the caller, or establishes user scope from the request
126
125
  * 3. Wraps the call in a telemetry span with a configurable timeout
127
126
  *
128
- * @param timeoutMs Per-call timeout. Defaults to 5 minutes — the floor
127
+ * @param timeoutMs Per-call timeout. Defaults to 5 minutes, the floor
129
128
  * for cold SQL Warehouse round-trips, long Genie conversations, and
130
129
  * busy serverless Lakebase queries. The agents plugin overrides this
131
130
  * per-app via `agents({ limits: { toolCallTimeoutMs } })`.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-context.d.ts","names":[],"sources":["../../src/core/plugin-context.ts"],"mappings":";;;;;;;;UAmBU,WAAA;EACR,YAAA,CAAa,EAAA,GAAK,GAAA,EAAK,OAAA,CAAQ,WAAA;AAAA;;;AAZX;;;;KAqBjB,kBAAA,GAAqB,UAAA,GACxB,YAAA;EAAiB,MAAA,GAAS,GAAA,EAAK,WAAA,KAAgB,YAAA;AAAA;;;;;AAVI;;;;;;;;;KAyBhD,cAAA;;;;;;;;AAfwD;;;;;AA8B7D;cAAa,aAAA;EAAA,QACH,WAAA;EAAA,QACA,WAAA;EAAA,QACA,aAAA;EAAA,QACA,OAAA;EAAA,QACA,cAAA;EAAA,QAIA,SAAA;EAiG+B;;;;;;;;cAvF3B,IAAA;IAAQ,SAAA,GAAY,UAAA;EAAA;EAuMY;;;;;;;EA3L5C,QAAA,CACE,MAAA,UACA,IAAA,aACG,QAAA,EAAU,OAAA,CAAQ,cAAA;EAzBf;;;;;EAuCR,aAAA,CAAc,IAAA,aAAiB,QAAA,EAAU,OAAA,CAAQ,cAAA;EAhB/C;;;;;;;;;EAiCF,qBAAA,CAAsB,MAAA,EAAQ,WAAA;EAA9B;;;;;;;;EA2BA,oBAAA,CAAqB,IAAA,UAAc,MAAA,EAAQ,kBAAA;EAcJ;;;;EAAvC,cAAA,CAAe,IAAA,UAAc,QAAA,EAAU,UAAA;EAkBvC;;;;;;EARA,UAAA,CAAA,GAAc,WAAA,SAAoB,UAAA;EA8BnB;;;;EAtBf,gBAAA,CAAA,GAAoB,KAAA;IAAQ,IAAA;IAAc,QAAA,EAAU,YAAA;EAAA;EA4BjD;;;;;;;;;;;;;;EAPG,WAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,UAAA,UACA,QAAA,UACA,IAAA,WACA,MAAA,GAAS,WAAA,EACT,SAAA,YACC,OAAA;EAwHoB;;;;;;;EAvEvB,WAAA,CAAY,KAAA,EAAO,cAAA,EAAgB,EAAA,eAAiB,OAAA;;;;;;;;;EAiB9C,aAAA,CAAc,KAAA,EAAO,cAAA,GAAiB,OAAA;;;;EA8B5C,cAAA,CAAA;;;;EAOA,SAAA,CAAU,IAAA;EAAA,QAIF,UAAA;EAAA,QAaA,eAAA;AAAA"}
1
+ {"version":3,"file":"plugin-context.d.ts","names":[],"sources":["../../src/core/plugin-context.ts"],"mappings":";;;;;;;;;UA6BU,WAAA;EACR,YAAA,CAAa,EAAA,GAAK,GAAA,EAAK,OAAA,CAAQ,WAAA;AAAA;;;AAZX;KAkBjB,kBAAA,GAAqB,UAAA,GAAa,YAAA;;;;;;;;;;AANc;;;;KAqBhD,cAAA;AAf8C;;;;;AA8BnD;;;;;;;;AA9BmD,cA8BtC,aAAA;EAAA,QACH,WAAA;EAAA,QACA,WAAA;EAAA,QACA,aAAA;EAAA,QACA,OAAA;EAAA,QACA,cAAA;EAAA,QAIA,SAAA;EAAA,QACA,mBAAA;EAyJL;;;;;;;;;cA7ID,IAAA;IACE,SAAA,GAAY,UAAA,EAlBR;IAoBJ,mBAAA,UAA6B,cAAA,CAAe,mBAAA;EAAA;;;;;;;;EAiBhD,QAAA,CACE,MAAA,UACA,IAAA,aACG,QAAA,EAAU,OAAA,CAAQ,cAAA;EAFrB;;;;;EAgBF,aAAA,CAAc,IAAA,aAAiB,QAAA,EAAU,OAAA,CAAQ,cAAA;EAAnC;;;;;;;;;EAiBd,qBAAA,CAAsB,MAAA,EAAQ,WAAA;EA2BK;;;;;;;;EAAnC,oBAAA,CAAqB,IAAA,UAAc,MAAA,EAAQ,kBAAA;EAgCvB;;;;EAlBpB,cAAA,CAAe,IAAA,UAAc,QAAA,EAAU,UAAA;EAwChC;;;;;;EA9BP,UAAA,CAAA,GAAc,WAAA,SAAoB,UAAA;EAkChC;;;;EA1BF,gBAAA,CAAA,GAAoB,KAAA;IAAQ,IAAA;IAAc,QAAA,EAAU,YAAA;EAAA;EAwG9C;;;;;;;;;;;;;;EAnFA,WAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,UAAA,UACA,QAAA,UACA,IAAA,WACA,MAAA,GAAS,WAAA,EACT,SAAA,YACC,OAAA;;;;;;;;EA2DH,WAAA,CAAY,KAAA,EAAO,cAAA,EAAgB,EAAA,eAAiB,OAAA;;;;;;;;;EAiB9C,aAAA,CAAc,KAAA,EAAO,cAAA,GAAiB,OAAA;;;;EA8B5C,cAAA,CAAA;;;;EAOA,SAAA,CAAU,IAAA;EAAA,QAIF,UAAA;EAAA,QAaA,eAAA;AAAA"}
@@ -1,6 +1,10 @@
1
1
  import { createLogger } from "../logging/logger.js";
2
+ import { ServiceContext } from "../context/service-context.js";
3
+ import { getCallerContext, normalizeIdentityError } from "../context/execution-context.js";
4
+ import { assertPluginExecution, getPluginResourceTypes } from "../context/resource-capabilities.js";
2
5
  import { TelemetryManager } from "../telemetry/telemetry-manager.js";
3
6
  import { SpanStatusCode } from "../telemetry/index.js";
7
+ import { createRequestScope } from "../context/request-scope.js";
4
8
  import { forwardAsyncErrors } from "../utils/safe-handler.js";
5
9
 
6
10
  //#region src/core/plugin-context.ts
@@ -25,16 +29,19 @@ var PluginContext = class {
25
29
  plugins = /* @__PURE__ */ new Map();
26
30
  lifecycleHooks = /* @__PURE__ */ new Map();
27
31
  telemetry;
32
+ createCallerContext;
28
33
  /**
29
34
  * @param deps.telemetry - Telemetry provider used for `executeTool` spans.
30
- * Defaults to the shared `"plugin-context"` provider — the production
35
+ * Defaults to the shared `"plugin-context"` provider, the production
31
36
  * path. Injectable so the testing kit can pass a mock provider and run
32
- * `executeTool` without a live OpenTelemetry pipeline. This is the only
33
- * seam the mock context needs; route buffering and the tool registry are
37
+ * `executeTool` without a live OpenTelemetry pipeline.
38
+ * @param deps.createCallerContext - Offline credential factory for tests.
39
+ * Header validation and ALS remain real; route buffering and the registry are
34
40
  * exercised through the existing public API.
35
41
  */
36
42
  constructor(deps = {}) {
37
43
  this.telemetry = deps.telemetry ?? TelemetryManager.getProvider("plugin-context");
44
+ this.createCallerContext = deps.createCallerContext ?? ((...args) => ServiceContext.createCallerContext(...args));
38
45
  }
39
46
  /**
40
47
  * Register a route on the root Express application.
@@ -126,15 +133,15 @@ var PluginContext = class {
126
133
  }));
127
134
  }
128
135
  /**
129
- * Execute a tool on a ToolProvider plugin with automatic user scoping
136
+ * Execute a tool on a ToolProvider plugin with the ambient principal
130
137
  * and telemetry.
131
138
  *
132
139
  * The context:
133
140
  * 1. Resolves the plugin by name
134
- * 2. Calls `asUser(req)` for user-scoped execution
141
+ * 2. Inherits the caller, or establishes user scope from the request
135
142
  * 3. Wraps the call in a telemetry span with a configurable timeout
136
143
  *
137
- * @param timeoutMs Per-call timeout. Defaults to 5 minutes — the floor
144
+ * @param timeoutMs Per-call timeout. Defaults to 5 minutes, the floor
138
145
  * for cold SQL Warehouse round-trips, long Genie conversations, and
139
146
  * busy serverless Lakebase queries. The agents plugin overrides this
140
147
  * per-app via `agents({ limits: { toolCallTimeoutMs } })`.
@@ -144,14 +151,16 @@ var PluginContext = class {
144
151
  if (!provider) throw new Error(`PluginContext: unknown plugin "${pluginName}". Available: ${Array.from(this.toolProviders.keys()).join(", ")}`);
145
152
  const tracer = this.telemetry.getTracer();
146
153
  const operationName = `executeTool:${pluginName}.${toolName}`;
147
- return tracer.startActiveSpan(operationName, async (span) => {
154
+ const executeInCurrentScope = () => tracer.startActiveSpan(operationName, async (span) => {
148
155
  const timeoutSignal = AbortSignal.timeout(timeoutMs);
149
156
  const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
150
157
  try {
151
- const result = await provider.asUser(req).executeAgentTool(toolName, args, combinedSignal);
158
+ assertPluginExecution(provider);
159
+ const result = await provider.executeAgentTool(toolName, args, combinedSignal);
152
160
  span.setStatus({ code: SpanStatusCode.OK });
153
161
  return result;
154
- } catch (error) {
162
+ } catch (caught) {
163
+ const error = normalizeIdentityError(caught);
155
164
  span.setStatus({
156
165
  code: SpanStatusCode.ERROR,
157
166
  message: error instanceof Error ? error.message : "Tool execution failed"
@@ -162,6 +171,10 @@ var PluginContext = class {
162
171
  span.end();
163
172
  }
164
173
  });
174
+ return getCallerContext() ? executeInCurrentScope() : createRequestScope(req, getPluginResourceTypes(provider), {
175
+ createCaller: this.createCallerContext,
176
+ legacy: true
177
+ }).run(executeInCurrentScope);
165
178
  }
166
179
  /**
167
180
  * Register a lifecycle hook callback.
@@ -223,13 +236,10 @@ var PluginContext = class {
223
236
  }
224
237
  };
225
238
  /**
226
- * Type guard: checks whether a plugin implements the ToolProvider interface
227
- * and exposes the user-scoped `asUser` helper that the {@link Plugin} base
228
- * class provides. Narrowing to {@link ToolProviderPlugin} lets `executeTool`
229
- * call `asUser` without an unsafe cast.
239
+ * Type guard for the tool-provider methods. Identity comes from the ALS scope.
230
240
  */
231
241
  function isToolProvider(plugin) {
232
- 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";
242
+ return typeof plugin === "object" && plugin !== null && "getAgentTools" in plugin && typeof plugin.getAgentTools === "function" && "executeAgentTool" in plugin && typeof plugin.executeAgentTool === "function";
233
243
  }
234
244
 
235
245
  //#endregion
@@ -1 +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\";\n\nimport { createLogger } from \"../logging/logger\";\nimport {\n type ITelemetry,\n SpanStatusCode,\n TelemetryManager,\n} from \"../telemetry\";\nimport { forwardAsyncErrors } from \"../utils/safe-handler\";\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\n/**\n * Lifecycle events emitted through {@link PluginContext.emitLifecycle}.\n *\n * - `\"setup:complete\"` — emitted by AppKit core after every plugin's\n * `setup()` has finished.\n * - `\"server:ready\"` — emitted when the HTTP server is listening.\n * - `\"shutdown\"` — emitted by the core lifecycle manager during graceful\n * shutdown, AFTER all plugin `shutdown()` hooks have completed. The emit is\n * bounded by a short timeout (see the lifecycle manager's shutdown budget),\n * so subscribers must not start long-running async work — finish quickly or\n * be cut off. (The server plugin subscribes here to force-close its\n * remaining sockets once peers have drained.)\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: ITelemetry;\n\n /**\n * @param deps.telemetry - Telemetry provider used for `executeTool` spans.\n * Defaults to the shared `\"plugin-context\"` provider — the production\n * path. Injectable so the testing kit can pass a mock provider and run\n * `executeTool` without a live OpenTelemetry pipeline. This is the only\n * seam the mock context needs; route buffering and the tool registry are\n * exercised through the existing public API.\n */\n constructor(deps: { telemetry?: ITelemetry } = {}) {\n this.telemetry =\n deps.telemetry ?? TelemetryManager.getProvider(\"plugin-context\");\n }\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 * See {@link LifecycleEvent} for event semantics. In particular,\n * `\"shutdown\"` subscribers run inside a bounded shutdown phase and must\n * not start long-running async work.\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: `setup:complete` after plugin setup,\n * and `shutdown` by the lifecycle manager during graceful shutdown.\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.map(forwardAsyncErrors),\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.map(forwardAsyncErrors));\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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"}
1
+ {"version":3,"file":"plugin-context.js","names":[],"sources":["../../src/core/plugin-context.ts"],"sourcesContent":["import type express from \"express\";\nimport type { BasePlugin, ToolProvider } from \"shared\";\n\nimport {\n getCallerContext,\n normalizeIdentityError,\n} from \"../context/execution-context\";\nimport { createRequestScope } from \"../context/request-scope\";\nimport {\n assertPluginExecution,\n getPluginResourceTypes,\n} from \"../context/resource-capabilities\";\nimport { ServiceContext } from \"../context/service-context\";\nimport { createLogger } from \"../logging/logger\";\nimport {\n type ITelemetry,\n SpanStatusCode,\n TelemetryManager,\n} from \"../telemetry\";\nimport { forwardAsyncErrors } from \"../utils/safe-handler\";\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 * Tool execution inherits the caller scope established by the entry point.\n */\ntype ToolProviderPlugin = BasePlugin & ToolProvider;\n\n/**\n * Lifecycle events emitted through {@link PluginContext.emitLifecycle}.\n *\n * - `\"setup:complete\"` — emitted by AppKit core after every plugin's\n * `setup()` has finished.\n * - `\"server:ready\"` — emitted when the HTTP server is listening.\n * - `\"shutdown\"` — emitted by the core lifecycle manager during graceful\n * shutdown, AFTER all plugin `shutdown()` hooks have completed. The emit is\n * bounded by a short timeout (see the lifecycle manager's shutdown budget),\n * so subscribers must not start long-running async work — finish quickly or\n * be cut off. (The server plugin subscribes here to force-close its\n * remaining sockets once peers have drained.)\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: ITelemetry;\n private createCallerContext: typeof ServiceContext.createCallerContext;\n\n /**\n * @param deps.telemetry - Telemetry provider used for `executeTool` spans.\n * Defaults to the shared `\"plugin-context\"` provider, the production\n * path. Injectable so the testing kit can pass a mock provider and run\n * `executeTool` without a live OpenTelemetry pipeline.\n * @param deps.createCallerContext - Offline credential factory for tests.\n * Header validation and ALS remain real; route buffering and the registry are\n * exercised through the existing public API.\n */\n constructor(\n deps: {\n telemetry?: ITelemetry;\n /** @internal Credential factory seam for offline testing. */\n createCallerContext?: typeof ServiceContext.createCallerContext;\n } = {},\n ) {\n this.telemetry =\n deps.telemetry ?? TelemetryManager.getProvider(\"plugin-context\");\n this.createCallerContext =\n deps.createCallerContext ??\n ((...args) => ServiceContext.createCallerContext(...args));\n }\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 the ambient principal\n * and telemetry.\n *\n * The context:\n * 1. Resolves the plugin by name\n * 2. Inherits the caller, or establishes user scope from the request\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 const executeInCurrentScope = () =>\n 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 assertPluginExecution(provider);\n const result = await provider.executeAgentTool(\n toolName,\n args,\n combinedSignal,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (caught) {\n const error = normalizeIdentityError(caught);\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 // Inherit the caller or establish request user scope before the span and tool run.\n return getCallerContext()\n ? executeInCurrentScope()\n : createRequestScope(req, getPluginResourceTypes(provider), {\n createCaller: this.createCallerContext,\n legacy: true,\n }).run(executeInCurrentScope);\n }\n\n /**\n * Register a lifecycle hook callback.\n *\n * See {@link LifecycleEvent} for event semantics. In particular,\n * `\"shutdown\"` subscribers run inside a bounded shutdown phase and must\n * not start long-running async work.\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: `setup:complete` after plugin setup,\n * and `shutdown` by the lifecycle manager during graceful shutdown.\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.map(forwardAsyncErrors),\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.map(forwardAsyncErrors));\n });\n }\n}\n\n/**\n * Type guard for the tool-provider methods. Identity comes from the ALS scope.\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 );\n}\n"],"mappings":"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"}
@@ -33,8 +33,8 @@ declare abstract class AppKitError extends Error {
33
33
  readonly context?: Record<string, unknown>;
34
34
  /**
35
35
  * Client-safe error message. When set, callers serializing the error to
36
- * a client (SSE, HTTP body) MUST prefer `clientMessage` over `message`
37
- * — `message` may contain raw upstream / SDK text including statement
36
+ * a client (SSE, HTTP body) MUST prefer `clientMessage` over `message`.
37
+ * `message` may contain raw upstream / SDK text including statement
38
38
  * fragments, internal object names, and correlation IDs.
39
39
  *
40
40
  * Subclasses can set this in their constructor for a fixed sanitized
@@ -37,8 +37,8 @@ var AppKitError = class extends Error {
37
37
  context;
38
38
  /**
39
39
  * Client-safe error message. When set, callers serializing the error to
40
- * a client (SSE, HTTP body) MUST prefer `clientMessage` over `message`
41
- * — `message` may contain raw upstream / SDK text including statement
40
+ * a client (SSE, HTTP body) MUST prefer `clientMessage` over `message`.
41
+ * `message` may contain raw upstream / SDK text including statement
42
42
  * fragments, internal object names, and correlation IDs.
43
43
  *
44
44
  * Subclasses can set this in their constructor for a fixed sanitized
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":[],"sources":["../../src/errors/base.ts"],"sourcesContent":["/** Fields that should be redacted in logs/serialization for security */\nconst SENSITIVE_FIELD_PATTERNS = [\n /token/i,\n /password/i,\n /secret/i,\n /credential/i,\n /auth/i,\n /key$/i,\n /apikey/i,\n];\n\n/**\n * Base error class for all AppKit errors.\n * Provides a consistent structure for error handling across the framework.\n *\n * @example\n * ```typescript\n * // Catching errors by type\n * try {\n * await lakebase.query(\"...\");\n * } catch (e) {\n * if (e instanceof AuthenticationError) {\n * // Re-authenticate\n * } else if (e instanceof ConnectionError && e.isRetryable) {\n * // Retry with backoff\n * }\n * }\n *\n * // Logging errors\n * console.error(error.toJSON()); // Safe for logging, sensitive values redacted\n * ```\n */\nexport abstract class AppKitError extends Error {\n /** Error code for programmatic error handling */\n abstract readonly code: string;\n\n /** HTTP status code suggestion (can be overridden) */\n abstract readonly statusCode: number;\n\n /** Whether this error type is generally safe to retry */\n abstract readonly isRetryable: boolean;\n\n /** Optional cause of the error */\n readonly cause?: Error;\n\n /** Additional context for the error */\n readonly context?: Record<string, unknown>;\n\n /**\n * Client-safe error message. When set, callers serializing the error to\n * a client (SSE, HTTP body) MUST prefer `clientMessage` over `message`\n * — `message` may contain raw upstream / SDK text including statement\n * fragments, internal object names, and correlation IDs.\n *\n * Subclasses can set this in their constructor for a fixed sanitized\n * string. When unset, `clientMessage` defaults to a generic per-code\n * string (see the getter), and the raw `message` is kept server-side\n * only.\n */\n protected readonly _clientMessage?: string;\n\n constructor(\n message: string,\n options?: {\n cause?: Error;\n context?: Record<string, unknown>;\n clientMessage?: string;\n },\n ) {\n super(message);\n this.name = this.constructor.name;\n this.cause = options?.cause;\n this.context = options?.context;\n this._clientMessage = options?.clientMessage;\n\n // Maintains proper stack trace for where the error was thrown\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n\n /**\n * Sanitized message safe to forward to clients. Override in subclasses\n * if a more specific default is appropriate.\n */\n get clientMessage(): string {\n return this._clientMessage ?? \"An internal error occurred\";\n }\n\n /**\n * Convert error to JSON for logging/serialization.\n * Sensitive values in context are automatically redacted.\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n isRetryable: this.isRetryable,\n context: this.sanitizeContext(this.context),\n cause: this.cause?.message,\n stack: this.stack,\n };\n }\n\n /**\n * Create a human-readable string representation\n */\n toString(): string {\n let str = `${this.name} [${this.code}]: ${this.message}`;\n if (this.cause) {\n str += ` (caused by: ${this.cause.message})`;\n }\n return str;\n }\n\n /**\n * Sanitize context by redacting sensitive field values\n */\n private sanitizeContext(\n context?: Record<string, unknown>,\n ): Record<string, unknown> | undefined {\n if (!context) return undefined;\n\n const sanitized: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(context)) {\n if (this.isSensitiveField(key)) {\n sanitized[key] = \"[REDACTED]\";\n } else if (typeof value === \"object\" && value !== null) {\n // Don't deep-sanitize nested objects, just indicate their type\n sanitized[key] = Array.isArray(value)\n ? `[Array(${value.length})]`\n : \"[Object]\";\n } else {\n sanitized[key] = value;\n }\n }\n return sanitized;\n }\n\n /**\n * Check if a field name matches sensitive patterns\n */\n private isSensitiveField(fieldName: string): boolean {\n return SENSITIVE_FIELD_PATTERNS.some((pattern) => pattern.test(fieldName));\n }\n}\n"],"mappings":";;AACA,MAAM,2BAA2B;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;;;;;;;;;;;;;;;AAuBD,IAAsB,cAAtB,cAA0C,MAAM;;CAW9C,AAAS;;CAGT,AAAS;;;;;;;;;;;;CAaT,AAAmB;CAEnB,YACE,SACA,SAKA;AACA,QAAM,QAAQ;AACd,OAAK,OAAO,KAAK,YAAY;AAC7B,OAAK,QAAQ,SAAS;AACtB,OAAK,UAAU,SAAS;AACxB,OAAK,iBAAiB,SAAS;AAG/B,MAAI,MAAM,kBACR,OAAM,kBAAkB,MAAM,KAAK,YAAY;;;;;;CAQnD,IAAI,gBAAwB;AAC1B,SAAO,KAAK,kBAAkB;;;;;;CAOhC,SAAkC;AAChC,SAAO;GACL,MAAM,KAAK;GACX,MAAM,KAAK;GACX,SAAS,KAAK;GACd,YAAY,KAAK;GACjB,aAAa,KAAK;GAClB,SAAS,KAAK,gBAAgB,KAAK,QAAQ;GAC3C,OAAO,KAAK,OAAO;GACnB,OAAO,KAAK;GACb;;;;;CAMH,WAAmB;EACjB,IAAI,MAAM,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK;AAC/C,MAAI,KAAK,MACP,QAAO,gBAAgB,KAAK,MAAM,QAAQ;AAE5C,SAAO;;;;;CAMT,AAAQ,gBACN,SACqC;AACrC,MAAI,CAAC,QAAS,QAAO;EAErB,MAAM,YAAqC,EAAE;AAC7C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,CAChD,KAAI,KAAK,iBAAiB,IAAI,CAC5B,WAAU,OAAO;WACR,OAAO,UAAU,YAAY,UAAU,KAEhD,WAAU,OAAO,MAAM,QAAQ,MAAM,GACjC,UAAU,MAAM,OAAO,MACvB;MAEJ,WAAU,OAAO;AAGrB,SAAO;;;;;CAMT,AAAQ,iBAAiB,WAA4B;AACnD,SAAO,yBAAyB,MAAM,YAAY,QAAQ,KAAK,UAAU,CAAC"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../../src/errors/base.ts"],"sourcesContent":["/** Fields that should be redacted in logs/serialization for security */\nconst SENSITIVE_FIELD_PATTERNS = [\n /token/i,\n /password/i,\n /secret/i,\n /credential/i,\n /auth/i,\n /key$/i,\n /apikey/i,\n];\n\n/**\n * Base error class for all AppKit errors.\n * Provides a consistent structure for error handling across the framework.\n *\n * @example\n * ```typescript\n * // Catching errors by type\n * try {\n * await lakebase.query(\"...\");\n * } catch (e) {\n * if (e instanceof AuthenticationError) {\n * // Re-authenticate\n * } else if (e instanceof ConnectionError && e.isRetryable) {\n * // Retry with backoff\n * }\n * }\n *\n * // Logging errors\n * console.error(error.toJSON()); // Safe for logging, sensitive values redacted\n * ```\n */\nexport abstract class AppKitError extends Error {\n /** Error code for programmatic error handling */\n abstract readonly code: string;\n\n /** HTTP status code suggestion (can be overridden) */\n abstract readonly statusCode: number;\n\n /** Whether this error type is generally safe to retry */\n abstract readonly isRetryable: boolean;\n\n /** Optional cause of the error */\n readonly cause?: Error;\n\n /** Additional context for the error */\n readonly context?: Record<string, unknown>;\n\n /**\n * Client-safe error message. When set, callers serializing the error to\n * a client (SSE, HTTP body) MUST prefer `clientMessage` over `message`.\n * `message` may contain raw upstream / SDK text including statement\n * fragments, internal object names, and correlation IDs.\n *\n * Subclasses can set this in their constructor for a fixed sanitized\n * string. When unset, `clientMessage` defaults to a generic per-code\n * string (see the getter), and the raw `message` is kept server-side\n * only.\n */\n protected readonly _clientMessage?: string;\n\n constructor(\n message: string,\n options?: {\n cause?: Error;\n context?: Record<string, unknown>;\n clientMessage?: string;\n },\n ) {\n super(message);\n this.name = this.constructor.name;\n this.cause = options?.cause;\n this.context = options?.context;\n this._clientMessage = options?.clientMessage;\n\n // Maintains proper stack trace for where the error was thrown\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n\n /**\n * Sanitized message safe to forward to clients. Override in subclasses\n * if a more specific default is appropriate.\n */\n get clientMessage(): string {\n return this._clientMessage ?? \"An internal error occurred\";\n }\n\n /**\n * Convert error to JSON for logging/serialization.\n * Sensitive values in context are automatically redacted.\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n isRetryable: this.isRetryable,\n context: this.sanitizeContext(this.context),\n cause: this.cause?.message,\n stack: this.stack,\n };\n }\n\n /**\n * Create a human-readable string representation\n */\n toString(): string {\n let str = `${this.name} [${this.code}]: ${this.message}`;\n if (this.cause) {\n str += ` (caused by: ${this.cause.message})`;\n }\n return str;\n }\n\n /**\n * Sanitize context by redacting sensitive field values\n */\n private sanitizeContext(\n context?: Record<string, unknown>,\n ): Record<string, unknown> | undefined {\n if (!context) return undefined;\n\n const sanitized: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(context)) {\n if (this.isSensitiveField(key)) {\n sanitized[key] = \"[REDACTED]\";\n } else if (typeof value === \"object\" && value !== null) {\n // Don't deep-sanitize nested objects, just indicate their type\n sanitized[key] = Array.isArray(value)\n ? `[Array(${value.length})]`\n : \"[Object]\";\n } else {\n sanitized[key] = value;\n }\n }\n return sanitized;\n }\n\n /**\n * Check if a field name matches sensitive patterns\n */\n private isSensitiveField(fieldName: string): boolean {\n return SENSITIVE_FIELD_PATTERNS.some((pattern) => pattern.test(fieldName));\n }\n}\n"],"mappings":";;AACA,MAAM,2BAA2B;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;;;;;;;;;;;;;;;AAuBD,IAAsB,cAAtB,cAA0C,MAAM;;CAW9C,AAAS;;CAGT,AAAS;;;;;;;;;;;;CAaT,AAAmB;CAEnB,YACE,SACA,SAKA;AACA,QAAM,QAAQ;AACd,OAAK,OAAO,KAAK,YAAY;AAC7B,OAAK,QAAQ,SAAS;AACtB,OAAK,UAAU,SAAS;AACxB,OAAK,iBAAiB,SAAS;AAG/B,MAAI,MAAM,kBACR,OAAM,kBAAkB,MAAM,KAAK,YAAY;;;;;;CAQnD,IAAI,gBAAwB;AAC1B,SAAO,KAAK,kBAAkB;;;;;;CAOhC,SAAkC;AAChC,SAAO;GACL,MAAM,KAAK;GACX,MAAM,KAAK;GACX,SAAS,KAAK;GACd,YAAY,KAAK;GACjB,aAAa,KAAK;GAClB,SAAS,KAAK,gBAAgB,KAAK,QAAQ;GAC3C,OAAO,KAAK,OAAO;GACnB,OAAO,KAAK;GACb;;;;;CAMH,WAAmB;EACjB,IAAI,MAAM,GAAG,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,KAAK;AAC/C,MAAI,KAAK,MACP,QAAO,gBAAgB,KAAK,MAAM,QAAQ;AAE5C,SAAO;;;;;CAMT,AAAQ,gBACN,SACqC;AACrC,MAAI,CAAC,QAAS,QAAO;EAErB,MAAM,YAAqC,EAAE;AAC7C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,CAChD,KAAI,KAAK,iBAAiB,IAAI,CAC5B,WAAU,OAAO;WACR,OAAO,UAAU,YAAY,UAAU,KAEhD,WAAU,OAAO,MAAM,QAAQ,MAAM,GACjC,UAAU,MAAM,OAAO,MACvB;MAEJ,WAAU,OAAO;AAGrB,SAAO;;;;;CAMT,AAAQ,iBAAiB,WAA4B;AACnD,SAAO,yBAAyB,MAAM,YAAY,QAAQ,KAAK,UAAU,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { AppKitError } from "./base.js";
2
+
3
+ //#region src/errors/identity-expired.d.ts
4
+ /** The downstream service rejected the active caller's credentials. */
5
+ declare class IdentityExpiredError extends AppKitError {
6
+ readonly tokenFingerprint?: string | undefined;
7
+ readonly code = "IDENTITY_EXPIRED";
8
+ readonly statusCode = 401;
9
+ readonly isRetryable = false;
10
+ constructor(tokenFingerprint?: string | undefined);
11
+ }
12
+ //#endregion
13
+ export { IdentityExpiredError };
14
+ //# sourceMappingURL=identity-expired.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-expired.d.ts","names":[],"sources":["../../src/errors/identity-expired.ts"],"mappings":";;;;cAGa,oBAAA,SAA6B,WAAA;EAAA,SAKnB,gBAAA;EAAA,SAJZ,IAAA;EAAA,SACA,UAAA;EAAA,SACA,WAAA;cAEY,gBAAA;AAAA"}
@@ -0,0 +1,33 @@
1
+ import { AppKitError } from "./base.js";
2
+
3
+ //#region src/errors/identity-expired.ts
4
+ /** The downstream service rejected the active caller's credentials. */
5
+ var IdentityExpiredError = class extends AppKitError {
6
+ code = "IDENTITY_EXPIRED";
7
+ statusCode = 401;
8
+ isRetryable = false;
9
+ constructor(tokenFingerprint) {
10
+ const message = "Caller credentials were rejected or expired. Reauthenticate and retry with a fresh user token.";
11
+ super(message, {
12
+ clientMessage: message,
13
+ context: { fingerprint: tokenFingerprint }
14
+ });
15
+ this.tokenFingerprint = tokenFingerprint;
16
+ }
17
+ };
18
+ /** Recognize structured HTTP failures, including connector cause wrappers. */
19
+ function isUnauthorized(error) {
20
+ const seen = /* @__PURE__ */ new Set();
21
+ let current = error;
22
+ while (current && typeof current === "object" && !seen.has(current)) {
23
+ seen.add(current);
24
+ const value = current;
25
+ if (!(current instanceof AppKitError) && (value.status === 401 || value.statusCode === 401 || value.response?.status === 401)) return true;
26
+ current = value.cause;
27
+ }
28
+ return false;
29
+ }
30
+
31
+ //#endregion
32
+ export { IdentityExpiredError, isUnauthorized };
33
+ //# sourceMappingURL=identity-expired.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity-expired.js","names":[],"sources":["../../src/errors/identity-expired.ts"],"sourcesContent":["import { AppKitError } from \"./base\";\n\n/** The downstream service rejected the active caller's credentials. */\nexport class IdentityExpiredError extends AppKitError {\n readonly code = \"IDENTITY_EXPIRED\";\n readonly statusCode = 401;\n readonly isRetryable = false;\n\n constructor(readonly tokenFingerprint?: string) {\n const message =\n \"Caller credentials were rejected or expired. Reauthenticate and retry with a fresh user token.\";\n // Do not retain upstream errors: SDK errors may contain authorization headers.\n super(message, {\n clientMessage: message,\n context: { fingerprint: tokenFingerprint },\n });\n }\n}\n\n/** Recognize structured HTTP failures, including connector cause wrappers. */\nexport function isUnauthorized(error: unknown): boolean {\n const seen = new Set<object>();\n let current = error;\n while (current && typeof current === \"object\" && !seen.has(current)) {\n seen.add(current);\n const value = current as {\n status?: unknown;\n statusCode?: unknown;\n response?: { status?: unknown };\n cause?: unknown;\n };\n if (\n !(current instanceof AppKitError) &&\n (value.status === 401 ||\n value.statusCode === 401 ||\n value.response?.status === 401)\n )\n return true;\n current = value.cause;\n }\n return false;\n}\n"],"mappings":";;;;AAGA,IAAa,uBAAb,cAA0C,YAAY;CACpD,AAAS,OAAO;CAChB,AAAS,aAAa;CACtB,AAAS,cAAc;CAEvB,YAAY,AAAS,kBAA2B;EAC9C,MAAM,UACJ;AAEF,QAAM,SAAS;GACb,eAAe;GACf,SAAS,EAAE,aAAa,kBAAkB;GAC3C,CAAC;EAPiB;;;;AAYvB,SAAgB,eAAe,OAAyB;CACtD,MAAM,uBAAO,IAAI,KAAa;CAC9B,IAAI,UAAU;AACd,QAAO,WAAW,OAAO,YAAY,YAAY,CAAC,KAAK,IAAI,QAAQ,EAAE;AACnE,OAAK,IAAI,QAAQ;EACjB,MAAM,QAAQ;AAMd,MACE,EAAE,mBAAmB,iBACpB,MAAM,WAAW,OAChB,MAAM,eAAe,OACrB,MAAM,UAAU,WAAW,KAE7B,QAAO;AACT,YAAU,MAAM;;AAElB,QAAO"}
@@ -1,5 +1,6 @@
1
1
  import { AppKitError } from "./base.js";
2
2
  import { AuthenticationError } from "./authentication.js";
3
+ import { IdentityExpiredError } from "./identity-expired.js";
3
4
  import { ConfigurationError } from "./configuration.js";
4
5
  import { ConnectionError } from "./connection.js";
5
6
  import { DatabaseValidationError } from "./database-validation.js";
package/dist/index.d.ts CHANGED
@@ -1,22 +1,24 @@
1
1
  import { StreamExecutionSettings } from "./shared/src/execute.js";
2
2
  import { ResourceFieldEntry } from "./shared/src/schemas/manifest.js";
3
- import { BasePluginConfig, IAppRouter, PluginData, ToPlugin } from "./shared/src/plugin.js";
3
+ import { AppKitApi, BasePluginConfig, IAppRouter, PluginData, ScopedPluginMap, ToPlugin, UserScopedApp } from "./shared/src/plugin.js";
4
4
  import { CacheConfig } from "./shared/src/cache.js";
5
5
  import { isSQLTypeMarker, sql } from "./shared/src/sql/helpers.js";
6
6
  import { ApiError } from "./shared/src/workspace-client/errors.js";
7
7
  import { createWorkspaceClient } from "./shared/src/workspace-client/factory.js";
8
8
  import "./shared/src/index.js";
9
9
  import { WorkspaceClient, WorkspaceClientOptions } from "./workspace-client/index.js";
10
+ import { UserContext, isUserContext } from "./context/user-context.js";
11
+ import { ServiceContext } from "./context/service-context.js";
12
+ import { CallerContext, CallerPrincipal, ExecutionContext } from "./context/caller-context.js";
13
+ import { getCallerContext, getCurrentActorId, getCurrentPrincipalKey, getCurrentUserId, getExecutionContext, getUserContext, isInUserContext, runInCallerContext, runInUserContext } from "./context/execution-context.js";
14
+ import "./context/index.js";
10
15
  import { DatabaseRegistry } from "./database/contract/registry.js";
11
16
  import { CacheManager } from "./cache/index.js";
12
17
  import { JobsConnectorConfig } from "./connectors/jobs/types.js";
13
18
  import "./connectors/jobs/index.js";
14
19
  import { LakebasePoolManager, createLakebasePoolManager } from "./connectors/lakebase/pool-manager.js";
15
- import { CallerContext, CallerPrincipal, ExecutionContext } from "./context/caller-context.js";
16
20
  import { LakebasePool } from "./connectors/lakebase/routing-pool.js";
17
21
  import { DatabaseCredential, GenerateDatabaseCredentialRequest, LakebasePoolConfig, RequestedClaims, RequestedClaimsPermissionSet, RequestedResource, createLakebasePool, generateDatabaseCredential, getLakebaseOrmConfig, getLakebasePgConfig, getUsernameWithApiLookup, getWorkspaceClient } from "./connectors/lakebase/index.js";
18
- import { getCurrentActorId, getCurrentPrincipalKey, getExecutionContext } from "./context/execution-context.js";
19
- import "./context/index.js";
20
22
  import { ITelemetry, TelemetryConfig } from "./telemetry/types.js";
21
23
  import { Counter, Histogram, SeverityNumber, Span, SpanStatusCode } from "./telemetry/index.js";
22
24
  import { createApp } from "./core/appkit.js";
@@ -27,6 +29,7 @@ import { getWarehouseId } from "./resources/warehouse.js";
27
29
  import "./resources/index.js";
28
30
  import { AppKitError } from "./errors/base.js";
29
31
  import { AuthenticationError } from "./errors/authentication.js";
32
+ import { IdentityExpiredError } from "./errors/identity-expired.js";
30
33
  import { ConfigurationError } from "./errors/configuration.js";
31
34
  import { ConnectionError } from "./errors/connection.js";
32
35
  import { DatabaseValidationError, DatabaseValidationIssue } from "./errors/database-validation.js";
@@ -57,4 +60,4 @@ import "./plugins/ga-exports.generated.js";
57
60
  import { extractServingEndpoints, findServerFile } from "./type-generator/serving/server-file-extractor.js";
58
61
  import { appKitServingTypesPlugin } from "./type-generator/serving/vite-plugin.js";
59
62
  import { appKitTypesPlugin } from "./type-generator/vite-plugin.js";
60
- export { ApiError, AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type CallerContext, type CallerPrincipal, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, type DatabaseCredential, type DatabaseRegistry, DatabaseValidationError, type DatabaseValidationIssue, type EndpointConfig, type ExecutionContext, ExecutionError, type ExecutionResult, type FileAction, type FilePolicy, type FilePolicyUser, type FileResource, type GenerateDatabaseCredentialRequest, type Histogram, type IAppRouter, type IJobsConfig, type ITelemetry, InitializationError, type JobAPI, type JobConfig, type JobsConnectorConfig, type JobsExport, type LakebasePool, type LakebasePoolConfig, type LakebasePoolManager, Plugin, type PluginData, type PluginManifest, PolicyDeniedError, READ_ACTIONS, type RequestedClaims, RequestedClaimsPermissionSet, type RequestedResource, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, ServerError, type ServingEndpointEntry, type ServingEndpointRegistry, type ServingFactory, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, type ToPlugin, TunnelError, ValidationError, type ValidationResult, WRITE_ACTIONS, type WorkspaceClient, type WorkspaceClientOptions, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, createLakebasePoolManager, createWorkspaceClient, defineManifest, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getCurrentActorId, getCurrentPrincipalKey, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWarehouseId, getWorkspaceClient, isSQLTypeMarker, jobs, lakebase, server, serving, sql, toPlugin };
63
+ export { ApiError, type AppKitApi, AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, type CallerContext, type CallerPrincipal, type ConfigSchema, ConfigurationError, ConnectionError, type Counter, type DatabaseCredential, type DatabaseRegistry, DatabaseValidationError, type DatabaseValidationIssue, type EndpointConfig, type ExecutionContext, ExecutionError, type ExecutionResult, type FileAction, type FilePolicy, type FilePolicyUser, type FileResource, type GenerateDatabaseCredentialRequest, type Histogram, type IAppRouter, type IJobsConfig, type ITelemetry, IdentityExpiredError, InitializationError, type JobAPI, type JobConfig, type JobsConnectorConfig, type JobsExport, type LakebasePool, type LakebasePoolConfig, type LakebasePoolManager, Plugin, type PluginData, type PluginManifest, PolicyDeniedError, READ_ACTIONS, type RequestedClaims, RequestedClaimsPermissionSet, type RequestedResource, type ResourceEntry, type ResourceFieldEntry, type ResourcePermission, ResourceRegistry, type ResourceRequirement, ResourceType, type ScopedPluginMap, ServerError, ServiceContext, type ServingEndpointEntry, type ServingEndpointRegistry, type ServingFactory, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, type ToPlugin, TunnelError, type UserContext, type UserScopedApp, ValidationError, type ValidationResult, WRITE_ACTIONS, type WorkspaceClient, type WorkspaceClientOptions, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, createLakebasePoolManager, createWorkspaceClient, defineManifest, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getCallerContext, getCurrentActorId, getCurrentPrincipalKey, getCurrentUserId, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUserContext, getUsernameWithApiLookup, getWarehouseId, getWorkspaceClient, isInUserContext, isSQLTypeMarker, isUserContext, jobs, lakebase, runInCallerContext, runInUserContext, server, serving, sql, toPlugin };
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { ApiError } from "./shared/src/workspace-client/errors.js";
3
3
  import { createWorkspaceClient } from "./shared/src/workspace-client/factory.js";
4
4
  import { AppKitError } from "./errors/base.js";
5
5
  import { AuthenticationError } from "./errors/authentication.js";
6
+ import { IdentityExpiredError } from "./errors/identity-expired.js";
6
7
  import { ConfigurationError } from "./errors/configuration.js";
7
8
  import { ConnectionError } from "./errors/connection.js";
8
9
  import { DatabaseValidationError } from "./errors/database-validation.js";
@@ -13,8 +14,10 @@ import { TunnelError } from "./errors/tunnel.js";
13
14
  import { ValidationError } from "./errors/validation.js";
14
15
  import "./errors/index.js";
15
16
  import { getWarehouseId } from "./resources/warehouse.js";
17
+ import { isUserContext } from "./context/user-context.js";
18
+ import { ServiceContext } from "./context/service-context.js";
16
19
  import { createLakebasePoolManager } from "./connectors/lakebase/pool-manager.js";
17
- import { getCurrentActorId, getCurrentPrincipalKey, getExecutionContext } from "./context/execution-context.js";
20
+ import { getCallerContext, getCurrentActorId, getCurrentPrincipalKey, getCurrentUserId, getExecutionContext, getUserContext, isInUserContext, runInCallerContext, runInUserContext } from "./context/execution-context.js";
18
21
  import { RequestedClaimsPermissionSet, createLakebasePool, generateDatabaseCredential, getLakebaseOrmConfig, getLakebasePgConfig, getUsernameWithApiLookup, getWorkspaceClient } from "./connectors/lakebase/index.js";
19
22
  import { SeverityNumber, SpanStatusCode } from "./telemetry/index.js";
20
23
  import { CacheManager } from "./cache/index.js";
@@ -42,4 +45,4 @@ import { server } from "./plugins/server/index.js";
42
45
  import { serving } from "./plugins/serving/serving.js";
43
46
  import "./plugins/ga-exports.generated.js";
44
47
 
45
- export { ApiError, AppKitError, AuthenticationError, CacheManager, ConfigurationError, ConnectionError, DatabaseValidationError, ExecutionError, InitializationError, Plugin, PolicyDeniedError, READ_ACTIONS, RequestedClaimsPermissionSet, ResourceRegistry, ResourceType, ServerError, SeverityNumber, SpanStatusCode, TunnelError, ValidationError, WRITE_ACTIONS, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, createLakebasePoolManager, createWorkspaceClient, defineManifest, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getCurrentActorId, getCurrentPrincipalKey, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUsernameWithApiLookup, getWarehouseId, getWorkspaceClient, isSQLTypeMarker, jobs, lakebase, server, serving, sql, toPlugin };
48
+ export { ApiError, AppKitError, AuthenticationError, CacheManager, ConfigurationError, ConnectionError, DatabaseValidationError, ExecutionError, IdentityExpiredError, InitializationError, Plugin, PolicyDeniedError, READ_ACTIONS, RequestedClaimsPermissionSet, ResourceRegistry, ResourceType, ServerError, ServiceContext, SeverityNumber, SpanStatusCode, TunnelError, ValidationError, WRITE_ACTIONS, analytics, appKitServingTypesPlugin, appKitTypesPlugin, createApp, createLakebasePool, createLakebasePoolManager, createWorkspaceClient, defineManifest, extractServingEndpoints, files, findServerFile, generateDatabaseCredential, genie, getCallerContext, getCurrentActorId, getCurrentPrincipalKey, getCurrentUserId, getExecutionContext, getLakebaseOrmConfig, getLakebasePgConfig, getPluginManifest, getResourceRequirements, getUserContext, getUsernameWithApiLookup, getWarehouseId, getWorkspaceClient, isInUserContext, isSQLTypeMarker, isUserContext, jobs, lakebase, runInCallerContext, runInUserContext, server, serving, sql, toPlugin };
@@ -1,8 +1,8 @@
1
1
  import { DEFAULT_SAMPLING_CONFIG, shouldSample } from "./sampling.js";
2
2
  import { WideEvent } from "./wide-event.js";
3
3
  import { WideEventEmitter } from "./wide-event-emitter.js";
4
- import { AsyncLocalStorage } from "node:async_hooks";
5
4
  import { format } from "node:util";
5
+ import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { trace } from "@opentelemetry/api";
7
7
  import { createDebug } from "obug";
8
8
 
@@ -1,3 +1,5 @@
1
+ import { IdentityExpiredError } from "../errors/identity-expired.js";
2
+
1
3
  //#region src/plugin/execution-result.d.ts
2
4
  /**
3
5
  * Discriminated union for plugin execution results.
@@ -19,7 +21,8 @@ type ExecutionResult<T> = {
19
21
  } | {
20
22
  ok: false;
21
23
  status: number;
22
- message: string;
24
+ message: string; /** Typed credential expiry without changing the existing failure envelope. */
25
+ error?: IdentityExpiredError;
23
26
  };
24
27
  //#endregion
25
28
  export { ExecutionResult };
@@ -1 +1 @@
1
- {"version":3,"file":"execution-result.d.ts","names":[],"sources":["../../src/plugin/execution-result.ts"],"mappings":";;AAcA;;;;;;;;;;;;;KAAY,eAAA;EACN,EAAA;EAAU,IAAA,EAAM,CAAA;AAAA;EAChB,EAAA;EAAW,MAAA;EAAgB,OAAA;AAAA"}
1
+ {"version":3,"file":"execution-result.d.ts","names":[],"sources":["../../src/plugin/execution-result.ts"],"mappings":";;;;;AAgBA;;;;;;;;;;;;KAAY,eAAA;EACN,EAAA;EAAU,IAAA,EAAM,CAAA;AAAA;EAEhB,EAAA;EACA,MAAA;EACA,OAAA;EAEA,KAAA,GAAQ,oBAAA;AAAA"}