@databricks/appkit 0.82.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.
- package/CLAUDE.md +21 -1
- package/dist/agents/databricks.d.ts +15 -3
- package/dist/agents/databricks.d.ts.map +1 -1
- package/dist/agents/databricks.js +25 -8
- package/dist/agents/databricks.js.map +1 -1
- package/dist/appkit/package.js +1 -1
- package/dist/beta.d.ts +2 -2
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +6 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/cli/commands/agent/eval.js +1 -1
- package/dist/cli/commands/generate-types.js +1 -1
- package/dist/cli/commands/plugin/sync/sync.js +28 -15
- package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
- package/dist/cli/commands/registry/add.js +3 -11
- package/dist/cli/commands/registry/add.js.map +1 -1
- package/dist/cli/commands/registry/config-writer.js +1 -1
- package/dist/connectors/lakebase/index.js +1 -1
- package/dist/connectors/lakebase/routing-pool.d.ts +1 -1
- package/dist/connectors/lakebase/routing-pool.d.ts.map +1 -1
- package/dist/connectors/lakebase/routing-pool.js +6 -12
- package/dist/connectors/lakebase/routing-pool.js.map +1 -1
- package/dist/context/caller-context.d.ts +25 -0
- package/dist/context/caller-context.d.ts.map +1 -0
- package/dist/context/caller-context.js +21 -0
- package/dist/context/caller-context.js.map +1 -0
- package/dist/context/deprecation.js +14 -0
- package/dist/context/deprecation.js.map +1 -0
- package/dist/context/execution-context.d.ts +40 -4
- package/dist/context/execution-context.d.ts.map +1 -1
- package/dist/context/execution-context.js +87 -33
- package/dist/context/execution-context.js.map +1 -1
- package/dist/context/index.d.ts +3 -2
- package/dist/context/index.js +3 -1
- package/dist/context/request-scope.d.ts +2 -0
- package/dist/context/request-scope.js +39 -0
- package/dist/context/request-scope.js.map +1 -0
- package/dist/context/resource-capabilities.js +59 -0
- package/dist/context/resource-capabilities.js.map +1 -0
- package/dist/context/scoped-api.js +104 -0
- package/dist/context/scoped-api.js.map +1 -0
- package/dist/context/service-context.d.ts +18 -12
- package/dist/context/service-context.d.ts.map +1 -1
- package/dist/context/service-context.js +45 -55
- package/dist/context/service-context.js.map +1 -1
- package/dist/context/user-context.d.ts +16 -22
- package/dist/context/user-context.d.ts.map +1 -1
- package/dist/context/user-context.js +57 -3
- package/dist/context/user-context.js.map +1 -1
- package/dist/core/agent/load-agents.d.ts.map +1 -1
- package/dist/core/agent/load-agents.js +5 -2
- package/dist/core/agent/load-agents.js.map +1 -1
- package/dist/core/agent/run-agent.d.ts +19 -6
- package/dist/core/agent/run-agent.d.ts.map +1 -1
- package/dist/core/agent/run-agent.js +53 -17
- package/dist/core/agent/run-agent.js.map +1 -1
- package/dist/core/agent/types.d.ts +18 -1
- package/dist/core/agent/types.d.ts.map +1 -1
- package/dist/core/agent/types.js.map +1 -1
- package/dist/core/appkit.d.ts +4 -3
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js +34 -11
- package/dist/core/appkit.js.map +1 -1
- package/dist/core/plugin-context.d.ts +14 -15
- package/dist/core/plugin-context.d.ts.map +1 -1
- package/dist/core/plugin-context.js +24 -14
- package/dist/core/plugin-context.js.map +1 -1
- package/dist/errors/base.d.ts +2 -2
- package/dist/errors/base.js +2 -2
- package/dist/errors/base.js.map +1 -1
- package/dist/errors/identity-expired.d.ts +14 -0
- package/dist/errors/identity-expired.d.ts.map +1 -0
- package/dist/errors/identity-expired.js +33 -0
- package/dist/errors/identity-expired.js.map +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/index.d.ts +12 -6
- package/dist/index.js +7 -2
- package/dist/logging/logger.js +1 -1
- package/dist/plugin/execution-result.d.ts +4 -1
- package/dist/plugin/execution-result.d.ts.map +1 -1
- package/dist/plugin/interceptors/telemetry.js +6 -4
- package/dist/plugin/interceptors/telemetry.js.map +1 -1
- package/dist/plugin/plugin.d.ts +14 -22
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +42 -123
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugins/agents/agents.d.ts +13 -1
- package/dist/plugins/agents/agents.d.ts.map +1 -1
- package/dist/plugins/agents/agents.js +65 -15
- package/dist/plugins/agents/agents.js.map +1 -1
- package/dist/plugins/agents/auth-mode.js +42 -0
- package/dist/plugins/agents/auth-mode.js.map +1 -0
- package/dist/plugins/agents/index.d.ts +1 -1
- package/dist/plugins/agents/mlflow.js +11 -3
- package/dist/plugins/agents/mlflow.js.map +1 -1
- package/dist/plugins/agents/tool-dispatch.js +9 -1
- package/dist/plugins/agents/tool-dispatch.js.map +1 -1
- package/dist/plugins/ai-search/ai-search.d.ts +1 -1
- package/dist/plugins/ai-search/ai-search.d.ts.map +1 -1
- package/dist/plugins/ai-search/ai-search.js +4 -4
- package/dist/plugins/ai-search/ai-search.js.map +1 -1
- package/dist/plugins/analytics/analytics.d.ts +3 -3
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js +9 -7
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/database/crud/contract.js +2 -2
- package/dist/plugins/database/crud/contract.js.map +1 -1
- package/dist/plugins/database/database.d.ts +1 -1
- package/dist/plugins/files/plugin.d.ts +21 -15
- package/dist/plugins/files/plugin.d.ts.map +1 -1
- package/dist/plugins/files/plugin.js +50 -29
- package/dist/plugins/files/plugin.js.map +1 -1
- package/dist/plugins/genie/genie.d.ts +10 -1
- package/dist/plugins/genie/genie.d.ts.map +1 -1
- package/dist/plugins/genie/genie.js +12 -3
- package/dist/plugins/genie/genie.js.map +1 -1
- package/dist/plugins/genie/manifest.js +1 -0
- package/dist/plugins/jobs/plugin.d.ts +1 -1
- package/dist/plugins/jobs/plugin.js +2 -2
- package/dist/plugins/jobs/plugin.js.map +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts +11 -18
- package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
- package/dist/plugins/lakebase/lakebase.js +16 -21
- package/dist/plugins/lakebase/lakebase.js.map +1 -1
- package/dist/plugins/server/client-config-sanitizer.js +1 -4
- package/dist/plugins/server/client-config-sanitizer.js.map +1 -1
- package/dist/plugins/server/dev-obo-middleware.js +60 -0
- package/dist/plugins/server/dev-obo-middleware.js.map +1 -0
- package/dist/plugins/server/index.d.ts +1 -1
- package/dist/plugins/server/index.d.ts.map +1 -1
- package/dist/plugins/server/index.js +3 -0
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/serving/manifest.js +1 -0
- package/dist/plugins/serving/serving.d.ts +1 -1
- package/dist/plugins/serving/serving.js +6 -6
- package/dist/plugins/serving/serving.js.map +1 -1
- package/dist/resources/index.d.ts +1 -0
- package/dist/resources/index.js +3 -0
- package/dist/resources/warehouse.d.ts +16 -0
- package/dist/resources/warehouse.d.ts.map +1 -0
- package/dist/resources/warehouse.js +93 -0
- package/dist/resources/warehouse.js.map +1 -0
- package/dist/schemas/manifest.d.ts +44 -1
- package/dist/schemas/manifest.d.ts.map +1 -1
- package/dist/schemas/manifest.js +122 -3
- package/dist/schemas/manifest.js.map +1 -1
- package/dist/shared/src/dev-obo.js +86 -0
- package/dist/shared/src/dev-obo.js.map +1 -0
- package/dist/shared/src/index.d.ts +1 -1
- package/dist/shared/src/plugin.d.ts +12 -1
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/dist/shared/src/schemas/manifest.d.ts +17 -0
- package/dist/shared/src/schemas/manifest.d.ts.map +1 -1
- package/dist/shared/src/schemas/manifest.js +53 -3
- package/dist/shared/src/schemas/manifest.js.map +1 -1
- package/dist/stream/stream-manager.d.ts.map +1 -1
- package/dist/stream/stream-manager.js +5 -2
- package/dist/stream/stream-manager.js.map +1 -1
- package/dist/telemetry/execution-span-processor.js +21 -0
- package/dist/telemetry/execution-span-processor.js.map +1 -0
- package/dist/telemetry/telemetry-manager.js +2 -1
- package/dist/telemetry/telemetry-manager.js.map +1 -1
- package/dist/testing/create-test-app.d.ts +2 -2
- package/dist/testing/create-test-app.js +2 -2
- package/dist/testing/create-test-app.js.map +1 -1
- package/dist/testing/fixtures.d.ts +3 -2
- package/dist/testing/fixtures.d.ts.map +1 -1
- package/dist/testing/fixtures.js +21 -14
- package/dist/testing/fixtures.js.map +1 -1
- package/dist/testing/reset-singletons.js +1 -1
- package/dist/testing/test-plugin-context.d.ts +3 -12
- package/dist/testing/test-plugin-context.d.ts.map +1 -1
- package/dist/testing/test-plugin-context.js +24 -16
- package/dist/testing/test-plugin-context.js.map +1 -1
- package/dist/utils/is-plain-object.js +11 -0
- package/dist/utils/is-plain-object.js.map +1 -0
- package/docs/api/appkit/Class.AppKitError.md +2 -1
- package/docs/api/appkit/Class.AuthenticationError.md +1 -1
- package/docs/api/appkit/Class.ConfigurationError.md +1 -1
- package/docs/api/appkit/Class.ConnectionError.md +1 -1
- package/docs/api/appkit/Class.DatabaseValidationError.md +1 -1
- package/docs/api/appkit/Class.ExecutionError.md +1 -1
- package/docs/api/appkit/Class.IdentityExpiredError.md +190 -0
- package/docs/api/appkit/Class.InitializationError.md +1 -1
- package/docs/api/appkit/Class.Plugin.md +9 -13
- package/docs/api/appkit/Class.ServerError.md +1 -1
- package/docs/api/appkit/Class.ServiceContext.md +169 -0
- package/docs/api/appkit/Class.TunnelError.md +1 -1
- package/docs/api/appkit/Class.ValidationError.md +1 -1
- package/docs/api/appkit/Function.createApp.md +12 -12
- package/docs/api/appkit/Function.getCallerContext.md +12 -0
- package/docs/api/appkit/Function.getCurrentActorId.md +12 -0
- package/docs/api/appkit/Function.getCurrentPrincipalKey.md +12 -0
- package/docs/api/appkit/Function.getCurrentUserId.md +14 -0
- package/docs/api/appkit/Function.getExecutionContext.md +5 -3
- package/docs/api/appkit/Function.getUserContext.md +16 -0
- package/docs/api/appkit/Function.getWarehouseId.md +20 -0
- package/docs/api/appkit/Function.isInUserContext.md +12 -0
- package/docs/api/appkit/Function.isUserContext.md +20 -0
- package/docs/api/appkit/Function.runAgent.md +1 -1
- package/docs/api/appkit/Function.runInCallerContext.md +27 -0
- package/docs/api/appkit/Function.runInUserContext.md +29 -0
- package/docs/api/appkit/Interface.AgentDefinition.md +11 -0
- package/docs/api/appkit/Interface.AgentsPluginConfig.md +11 -0
- package/docs/api/appkit/Interface.CallerContext.md +54 -0
- package/docs/api/appkit/Interface.IndexConfig.md +1 -1
- package/docs/api/appkit/Interface.PluginManifest.md +33 -2
- package/docs/api/appkit/Interface.RegisteredAgent.md +11 -0
- package/docs/api/appkit/Interface.RunAgentInput.md +45 -1
- package/docs/api/appkit/TypeAlias.AgentAuth.md +8 -0
- package/docs/api/appkit/TypeAlias.AppKitApi.md +35 -0
- package/docs/api/appkit/TypeAlias.CallerPrincipal.md +13 -0
- package/docs/api/appkit/TypeAlias.ExecutionContext.md +9 -0
- package/docs/api/appkit/TypeAlias.ExecutionResult.md +65 -0
- package/docs/api/appkit/TypeAlias.ScopedPluginMap.md +12 -0
- package/docs/api/appkit/TypeAlias.UserContext.md +109 -0
- package/docs/api/appkit/TypeAlias.UserScopedApp.md +39 -0
- package/docs/api/appkit.md +20 -0
- package/docs/plugins/agents.md +45 -0
- package/docs/plugins/analytics.md +8 -0
- package/docs/plugins/execution-context.md +101 -50
- package/docs/plugins/lakebase.md +6 -82
- package/llms.txt +21 -1
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"genie.js","names":["manifest"],"sources":["../../../src/plugins/genie/genie.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\n\nimport type express from \"express\";\nimport type {\n AgentToolDefinition,\n IAppRouter,\n StreamExecutionSettings,\n ToolProvider,\n} from \"shared\";\nimport { z } from \"zod\";\n\nimport { GenieConnector } from \"../../connectors\";\nimport { getWorkspaceClient } from \"../../context\";\nimport { buildToolkitEntries } from \"../../core/agent/build-toolkit\";\nimport {\n defineTool,\n executeFromRegistry,\n type ToolRegistry,\n toolsFromRegistry,\n} from \"../../core/agent/tools/define-tool\";\nimport { createLogger } from \"../../logging\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport { defineManifest } from \"../../registry\";\nimport { genieStreamDefaults } from \"./defaults\";\nimport manifest from \"./manifest.json\";\nimport type {\n GenieConversationHistoryResponse,\n GenieSendMessageRequest,\n GenieStreamEvent,\n IGenieConfig,\n} from \"./types\";\n\nconst logger = createLogger(\"genie\");\n\nexport class GeniePlugin extends Plugin implements ToolProvider {\n static manifest = defineManifest<\"genie\">(manifest);\n\n protected static description =\n \"AI/BI Genie space integration for natural language data queries\";\n declare protected config: IGenieConfig;\n\n private readonly genieConnector: GenieConnector;\n private tools: ToolRegistry = {};\n\n constructor(config: IGenieConfig) {\n super(config);\n this.config = {\n ...config,\n spaces: config.spaces ?? this.defaultSpaces(),\n };\n this.genieConnector = new GenieConnector({\n timeout: this.config.timeout,\n maxMessages: 200,\n });\n\n const spaces = this.config.spaces ?? {};\n const missingAliases = Object.entries(spaces)\n .filter(([, id]) => !id)\n .map(([alias]) => alias);\n if (missingAliases.length > 0) {\n const plural = missingAliases.length > 1;\n throw new Error(\n `GeniePlugin: space ${plural ? \"aliases\" : \"alias\"} ${missingAliases\n .map((a) => `\"${a}\"`)\n .join(\n \", \",\n )} ${plural ? \"were\" : \"was\"} configured with a missing Genie Space ID. ` +\n \"This usually means an environment variable used to populate the config is unset. \" +\n \"Set the env var, or remove the alias from the config.\",\n );\n }\n\n for (const alias of Object.keys(spaces)) {\n Object.assign(this.tools, this._defineSpaceTools(alias));\n }\n }\n\n /**\n * Builds the registry entries for a single Genie space alias.\n * One set of tools per configured space, keyed by `${alias}.${method}`.\n */\n private _defineSpaceTools(alias: string): ToolRegistry {\n return {\n [`${alias}.sendMessage`]: defineTool({\n description: `Send a natural language question to the Genie space \"${alias}\" and get data analysis results`,\n schema: z.object({\n content: z.string().describe(\"The natural language question to ask\"),\n conversationId: z\n .string()\n .optional()\n .describe(\n \"Optional conversation ID to continue an existing conversation\",\n ),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n execute: async (args, signal) => {\n const events: GenieStreamEvent[] = [];\n for await (const event of this.sendMessage(\n alias,\n args.content,\n args.conversationId,\n { signal },\n )) {\n events.push(event);\n }\n return events;\n },\n }),\n [`${alias}.getConversation`]: defineTool({\n description: `Retrieve the conversation history from the Genie space \"${alias}\"`,\n schema: z.object({\n conversationId: z\n .string()\n .describe(\"The conversation ID to retrieve\"),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n autoInheritable: true,\n execute: (args, signal) =>\n this.getConversation(alias, args.conversationId, signal),\n }),\n };\n }\n\n private defaultSpaces(): Record<string, string | undefined> {\n const spaceId = process.env.DATABRICKS_GENIE_SPACE_ID;\n return spaceId ? { default: spaceId } : {};\n }\n\n private resolveSpaceId(alias: string): string | null {\n return this.config.spaces?.[alias] ?? null;\n }\n\n injectRoutes(router: IAppRouter) {\n this.route(router, {\n name: \"sendMessage\",\n method: \"post\",\n path: \"/:alias/messages\",\n handler: async (req: express.Request, res: express.Response) => {\n await this.asUser(req)._handleSendMessage(req, res);\n },\n });\n\n this.route(router, {\n name: \"getConversation\",\n method: \"get\",\n path: \"/:alias/conversations/:conversationId\",\n handler: async (req: express.Request, res: express.Response) => {\n await this.asUser(req)._handleGetConversation(req, res);\n },\n });\n\n this.route(router, {\n name: \"getMessage\",\n method: \"get\",\n path: \"/:alias/conversations/:conversationId/messages/:messageId\",\n handler: async (req: express.Request, res: express.Response) => {\n await this.asUser(req)._handleGetMessage(req, res);\n },\n });\n }\n\n async _handleSendMessage(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { alias } = req.params;\n const spaceId = this.resolveSpaceId(alias);\n\n if (!spaceId) {\n res.status(404).json({ error: `Unknown space alias: ${alias}` });\n return;\n }\n\n const { content, conversationId } = req.body as GenieSendMessageRequest;\n\n if (!content) {\n res.status(400).json({ error: \"content is required\" });\n return;\n }\n\n logger.debug(\n \"Sending message to space %s (alias=%s, conversationId=%s)\",\n spaceId,\n alias,\n conversationId ?? \"new\",\n );\n\n const timeout = this.config.timeout ?? 120_000;\n const requestId =\n (typeof req.query.requestId === \"string\" && req.query.requestId) ||\n randomUUID();\n\n const streamSettings: StreamExecutionSettings = {\n ...genieStreamDefaults,\n default: {\n ...genieStreamDefaults.default,\n timeout,\n },\n stream: {\n ...genieStreamDefaults.stream,\n streamId: requestId,\n },\n };\n\n const workspaceClient = getWorkspaceClient();\n\n await this.executeStream<GenieStreamEvent>(\n res,\n (signal) =>\n this.genieConnector.streamSendMessage(\n workspaceClient,\n spaceId,\n content,\n conversationId,\n { timeout, signal },\n ),\n streamSettings,\n );\n }\n\n async _handleGetConversation(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { alias, conversationId } = req.params;\n const spaceId = this.resolveSpaceId(alias);\n\n if (!spaceId) {\n res.status(404).json({ error: `Unknown space alias: ${alias}` });\n return;\n }\n\n const includeQueryResults = req.query.includeQueryResults !== \"false\";\n const pageToken =\n typeof req.query.pageToken === \"string\" ? req.query.pageToken : undefined;\n const requestId =\n (typeof req.query.requestId === \"string\" && req.query.requestId) ||\n randomUUID();\n\n logger.debug(\n \"Fetching conversation %s from space %s (alias=%s, includeQueryResults=%s, pageToken=%s)\",\n conversationId,\n spaceId,\n alias,\n includeQueryResults,\n pageToken ?? \"none\",\n );\n\n const streamSettings: StreamExecutionSettings = {\n ...genieStreamDefaults,\n stream: {\n ...genieStreamDefaults.stream,\n streamId: requestId,\n },\n };\n\n const workspaceClient = getWorkspaceClient();\n\n await this.executeStream<GenieStreamEvent>(\n res,\n (signal) =>\n this.genieConnector.streamConversation(\n workspaceClient,\n spaceId,\n conversationId,\n { includeQueryResults, pageToken, signal },\n ),\n streamSettings,\n );\n }\n\n async _handleGetMessage(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { alias, conversationId, messageId } = req.params;\n const spaceId = this.resolveSpaceId(alias);\n\n if (!spaceId) {\n res.status(404).json({ error: `Unknown space alias: ${alias}` });\n return;\n }\n\n const requestId =\n (typeof req.query.requestId === \"string\" && req.query.requestId) ||\n randomUUID();\n\n logger.debug(\n \"Polling message %s in conversation %s from space %s (alias=%s)\",\n messageId,\n conversationId,\n spaceId,\n alias,\n );\n\n const timeout = this.config.timeout ?? 120_000;\n const streamSettings: StreamExecutionSettings = {\n ...genieStreamDefaults,\n default: {\n ...genieStreamDefaults.default,\n timeout,\n },\n stream: {\n ...genieStreamDefaults.stream,\n streamId: requestId,\n },\n };\n\n const workspaceClient = getWorkspaceClient();\n\n await this.executeStream<GenieStreamEvent>(\n res,\n (signal) =>\n this.genieConnector.streamGetMessage(\n workspaceClient,\n spaceId,\n conversationId,\n messageId,\n { timeout, signal },\n ),\n streamSettings,\n );\n }\n\n async getConversation(\n alias: string,\n conversationId: string,\n signal?: AbortSignal,\n ): Promise<GenieConversationHistoryResponse> {\n // Honour an already-cancelled stream before paying any I/O cost. The\n // underlying connector's pagination loop is signal-agnostic today, so\n // this catches the common case (tool dispatched after the user\n // cancelled) without a deeper connector change.\n signal?.throwIfAborted();\n const spaceId = this.resolveSpaceId(alias);\n\n if (!spaceId) {\n throw new Error(`Unknown space alias: ${alias}`);\n }\n\n const workspaceClient = getWorkspaceClient();\n\n return this.genieConnector.getConversation(\n workspaceClient,\n spaceId,\n conversationId,\n );\n }\n\n /**\n * Send a message and consume events as a stream (message_start, status,\n * message_result, query_result, error).\n */\n async *sendMessage(\n alias: string,\n content: string,\n conversationId?: string,\n options?: { timeout?: number; signal?: AbortSignal },\n ): AsyncGenerator<GenieStreamEvent> {\n options?.signal?.throwIfAborted();\n const spaceId = this.resolveSpaceId(alias);\n if (!spaceId) {\n throw new Error(`Unknown space alias: ${alias}`);\n }\n const workspaceClient = getWorkspaceClient();\n const timeout = options?.timeout ?? this.config.timeout ?? 120_000;\n yield* this.genieConnector.streamSendMessage(\n workspaceClient,\n spaceId,\n content,\n conversationId,\n { timeout, signal: options?.signal },\n );\n }\n\n async shutdown(): Promise<void> {\n this.streamManager.abortAll();\n }\n\n getAgentTools(): AgentToolDefinition[] {\n return toolsFromRegistry(this.tools);\n }\n\n async executeAgentTool(\n name: string,\n args: unknown,\n signal?: AbortSignal,\n ): Promise<unknown> {\n return executeFromRegistry(this.tools, name, args, signal);\n }\n\n toolkit(opts?: import(\"../../core/agent/types\").ToolkitOptions) {\n return buildToolkitEntries(this.name, this.tools, opts);\n }\n\n exports() {\n return {\n sendMessage: this.sendMessage,\n getConversation: this.getConversation,\n };\n }\n}\n\n/**\n * @internal\n */\nexport const genie = toPlugin(GeniePlugin);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"genie.js","names":["manifest"],"sources":["../../../src/plugins/genie/genie.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\n\nimport type express from \"express\";\nimport type {\n AgentToolDefinition,\n IAppRouter,\n StreamExecutionSettings,\n ToolProvider,\n} from \"shared\";\nimport { z } from \"zod\";\n\nimport { GenieConnector } from \"../../connectors\";\nimport { getWorkspaceClient } from \"../../context\";\nimport { buildToolkitEntries } from \"../../core/agent/build-toolkit\";\nimport {\n defineTool,\n executeFromRegistry,\n type ToolRegistry,\n toolsFromRegistry,\n} from \"../../core/agent/tools/define-tool\";\nimport { createLogger } from \"../../logging\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport { defineManifest } from \"../../registry\";\nimport { genieStreamDefaults } from \"./defaults\";\nimport manifest from \"./manifest.json\";\nimport type {\n GenieConversationHistoryResponse,\n GenieSendMessageRequest,\n GenieStreamEvent,\n IGenieConfig,\n} from \"./types\";\n\nconst logger = createLogger(\"genie\");\n\nexport class GeniePlugin extends Plugin implements ToolProvider {\n static manifest = defineManifest<\"genie\">(manifest);\n\n protected static description =\n \"AI/BI Genie space integration for natural language data queries\";\n declare protected config: IGenieConfig;\n\n private readonly genieConnector: GenieConnector;\n private tools: ToolRegistry = {};\n\n constructor(config: IGenieConfig) {\n super(config);\n this.config = {\n ...config,\n spaces: config.spaces ?? this.defaultSpaces(),\n };\n this.genieConnector = new GenieConnector({\n timeout: this.config.timeout,\n maxMessages: 200,\n });\n\n const spaces = this.config.spaces ?? {};\n const missingAliases = Object.entries(spaces)\n .filter(([, id]) => !id)\n .map(([alias]) => alias);\n if (missingAliases.length > 0) {\n const plural = missingAliases.length > 1;\n throw new Error(\n `GeniePlugin: space ${plural ? \"aliases\" : \"alias\"} ${missingAliases\n .map((a) => `\"${a}\"`)\n .join(\n \", \",\n )} ${plural ? \"were\" : \"was\"} configured with a missing Genie Space ID. ` +\n \"This usually means an environment variable used to populate the config is unset. \" +\n \"Set the env var, or remove the alias from the config.\",\n );\n }\n\n for (const alias of Object.keys(spaces)) {\n Object.assign(this.tools, this._defineSpaceTools(alias));\n }\n }\n\n /**\n * Builds the registry entries for a single Genie space alias.\n * One set of tools per configured space, keyed by `${alias}.${method}`.\n */\n private _defineSpaceTools(alias: string): ToolRegistry {\n return {\n [`${alias}.sendMessage`]: defineTool({\n description: `Send a natural language question to the Genie space \"${alias}\" and get data analysis results`,\n schema: z.object({\n content: z.string().describe(\"The natural language question to ask\"),\n conversationId: z\n .string()\n .optional()\n .describe(\n \"Optional conversation ID to continue an existing conversation\",\n ),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n execute: async (args, signal) => {\n const events: GenieStreamEvent[] = [];\n for await (const event of this.sendMessage(\n alias,\n args.content,\n args.conversationId,\n { signal },\n )) {\n events.push(event);\n }\n return events;\n },\n }),\n [`${alias}.getConversation`]: defineTool({\n description: `Retrieve the conversation history from the Genie space \"${alias}\"`,\n schema: z.object({\n conversationId: z\n .string()\n .describe(\"The conversation ID to retrieve\"),\n }),\n annotations: { effect: \"read\", requiresUserContext: true },\n autoInheritable: true,\n execute: (args, signal) =>\n this.getConversation(alias, args.conversationId, signal),\n }),\n };\n }\n\n private defaultSpaces(): Record<string, string | undefined> {\n const spaceId = process.env.DATABRICKS_GENIE_SPACE_ID;\n return spaceId ? { default: spaceId } : {};\n }\n\n private resolveSpaceId(alias: string): string | null {\n return this.config.spaces?.[alias] ?? null;\n }\n\n /**\n * Every route runs on behalf of the requesting user, whatever auth mode the\n * genie_space resource is bound with. That is why manifest.json declares the\n * plugin-level `scopes: [\"genie\"]`: the user token always needs the genie\n * scope, even when the space is bound to the service principal. If these\n * routes ever follow the resource's auth mode (as analytics does with\n * `.obo.sql`), drop that plugin-level scope and let the resource-level scope\n * from SCOPE_BY_TYPE cover it.\n */\n injectRoutes(router: IAppRouter) {\n this.route(router, {\n name: \"sendMessage\",\n method: \"post\",\n path: \"/:alias/messages\",\n handler: async (req: express.Request, res: express.Response) => {\n await this._asUserScoped(req)._handleSendMessage(req, res);\n },\n });\n\n this.route(router, {\n name: \"getConversation\",\n method: \"get\",\n path: \"/:alias/conversations/:conversationId\",\n handler: async (req: express.Request, res: express.Response) => {\n await this._asUserScoped(req)._handleGetConversation(req, res);\n },\n });\n\n this.route(router, {\n name: \"getMessage\",\n method: \"get\",\n path: \"/:alias/conversations/:conversationId/messages/:messageId\",\n handler: async (req: express.Request, res: express.Response) => {\n await this._asUserScoped(req)._handleGetMessage(req, res);\n },\n });\n }\n\n async _handleSendMessage(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { alias } = req.params;\n const spaceId = this.resolveSpaceId(alias);\n\n if (!spaceId) {\n res.status(404).json({ error: `Unknown space alias: ${alias}` });\n return;\n }\n\n const { content, conversationId } = req.body as GenieSendMessageRequest;\n\n if (!content) {\n res.status(400).json({ error: \"content is required\" });\n return;\n }\n\n logger.debug(\n \"Sending message to space %s (alias=%s, conversationId=%s)\",\n spaceId,\n alias,\n conversationId ?? \"new\",\n );\n\n const timeout = this.config.timeout ?? 120_000;\n const requestId =\n (typeof req.query.requestId === \"string\" && req.query.requestId) ||\n randomUUID();\n\n const streamSettings: StreamExecutionSettings = {\n ...genieStreamDefaults,\n default: {\n ...genieStreamDefaults.default,\n timeout,\n },\n stream: {\n ...genieStreamDefaults.stream,\n streamId: requestId,\n },\n };\n\n const workspaceClient = getWorkspaceClient();\n\n await this.executeStream<GenieStreamEvent>(\n res,\n (signal) =>\n this.genieConnector.streamSendMessage(\n workspaceClient,\n spaceId,\n content,\n conversationId,\n { timeout, signal },\n ),\n streamSettings,\n );\n }\n\n async _handleGetConversation(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { alias, conversationId } = req.params;\n const spaceId = this.resolveSpaceId(alias);\n\n if (!spaceId) {\n res.status(404).json({ error: `Unknown space alias: ${alias}` });\n return;\n }\n\n const includeQueryResults = req.query.includeQueryResults !== \"false\";\n const pageToken =\n typeof req.query.pageToken === \"string\" ? req.query.pageToken : undefined;\n const requestId =\n (typeof req.query.requestId === \"string\" && req.query.requestId) ||\n randomUUID();\n\n logger.debug(\n \"Fetching conversation %s from space %s (alias=%s, includeQueryResults=%s, pageToken=%s)\",\n conversationId,\n spaceId,\n alias,\n includeQueryResults,\n pageToken ?? \"none\",\n );\n\n const streamSettings: StreamExecutionSettings = {\n ...genieStreamDefaults,\n stream: {\n ...genieStreamDefaults.stream,\n streamId: requestId,\n },\n };\n\n const workspaceClient = getWorkspaceClient();\n\n await this.executeStream<GenieStreamEvent>(\n res,\n (signal) =>\n this.genieConnector.streamConversation(\n workspaceClient,\n spaceId,\n conversationId,\n { includeQueryResults, pageToken, signal },\n ),\n streamSettings,\n );\n }\n\n async _handleGetMessage(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { alias, conversationId, messageId } = req.params;\n const spaceId = this.resolveSpaceId(alias);\n\n if (!spaceId) {\n res.status(404).json({ error: `Unknown space alias: ${alias}` });\n return;\n }\n\n const requestId =\n (typeof req.query.requestId === \"string\" && req.query.requestId) ||\n randomUUID();\n\n logger.debug(\n \"Polling message %s in conversation %s from space %s (alias=%s)\",\n messageId,\n conversationId,\n spaceId,\n alias,\n );\n\n const timeout = this.config.timeout ?? 120_000;\n const streamSettings: StreamExecutionSettings = {\n ...genieStreamDefaults,\n default: {\n ...genieStreamDefaults.default,\n timeout,\n },\n stream: {\n ...genieStreamDefaults.stream,\n streamId: requestId,\n },\n };\n\n const workspaceClient = getWorkspaceClient();\n\n await this.executeStream<GenieStreamEvent>(\n res,\n (signal) =>\n this.genieConnector.streamGetMessage(\n workspaceClient,\n spaceId,\n conversationId,\n messageId,\n { timeout, signal },\n ),\n streamSettings,\n );\n }\n\n async getConversation(\n alias: string,\n conversationId: string,\n signal?: AbortSignal,\n ): Promise<GenieConversationHistoryResponse> {\n // Honour an already-cancelled stream before paying any I/O cost. The\n // underlying connector's pagination loop is signal-agnostic today, so\n // this catches the common case (tool dispatched after the user\n // cancelled) without a deeper connector change.\n signal?.throwIfAborted();\n const spaceId = this.resolveSpaceId(alias);\n\n if (!spaceId) {\n throw new Error(`Unknown space alias: ${alias}`);\n }\n\n const workspaceClient = getWorkspaceClient();\n\n return this.genieConnector.getConversation(\n workspaceClient,\n spaceId,\n conversationId,\n );\n }\n\n /**\n * Send a message and consume events as a stream (message_start, status,\n * message_result, query_result, error).\n */\n async *sendMessage(\n alias: string,\n content: string,\n conversationId?: string,\n options?: { timeout?: number; signal?: AbortSignal },\n ): AsyncGenerator<GenieStreamEvent> {\n options?.signal?.throwIfAborted();\n const spaceId = this.resolveSpaceId(alias);\n if (!spaceId) {\n throw new Error(`Unknown space alias: ${alias}`);\n }\n const workspaceClient = getWorkspaceClient();\n const timeout = options?.timeout ?? this.config.timeout ?? 120_000;\n yield* this.genieConnector.streamSendMessage(\n workspaceClient,\n spaceId,\n content,\n conversationId,\n { timeout, signal: options?.signal },\n );\n }\n\n async shutdown(): Promise<void> {\n this.streamManager.abortAll();\n }\n\n getAgentTools(): AgentToolDefinition[] {\n return toolsFromRegistry(this.tools);\n }\n\n async executeAgentTool(\n name: string,\n args: unknown,\n signal?: AbortSignal,\n ): Promise<unknown> {\n return executeFromRegistry(this.tools, name, args, signal);\n }\n\n toolkit(opts?: import(\"../../core/agent/types\").ToolkitOptions) {\n return buildToolkitEntries(this.name, this.tools, opts);\n }\n\n exports() {\n return {\n sendMessage: this.sendMessage,\n getConversation: this.getConversation,\n };\n }\n}\n\n/**\n * @internal\n */\nexport const genie = toPlugin(GeniePlugin);\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { IAppRouter, ToPlugin } from "../../shared/src/plugin.js";
|
|
2
2
|
import "../../shared/src/index.js";
|
|
3
|
+
import { PluginManifest, ResourceRequirement } from "../../registry/types.js";
|
|
3
4
|
import { Plugin } from "../../plugin/plugin.js";
|
|
4
5
|
import "../../plugin/index.js";
|
|
5
|
-
import { PluginManifest, ResourceRequirement } from "../../registry/types.js";
|
|
6
6
|
import "../../registry/index.js";
|
|
7
7
|
import { IJobsConfig, JobAPI, JobConfig, JobsExport } from "./types.js";
|
|
8
8
|
import "../../index.js";
|
|
@@ -2,7 +2,7 @@ import { ExecutionError } from "../../errors/execution.js";
|
|
|
2
2
|
import { ValidationError } from "../../errors/validation.js";
|
|
3
3
|
import "../../errors/index.js";
|
|
4
4
|
import { createLogger } from "../../logging/logger.js";
|
|
5
|
-
import {
|
|
5
|
+
import { getCurrentPrincipalId, getWorkspaceClient } from "../../context/execution-context.js";
|
|
6
6
|
import "../../context/index.js";
|
|
7
7
|
import { Plugin } from "../../plugin/plugin.js";
|
|
8
8
|
import { toPlugin } from "../../plugin/to-plugin.js";
|
|
@@ -172,7 +172,7 @@ var JobsPlugin = class JobsPlugin extends Plugin {
|
|
|
172
172
|
const jobConfig = this.jobConfigs[jobKey];
|
|
173
173
|
const self = this;
|
|
174
174
|
const client = this.client;
|
|
175
|
-
const userKey =
|
|
175
|
+
const userKey = getCurrentPrincipalId();
|
|
176
176
|
/**
|
|
177
177
|
* Verify that `runId` belongs to this job's configured `jobId`. Returns
|
|
178
178
|
* null if the run is in scope; otherwise returns a 404 `ExecutionResult`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":["manifest"],"sources":["../../../src/plugins/jobs/plugin.ts"],"sourcesContent":["import { STATUS_CODES } from \"node:http\";\n\nimport type express from \"express\";\nimport type {\n IAppRouter,\n PluginExecutionSettings,\n StreamExecutionSettings,\n} from \"shared\";\nimport { toJSONSchema } from \"zod\";\n\nimport { JobsConnector } from \"../../connectors/jobs\";\nimport { getCurrentUserId, getWorkspaceClient } from \"../../context\";\nimport { ExecutionError, ValidationError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { ExecutionResult } from \"../../plugin\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { ResourceRequirement } from \"../../registry\";\nimport { defineManifest, ResourceType } from \"../../registry\";\nimport type { jobs as jobsTypes } from \"../../workspace-client\";\nimport {\n JOBS_READ_DEFAULTS,\n JOBS_STREAM_DEFAULTS,\n JOBS_WRITE_DEFAULTS,\n} from \"./defaults\";\nimport manifest from \"./manifest.json\";\nimport { mapParams } from \"./params\";\nimport type {\n IJobsConfig,\n JobAPI,\n JobConfig,\n JobRunStatus,\n JobsExport,\n} from \"./types\";\n\nconst logger = createLogger(\"jobs\");\n\nconst DEFAULT_WAIT_TIMEOUT = 600_000;\nconst DEFAULT_POLL_INTERVAL = 5_000;\n/** Cap on param-key count when a job has no Zod schema. Jobs that need more keys must define a schema. */\nconst MAX_UNVALIDATED_PARAM_KEYS = 50;\n\n/** Replace upstream error messages with generic descriptions keyed by HTTP status. */\nfunction errorResult(status: number): ExecutionResult<never> {\n return {\n ok: false,\n status,\n message: STATUS_CODES[status] ?? \"Request failed\",\n };\n}\n\nfunction isTerminalRunState(state: string | undefined): boolean {\n return (\n state === \"TERMINATED\" || state === \"SKIPPED\" || state === \"INTERNAL_ERROR\"\n );\n}\n\n/** Exponential backoff (1.5x) with +/- 20% jitter, capped at `max`. */\nfunction nextPollDelay(\n current: number,\n max: number,\n): { delay: number; next: number } {\n const jitter = 1 + (Math.random() * 0.4 - 0.2);\n return {\n delay: Math.min(current * jitter, max),\n next: Math.min(current * 1.5, max),\n };\n}\n\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve) => {\n if (signal?.aborted) {\n resolve();\n return;\n }\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true },\n );\n });\n}\n\nclass JobsPlugin extends Plugin {\n static manifest = defineManifest<\"jobs\">(manifest);\n\n declare protected config: IJobsConfig;\n private connector: JobsConnector;\n private jobIds: Record<string, number> = {};\n private jobConfigs: Record<string, JobConfig> = {};\n private jobKeys: string[] = [];\n\n /**\n * Scans process.env for DATABRICKS_JOB_* keys and merges with explicit config.\n * Explicit config wins for per-job overrides; auto-discovered jobs get default `{}` config.\n */\n static discoverJobs(config: IJobsConfig): Record<string, JobConfig> {\n const explicit = config.jobs ?? {};\n const discovered: Record<string, JobConfig> = {};\n\n const prefix = \"DATABRICKS_JOB_\";\n for (const key of Object.keys(process.env)) {\n if (!key.startsWith(prefix)) continue;\n if (key === \"DATABRICKS_JOB_ID\") continue;\n const suffix = key.slice(prefix.length);\n if (!suffix || !process.env[key]) continue;\n const jobKey = suffix.toLowerCase();\n if (!(jobKey in explicit)) {\n discovered[jobKey] = {};\n }\n }\n\n // Single-job shorthand: DATABRICKS_JOB_ID maps to \"default\" key\n if (\n process.env.DATABRICKS_JOB_ID &&\n Object.keys(explicit).length === 0 &&\n Object.keys(discovered).length === 0\n ) {\n discovered.default = {};\n }\n\n return { ...discovered, ...explicit };\n }\n\n /**\n * Generates resource requirements dynamically from discovered + configured jobs.\n * Each job key maps to a `DATABRICKS_JOB_{KEY_UPPERCASE}` env var (or `DATABRICKS_JOB_ID` for \"default\").\n */\n static getResourceRequirements(config: IJobsConfig): ResourceRequirement[] {\n const jobs = JobsPlugin.discoverJobs(config);\n return Object.keys(jobs).map((key) => ({\n type: ResourceType.JOB,\n alias: `job-${key}`,\n resourceKey: `job-${key}`,\n description: `Databricks Job \"${key}\"`,\n permission: \"CAN_MANAGE_RUN\" as const,\n fields: {\n id: {\n env:\n key === \"default\"\n ? \"DATABRICKS_JOB_ID\"\n : `DATABRICKS_JOB_${key.toUpperCase()}`,\n description: `Job ID for \"${key}\"`,\n },\n },\n required: true,\n }));\n }\n\n constructor(config: IJobsConfig) {\n super(config);\n this.config = config;\n this.connector = new JobsConnector({\n telemetry: config.telemetry,\n });\n\n const jobs = JobsPlugin.discoverJobs(config);\n this.jobKeys = Object.keys(jobs);\n this.jobConfigs = jobs;\n\n for (const key of this.jobKeys) {\n const envVar =\n key === \"default\"\n ? \"DATABRICKS_JOB_ID\"\n : `DATABRICKS_JOB_${key.toUpperCase()}`;\n const jobIdStr = process.env[envVar];\n if (jobIdStr) {\n const parsed = Number.parseInt(jobIdStr, 10);\n if (!Number.isNaN(parsed)) {\n this.jobIds[key] = parsed;\n }\n }\n }\n }\n\n async setup() {\n logger.info(\n `Jobs plugin initialized with ${this.jobKeys.length} job(s): ${this.jobKeys.join(\", \")}`,\n );\n }\n\n private get client() {\n return getWorkspaceClient();\n }\n\n private getJobId(jobKey: string): number {\n const id = this.jobIds[jobKey];\n if (!id) {\n const envVar =\n jobKey === \"default\"\n ? \"DATABRICKS_JOB_ID\"\n : `DATABRICKS_JOB_${jobKey.toUpperCase()}`;\n throw new Error(\n `Job \"${jobKey}\" has no configured job ID. Set ${envVar} env var.`,\n );\n }\n return id;\n }\n\n private _readSettings(\n cacheKey: (string | number | object)[],\n ): PluginExecutionSettings {\n return {\n default: {\n ...JOBS_READ_DEFAULTS,\n ...(this.config.timeout != null && { timeout: this.config.timeout }),\n cache: { ...JOBS_READ_DEFAULTS.cache, cacheKey },\n },\n };\n }\n\n private _writeSettings(): PluginExecutionSettings {\n return {\n default: {\n ...JOBS_WRITE_DEFAULTS,\n ...(this.config.timeout != null && { timeout: this.config.timeout }),\n },\n };\n }\n\n /**\n * Validates params against the job's Zod schema (if any) and maps them\n * to SDK request fields based on the task type. Shared by runNow and runAndWait.\n */\n private _validateAndMap(\n jobKey: string,\n params?: Record<string, unknown>,\n ): Record<string, unknown> {\n const jobConfig = this.jobConfigs[jobKey];\n let validated = params;\n\n if (jobConfig?.params) {\n const result = jobConfig.params.safeParse(params ?? {});\n if (!result.success) {\n throw new ValidationError(\n `Parameter validation failed for job \"${jobKey}\": ${result.error.message}`,\n );\n }\n validated = result.data as Record<string, unknown>;\n }\n\n return jobConfig?.taskType && validated\n ? mapParams(jobConfig.taskType, validated)\n : (validated ?? {});\n }\n\n /**\n * Creates a JobAPI for a specific configured job key.\n * Each method is scoped to the job's configured ID.\n */\n protected createJobAPI(jobKey: string): JobAPI {\n const jobId = this.getJobId(jobKey);\n const jobConfig = this.jobConfigs[jobKey];\n // Capture `this` for use in the async generator\n const self = this;\n // Capture client and userId eagerly: the closures below run later, after the ALS context may have exited.\n const client = this.client;\n const userKey = getCurrentUserId();\n\n /**\n * Verify that `runId` belongs to this job's configured `jobId`. Returns\n * null if the run is in scope; otherwise returns a 404 `ExecutionResult`.\n * Prevents cross-job access via the `/:jobKey/runs/:runId` HTTP surface.\n */\n const verifyRunScope = async (\n runId: number,\n ): Promise<ExecutionResult<never> | null> => {\n const result = await self.execute(\n async (signal) =>\n self.connector.getRun(client, { run_id: runId }, signal),\n self._readSettings([\"jobs:getRun\", jobKey, runId]),\n userKey,\n );\n if (!result.ok) return errorResult(result.status);\n if (result.data.job_id !== jobId) return errorResult(404);\n return null;\n };\n\n return {\n runNow: async (\n params?: Record<string, unknown>,\n ): Promise<ExecutionResult<jobsTypes.RunNowResponse>> => {\n const sdkFields = self._validateAndMap(jobKey, params);\n\n const result = await self.execute(\n async (signal) =>\n self.connector.runNow(\n client,\n { ...sdkFields, job_id: jobId },\n signal,\n ),\n self._writeSettings(),\n userKey,\n );\n return result.ok ? result : errorResult(result.status);\n },\n\n async *runAndWait(\n params?: Record<string, unknown>,\n signal?: AbortSignal,\n ): AsyncGenerator<JobRunStatus, void, unknown> {\n const sdkFields = self._validateAndMap(jobKey, params);\n\n const runResult = await self.execute(\n async (signal) =>\n self.connector.runNow(\n client,\n { ...sdkFields, job_id: jobId },\n signal,\n ),\n self._writeSettings(),\n userKey,\n );\n\n if (!runResult.ok) {\n throw new ExecutionError(\"Failed to trigger job run\");\n }\n const runId = runResult.data.run_id;\n if (!runId) {\n throw new Error(\"runNow did not return a run_id\");\n }\n\n const basePollInterval =\n self.config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL;\n const maxPollInterval = basePollInterval * 6;\n const timeout = jobConfig?.waitTimeout ?? DEFAULT_WAIT_TIMEOUT;\n const startTime = Date.now();\n let currentInterval = basePollInterval;\n\n while (!signal?.aborted) {\n if (Date.now() - startTime > timeout) {\n throw new Error(\n `Job run ${runId} polling timeout after ${timeout}ms`,\n );\n }\n\n const runStatusResult = await self.execute(\n async (signal) =>\n self.connector.getRun(client, { run_id: runId }, signal),\n {\n default: {\n ...JOBS_READ_DEFAULTS,\n cache: { enabled: false },\n },\n },\n userKey,\n );\n if (!runStatusResult.ok) {\n throw new ExecutionError(\n `Failed to poll run status for run ${runId}`,\n );\n }\n const run = runStatusResult.data;\n const state = run.state?.life_cycle_state;\n\n yield { status: state, timestamp: Date.now(), run };\n\n if (isTerminalRunState(state)) return;\n\n const { delay, next } = nextPollDelay(\n currentInterval,\n maxPollInterval,\n );\n currentInterval = next;\n await abortableSleep(delay, signal);\n }\n },\n\n lastRun: async (): Promise<\n ExecutionResult<jobsTypes.BaseRun | undefined>\n > => {\n const result = await self.execute(\n async (signal) =>\n self.connector.listRuns(\n client,\n { job_id: jobId, limit: 1 },\n signal,\n ),\n self._readSettings([\"jobs:lastRun\", jobKey]),\n userKey,\n );\n if (!result.ok) return errorResult(result.status);\n return { ok: true, data: result.data[0] };\n },\n\n listRuns: async (options?: {\n limit?: number;\n }): Promise<ExecutionResult<jobsTypes.BaseRun[]>> => {\n const result = await self.execute(\n async (signal) =>\n self.connector.listRuns(\n client,\n { job_id: jobId, limit: options?.limit },\n signal,\n ),\n self._readSettings([\n \"jobs:listRuns\",\n jobKey,\n options?.limit ?? \"default\",\n ]),\n userKey,\n );\n return result.ok ? result : errorResult(result.status);\n },\n\n getRun: async (\n runId: number,\n ): Promise<ExecutionResult<jobsTypes.Run>> => {\n const result = await self.execute(\n async (signal) =>\n self.connector.getRun(client, { run_id: runId }, signal),\n self._readSettings([\"jobs:getRun\", jobKey, runId]),\n userKey,\n );\n if (!result.ok) return errorResult(result.status);\n if (result.data.job_id !== jobId) return errorResult(404);\n return result;\n },\n\n getRunOutput: async (\n runId: number,\n ): Promise<ExecutionResult<jobsTypes.RunOutput>> => {\n const scopeError = await verifyRunScope(runId);\n if (scopeError) return scopeError;\n const result = await self.execute(\n async (signal) =>\n self.connector.getRunOutput(client, { run_id: runId }, signal),\n self._readSettings([\"jobs:getRunOutput\", jobKey, runId]),\n userKey,\n );\n return result.ok ? result : errorResult(result.status);\n },\n\n cancelRun: async (runId: number): Promise<ExecutionResult<void>> => {\n const scopeError = await verifyRunScope(runId);\n if (scopeError) return scopeError;\n const result = await self.execute(\n async (signal) =>\n self.connector.cancelRun(client, { run_id: runId }, signal),\n self._writeSettings(),\n userKey,\n );\n return result.ok ? result : errorResult(result.status);\n },\n\n getJob: async (): Promise<ExecutionResult<jobsTypes.Job>> => {\n const result = await self.execute(\n async (signal) =>\n self.connector.getJob(client, { job_id: jobId }, signal),\n self._readSettings([\"jobs:getJob\", jobKey]),\n userKey,\n );\n return result.ok ? result : errorResult(result.status);\n },\n };\n }\n\n /**\n * Resolve `:jobKey` from the request. Returns the key and ID,\n * or sends a 404 and returns `{ jobKey: undefined, jobId: undefined }`.\n */\n private _resolveJob(\n req: express.Request,\n res: express.Response,\n ):\n | { jobKey: string; jobId: number }\n | { jobKey: undefined; jobId: undefined } {\n const jobKey = req.params.jobKey;\n if (!this.jobKeys.includes(jobKey)) {\n const safeKey = jobKey.replace(/[^a-zA-Z0-9_-]/g, \"\");\n res.status(404).json({\n error: `Unknown job \"${safeKey}\"`,\n plugin: this.name,\n });\n return { jobKey: undefined, jobId: undefined };\n }\n const jobId = this.jobIds[jobKey];\n if (!jobId) {\n res.status(404).json({\n error: `Job \"${jobKey}\" has no configured job ID`,\n plugin: this.name,\n });\n return { jobKey: undefined, jobId: undefined };\n }\n return { jobKey, jobId };\n }\n\n private _sendStatusError(res: express.Response, status: number): void {\n res.status(status).json({\n error: STATUS_CODES[status] ?? \"Unknown Error\",\n plugin: this.name,\n });\n }\n\n /**\n * Validate params from an HTTP request body. Eager validation lets streaming\n * requests get a clean 400 instead of a generic SSE error event. Throws\n * ValidationError so handlers can map to a 400 response via their catch block.\n */\n private _parseRunParams(\n jobKey: string,\n rawParams: unknown,\n ): Record<string, unknown> | undefined {\n if (\n rawParams !== undefined &&\n (typeof rawParams !== \"object\" ||\n rawParams === null ||\n Array.isArray(rawParams))\n ) {\n throw new ValidationError(\"params must be a plain object\");\n }\n\n const jobConfig = this.jobConfigs[jobKey];\n if (jobConfig?.params) {\n const result = jobConfig.params.safeParse(rawParams ?? {});\n if (!result.success) {\n throw new ValidationError(\"Invalid job parameters\");\n }\n // Pass rawParams — not result.data — to avoid double-transforming\n // when _validateAndMap calls safeParse again downstream.\n return rawParams as Record<string, unknown>;\n }\n // No schema. Either reject (no taskType) or enforce a key cap so that\n // untrusted clients can't spread arbitrarily many fields into the SDK.\n if (rawParams !== undefined) {\n if (!jobConfig?.taskType) {\n throw new ValidationError(\"This job does not accept parameters\");\n }\n const keyCount = Object.keys(rawParams as Record<string, unknown>).length;\n if (keyCount > MAX_UNVALIDATED_PARAM_KEYS) {\n throw new ValidationError(\n `Too many parameters (${keyCount}). Define a Zod schema to accept more than ${MAX_UNVALIDATED_PARAM_KEYS}.`,\n );\n }\n }\n return rawParams as Record<string, unknown> | undefined;\n }\n\n private async _handleRun(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { jobKey } = this._resolveJob(req, res);\n if (!jobKey) return;\n\n const stream = req.query.stream === \"true\";\n\n try {\n const params = this._parseRunParams(jobKey, req.body?.params);\n const api = this.createJobAPI(jobKey);\n\n if (stream) {\n const streamSettings: StreamExecutionSettings = {\n default: JOBS_STREAM_DEFAULTS,\n };\n await this.executeStream<JobRunStatus>(\n res,\n (signal) => api.runAndWait(params, signal),\n streamSettings,\n );\n } else {\n const result = await api.runNow(params);\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json({ runId: result.data.run_id });\n }\n } catch (error) {\n if (error instanceof ValidationError) {\n if (!res.headersSent) {\n res.status(400).json({ error: error.message, plugin: this.name });\n }\n return;\n }\n logger.error(\"Run failed for job %s: %O\", jobKey, error);\n if (!res.headersSent) {\n res.status(500).json({ error: \"Run failed\", plugin: this.name });\n }\n }\n }\n\n injectRoutes(router: IAppRouter) {\n this.route(router, {\n name: \"run\",\n method: \"post\",\n path: \"/:jobKey/run\",\n handler: (req, res) => this._handleRun(req, res),\n });\n\n // GET /:jobKey/runs\n this.route(router, {\n name: \"runs\",\n method: \"get\",\n path: \"/:jobKey/runs\",\n handler: async (req: express.Request, res: express.Response) => {\n const { jobKey } = this._resolveJob(req, res);\n if (!jobKey) return;\n\n const limit = Math.max(\n 1,\n Math.min(Number.parseInt(req.query.limit as string, 10) || 20, 100),\n );\n\n try {\n const api = this.createJobAPI(jobKey);\n const result = await api.listRuns({ limit });\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json({ runs: result.data });\n } catch (error) {\n logger.error(\"List runs failed for job %s: %O\", jobKey, error);\n res\n .status(500)\n .json({ error: \"List runs failed\", plugin: this.name });\n }\n },\n });\n\n // GET /:jobKey/runs/:runId\n this.route(router, {\n name: \"run-detail\",\n method: \"get\",\n path: \"/:jobKey/runs/:runId\",\n handler: async (req: express.Request, res: express.Response) => {\n const { jobKey } = this._resolveJob(req, res);\n if (!jobKey) return;\n\n const runId = Number.parseInt(req.params.runId, 10);\n if (Number.isNaN(runId) || runId <= 0) {\n res.status(400).json({ error: \"Invalid runId\", plugin: this.name });\n return;\n }\n\n try {\n const api = this.createJobAPI(jobKey);\n const result = await api.getRun(runId);\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json(result.data);\n } catch (error) {\n logger.error(\n \"Get run failed for job %s run %d: %O\",\n jobKey,\n runId,\n error,\n );\n res.status(500).json({ error: \"Get run failed\", plugin: this.name });\n }\n },\n });\n\n // GET /:jobKey/status\n this.route(router, {\n name: \"status\",\n method: \"get\",\n path: \"/:jobKey/status\",\n handler: async (req: express.Request, res: express.Response) => {\n const { jobKey } = this._resolveJob(req, res);\n if (!jobKey) return;\n\n try {\n const api = this.createJobAPI(jobKey);\n const result = await api.lastRun();\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json({\n status: result.data?.state?.life_cycle_state ?? null,\n run: result.data ?? null,\n });\n } catch (error) {\n logger.error(\"Status check failed for job %s: %O\", jobKey, error);\n res\n .status(500)\n .json({ error: \"Status check failed\", plugin: this.name });\n }\n },\n });\n\n // DELETE /:jobKey/runs/:runId\n this.route(router, {\n name: \"cancel-run\",\n method: \"delete\",\n path: \"/:jobKey/runs/:runId\",\n handler: async (req: express.Request, res: express.Response) => {\n const { jobKey } = this._resolveJob(req, res);\n if (!jobKey) return;\n\n const runId = Number.parseInt(req.params.runId, 10);\n if (Number.isNaN(runId) || runId <= 0) {\n res.status(400).json({ error: \"Invalid runId\", plugin: this.name });\n return;\n }\n\n try {\n const api = this.createJobAPI(jobKey);\n const result = await api.cancelRun(runId);\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.status(204).end();\n } catch (error) {\n logger.error(\n \"Cancel run failed for job %s run %d: %O\",\n jobKey,\n runId,\n error,\n );\n res\n .status(500)\n .json({ error: \"Cancel run failed\", plugin: this.name });\n }\n },\n });\n }\n\n exports(): JobsExport {\n const resolveJob = (jobKey: string): JobAPI => {\n if (!this.jobKeys.includes(jobKey)) {\n throw new Error(\n `Unknown job \"${jobKey}\". Available jobs: ${this.jobKeys.join(\", \")}`,\n );\n }\n\n return this.createJobAPI(jobKey);\n };\n\n return resolveJob as JobsExport;\n }\n\n clientConfig(): Record<string, unknown> {\n const jobs: Record<string, { params: unknown; taskType: string | null }> =\n {};\n for (const key of this.jobKeys) {\n const config = this.jobConfigs[key];\n jobs[key] = {\n params: config?.params ? toJSONSchema(config.params) : null,\n taskType: config?.taskType ?? null,\n };\n }\n return { jobs };\n }\n}\n\n/**\n * @internal\n */\nexport const jobs = toPlugin(JobsPlugin);\n\n/**\n * @internal\n */\nexport { JobsPlugin };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","names":["manifest"],"sources":["../../../src/plugins/jobs/plugin.ts"],"sourcesContent":["import { STATUS_CODES } from \"node:http\";\n\nimport type express from \"express\";\nimport type {\n IAppRouter,\n PluginExecutionSettings,\n StreamExecutionSettings,\n} from \"shared\";\nimport { toJSONSchema } from \"zod\";\n\nimport { JobsConnector } from \"../../connectors/jobs\";\nimport { getCurrentPrincipalId, getWorkspaceClient } from \"../../context\";\nimport { ExecutionError, ValidationError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { ExecutionResult } from \"../../plugin\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { ResourceRequirement } from \"../../registry\";\nimport { defineManifest, ResourceType } from \"../../registry\";\nimport type { jobs as jobsTypes } from \"../../workspace-client\";\nimport {\n JOBS_READ_DEFAULTS,\n JOBS_STREAM_DEFAULTS,\n JOBS_WRITE_DEFAULTS,\n} from \"./defaults\";\nimport manifest from \"./manifest.json\";\nimport { mapParams } from \"./params\";\nimport type {\n IJobsConfig,\n JobAPI,\n JobConfig,\n JobRunStatus,\n JobsExport,\n} from \"./types\";\n\nconst logger = createLogger(\"jobs\");\n\nconst DEFAULT_WAIT_TIMEOUT = 600_000;\nconst DEFAULT_POLL_INTERVAL = 5_000;\n/** Cap on param-key count when a job has no Zod schema. Jobs that need more keys must define a schema. */\nconst MAX_UNVALIDATED_PARAM_KEYS = 50;\n\n/** Replace upstream error messages with generic descriptions keyed by HTTP status. */\nfunction errorResult(status: number): ExecutionResult<never> {\n return {\n ok: false,\n status,\n message: STATUS_CODES[status] ?? \"Request failed\",\n };\n}\n\nfunction isTerminalRunState(state: string | undefined): boolean {\n return (\n state === \"TERMINATED\" || state === \"SKIPPED\" || state === \"INTERNAL_ERROR\"\n );\n}\n\n/** Exponential backoff (1.5x) with +/- 20% jitter, capped at `max`. */\nfunction nextPollDelay(\n current: number,\n max: number,\n): { delay: number; next: number } {\n const jitter = 1 + (Math.random() * 0.4 - 0.2);\n return {\n delay: Math.min(current * jitter, max),\n next: Math.min(current * 1.5, max),\n };\n}\n\nfunction abortableSleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve) => {\n if (signal?.aborted) {\n resolve();\n return;\n }\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true },\n );\n });\n}\n\nclass JobsPlugin extends Plugin {\n static manifest = defineManifest<\"jobs\">(manifest);\n\n declare protected config: IJobsConfig;\n private connector: JobsConnector;\n private jobIds: Record<string, number> = {};\n private jobConfigs: Record<string, JobConfig> = {};\n private jobKeys: string[] = [];\n\n /**\n * Scans process.env for DATABRICKS_JOB_* keys and merges with explicit config.\n * Explicit config wins for per-job overrides; auto-discovered jobs get default `{}` config.\n */\n static discoverJobs(config: IJobsConfig): Record<string, JobConfig> {\n const explicit = config.jobs ?? {};\n const discovered: Record<string, JobConfig> = {};\n\n const prefix = \"DATABRICKS_JOB_\";\n for (const key of Object.keys(process.env)) {\n if (!key.startsWith(prefix)) continue;\n if (key === \"DATABRICKS_JOB_ID\") continue;\n const suffix = key.slice(prefix.length);\n if (!suffix || !process.env[key]) continue;\n const jobKey = suffix.toLowerCase();\n if (!(jobKey in explicit)) {\n discovered[jobKey] = {};\n }\n }\n\n // Single-job shorthand: DATABRICKS_JOB_ID maps to \"default\" key\n if (\n process.env.DATABRICKS_JOB_ID &&\n Object.keys(explicit).length === 0 &&\n Object.keys(discovered).length === 0\n ) {\n discovered.default = {};\n }\n\n return { ...discovered, ...explicit };\n }\n\n /**\n * Generates resource requirements dynamically from discovered + configured jobs.\n * Each job key maps to a `DATABRICKS_JOB_{KEY_UPPERCASE}` env var (or `DATABRICKS_JOB_ID` for \"default\").\n */\n static getResourceRequirements(config: IJobsConfig): ResourceRequirement[] {\n const jobs = JobsPlugin.discoverJobs(config);\n return Object.keys(jobs).map((key) => ({\n type: ResourceType.JOB,\n alias: `job-${key}`,\n resourceKey: `job-${key}`,\n description: `Databricks Job \"${key}\"`,\n permission: \"CAN_MANAGE_RUN\" as const,\n fields: {\n id: {\n env:\n key === \"default\"\n ? \"DATABRICKS_JOB_ID\"\n : `DATABRICKS_JOB_${key.toUpperCase()}`,\n description: `Job ID for \"${key}\"`,\n },\n },\n required: true,\n }));\n }\n\n constructor(config: IJobsConfig) {\n super(config);\n this.config = config;\n this.connector = new JobsConnector({\n telemetry: config.telemetry,\n });\n\n const jobs = JobsPlugin.discoverJobs(config);\n this.jobKeys = Object.keys(jobs);\n this.jobConfigs = jobs;\n\n for (const key of this.jobKeys) {\n const envVar =\n key === \"default\"\n ? \"DATABRICKS_JOB_ID\"\n : `DATABRICKS_JOB_${key.toUpperCase()}`;\n const jobIdStr = process.env[envVar];\n if (jobIdStr) {\n const parsed = Number.parseInt(jobIdStr, 10);\n if (!Number.isNaN(parsed)) {\n this.jobIds[key] = parsed;\n }\n }\n }\n }\n\n async setup() {\n logger.info(\n `Jobs plugin initialized with ${this.jobKeys.length} job(s): ${this.jobKeys.join(\", \")}`,\n );\n }\n\n private get client() {\n return getWorkspaceClient();\n }\n\n private getJobId(jobKey: string): number {\n const id = this.jobIds[jobKey];\n if (!id) {\n const envVar =\n jobKey === \"default\"\n ? \"DATABRICKS_JOB_ID\"\n : `DATABRICKS_JOB_${jobKey.toUpperCase()}`;\n throw new Error(\n `Job \"${jobKey}\" has no configured job ID. Set ${envVar} env var.`,\n );\n }\n return id;\n }\n\n private _readSettings(\n cacheKey: (string | number | object)[],\n ): PluginExecutionSettings {\n return {\n default: {\n ...JOBS_READ_DEFAULTS,\n ...(this.config.timeout != null && { timeout: this.config.timeout }),\n cache: { ...JOBS_READ_DEFAULTS.cache, cacheKey },\n },\n };\n }\n\n private _writeSettings(): PluginExecutionSettings {\n return {\n default: {\n ...JOBS_WRITE_DEFAULTS,\n ...(this.config.timeout != null && { timeout: this.config.timeout }),\n },\n };\n }\n\n /**\n * Validates params against the job's Zod schema (if any) and maps them\n * to SDK request fields based on the task type. Shared by runNow and runAndWait.\n */\n private _validateAndMap(\n jobKey: string,\n params?: Record<string, unknown>,\n ): Record<string, unknown> {\n const jobConfig = this.jobConfigs[jobKey];\n let validated = params;\n\n if (jobConfig?.params) {\n const result = jobConfig.params.safeParse(params ?? {});\n if (!result.success) {\n throw new ValidationError(\n `Parameter validation failed for job \"${jobKey}\": ${result.error.message}`,\n );\n }\n validated = result.data as Record<string, unknown>;\n }\n\n return jobConfig?.taskType && validated\n ? mapParams(jobConfig.taskType, validated)\n : (validated ?? {});\n }\n\n /**\n * Creates a JobAPI for a specific configured job key.\n * Each method is scoped to the job's configured ID.\n */\n protected createJobAPI(jobKey: string): JobAPI {\n const jobId = this.getJobId(jobKey);\n const jobConfig = this.jobConfigs[jobKey];\n // Capture `this` for use in the async generator\n const self = this;\n // Capture client and userId eagerly: the closures below run later, after the ALS context may have exited.\n const client = this.client;\n const userKey = getCurrentPrincipalId();\n\n /**\n * Verify that `runId` belongs to this job's configured `jobId`. Returns\n * null if the run is in scope; otherwise returns a 404 `ExecutionResult`.\n * Prevents cross-job access via the `/:jobKey/runs/:runId` HTTP surface.\n */\n const verifyRunScope = async (\n runId: number,\n ): Promise<ExecutionResult<never> | null> => {\n const result = await self.execute(\n async (signal) =>\n self.connector.getRun(client, { run_id: runId }, signal),\n self._readSettings([\"jobs:getRun\", jobKey, runId]),\n userKey,\n );\n if (!result.ok) return errorResult(result.status);\n if (result.data.job_id !== jobId) return errorResult(404);\n return null;\n };\n\n return {\n runNow: async (\n params?: Record<string, unknown>,\n ): Promise<ExecutionResult<jobsTypes.RunNowResponse>> => {\n const sdkFields = self._validateAndMap(jobKey, params);\n\n const result = await self.execute(\n async (signal) =>\n self.connector.runNow(\n client,\n { ...sdkFields, job_id: jobId },\n signal,\n ),\n self._writeSettings(),\n userKey,\n );\n return result.ok ? result : errorResult(result.status);\n },\n\n async *runAndWait(\n params?: Record<string, unknown>,\n signal?: AbortSignal,\n ): AsyncGenerator<JobRunStatus, void, unknown> {\n const sdkFields = self._validateAndMap(jobKey, params);\n\n const runResult = await self.execute(\n async (signal) =>\n self.connector.runNow(\n client,\n { ...sdkFields, job_id: jobId },\n signal,\n ),\n self._writeSettings(),\n userKey,\n );\n\n if (!runResult.ok) {\n throw new ExecutionError(\"Failed to trigger job run\");\n }\n const runId = runResult.data.run_id;\n if (!runId) {\n throw new Error(\"runNow did not return a run_id\");\n }\n\n const basePollInterval =\n self.config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL;\n const maxPollInterval = basePollInterval * 6;\n const timeout = jobConfig?.waitTimeout ?? DEFAULT_WAIT_TIMEOUT;\n const startTime = Date.now();\n let currentInterval = basePollInterval;\n\n while (!signal?.aborted) {\n if (Date.now() - startTime > timeout) {\n throw new Error(\n `Job run ${runId} polling timeout after ${timeout}ms`,\n );\n }\n\n const runStatusResult = await self.execute(\n async (signal) =>\n self.connector.getRun(client, { run_id: runId }, signal),\n {\n default: {\n ...JOBS_READ_DEFAULTS,\n cache: { enabled: false },\n },\n },\n userKey,\n );\n if (!runStatusResult.ok) {\n throw new ExecutionError(\n `Failed to poll run status for run ${runId}`,\n );\n }\n const run = runStatusResult.data;\n const state = run.state?.life_cycle_state;\n\n yield { status: state, timestamp: Date.now(), run };\n\n if (isTerminalRunState(state)) return;\n\n const { delay, next } = nextPollDelay(\n currentInterval,\n maxPollInterval,\n );\n currentInterval = next;\n await abortableSleep(delay, signal);\n }\n },\n\n lastRun: async (): Promise<\n ExecutionResult<jobsTypes.BaseRun | undefined>\n > => {\n const result = await self.execute(\n async (signal) =>\n self.connector.listRuns(\n client,\n { job_id: jobId, limit: 1 },\n signal,\n ),\n self._readSettings([\"jobs:lastRun\", jobKey]),\n userKey,\n );\n if (!result.ok) return errorResult(result.status);\n return { ok: true, data: result.data[0] };\n },\n\n listRuns: async (options?: {\n limit?: number;\n }): Promise<ExecutionResult<jobsTypes.BaseRun[]>> => {\n const result = await self.execute(\n async (signal) =>\n self.connector.listRuns(\n client,\n { job_id: jobId, limit: options?.limit },\n signal,\n ),\n self._readSettings([\n \"jobs:listRuns\",\n jobKey,\n options?.limit ?? \"default\",\n ]),\n userKey,\n );\n return result.ok ? result : errorResult(result.status);\n },\n\n getRun: async (\n runId: number,\n ): Promise<ExecutionResult<jobsTypes.Run>> => {\n const result = await self.execute(\n async (signal) =>\n self.connector.getRun(client, { run_id: runId }, signal),\n self._readSettings([\"jobs:getRun\", jobKey, runId]),\n userKey,\n );\n if (!result.ok) return errorResult(result.status);\n if (result.data.job_id !== jobId) return errorResult(404);\n return result;\n },\n\n getRunOutput: async (\n runId: number,\n ): Promise<ExecutionResult<jobsTypes.RunOutput>> => {\n const scopeError = await verifyRunScope(runId);\n if (scopeError) return scopeError;\n const result = await self.execute(\n async (signal) =>\n self.connector.getRunOutput(client, { run_id: runId }, signal),\n self._readSettings([\"jobs:getRunOutput\", jobKey, runId]),\n userKey,\n );\n return result.ok ? result : errorResult(result.status);\n },\n\n cancelRun: async (runId: number): Promise<ExecutionResult<void>> => {\n const scopeError = await verifyRunScope(runId);\n if (scopeError) return scopeError;\n const result = await self.execute(\n async (signal) =>\n self.connector.cancelRun(client, { run_id: runId }, signal),\n self._writeSettings(),\n userKey,\n );\n return result.ok ? result : errorResult(result.status);\n },\n\n getJob: async (): Promise<ExecutionResult<jobsTypes.Job>> => {\n const result = await self.execute(\n async (signal) =>\n self.connector.getJob(client, { job_id: jobId }, signal),\n self._readSettings([\"jobs:getJob\", jobKey]),\n userKey,\n );\n return result.ok ? result : errorResult(result.status);\n },\n };\n }\n\n /**\n * Resolve `:jobKey` from the request. Returns the key and ID,\n * or sends a 404 and returns `{ jobKey: undefined, jobId: undefined }`.\n */\n private _resolveJob(\n req: express.Request,\n res: express.Response,\n ):\n | { jobKey: string; jobId: number }\n | { jobKey: undefined; jobId: undefined } {\n const jobKey = req.params.jobKey;\n if (!this.jobKeys.includes(jobKey)) {\n const safeKey = jobKey.replace(/[^a-zA-Z0-9_-]/g, \"\");\n res.status(404).json({\n error: `Unknown job \"${safeKey}\"`,\n plugin: this.name,\n });\n return { jobKey: undefined, jobId: undefined };\n }\n const jobId = this.jobIds[jobKey];\n if (!jobId) {\n res.status(404).json({\n error: `Job \"${jobKey}\" has no configured job ID`,\n plugin: this.name,\n });\n return { jobKey: undefined, jobId: undefined };\n }\n return { jobKey, jobId };\n }\n\n private _sendStatusError(res: express.Response, status: number): void {\n res.status(status).json({\n error: STATUS_CODES[status] ?? \"Unknown Error\",\n plugin: this.name,\n });\n }\n\n /**\n * Validate params from an HTTP request body. Eager validation lets streaming\n * requests get a clean 400 instead of a generic SSE error event. Throws\n * ValidationError so handlers can map to a 400 response via their catch block.\n */\n private _parseRunParams(\n jobKey: string,\n rawParams: unknown,\n ): Record<string, unknown> | undefined {\n if (\n rawParams !== undefined &&\n (typeof rawParams !== \"object\" ||\n rawParams === null ||\n Array.isArray(rawParams))\n ) {\n throw new ValidationError(\"params must be a plain object\");\n }\n\n const jobConfig = this.jobConfigs[jobKey];\n if (jobConfig?.params) {\n const result = jobConfig.params.safeParse(rawParams ?? {});\n if (!result.success) {\n throw new ValidationError(\"Invalid job parameters\");\n }\n // Pass rawParams — not result.data — to avoid double-transforming\n // when _validateAndMap calls safeParse again downstream.\n return rawParams as Record<string, unknown>;\n }\n // No schema. Either reject (no taskType) or enforce a key cap so that\n // untrusted clients can't spread arbitrarily many fields into the SDK.\n if (rawParams !== undefined) {\n if (!jobConfig?.taskType) {\n throw new ValidationError(\"This job does not accept parameters\");\n }\n const keyCount = Object.keys(rawParams as Record<string, unknown>).length;\n if (keyCount > MAX_UNVALIDATED_PARAM_KEYS) {\n throw new ValidationError(\n `Too many parameters (${keyCount}). Define a Zod schema to accept more than ${MAX_UNVALIDATED_PARAM_KEYS}.`,\n );\n }\n }\n return rawParams as Record<string, unknown> | undefined;\n }\n\n private async _handleRun(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { jobKey } = this._resolveJob(req, res);\n if (!jobKey) return;\n\n const stream = req.query.stream === \"true\";\n\n try {\n const params = this._parseRunParams(jobKey, req.body?.params);\n const api = this.createJobAPI(jobKey);\n\n if (stream) {\n const streamSettings: StreamExecutionSettings = {\n default: JOBS_STREAM_DEFAULTS,\n };\n await this.executeStream<JobRunStatus>(\n res,\n (signal) => api.runAndWait(params, signal),\n streamSettings,\n );\n } else {\n const result = await api.runNow(params);\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json({ runId: result.data.run_id });\n }\n } catch (error) {\n if (error instanceof ValidationError) {\n if (!res.headersSent) {\n res.status(400).json({ error: error.message, plugin: this.name });\n }\n return;\n }\n logger.error(\"Run failed for job %s: %O\", jobKey, error);\n if (!res.headersSent) {\n res.status(500).json({ error: \"Run failed\", plugin: this.name });\n }\n }\n }\n\n injectRoutes(router: IAppRouter) {\n this.route(router, {\n name: \"run\",\n method: \"post\",\n path: \"/:jobKey/run\",\n handler: (req, res) => this._handleRun(req, res),\n });\n\n // GET /:jobKey/runs\n this.route(router, {\n name: \"runs\",\n method: \"get\",\n path: \"/:jobKey/runs\",\n handler: async (req: express.Request, res: express.Response) => {\n const { jobKey } = this._resolveJob(req, res);\n if (!jobKey) return;\n\n const limit = Math.max(\n 1,\n Math.min(Number.parseInt(req.query.limit as string, 10) || 20, 100),\n );\n\n try {\n const api = this.createJobAPI(jobKey);\n const result = await api.listRuns({ limit });\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json({ runs: result.data });\n } catch (error) {\n logger.error(\"List runs failed for job %s: %O\", jobKey, error);\n res\n .status(500)\n .json({ error: \"List runs failed\", plugin: this.name });\n }\n },\n });\n\n // GET /:jobKey/runs/:runId\n this.route(router, {\n name: \"run-detail\",\n method: \"get\",\n path: \"/:jobKey/runs/:runId\",\n handler: async (req: express.Request, res: express.Response) => {\n const { jobKey } = this._resolveJob(req, res);\n if (!jobKey) return;\n\n const runId = Number.parseInt(req.params.runId, 10);\n if (Number.isNaN(runId) || runId <= 0) {\n res.status(400).json({ error: \"Invalid runId\", plugin: this.name });\n return;\n }\n\n try {\n const api = this.createJobAPI(jobKey);\n const result = await api.getRun(runId);\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json(result.data);\n } catch (error) {\n logger.error(\n \"Get run failed for job %s run %d: %O\",\n jobKey,\n runId,\n error,\n );\n res.status(500).json({ error: \"Get run failed\", plugin: this.name });\n }\n },\n });\n\n // GET /:jobKey/status\n this.route(router, {\n name: \"status\",\n method: \"get\",\n path: \"/:jobKey/status\",\n handler: async (req: express.Request, res: express.Response) => {\n const { jobKey } = this._resolveJob(req, res);\n if (!jobKey) return;\n\n try {\n const api = this.createJobAPI(jobKey);\n const result = await api.lastRun();\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.json({\n status: result.data?.state?.life_cycle_state ?? null,\n run: result.data ?? null,\n });\n } catch (error) {\n logger.error(\"Status check failed for job %s: %O\", jobKey, error);\n res\n .status(500)\n .json({ error: \"Status check failed\", plugin: this.name });\n }\n },\n });\n\n // DELETE /:jobKey/runs/:runId\n this.route(router, {\n name: \"cancel-run\",\n method: \"delete\",\n path: \"/:jobKey/runs/:runId\",\n handler: async (req: express.Request, res: express.Response) => {\n const { jobKey } = this._resolveJob(req, res);\n if (!jobKey) return;\n\n const runId = Number.parseInt(req.params.runId, 10);\n if (Number.isNaN(runId) || runId <= 0) {\n res.status(400).json({ error: \"Invalid runId\", plugin: this.name });\n return;\n }\n\n try {\n const api = this.createJobAPI(jobKey);\n const result = await api.cancelRun(runId);\n if (!result.ok) {\n this._sendStatusError(res, result.status);\n return;\n }\n res.status(204).end();\n } catch (error) {\n logger.error(\n \"Cancel run failed for job %s run %d: %O\",\n jobKey,\n runId,\n error,\n );\n res\n .status(500)\n .json({ error: \"Cancel run failed\", plugin: this.name });\n }\n },\n });\n }\n\n exports(): JobsExport {\n const resolveJob = (jobKey: string): JobAPI => {\n if (!this.jobKeys.includes(jobKey)) {\n throw new Error(\n `Unknown job \"${jobKey}\". Available jobs: ${this.jobKeys.join(\", \")}`,\n );\n }\n\n return this.createJobAPI(jobKey);\n };\n\n return resolveJob as JobsExport;\n }\n\n clientConfig(): Record<string, unknown> {\n const jobs: Record<string, { params: unknown; taskType: string | null }> =\n {};\n for (const key of this.jobKeys) {\n const config = this.jobConfigs[key];\n jobs[key] = {\n params: config?.params ? toJSONSchema(config.params) : null,\n taskType: config?.taskType ?? null,\n };\n }\n return { jobs };\n }\n}\n\n/**\n * @internal\n */\nexport const jobs = toPlugin(JobsPlugin);\n\n/**\n * @internal\n */\nexport { JobsPlugin };\n"],"mappings":"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"}
|
|
@@ -4,9 +4,9 @@ import "../../shared/src/index.js";
|
|
|
4
4
|
import { ToolkitEntry, ToolkitOptions } from "../../core/agent/types.js";
|
|
5
5
|
import { LakebasePool } from "../../connectors/lakebase/routing-pool.js";
|
|
6
6
|
import "../../connectors/lakebase/index.js";
|
|
7
|
+
import { PluginManifest } from "../../registry/types.js";
|
|
7
8
|
import { Plugin } from "../../plugin/plugin.js";
|
|
8
9
|
import "../../plugin/index.js";
|
|
9
|
-
import { PluginManifest } from "../../registry/types.js";
|
|
10
10
|
import { ILakebaseConfig } from "./types.js";
|
|
11
11
|
import "../../index.js";
|
|
12
12
|
import "../agents/index.js";
|
|
@@ -21,11 +21,8 @@ import * as stream from "stream";
|
|
|
21
21
|
* Wraps `@databricks/lakebase` to provide a standard `pg.Pool` with automatic
|
|
22
22
|
* OAuth token refresh, integrated with AppKit's logger and OpenTelemetry setup.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* like Row-Level Security (RLS). Routing is handled transparently by
|
|
27
|
-
* {@link RoutingPool}, which reads the execution context set by the base
|
|
28
|
-
* class `asUser()`.
|
|
24
|
+
* App-only in v1. Caller-scoped operations reject before opening a connection.
|
|
25
|
+
* The platform's postgres user scope is deferred for this connector.
|
|
29
26
|
*
|
|
30
27
|
* @example
|
|
31
28
|
* ```ts
|
|
@@ -38,8 +35,6 @@ import * as stream from "stream";
|
|
|
38
35
|
* // Service principal query
|
|
39
36
|
* const result = await AppKit.lakebase.query("SELECT * FROM users WHERE id = $1", [userId]);
|
|
40
37
|
*
|
|
41
|
-
* // User-scoped query (per-user pool, RLS enforced)
|
|
42
|
-
* const mine = await AppKit.lakebase.asUser(req).query("SELECT * FROM my_data");
|
|
43
38
|
* ```
|
|
44
39
|
*/
|
|
45
40
|
declare class LakebasePlugin extends Plugin implements ToolProvider {
|
|
@@ -60,8 +55,8 @@ declare class LakebasePlugin extends Plugin implements ToolProvider {
|
|
|
60
55
|
/**
|
|
61
56
|
* Executes a parameterized SQL query against the Lakebase pool.
|
|
62
57
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
58
|
+
* New caller scopes enforce the app-only resource contract. Deprecated
|
|
59
|
+
* user entry points retain their established per-user pool routing.
|
|
65
60
|
*
|
|
66
61
|
* @param text - SQL query string, using `$1`, `$2`, ... placeholders
|
|
67
62
|
* @param values - Parameter values corresponding to placeholders
|
|
@@ -117,20 +112,18 @@ declare class LakebasePlugin extends Plugin implements ToolProvider {
|
|
|
117
112
|
toolkit(opts?: ToolkitOptions): Record<string, ToolkitEntry>;
|
|
118
113
|
/**
|
|
119
114
|
* Returns the pool config for the current execution context.
|
|
120
|
-
*
|
|
115
|
+
* Caller scopes reject; plain execution returns SP configuration.
|
|
121
116
|
*/
|
|
122
117
|
private activePoolConfig;
|
|
123
118
|
/**
|
|
124
119
|
* Returns the plugin's public API, accessible via `AppKit.lakebase`.
|
|
125
120
|
*
|
|
126
|
-
* - `pool
|
|
127
|
-
* - `query
|
|
128
|
-
* - `getOrmConfig()
|
|
129
|
-
*
|
|
130
|
-
* - `getPgConfig()` — Returns a `pg.PoolConfig` object for manual pool construction.
|
|
131
|
-
* Inside `asUser(req)`, returns user-scoped config.
|
|
121
|
+
* - `pool`: The service-principal connection pool.
|
|
122
|
+
* - `query`: Convenience method for executing parameterized SQL queries.
|
|
123
|
+
* - `getOrmConfig()`: SP configuration for Drizzle, TypeORM, Sequelize, etc.
|
|
124
|
+
* - `getPgConfig()`: A service-principal `pg.PoolConfig` for manual pools.
|
|
132
125
|
*
|
|
133
|
-
*
|
|
126
|
+
* Caller-scoped access is rejected in v1, including cached pool handles.
|
|
134
127
|
*/
|
|
135
128
|
exports(): {
|
|
136
129
|
pool: LakebasePool;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lakebase.d.ts","names":[],"sources":["../../../src/plugins/lakebase/lakebase.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"lakebase.d.ts","names":[],"sources":["../../../src/plugins/lakebase/lakebase.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DA;;;;;;;;;;cAAa,cAAA,SAAuB,MAAA,YAAkB,YAAA;EA+JhC;EAAA,OA7Jb,QAAA,EAFmB,cAAA;EAAA,UAIR,MAAA,EAAQ,eAAA;EAAA,QAClB,IAAA;EAAA,QACA,cAAA;EA4NsD;;;;;;;;EAlNxD,KAAA,CAAA,GAAK,OAAA;;;;;;;;;;;;;;;;;;;EA2DL,KAAA,WAAgB,cAAA,OAAA,CACpB,IAAA,UACA,MAAA,eACC,OAAA,CAAQ,WAAA,CAAY,CAAA;EAHX;;;;;;;;;;;;;;EAAA,QAsBE,oBAAA;EAqHd;;;;;;;;;;;;EAtFM,QAAA,CAAA,GAAY,OAAA;EA0GV;;;;;EAAA,QA7EA,KAAA;cAEI,MAAA,EAAQ,eAAA;EAAA,QAYZ,cAAA;EA2CR,aAAA,CAAA,GAAiB,mBAAA;EAIX,gBAAA,CACJ,IAAA,UACA,IAAA,WACA,MAAA,GAAS,WAAA,GACR,OAAA;EAIH,OAAA,CAAQ,IAAA,GAJE,cAAA,GAIoD,MAAA,SAAA,YAAA;EApJvC;;;;EAAA,QA4Jf,gBAAA;;;;;;;;;;;EAwBR,OAAA,CAAA;UAGwB,YAAA;sBA1LF,cAAA,QAAc,IAAA,UACtB,MAAA,iBAEX,OAAA,CAAQ,WAAA,CAAY,CAAA;;;;;;;;;;iBAzFgC,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2R5C,QAAA,EAAQ,QAAA,QAAA,cAAA,EAAA,eAAA"}
|
|
@@ -2,7 +2,8 @@ import { createWorkspaceClient } from "../../shared/src/workspace-client/factory
|
|
|
2
2
|
import { getClientOptions } from "../../context/client-options.js";
|
|
3
3
|
import { createLogger } from "../../logging/logger.js";
|
|
4
4
|
import { createLakebasePoolManager } from "../../connectors/lakebase/pool-manager.js";
|
|
5
|
-
import {
|
|
5
|
+
import { getCallerContext } from "../../context/execution-context.js";
|
|
6
|
+
import { assertResourceExecution } from "../../context/resource-capabilities.js";
|
|
6
7
|
import { RoutingPool } from "../../connectors/lakebase/routing-pool.js";
|
|
7
8
|
import { createLakebasePool, getLakebaseOrmConfig, getLakebasePgConfig, getUsernameWithApiLookup } from "../../connectors/lakebase/index.js";
|
|
8
9
|
import { Plugin } from "../../plugin/plugin.js";
|
|
@@ -30,11 +31,8 @@ const OBO_POOL_DEFAULTS = {
|
|
|
30
31
|
* Wraps `@databricks/lakebase` to provide a standard `pg.Pool` with automatic
|
|
31
32
|
* OAuth token refresh, integrated with AppKit's logger and OpenTelemetry setup.
|
|
32
33
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* like Row-Level Security (RLS). Routing is handled transparently by
|
|
36
|
-
* {@link RoutingPool}, which reads the execution context set by the base
|
|
37
|
-
* class `asUser()`.
|
|
34
|
+
* App-only in v1. Caller-scoped operations reject before opening a connection.
|
|
35
|
+
* The platform's postgres user scope is deferred for this connector.
|
|
38
36
|
*
|
|
39
37
|
* @example
|
|
40
38
|
* ```ts
|
|
@@ -47,8 +45,6 @@ const OBO_POOL_DEFAULTS = {
|
|
|
47
45
|
* // Service principal query
|
|
48
46
|
* const result = await AppKit.lakebase.query("SELECT * FROM users WHERE id = $1", [userId]);
|
|
49
47
|
*
|
|
50
|
-
* // User-scoped query (per-user pool, RLS enforced)
|
|
51
|
-
* const mine = await AppKit.lakebase.asUser(req).query("SELECT * FROM my_data");
|
|
52
48
|
* ```
|
|
53
49
|
*/
|
|
54
50
|
var LakebasePlugin = class extends Plugin {
|
|
@@ -83,7 +79,7 @@ var LakebasePlugin = class extends Plugin {
|
|
|
83
79
|
const oboManager = this.oboPoolManager;
|
|
84
80
|
this.pool = new RoutingPool(spPool, (ctx) => {
|
|
85
81
|
if (!oboManager) throw new Error("OBO pool manager not initialized");
|
|
86
|
-
const userKey = ctx.userEmail ?? ctx.userId;
|
|
82
|
+
const userKey = ctx.principal.userEmail ?? ctx.principal.userId;
|
|
87
83
|
const isNew = !oboManager.hasPool(userKey);
|
|
88
84
|
const pool = oboManager.getPool(userKey, {
|
|
89
85
|
workspaceClient: ctx.client.toLegacyWorkspaceClient(),
|
|
@@ -96,8 +92,8 @@ var LakebasePlugin = class extends Plugin {
|
|
|
96
92
|
/**
|
|
97
93
|
* Executes a parameterized SQL query against the Lakebase pool.
|
|
98
94
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
95
|
+
* New caller scopes enforce the app-only resource contract. Deprecated
|
|
96
|
+
* user entry points retain their established per-user pool routing.
|
|
101
97
|
*
|
|
102
98
|
* @param text - SQL query string, using `$1`, `$2`, ... placeholders
|
|
103
99
|
* @param values - Parameter values corresponding to placeholders
|
|
@@ -221,12 +217,13 @@ var LakebasePlugin = class extends Plugin {
|
|
|
221
217
|
}
|
|
222
218
|
/**
|
|
223
219
|
* Returns the pool config for the current execution context.
|
|
224
|
-
*
|
|
220
|
+
* Caller scopes reject; plain execution returns SP configuration.
|
|
225
221
|
*/
|
|
226
222
|
activePoolConfig() {
|
|
227
|
-
|
|
223
|
+
assertResourceExecution("postgres");
|
|
224
|
+
const ctx = getCallerContext();
|
|
228
225
|
if (ctx) {
|
|
229
|
-
const user = ctx.userEmail ?? ctx.userId;
|
|
226
|
+
const user = ctx.principal.userEmail ?? ctx.principal.userId;
|
|
230
227
|
return {
|
|
231
228
|
...this.config.pool,
|
|
232
229
|
workspaceClient: ctx.client.toLegacyWorkspaceClient(),
|
|
@@ -238,14 +235,12 @@ var LakebasePlugin = class extends Plugin {
|
|
|
238
235
|
/**
|
|
239
236
|
* Returns the plugin's public API, accessible via `AppKit.lakebase`.
|
|
240
237
|
*
|
|
241
|
-
* - `pool
|
|
242
|
-
* - `query
|
|
243
|
-
* - `getOrmConfig()
|
|
244
|
-
*
|
|
245
|
-
* - `getPgConfig()` — Returns a `pg.PoolConfig` object for manual pool construction.
|
|
246
|
-
* Inside `asUser(req)`, returns user-scoped config.
|
|
238
|
+
* - `pool`: The service-principal connection pool.
|
|
239
|
+
* - `query`: Convenience method for executing parameterized SQL queries.
|
|
240
|
+
* - `getOrmConfig()`: SP configuration for Drizzle, TypeORM, Sequelize, etc.
|
|
241
|
+
* - `getPgConfig()`: A service-principal `pg.PoolConfig` for manual pools.
|
|
247
242
|
*
|
|
248
|
-
*
|
|
243
|
+
* Caller-scoped access is rejected in v1, including cached pool handles.
|
|
249
244
|
*/
|
|
250
245
|
exports() {
|
|
251
246
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lakebase.js","names":["manifest"],"sources":["../../../src/plugins/lakebase/lakebase.ts"],"sourcesContent":["import type { QueryResult, QueryResultRow } from \"pg\";\nimport type { AgentToolDefinition, ToolProvider } from \"shared\";\nimport { z } from \"zod\";\n\nimport {\n createLakebasePool,\n createLakebasePoolManager,\n getLakebaseOrmConfig,\n getLakebasePgConfig,\n getUsernameWithApiLookup,\n type LakebasePool,\n type LakebasePoolManager,\n RoutingPool,\n} from \"../../connectors/lakebase\";\nimport { getClientOptions } from \"../../context/client-options\";\nimport { getUserContext } from \"../../context/execution-context\";\nimport { buildToolkitEntries } from \"../../core/agent/build-toolkit\";\nimport {\n defineTool,\n executeFromRegistry,\n toolsFromRegistry,\n} from \"../../core/agent/tools/define-tool\";\nimport { assertReadOnlySql } from \"../../core/agent/tools/sql-policy\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport { defineManifest } from \"../../registry\";\nimport { createWorkspaceClient } from \"../../workspace-client\";\nimport manifest from \"./manifest.json\";\nimport type { ILakebaseConfig } from \"./types\";\n\nconst logger = createLogger(\"lakebase\");\n\n/** Default pool settings for per-user OBO pools. */\nconst OBO_POOL_DEFAULTS = {\n max: 3,\n allowExitOnIdle: true,\n idleTimeoutMillis: 30_000,\n};\n\n/**\n * AppKit plugin for Databricks Lakebase Autoscaling.\n *\n * Wraps `@databricks/lakebase` to provide a standard `pg.Pool` with automatic\n * OAuth token refresh, integrated with AppKit's logger and OpenTelemetry setup.\n *\n * Supports On-Behalf-Of (OBO) via `asUser(req)` — each user gets a separate\n * `pg.Pool` authenticated with their Databricks identity, enabling features\n * like Row-Level Security (RLS). Routing is handled transparently by\n * {@link RoutingPool}, which reads the execution context set by the base\n * class `asUser()`.\n *\n * @example\n * ```ts\n * import { createApp, lakebase, server } from \"@databricks/appkit\";\n *\n * const AppKit = await createApp({\n * plugins: [server(), lakebase()],\n * });\n *\n * // Service principal query\n * const result = await AppKit.lakebase.query(\"SELECT * FROM users WHERE id = $1\", [userId]);\n *\n * // User-scoped query (per-user pool, RLS enforced)\n * const mine = await AppKit.lakebase.asUser(req).query(\"SELECT * FROM my_data\");\n * ```\n */\nexport class LakebasePlugin extends Plugin implements ToolProvider {\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = defineManifest<\"lakebase\">(manifest);\n\n declare protected config: ILakebaseConfig;\n private pool: RoutingPool | null = null;\n private oboPoolManager: LakebasePoolManager | null = null;\n\n /**\n * Initializes the Lakebase connection pool and OBO pool manager.\n * Called automatically by AppKit during the plugin setup phase.\n *\n * Creates a {@link RoutingPool} that automatically routes queries to either\n * the service-principal pool or a per-user pool based on the execution\n * context (set by `Plugin.asUser(req)` via AsyncLocalStorage).\n */\n async setup() {\n const poolConfig = {\n ...this.config.pool,\n workspaceClient:\n this.config.pool?.workspaceClient ??\n createWorkspaceClient({\n clientOptions: getClientOptions(),\n }).toLegacyWorkspaceClient(),\n };\n const user = await getUsernameWithApiLookup(poolConfig);\n\n const spPool = createLakebasePool({ ...poolConfig, user });\n logger.info(\"Lakebase SP pool initialized\");\n\n this.oboPoolManager = createLakebasePoolManager({\n ...poolConfig,\n ...OBO_POOL_DEFAULTS,\n });\n logger.info(\"Lakebase OBO pool manager initialized\");\n\n const oboManager = this.oboPoolManager;\n this.pool = new RoutingPool(spPool, (ctx) => {\n if (!oboManager) throw new Error(\"OBO pool manager not initialized\");\n // Lakebase OAuth roles use email as the postgres role when available\n const userKey = ctx.userEmail ?? ctx.userId;\n const isNew = !oboManager.hasPool(userKey);\n const pool = oboManager.getPool(\n userKey,\n {\n workspaceClient: ctx.client.toLegacyWorkspaceClient(),\n user: userKey,\n },\n ctx.tokenFingerprint,\n );\n if (isNew) {\n logger.debug(\"Created OBO pool for user (total: %d)\", oboManager.size);\n }\n return pool;\n });\n }\n\n /**\n * Executes a parameterized SQL query against the Lakebase pool.\n *\n * When called inside `asUser(req)`, the query automatically routes to\n * the per-user pool via {@link RoutingPool}.\n *\n * @param text - SQL query string, using `$1`, `$2`, ... placeholders\n * @param values - Parameter values corresponding to placeholders\n * @returns Query result with typed rows\n *\n * @example\n * ```ts\n * const result = await AppKit.lakebase.query<{ id: number; name: string }>(\n * \"SELECT id, name FROM users WHERE active = $1\",\n * [true],\n * );\n * ```\n */\n async query<T extends QueryResultRow = any>(\n text: string,\n values?: unknown[],\n ): Promise<QueryResult<T>> {\n // oxlint-disable-next-line typescript/no-non-null-assertion -- pool is initialized during setup()\n return this.pool!.query<T>(text, values);\n }\n\n /**\n * Execute a single statement inside a `BEGIN READ ONLY … ROLLBACK`\n * transaction on a dedicated client.\n *\n * The three commands MUST share a connection — a naive\n * `pool.query(\"BEGIN READ ONLY; <stmt>; ROLLBACK\")` batch cannot accept\n * parameter values (PostgreSQL's Extended Query protocol rejects multi-\n * statement prepared queries), which would silently break every\n * parameterized query the agent tool issues.\n *\n * Returns the raw `rows` array for the user's statement. Side effects the\n * statement may attempt (writes, writable-function side effects) are\n * rejected by PostgreSQL under the read-only transaction posture.\n */\n private async runReadOnlyStatement(\n text: string,\n values?: unknown[],\n ): Promise<unknown[]> {\n // oxlint-disable-next-line typescript/no-non-null-assertion -- pool is initialized during setup()\n const client = await this.pool!.connect();\n try {\n await client.query(\"BEGIN READ ONLY\");\n const result = await client.query(text, values);\n return result.rows;\n } finally {\n try {\n await client.query(\"ROLLBACK\");\n } finally {\n client.release();\n }\n }\n }\n\n /**\n * Gracefully drains and closes all connection pools (SP + OBO).\n *\n * Runs as the plugin's `shutdown()` hook (phase 3 of the core lifecycle\n * manager's graceful shutdown), NOT in `abortActiveOperations()` (phase 2):\n * other plugins' `shutdown()` hooks may still need database connections to\n * drain state,\n * so the pools must outlive the abort phase. `pg.Pool#end()` waits for\n * checked-out clients to be released, so hooks running concurrently with\n * this one can still finish their in-flight queries. Errors are caught\n * and logged; this hook never throws.\n */\n async shutdown(): Promise<void> {\n if (this.pool) {\n logger.info(\"Closing Lakebase SP pool\");\n try {\n await this.pool.end();\n } catch (err) {\n logger.error(\"Error closing Lakebase SP pool: %O\", err);\n }\n this.pool = null;\n }\n if (this.oboPoolManager) {\n logger.info(\n \"Closing all Lakebase OBO pools (%d)\",\n this.oboPoolManager.size,\n );\n try {\n await this.oboPoolManager.closeAll();\n } catch (err) {\n logger.error(\"Error closing Lakebase OBO pools: %O\", err);\n }\n this.oboPoolManager = null;\n }\n }\n\n /**\n * Agent tool registry. Empty by default — the Lakebase plugin does NOT\n * expose its SQL connection to LLM agents unless the developer explicitly\n * opts in via `config.exposeAsAgentTool`. See {@link buildQueryTool}.\n */\n private tools: Record<string, ReturnType<typeof this.buildQueryTool>> = {};\n\n constructor(config: ILakebaseConfig) {\n super(config);\n this.config = config;\n if (config.exposeAsAgentTool) {\n this.tools = { query: this.buildQueryTool(config.exposeAsAgentTool) };\n logger.warn(\n \"Lakebase agent tool is enabled (readOnly=%s). Every agent with access to this plugin can execute SQL against the Lakebase database as the requesting user's identity.\",\n config.exposeAsAgentTool.readOnly !== false,\n );\n }\n }\n\n private buildQueryTool(\n opt: NonNullable<ILakebaseConfig[\"exposeAsAgentTool\"]>,\n ) {\n const readOnly = opt.readOnly !== false;\n return defineTool({\n description: readOnly\n ? \"Execute a read-only SQL query against the Lakebase PostgreSQL database. Only SELECT, WITH, SHOW, EXPLAIN, and DESCRIBE statements are accepted. Use $1, $2, etc. as placeholders and pass values separately.\"\n : \"Execute a parameterized SQL statement against the Lakebase PostgreSQL database. Use $1, $2, etc. as placeholders and pass values separately. This tool can modify data; every invocation requires explicit human approval.\",\n schema: z.object({\n text: z\n .string()\n .describe(\n \"SQL statement with $1, $2, ... placeholders for parameters\",\n ),\n values: z\n .array(z.unknown())\n .optional()\n .describe(\"Parameter values corresponding to placeholders\"),\n }),\n annotations: {\n effect: readOnly ? \"read\" : \"destructive\",\n idempotent: false,\n requiresUserContext: true,\n },\n execute: async (args, signal) => {\n // Matches the files plugin pattern: the pg connection API\n // doesn't accept AbortSignal in its current shape, so deeper\n // mid-call cancellation needs a separate plumbing pass on the\n // connector. This entry check still catches the common case —\n // a tool dispatched after the user already cancelled the\n // stream — and unwinds cleanly instead of running to\n // completion against the SQL warehouse.\n signal?.throwIfAborted();\n if (readOnly) {\n assertReadOnlySql(args.text);\n return this.runReadOnlyStatement(args.text, args.values);\n }\n const result = await this.query(args.text, args.values);\n return result.rows;\n },\n });\n }\n\n getAgentTools(): AgentToolDefinition[] {\n return toolsFromRegistry(this.tools);\n }\n\n async executeAgentTool(\n name: string,\n args: unknown,\n signal?: AbortSignal,\n ): Promise<unknown> {\n return executeFromRegistry(this.tools, name, args, signal);\n }\n\n toolkit(opts?: import(\"../../core/agent/types\").ToolkitOptions) {\n return buildToolkitEntries(this.name, this.tools, opts);\n }\n\n /**\n * Returns the pool config for the current execution context.\n * Inside `asUser(req)`, returns user-scoped config; otherwise SP config.\n */\n private activePoolConfig() {\n const ctx = getUserContext();\n if (ctx) {\n const user = ctx.userEmail ?? ctx.userId;\n return {\n ...this.config.pool,\n workspaceClient: ctx.client.toLegacyWorkspaceClient(),\n user,\n };\n }\n return this.config.pool;\n }\n\n /**\n * Returns the plugin's public API, accessible via `AppKit.lakebase`.\n *\n * - `pool` — The connection pool (routes to per-user pool when inside `asUser(req)`)\n * - `query` — Convenience method for executing parameterized SQL queries\n * - `getOrmConfig()` — Returns a config object compatible with Drizzle, TypeORM, Sequelize, etc.\n * Inside `asUser(req)`, returns user-scoped config.\n * - `getPgConfig()` — Returns a `pg.PoolConfig` object for manual pool construction.\n * Inside `asUser(req)`, returns user-scoped config.\n *\n * Use `AppKit.lakebase.asUser(req)` to get the same API backed by a per-user pool.\n */\n exports() {\n return {\n // oxlint-disable-next-line typescript/no-non-null-assertion -- pool is initialized during setup()\n pool: this.pool! as LakebasePool,\n query: this.query.bind(this),\n getOrmConfig: () => getLakebaseOrmConfig(this.activePoolConfig()),\n getPgConfig: () => getLakebasePgConfig(this.activePoolConfig()),\n };\n }\n}\n\n/**\n * @internal\n */\nexport const lakebase = toPlugin(LakebasePlugin);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lakebase.js","names":["manifest"],"sources":["../../../src/plugins/lakebase/lakebase.ts"],"sourcesContent":["import type { QueryResult, QueryResultRow } from \"pg\";\nimport type { AgentToolDefinition, ToolProvider } from \"shared\";\nimport { z } from \"zod\";\n\nimport {\n createLakebasePool,\n createLakebasePoolManager,\n getLakebaseOrmConfig,\n getLakebasePgConfig,\n getUsernameWithApiLookup,\n type LakebasePool,\n type LakebasePoolManager,\n RoutingPool,\n} from \"../../connectors/lakebase\";\nimport { getClientOptions } from \"../../context/client-options\";\nimport { getCallerContext } from \"../../context/execution-context\";\nimport { assertResourceExecution } from \"../../context/resource-capabilities\";\nimport { buildToolkitEntries } from \"../../core/agent/build-toolkit\";\nimport {\n defineTool,\n executeFromRegistry,\n toolsFromRegistry,\n} from \"../../core/agent/tools/define-tool\";\nimport { assertReadOnlySql } from \"../../core/agent/tools/sql-policy\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport { defineManifest } from \"../../registry\";\nimport { createWorkspaceClient } from \"../../workspace-client\";\nimport manifest from \"./manifest.json\";\nimport type { ILakebaseConfig } from \"./types\";\n\nconst logger = createLogger(\"lakebase\");\n\n/** Default pool settings for per-user OBO pools. */\nconst OBO_POOL_DEFAULTS = {\n max: 3,\n allowExitOnIdle: true,\n idleTimeoutMillis: 30_000,\n};\n\n/**\n * AppKit plugin for Databricks Lakebase Autoscaling.\n *\n * Wraps `@databricks/lakebase` to provide a standard `pg.Pool` with automatic\n * OAuth token refresh, integrated with AppKit's logger and OpenTelemetry setup.\n *\n * App-only in v1. Caller-scoped operations reject before opening a connection.\n * The platform's postgres user scope is deferred for this connector.\n *\n * @example\n * ```ts\n * import { createApp, lakebase, server } from \"@databricks/appkit\";\n *\n * const AppKit = await createApp({\n * plugins: [server(), lakebase()],\n * });\n *\n * // Service principal query\n * const result = await AppKit.lakebase.query(\"SELECT * FROM users WHERE id = $1\", [userId]);\n *\n * ```\n */\nexport class LakebasePlugin extends Plugin implements ToolProvider {\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = defineManifest<\"lakebase\">(manifest);\n\n declare protected config: ILakebaseConfig;\n private pool: RoutingPool | null = null;\n private oboPoolManager: LakebasePoolManager | null = null;\n\n /**\n * Initializes the Lakebase connection pool and OBO pool manager.\n * Called automatically by AppKit during the plugin setup phase.\n *\n * Creates a {@link RoutingPool} that automatically routes queries to either\n * the service-principal pool or a per-user pool based on the execution\n * context (set by `Plugin.asUser(req)` via AsyncLocalStorage).\n */\n async setup() {\n const poolConfig = {\n ...this.config.pool,\n workspaceClient:\n this.config.pool?.workspaceClient ??\n createWorkspaceClient({\n clientOptions: getClientOptions(),\n }).toLegacyWorkspaceClient(),\n };\n const user = await getUsernameWithApiLookup(poolConfig);\n\n const spPool = createLakebasePool({ ...poolConfig, user });\n logger.info(\"Lakebase SP pool initialized\");\n\n this.oboPoolManager = createLakebasePoolManager({\n ...poolConfig,\n ...OBO_POOL_DEFAULTS,\n });\n logger.info(\"Lakebase OBO pool manager initialized\");\n\n const oboManager = this.oboPoolManager;\n this.pool = new RoutingPool(spPool, (ctx) => {\n if (!oboManager) throw new Error(\"OBO pool manager not initialized\");\n // Lakebase OAuth roles use email as the postgres role when available\n const userKey = ctx.principal.userEmail ?? ctx.principal.userId;\n const isNew = !oboManager.hasPool(userKey);\n const pool = oboManager.getPool(\n userKey,\n {\n workspaceClient: ctx.client.toLegacyWorkspaceClient(),\n user: userKey,\n },\n ctx.tokenFingerprint,\n );\n if (isNew) {\n logger.debug(\"Created OBO pool for user (total: %d)\", oboManager.size);\n }\n return pool;\n });\n }\n\n /**\n * Executes a parameterized SQL query against the Lakebase pool.\n *\n * New caller scopes enforce the app-only resource contract. Deprecated\n * user entry points retain their established per-user pool routing.\n *\n * @param text - SQL query string, using `$1`, `$2`, ... placeholders\n * @param values - Parameter values corresponding to placeholders\n * @returns Query result with typed rows\n *\n * @example\n * ```ts\n * const result = await AppKit.lakebase.query<{ id: number; name: string }>(\n * \"SELECT id, name FROM users WHERE active = $1\",\n * [true],\n * );\n * ```\n */\n async query<T extends QueryResultRow = any>(\n text: string,\n values?: unknown[],\n ): Promise<QueryResult<T>> {\n // oxlint-disable-next-line typescript/no-non-null-assertion -- pool is initialized during setup()\n return this.pool!.query<T>(text, values);\n }\n\n /**\n * Execute a single statement inside a `BEGIN READ ONLY … ROLLBACK`\n * transaction on a dedicated client.\n *\n * The three commands MUST share a connection — a naive\n * `pool.query(\"BEGIN READ ONLY; <stmt>; ROLLBACK\")` batch cannot accept\n * parameter values (PostgreSQL's Extended Query protocol rejects multi-\n * statement prepared queries), which would silently break every\n * parameterized query the agent tool issues.\n *\n * Returns the raw `rows` array for the user's statement. Side effects the\n * statement may attempt (writes, writable-function side effects) are\n * rejected by PostgreSQL under the read-only transaction posture.\n */\n private async runReadOnlyStatement(\n text: string,\n values?: unknown[],\n ): Promise<unknown[]> {\n // oxlint-disable-next-line typescript/no-non-null-assertion -- pool is initialized during setup()\n const client = await this.pool!.connect();\n try {\n await client.query(\"BEGIN READ ONLY\");\n const result = await client.query(text, values);\n return result.rows;\n } finally {\n try {\n await client.query(\"ROLLBACK\");\n } finally {\n client.release();\n }\n }\n }\n\n /**\n * Gracefully drains and closes all connection pools (SP + OBO).\n *\n * Runs as the plugin's `shutdown()` hook (phase 3 of the core lifecycle\n * manager's graceful shutdown), NOT in `abortActiveOperations()` (phase 2):\n * other plugins' `shutdown()` hooks may still need database connections to\n * drain state,\n * so the pools must outlive the abort phase. `pg.Pool#end()` waits for\n * checked-out clients to be released, so hooks running concurrently with\n * this one can still finish their in-flight queries. Errors are caught\n * and logged; this hook never throws.\n */\n async shutdown(): Promise<void> {\n if (this.pool) {\n logger.info(\"Closing Lakebase SP pool\");\n try {\n await this.pool.end();\n } catch (err) {\n logger.error(\"Error closing Lakebase SP pool: %O\", err);\n }\n this.pool = null;\n }\n if (this.oboPoolManager) {\n logger.info(\n \"Closing all Lakebase OBO pools (%d)\",\n this.oboPoolManager.size,\n );\n try {\n await this.oboPoolManager.closeAll();\n } catch (err) {\n logger.error(\"Error closing Lakebase OBO pools: %O\", err);\n }\n this.oboPoolManager = null;\n }\n }\n\n /**\n * Agent tool registry. Empty by default — the Lakebase plugin does NOT\n * expose its SQL connection to LLM agents unless the developer explicitly\n * opts in via `config.exposeAsAgentTool`. See {@link buildQueryTool}.\n */\n private tools: Record<string, ReturnType<typeof this.buildQueryTool>> = {};\n\n constructor(config: ILakebaseConfig) {\n super(config);\n this.config = config;\n if (config.exposeAsAgentTool) {\n this.tools = { query: this.buildQueryTool(config.exposeAsAgentTool) };\n logger.warn(\n \"Lakebase agent tool is enabled (readOnly=%s). Every agent with access to this plugin can execute SQL against the Lakebase database as the requesting user's identity.\",\n config.exposeAsAgentTool.readOnly !== false,\n );\n }\n }\n\n private buildQueryTool(\n opt: NonNullable<ILakebaseConfig[\"exposeAsAgentTool\"]>,\n ) {\n const readOnly = opt.readOnly !== false;\n return defineTool({\n description: readOnly\n ? \"Execute a read-only SQL query against the Lakebase PostgreSQL database. Only SELECT, WITH, SHOW, EXPLAIN, and DESCRIBE statements are accepted. Use $1, $2, etc. as placeholders and pass values separately.\"\n : \"Execute a parameterized SQL statement against the Lakebase PostgreSQL database. Use $1, $2, etc. as placeholders and pass values separately. This tool can modify data; every invocation requires explicit human approval.\",\n schema: z.object({\n text: z\n .string()\n .describe(\n \"SQL statement with $1, $2, ... placeholders for parameters\",\n ),\n values: z\n .array(z.unknown())\n .optional()\n .describe(\"Parameter values corresponding to placeholders\"),\n }),\n annotations: {\n effect: readOnly ? \"read\" : \"destructive\",\n idempotent: false,\n requiresUserContext: true,\n },\n execute: async (args, signal) => {\n // Matches the files plugin pattern: the pg connection API\n // doesn't accept AbortSignal in its current shape, so deeper\n // mid-call cancellation needs a separate plumbing pass on the\n // connector. This entry check still catches the common case —\n // a tool dispatched after the user already cancelled the\n // stream — and unwinds cleanly instead of running to\n // completion against the SQL warehouse.\n signal?.throwIfAborted();\n if (readOnly) {\n assertReadOnlySql(args.text);\n return this.runReadOnlyStatement(args.text, args.values);\n }\n const result = await this.query(args.text, args.values);\n return result.rows;\n },\n });\n }\n\n getAgentTools(): AgentToolDefinition[] {\n return toolsFromRegistry(this.tools);\n }\n\n async executeAgentTool(\n name: string,\n args: unknown,\n signal?: AbortSignal,\n ): Promise<unknown> {\n return executeFromRegistry(this.tools, name, args, signal);\n }\n\n toolkit(opts?: import(\"../../core/agent/types\").ToolkitOptions) {\n return buildToolkitEntries(this.name, this.tools, opts);\n }\n\n /**\n * Returns the pool config for the current execution context.\n * Caller scopes reject; plain execution returns SP configuration.\n */\n private activePoolConfig() {\n assertResourceExecution(\"postgres\");\n const ctx = getCallerContext();\n if (ctx) {\n const user = ctx.principal.userEmail ?? ctx.principal.userId;\n return {\n ...this.config.pool,\n workspaceClient: ctx.client.toLegacyWorkspaceClient(),\n user,\n };\n }\n return this.config.pool;\n }\n\n /**\n * Returns the plugin's public API, accessible via `AppKit.lakebase`.\n *\n * - `pool`: The service-principal connection pool.\n * - `query`: Convenience method for executing parameterized SQL queries.\n * - `getOrmConfig()`: SP configuration for Drizzle, TypeORM, Sequelize, etc.\n * - `getPgConfig()`: A service-principal `pg.PoolConfig` for manual pools.\n *\n * Caller-scoped access is rejected in v1, including cached pool handles.\n */\n exports() {\n return {\n // oxlint-disable-next-line typescript/no-non-null-assertion -- pool is initialized during setup()\n pool: this.pool! as LakebasePool,\n query: this.query.bind(this),\n getOrmConfig: () => getLakebaseOrmConfig(this.activePoolConfig()),\n getPgConfig: () => getLakebasePgConfig(this.activePoolConfig()),\n };\n }\n}\n\n/**\n * @internal\n */\nexport const lakebase = toPlugin(LakebasePlugin);\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createLogger } from "../../logging/logger.js";
|
|
2
|
+
import { isPlainObject } from "../../utils/is-plain-object.js";
|
|
2
3
|
import pc from "picocolors";
|
|
3
4
|
|
|
4
5
|
//#region src/plugins/server/client-config-sanitizer.ts
|
|
@@ -47,10 +48,6 @@ function isSecretCoveredByPublicValue(value, envValue, publicValues) {
|
|
|
47
48
|
if (publicRanges.length === 0) return false;
|
|
48
49
|
return getMatchRanges(value, envValue).every(([secretStart, secretEnd]) => publicRanges.some(([publicStart, publicEnd]) => publicStart <= secretStart && publicEnd >= secretEnd));
|
|
49
50
|
}
|
|
50
|
-
function isPlainObject(value) {
|
|
51
|
-
const proto = Object.getPrototypeOf(value);
|
|
52
|
-
return proto === Object.prototype || proto === null;
|
|
53
|
-
}
|
|
54
51
|
function invalidClientConfig(pluginName, path, message) {
|
|
55
52
|
return /* @__PURE__ */ new Error(`Plugin '${pluginName}' clientConfig() ${message} at ${path}. Only JSON-serializable plain data is supported.`);
|
|
56
53
|
}
|