@databricks/appkit 0.54.0 → 0.55.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CLAUDE.md +9 -2
  2. package/dist/appkit/package.js +1 -1
  3. package/dist/beta.d.ts +3 -1
  4. package/dist/beta.js +2 -1
  5. package/dist/cli/commands/plugin/create/create.js +12 -14
  6. package/dist/cli/commands/plugin/create/create.js.map +1 -1
  7. package/dist/cli/commands/plugin/promote/promote.js +2 -12
  8. package/dist/cli/commands/plugin/promote/promote.js.map +1 -1
  9. package/dist/connectors/ai-search/client.js +145 -0
  10. package/dist/connectors/ai-search/client.js.map +1 -0
  11. package/dist/connectors/ai-search/index.js +3 -0
  12. package/dist/connectors/context.js +38 -0
  13. package/dist/connectors/context.js.map +1 -0
  14. package/dist/connectors/index.js +2 -1
  15. package/dist/connectors/serving/client.d.ts.map +1 -1
  16. package/dist/connectors/serving/client.js +2 -26
  17. package/dist/connectors/serving/client.js.map +1 -1
  18. package/dist/core/appkit.d.ts.map +1 -1
  19. package/dist/core/appkit.js.map +1 -1
  20. package/dist/index.d.ts +2 -2
  21. package/dist/naming.js +29 -0
  22. package/dist/plugin/plugin.d.ts.map +1 -1
  23. package/dist/plugin/plugin.js +2 -1
  24. package/dist/plugin/plugin.js.map +1 -1
  25. package/dist/plugins/ai-search/ai-search.d.ts +63 -0
  26. package/dist/plugins/ai-search/ai-search.d.ts.map +1 -0
  27. package/dist/plugins/ai-search/ai-search.js +296 -0
  28. package/dist/plugins/ai-search/ai-search.js.map +1 -0
  29. package/dist/plugins/ai-search/defaults.js +14 -0
  30. package/dist/plugins/ai-search/defaults.js.map +1 -0
  31. package/dist/plugins/ai-search/index.d.ts +2 -0
  32. package/dist/plugins/ai-search/index.js +3 -0
  33. package/dist/plugins/ai-search/manifest.js +73 -0
  34. package/dist/plugins/ai-search/manifest.js.map +1 -0
  35. package/dist/plugins/ai-search/types.d.ts +80 -0
  36. package/dist/plugins/ai-search/types.d.ts.map +1 -0
  37. package/dist/plugins/beta-exports.generated.d.ts +3 -1
  38. package/dist/plugins/beta-exports.generated.js +2 -0
  39. package/dist/plugins/jobs/index.d.ts +1 -1
  40. package/dist/plugins/jobs/plugin.d.ts.map +1 -1
  41. package/dist/plugins/jobs/plugin.js +1 -6
  42. package/dist/plugins/jobs/plugin.js.map +1 -1
  43. package/dist/plugins/jobs/types.d.ts +3 -13
  44. package/dist/plugins/jobs/types.d.ts.map +1 -1
  45. package/dist/plugins/server/index.d.ts.map +1 -1
  46. package/dist/plugins/server/index.js +2 -1
  47. package/dist/plugins/server/index.js.map +1 -1
  48. package/dist/plugins/ui-variants/index.js.map +1 -1
  49. package/dist/plugins/ui-variants/manifest.js +1 -1
  50. package/dist/registry/resource-registry.d.ts.map +1 -1
  51. package/dist/registry/resource-registry.js +2 -7
  52. package/dist/registry/resource-registry.js.map +1 -1
  53. package/dist/schemas/manifest.d.ts.map +1 -1
  54. package/dist/schemas/manifest.js +3 -2
  55. package/dist/schemas/manifest.js.map +1 -1
  56. package/dist/shared/src/naming.js +13 -0
  57. package/dist/shared/src/naming.js.map +1 -0
  58. package/dist/shared/src/plugin.d.ts +6 -4
  59. package/dist/shared/src/plugin.d.ts.map +1 -1
  60. package/dist/shared/src/schemas/manifest.d.ts.map +1 -1
  61. package/dist/utils/banner.js +19 -0
  62. package/dist/utils/banner.js.map +1 -0
  63. package/docs/api/appkit/Interface.BasePluginConfig.md +1 -0
  64. package/docs/api/appkit/Interface.IAiSearchConfig.md +71 -0
  65. package/docs/api/appkit/Interface.IndexConfig.md +110 -0
  66. package/docs/api/appkit/Interface.RerankerConfig.md +10 -0
  67. package/docs/api/appkit/Interface.SearchRequest.md +64 -0
  68. package/docs/api/appkit/Interface.SearchResponse.md +52 -0
  69. package/docs/api/appkit/Interface.SearchResult.md +25 -0
  70. package/docs/api/appkit/TypeAlias.JobsExport.md +2 -5
  71. package/docs/api/appkit/TypeAlias.SearchFilters.md +6 -0
  72. package/docs/api/appkit/Variable.aiSearch.md +6 -0
  73. package/docs/api/appkit.md +8 -1
  74. package/docs/plugins/{vector-search.md → ai-search.md} +70 -27
  75. package/docs/plugins/jobs.md +1 -14
  76. package/llms.txt +9 -2
  77. package/package.json +1 -1
  78. package/sbom.cdx.json +1 -1
  79. package/dist/connectors/vector-search/client.js +0 -9
  80. package/dist/connectors/vector-search/client.js.map +0 -1
  81. package/dist/connectors/vector-search/index.js +0 -3
  82. package/docs/api/appkit/TypeAlias.JobHandle.md +0 -29
@@ -0,0 +1,296 @@
1
+ import { createLogger } from "../../logging/logger.js";
2
+ import { getWorkspaceClient } from "../../context/execution-context.js";
3
+ import { formatWarningBanner } from "../../utils/banner.js";
4
+ import "../../context/index.js";
5
+ import { Plugin } from "../../plugin/plugin.js";
6
+ import { toPlugin } from "../../plugin/to-plugin.js";
7
+ import "../../plugin/index.js";
8
+ import { AiSearchConnector } from "../../connectors/ai-search/client.js";
9
+ import { aiSearchDefaults } from "./defaults.js";
10
+ import manifest_default from "./manifest.js";
11
+
12
+ //#region src/plugins/ai-search/ai-search.ts
13
+ const logger = createLogger("ai-search");
14
+ const querySettings = { default: aiSearchDefaults };
15
+ var AiSearchPlugin = class extends Plugin {
16
+ static manifest = manifest_default;
17
+ static description = "Query Databricks Vector Search indexes with hybrid search, reranking, and pagination";
18
+ connector;
19
+ constructor(config) {
20
+ super(config);
21
+ this.config = {
22
+ ...config,
23
+ indexes: config.indexes ?? this._defaultIndexes()
24
+ };
25
+ this.connector = new AiSearchConnector({
26
+ timeout: config.timeout,
27
+ telemetry: config.telemetry
28
+ });
29
+ }
30
+ /**
31
+ * Seeds a `default` index from `DATABRICKS_VS_INDEX_NAME` when no `indexes`
32
+ * are configured, so `aiSearch()` works with just the env var.
33
+ */
34
+ _defaultIndexes() {
35
+ const indexName = process.env.DATABRICKS_VS_INDEX_NAME;
36
+ return indexName ? { default: { indexName } } : {};
37
+ }
38
+ async setup() {
39
+ for (const [alias, idx] of Object.entries(this.config.indexes ?? {})) if (idx.pagination && !idx.endpointName) throw new Error(`Index "${alias}" has pagination enabled but is missing "endpointName"`);
40
+ if (process.env.NODE_ENV === "development") await this._autoDiscoverColumns();
41
+ else for (const [alias, idx] of Object.entries(this.config.indexes ?? {})) if (!idx.columns || idx.columns.length === 0) throw new Error(`Index "${alias}" has no columns configured. Vector Search queries require "columns"; set them explicitly (auto-discovered only in development).`);
42
+ }
43
+ /**
44
+ * For each configured index missing `columns`, fill them from its Delta-Sync
45
+ * source table (all source columns minus embedding vectors). Best-effort:
46
+ * failures are logged and skipped, never thrown. A partial `columns_to_sync`
47
+ * isn't honored, so the discovered list is a starting point to trim.
48
+ */
49
+ async _autoDiscoverColumns() {
50
+ const discovered = {};
51
+ for (const [alias, idx] of Object.entries(this.config.indexes ?? {})) {
52
+ if (idx.columns && idx.columns.length > 0) continue;
53
+ const indexName = idx.indexName ?? process.env.DATABRICKS_VS_INDEX_NAME;
54
+ if (!indexName) continue;
55
+ try {
56
+ const client = getWorkspaceClient();
57
+ const info = await this.connector.getIndex(client, indexName);
58
+ const sourceTable = info.delta_sync_index_spec?.source_table;
59
+ if (!sourceTable) continue;
60
+ const excluded = new Set((info.delta_sync_index_spec?.embedding_vector_columns ?? []).map((c) => c.name));
61
+ const columns = (await this.connector.getSourceColumns(client, sourceTable)).filter((c) => !excluded.has(c));
62
+ if (columns.length > 0) {
63
+ idx.columns = columns;
64
+ discovered[alias] = columns;
65
+ }
66
+ } catch (error) {
67
+ logger.warn("Could not auto-discover columns for index \"%s\": %s", alias, error instanceof Error ? error.message : String(error));
68
+ }
69
+ }
70
+ if (Object.keys(discovered).length > 0) logger.warn("\n%s", this._formatColumnDiscoveryBanner(discovered));
71
+ }
72
+ _formatColumnDiscoveryBanner(discovered) {
73
+ const lines = ["AI SEARCH: columns auto-discovered (dev mode — would fail in production)", ""];
74
+ for (const [alias, columns] of Object.entries(discovered)) lines.push(` ${alias}: ${columns.join(", ")}`);
75
+ lines.push("");
76
+ lines.push("Set `columns` explicitly in the plugin config before deploying.");
77
+ return formatWarningBanner(lines);
78
+ }
79
+ injectRoutes(router) {
80
+ this.route(router, {
81
+ name: "query",
82
+ method: "post",
83
+ path: "/:alias/query",
84
+ handler: async (req, res) => {
85
+ const indexConfig = this._resolveOr404(req, res);
86
+ if (!indexConfig) return;
87
+ const body = req.body;
88
+ if (!body.queryText && !body.queryVector) {
89
+ res.status(400).json({
90
+ error: "queryText or queryVector is required",
91
+ plugin: this.name
92
+ });
93
+ return;
94
+ }
95
+ const { columns: _clientColumns, ...safeBody } = body;
96
+ const plugin = indexConfig.auth === "on-behalf-of-user" ? this.asUser(req) : this;
97
+ const queryType = safeBody.queryType ?? indexConfig.queryType ?? "hybrid";
98
+ try {
99
+ const result = await plugin.execute(async (signal) => {
100
+ const prepared = await this._prepareQuery(safeBody, indexConfig);
101
+ return this.connector.query(getWorkspaceClient(), {
102
+ indexName: indexConfig.indexName,
103
+ ...prepared
104
+ }, signal);
105
+ }, querySettings);
106
+ this._sendResult(res, result, queryType);
107
+ } catch (error) {
108
+ this._handleError(res, error, "Query failed");
109
+ }
110
+ }
111
+ });
112
+ this.route(router, {
113
+ name: "queryNextPage",
114
+ method: "post",
115
+ path: "/:alias/next-page",
116
+ handler: async (req, res) => {
117
+ const indexConfig = this._resolveOr404(req, res);
118
+ if (!indexConfig) return;
119
+ if (!indexConfig.pagination) {
120
+ res.status(400).json({
121
+ error: `Pagination is not enabled for index "${req.params.alias}"`,
122
+ plugin: this.name
123
+ });
124
+ return;
125
+ }
126
+ if (!indexConfig.endpointName) {
127
+ res.status(400).json({
128
+ error: `Index "${req.params.alias}" is missing endpointName required for pagination`,
129
+ plugin: this.name
130
+ });
131
+ return;
132
+ }
133
+ const { pageToken, queryType } = req.body;
134
+ if (!pageToken) {
135
+ res.status(400).json({
136
+ error: "pageToken is required",
137
+ plugin: this.name
138
+ });
139
+ return;
140
+ }
141
+ const pageQueryType = queryType ?? indexConfig.queryType ?? "hybrid";
142
+ try {
143
+ const result = await (indexConfig.auth === "on-behalf-of-user" ? this.asUser(req) : this).execute(async (signal) => this.connector.queryNextPage(getWorkspaceClient(), {
144
+ indexName: indexConfig.indexName,
145
+ endpointName: indexConfig.endpointName,
146
+ pageToken
147
+ }, signal), querySettings);
148
+ this._sendResult(res, result, pageQueryType);
149
+ } catch (error) {
150
+ this._handleError(res, error, "Next-page query failed");
151
+ }
152
+ }
153
+ });
154
+ this.route(router, {
155
+ name: "getConfig",
156
+ method: "get",
157
+ path: "/:alias/config",
158
+ handler: async (req, res) => {
159
+ const indexConfig = this._resolveOr404(req, res);
160
+ if (!indexConfig) return;
161
+ res.json({
162
+ alias: req.params.alias,
163
+ columns: indexConfig.columns,
164
+ queryType: indexConfig.queryType ?? "hybrid",
165
+ numResults: indexConfig.numResults ?? 20,
166
+ reranker: !!indexConfig.reranker,
167
+ pagination: !!indexConfig.pagination
168
+ });
169
+ }
170
+ });
171
+ }
172
+ /**
173
+ * Index aliases + non-sensitive query metadata, serialized to the client so
174
+ * the UI can discover available indexes instead of hardcoding an alias.
175
+ */
176
+ clientConfig() {
177
+ return { indexes: Object.entries(this.config.indexes ?? {}).map(([alias, idx]) => ({
178
+ alias,
179
+ queryType: idx.queryType ?? "hybrid",
180
+ pagination: !!idx.pagination
181
+ })) };
182
+ }
183
+ /**
184
+ * Programmatic query API — available as `appkit.aiSearch.query()`.
185
+ * When called through `asUser(req)`, executes with the user's credentials.
186
+ *
187
+ * @remarks `T` types each result's `data` but is an unchecked cast — the row
188
+ * shape isn't validated at runtime.
189
+ */
190
+ async query(alias, request) {
191
+ const indexConfig = this._resolveIndex(alias);
192
+ if (!indexConfig) throw new Error(`No index configured with alias "${alias}"`);
193
+ const prepared = await this._prepareQuery(request, indexConfig);
194
+ const result = await this.execute(async (signal) => this.connector.query(getWorkspaceClient(), {
195
+ indexName: indexConfig.indexName,
196
+ ...prepared
197
+ }, signal), querySettings);
198
+ if (!result.ok) throw new Error(`Vector search query failed for index "${alias}": ${result.message}`);
199
+ return this._parseResponse(result.data, prepared.queryType);
200
+ }
201
+ async shutdown() {}
202
+ exports() {
203
+ return { query: this.query.bind(this) };
204
+ }
205
+ _resolveIndex(alias) {
206
+ const idx = this.config.indexes?.[alias];
207
+ if (!idx) return void 0;
208
+ const indexName = idx.indexName ?? process.env.DATABRICKS_VS_INDEX_NAME;
209
+ if (!indexName) return void 0;
210
+ return {
211
+ ...idx,
212
+ indexName
213
+ };
214
+ }
215
+ /** Resolve an index by route alias, or send a 404 and return null. */
216
+ _resolveOr404(req, res) {
217
+ const indexConfig = this._resolveIndex(req.params.alias);
218
+ if (!indexConfig) {
219
+ res.status(404).json({
220
+ error: `No index configured with alias "${req.params.alias}"`,
221
+ plugin: this.name
222
+ });
223
+ return null;
224
+ }
225
+ return indexConfig;
226
+ }
227
+ /** Send an execution result as JSON, or its error status/message. */
228
+ _sendResult(res, result, queryType) {
229
+ if (!result.ok) {
230
+ res.status(result.status).json({
231
+ error: result.message,
232
+ plugin: this.name
233
+ });
234
+ return;
235
+ }
236
+ res.json(this._parseResponse(result.data, queryType));
237
+ }
238
+ async _prepareQuery(request, indexConfig) {
239
+ const queryType = request.queryType ?? indexConfig.queryType ?? "hybrid";
240
+ let queryText = request.queryText;
241
+ let queryVector = request.queryVector;
242
+ if (indexConfig.embeddingFn && queryText && !queryVector && queryType !== "full_text") try {
243
+ queryVector = await indexConfig.embeddingFn(queryText);
244
+ if (queryType === "ann") queryText = void 0;
245
+ } catch (error) {
246
+ throw new Error(`Embedding generation failed: ${error instanceof Error ? error.message : String(error)}`);
247
+ }
248
+ const columns = request.columns ?? indexConfig.columns ?? [];
249
+ return {
250
+ queryText,
251
+ queryVector,
252
+ queryType,
253
+ columns,
254
+ numResults: request.numResults ?? indexConfig.numResults ?? 20,
255
+ filters: request.filters,
256
+ reranker: this._resolveReranker(request.reranker, indexConfig, columns)
257
+ };
258
+ }
259
+ _resolveReranker(requestReranker, indexConfig, columns) {
260
+ if (!(requestReranker ?? indexConfig.reranker)) return void 0;
261
+ if (typeof indexConfig.reranker === "object") return indexConfig.reranker;
262
+ const columnsToRerank = columns.filter((c) => c !== "id");
263
+ return columnsToRerank.length > 0 ? { columnsToRerank } : void 0;
264
+ }
265
+ _parseResponse(raw, queryType) {
266
+ const columnNames = raw.manifest.columns.map((c) => c.name);
267
+ const scoreIndex = columnNames.indexOf("score");
268
+ return {
269
+ results: raw.result.data_array.map((row) => {
270
+ const data = {};
271
+ for (let i = 0; i < columnNames.length; i++) if (i !== scoreIndex) data[columnNames[i]] = row[i];
272
+ return {
273
+ score: scoreIndex >= 0 ? row[scoreIndex] : 0,
274
+ data
275
+ };
276
+ }),
277
+ totalCount: raw.result.row_count,
278
+ queryTimeMs: raw.debug_info?.response_time ?? raw.debug_info?.latency_ms ?? 0,
279
+ queryType,
280
+ nextPageToken: raw.next_page_token ?? null
281
+ };
282
+ }
283
+ _handleError(res, error, fallbackMessage) {
284
+ logger.error("%s: %O", fallbackMessage, error);
285
+ const message = process.env.NODE_ENV !== "production" && error instanceof Error ? error.message : fallbackMessage;
286
+ res.status(500).json({
287
+ error: message,
288
+ plugin: this.name
289
+ });
290
+ }
291
+ };
292
+ const aiSearch = toPlugin(AiSearchPlugin);
293
+
294
+ //#endregion
295
+ export { AiSearchPlugin, aiSearch };
296
+ //# sourceMappingURL=ai-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-search.js","names":["manifest"],"sources":["../../../src/plugins/ai-search/ai-search.ts"],"sourcesContent":["import type express from \"express\";\nimport type { IAppRouter, PluginExecutionSettings } from \"shared\";\nimport { AiSearchConnector } from \"../../connectors/ai-search/client\";\nimport type {\n VsQueryParams,\n VsRawResponse,\n} from \"../../connectors/ai-search/types\";\nimport { getWorkspaceClient } from \"../../context\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { formatWarningBanner } from \"../../utils/banner\";\nimport { aiSearchDefaults } from \"./defaults\";\nimport manifest from \"./manifest.json\";\nimport type {\n IAiSearchConfig,\n IndexConfig,\n IndexSummary,\n SearchQueryType,\n SearchRequest,\n SearchResponse,\n SearchResult,\n} from \"./types\";\n\nconst logger = createLogger(\"ai-search\");\n\nconst querySettings: PluginExecutionSettings = {\n default: aiSearchDefaults,\n};\n\nexport class AiSearchPlugin extends Plugin<IAiSearchConfig> {\n static manifest = manifest as PluginManifest<\"aiSearch\">;\n\n protected static description =\n \"Query Databricks Vector Search indexes with hybrid search, reranking, and pagination\";\n protected declare config: IAiSearchConfig;\n\n private connector: AiSearchConnector;\n\n constructor(config: IAiSearchConfig) {\n super(config);\n this.config = {\n ...config,\n indexes: config.indexes ?? this._defaultIndexes(),\n };\n this.connector = new AiSearchConnector({\n timeout: config.timeout,\n telemetry: config.telemetry,\n });\n }\n\n /**\n * Seeds a `default` index from `DATABRICKS_VS_INDEX_NAME` when no `indexes`\n * are configured, so `aiSearch()` works with just the env var.\n */\n private _defaultIndexes(): Record<string, IndexConfig> {\n const indexName = process.env.DATABRICKS_VS_INDEX_NAME;\n return indexName ? { default: { indexName } } : {};\n }\n\n async setup(): Promise<void> {\n // pagination needs an endpointName the framework's resource validation\n // can't see, so check it here.\n for (const [alias, idx] of Object.entries(this.config.indexes ?? {})) {\n if (idx.pagination && !idx.endpointName) {\n throw new Error(\n `Index \"${alias}\" has pagination enabled but is missing \"endpointName\"`,\n );\n }\n }\n\n // Dev fills in missing `columns` from the source table; prod can't query\n // without them (VS requires `columns`), so fail fast at boot.\n if (process.env.NODE_ENV === \"development\") {\n await this._autoDiscoverColumns();\n } else {\n for (const [alias, idx] of Object.entries(this.config.indexes ?? {})) {\n if (!idx.columns || idx.columns.length === 0) {\n throw new Error(\n `Index \"${alias}\" has no columns configured. Vector Search queries require \"columns\"; set them explicitly (auto-discovered only in development).`,\n );\n }\n }\n }\n }\n\n /**\n * For each configured index missing `columns`, fill them from its Delta-Sync\n * source table (all source columns minus embedding vectors). Best-effort:\n * failures are logged and skipped, never thrown. A partial `columns_to_sync`\n * isn't honored, so the discovered list is a starting point to trim.\n */\n private async _autoDiscoverColumns(): Promise<void> {\n const discovered: Record<string, string[]> = {};\n for (const [alias, idx] of Object.entries(this.config.indexes ?? {})) {\n if (idx.columns && idx.columns.length > 0) continue;\n const indexName = idx.indexName ?? process.env.DATABRICKS_VS_INDEX_NAME;\n if (!indexName) continue;\n try {\n const client = getWorkspaceClient();\n const info = await this.connector.getIndex(client, indexName);\n const sourceTable = info.delta_sync_index_spec?.source_table;\n if (!sourceTable) continue;\n const excluded = new Set(\n (info.delta_sync_index_spec?.embedding_vector_columns ?? []).map(\n (c) => c.name,\n ),\n );\n const columns = (\n await this.connector.getSourceColumns(client, sourceTable)\n ).filter((c) => !excluded.has(c));\n if (columns.length > 0) {\n idx.columns = columns;\n discovered[alias] = columns;\n }\n } catch (error) {\n logger.warn(\n 'Could not auto-discover columns for index \"%s\": %s',\n alias,\n error instanceof Error ? error.message : String(error),\n );\n }\n }\n if (Object.keys(discovered).length > 0) {\n logger.warn(\"\\n%s\", this._formatColumnDiscoveryBanner(discovered));\n }\n }\n\n private _formatColumnDiscoveryBanner(\n discovered: Record<string, string[]>,\n ): string {\n const lines = [\n \"AI SEARCH: columns auto-discovered (dev mode — would fail in production)\",\n \"\",\n ];\n for (const [alias, columns] of Object.entries(discovered)) {\n lines.push(` ${alias}: ${columns.join(\", \")}`);\n }\n lines.push(\"\");\n lines.push(\n \"Set `columns` explicitly in the plugin config before deploying.\",\n );\n\n return formatWarningBanner(lines);\n }\n\n injectRoutes(router: IAppRouter) {\n this.route(router, {\n name: \"query\",\n method: \"post\",\n path: \"/:alias/query\",\n handler: async (req: express.Request, res: express.Response) => {\n const indexConfig = this._resolveOr404(req, res);\n if (!indexConfig) return;\n\n const body: SearchRequest = req.body;\n if (!body.queryText && !body.queryVector) {\n res.status(400).json({\n error: \"queryText or queryVector is required\",\n plugin: this.name,\n });\n return;\n }\n\n // Drop client-supplied `columns` so an HTTP caller can't widen the\n // projection past what the app configured. (query() callers are\n // trusted and keep the override.)\n const { columns: _clientColumns, ...safeBody } = body;\n const plugin =\n indexConfig.auth === \"on-behalf-of-user\" ? this.asUser(req) : this;\n const queryType =\n safeBody.queryType ?? indexConfig.queryType ?? \"hybrid\";\n\n try {\n // Prepare inside execute so a self-managed embeddingFn runs in the\n // same OBO context as the query, not as the service principal.\n const result = await plugin.execute(async (signal) => {\n const prepared = await this._prepareQuery(safeBody, indexConfig);\n return this.connector.query(\n getWorkspaceClient(),\n { indexName: indexConfig.indexName, ...prepared },\n signal,\n );\n }, querySettings);\n\n this._sendResult(res, result, queryType);\n } catch (error) {\n this._handleError(res, error, \"Query failed\");\n }\n },\n });\n\n this.route(router, {\n name: \"queryNextPage\",\n method: \"post\",\n path: \"/:alias/next-page\",\n handler: async (req: express.Request, res: express.Response) => {\n const indexConfig = this._resolveOr404(req, res);\n if (!indexConfig) return;\n\n if (!indexConfig.pagination) {\n res.status(400).json({\n error: `Pagination is not enabled for index \"${req.params.alias}\"`,\n plugin: this.name,\n });\n return;\n }\n\n if (!indexConfig.endpointName) {\n res.status(400).json({\n error: `Index \"${req.params.alias}\" is missing endpointName required for pagination`,\n plugin: this.name,\n });\n return;\n }\n\n const { pageToken, queryType } = req.body;\n if (!pageToken) {\n res.status(400).json({\n error: \"pageToken is required\",\n plugin: this.name,\n });\n return;\n }\n // Echo the original query's queryType so paged responses stay\n // consistent with page 1; fall back to the index default.\n const pageQueryType = queryType ?? indexConfig.queryType ?? \"hybrid\";\n\n try {\n const plugin =\n indexConfig.auth === \"on-behalf-of-user\" ? this.asUser(req) : this;\n\n const result = await plugin.execute(\n async (signal) =>\n this.connector.queryNextPage(\n getWorkspaceClient(),\n {\n indexName: indexConfig.indexName,\n endpointName: indexConfig.endpointName as string,\n pageToken,\n },\n signal,\n ),\n querySettings,\n );\n\n this._sendResult(res, result, pageQueryType);\n } catch (error) {\n this._handleError(res, error, \"Next-page query failed\");\n }\n },\n });\n\n this.route(router, {\n name: \"getConfig\",\n method: \"get\",\n path: \"/:alias/config\",\n handler: async (req: express.Request, res: express.Response) => {\n const indexConfig = this._resolveOr404(req, res);\n if (!indexConfig) return;\n res.json({\n alias: req.params.alias,\n columns: indexConfig.columns,\n queryType: indexConfig.queryType ?? \"hybrid\",\n numResults: indexConfig.numResults ?? 20,\n reranker: !!indexConfig.reranker,\n pagination: !!indexConfig.pagination,\n });\n },\n });\n }\n\n /**\n * Index aliases + non-sensitive query metadata, serialized to the client so\n * the UI can discover available indexes instead of hardcoding an alias.\n */\n clientConfig(): { indexes: IndexSummary[] } {\n const indexes = Object.entries(this.config.indexes ?? {}).map(\n ([alias, idx]) => ({\n alias,\n queryType: idx.queryType ?? \"hybrid\",\n pagination: !!idx.pagination,\n }),\n );\n return { indexes };\n }\n\n /**\n * Programmatic query API — available as `appkit.aiSearch.query()`.\n * When called through `asUser(req)`, executes with the user's credentials.\n *\n * @remarks `T` types each result's `data` but is an unchecked cast — the row\n * shape isn't validated at runtime.\n */\n async query<T extends Record<string, unknown> = Record<string, unknown>>(\n alias: string,\n request: SearchRequest,\n ): Promise<SearchResponse<T>> {\n const indexConfig = this._resolveIndex(alias);\n if (!indexConfig) {\n throw new Error(`No index configured with alias \"${alias}\"`);\n }\n\n const prepared = await this._prepareQuery(request, indexConfig);\n\n const result = await this.execute(\n async (signal) =>\n this.connector.query(\n getWorkspaceClient(),\n { indexName: indexConfig.indexName, ...prepared },\n signal,\n ),\n querySettings,\n );\n\n if (!result.ok) {\n throw new Error(\n `Vector search query failed for index \"${alias}\": ${result.message}`,\n );\n }\n\n return this._parseResponse(result.data, prepared.queryType);\n }\n\n async shutdown(): Promise<void> {\n // No streams or persistent connections to clean up\n }\n\n exports() {\n return {\n query: this.query.bind(this),\n };\n }\n\n private _resolveIndex(\n alias: string,\n ): (IndexConfig & { indexName: string }) | undefined {\n const idx = this.config.indexes?.[alias];\n if (!idx) return undefined;\n const indexName = idx.indexName ?? process.env.DATABRICKS_VS_INDEX_NAME;\n if (!indexName) return undefined;\n return { ...idx, indexName };\n }\n\n /** Resolve an index by route alias, or send a 404 and return null. */\n private _resolveOr404(\n req: express.Request,\n res: express.Response,\n ): (IndexConfig & { indexName: string }) | null {\n const indexConfig = this._resolveIndex(req.params.alias);\n if (!indexConfig) {\n res.status(404).json({\n error: `No index configured with alias \"${req.params.alias}\"`,\n plugin: this.name,\n });\n return null;\n }\n return indexConfig;\n }\n\n /** Send an execution result as JSON, or its error status/message. */\n private _sendResult(\n res: express.Response,\n result: Awaited<ReturnType<typeof this.execute<VsRawResponse>>>,\n queryType: SearchQueryType,\n ): void {\n if (!result.ok) {\n res\n .status(result.status)\n .json({ error: result.message, plugin: this.name });\n return;\n }\n res.json(this._parseResponse(result.data, queryType));\n }\n\n private async _prepareQuery(\n request: SearchRequest,\n indexConfig: IndexConfig,\n ): Promise<Omit<VsQueryParams, \"indexName\">> {\n const queryType = request.queryType ?? indexConfig.queryType ?? \"hybrid\";\n let queryText = request.queryText;\n let queryVector = request.queryVector;\n\n // full_text uses no vector; hybrid keeps the text for its keyword half.\n if (\n indexConfig.embeddingFn &&\n queryText &&\n !queryVector &&\n queryType !== \"full_text\"\n ) {\n try {\n queryVector = await indexConfig.embeddingFn(queryText);\n if (queryType === \"ann\") queryText = undefined;\n } catch (error) {\n throw new Error(\n `Embedding generation failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n const columns = request.columns ?? indexConfig.columns ?? [];\n return {\n queryText,\n queryVector,\n queryType,\n columns,\n numResults: request.numResults ?? indexConfig.numResults ?? 20,\n filters: request.filters,\n reranker: this._resolveReranker(request.reranker, indexConfig, columns),\n };\n }\n\n private _resolveReranker(\n requestReranker: boolean | undefined,\n indexConfig: IndexConfig,\n columns: string[],\n ): { columnsToRerank: string[] } | undefined {\n const shouldRerank = requestReranker ?? indexConfig.reranker;\n if (!shouldRerank) return undefined;\n\n if (typeof indexConfig.reranker === \"object\") {\n return indexConfig.reranker;\n }\n // Auto-derive from returnable columns (excluding the id). With no columns\n // resolved there's nothing to rerank on, so skip it.\n const columnsToRerank = columns.filter((c) => c !== \"id\");\n return columnsToRerank.length > 0 ? { columnsToRerank } : undefined;\n }\n\n private _parseResponse<\n T extends Record<string, unknown> = Record<string, unknown>,\n >(raw: VsRawResponse, queryType: SearchQueryType): SearchResponse<T> {\n const columnNames = raw.manifest.columns.map((c) => c.name);\n const scoreIndex = columnNames.indexOf(\"score\");\n\n const results: SearchResult<T>[] = raw.result.data_array.map((row) => {\n const data: Record<string, unknown> = {};\n for (let i = 0; i < columnNames.length; i++) {\n if (i !== scoreIndex) data[columnNames[i]] = row[i];\n }\n return {\n score: scoreIndex >= 0 ? (row[scoreIndex] as number) : 0,\n data: data as T,\n };\n });\n\n return {\n results,\n totalCount: raw.result.row_count,\n queryTimeMs:\n raw.debug_info?.response_time ?? raw.debug_info?.latency_ms ?? 0,\n queryType,\n nextPageToken: raw.next_page_token ?? null,\n };\n }\n\n private _handleError(\n res: express.Response,\n error: unknown,\n fallbackMessage: string,\n ): void {\n logger.error(\"%s: %O\", fallbackMessage, error);\n // Match Plugin.execute(): the raw message is only exposed outside production.\n const isDev = process.env.NODE_ENV !== \"production\";\n const message =\n isDev && error instanceof Error ? error.message : fallbackMessage;\n res.status(500).json({ error: message, plugin: this.name });\n }\n}\n\nexport const aiSearch = toPlugin(AiSearchPlugin);\n"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ //#region src/plugins/ai-search/defaults.ts
2
+ const aiSearchDefaults = {
3
+ cache: { enabled: false },
4
+ retry: {
5
+ enabled: true,
6
+ initialDelay: 1e3,
7
+ attempts: 3
8
+ },
9
+ timeout: 3e4
10
+ };
11
+
12
+ //#endregion
13
+ export { aiSearchDefaults };
14
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/plugins/ai-search/defaults.ts"],"sourcesContent":["import type { PluginExecuteConfig } from \"shared\";\n\nexport const aiSearchDefaults: PluginExecuteConfig = {\n cache: { enabled: false },\n retry: { enabled: true, initialDelay: 1000, attempts: 3 },\n timeout: 30_000,\n};\n"],"mappings":";AAEA,MAAa,mBAAwC;CACnD,OAAO,EAAE,SAAS,OAAO;CACzB,OAAO;EAAE,SAAS;EAAM,cAAc;EAAM,UAAU;EAAG;CACzD,SAAS;CACV"}
@@ -0,0 +1,2 @@
1
+ import { IAiSearchConfig, IndexConfig, IndexSummary, RerankerConfig, SearchFilters, SearchQueryType, SearchRequest, SearchResponse, SearchResult } from "./types.js";
2
+ import { AiSearchPlugin, aiSearch } from "./ai-search.js";
@@ -0,0 +1,3 @@
1
+ import { AiSearchPlugin, aiSearch } from "./ai-search.js";
2
+
3
+ export { };
@@ -0,0 +1,73 @@
1
+ //#region src/plugins/ai-search/manifest.json
2
+ var manifest_default = {
3
+ $schema: "https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json",
4
+ name: "aiSearch",
5
+ displayName: "AI Search Plugin",
6
+ stability: "beta",
7
+ description: "Query Databricks Vector Search indexes with built-in hybrid search, reranking, and pagination",
8
+ resources: {
9
+ "required": [{
10
+ "type": "vector_search_index",
11
+ "alias": "Vector Search Index",
12
+ "resourceKey": "vector-search-index",
13
+ "description": "A Databricks Vector Search index to query. Index names configured via plugin config.",
14
+ "permission": "SELECT",
15
+ "fields": { "id": {
16
+ "env": "DATABRICKS_VS_INDEX_NAME",
17
+ "description": "Three-level UC name of the default index (catalog.schema.index_name)"
18
+ } }
19
+ }],
20
+ "optional": []
21
+ },
22
+ scaffolding: { "rules": { "must": [
23
+ "List the vector search index's source-table columns (unity-catalog tables API) so the user can pick which to return",
24
+ "Ask the user which of those columns the search should return",
25
+ "Write aiSearch({ indexes: { default: { columns: [...] } } }) in the server file; the client queries the 'default' alias"
26
+ ] } },
27
+ config: { "schema": {
28
+ "type": "object",
29
+ "properties": {
30
+ "indexes": {
31
+ "type": "object",
32
+ "description": "Map of alias names to index configurations",
33
+ "additionalProperties": {
34
+ "type": "object",
35
+ "properties": {
36
+ "indexName": {
37
+ "type": "string",
38
+ "description": "Three-level UC name: catalog.schema.index_name"
39
+ },
40
+ "columns": {
41
+ "type": "array",
42
+ "items": { "type": "string" },
43
+ "description": "Columns to return in results"
44
+ },
45
+ "queryType": {
46
+ "type": "string",
47
+ "enum": [
48
+ "ann",
49
+ "hybrid",
50
+ "full_text"
51
+ ],
52
+ "default": "hybrid"
53
+ },
54
+ "numResults": {
55
+ "type": "number",
56
+ "default": 20
57
+ }
58
+ }
59
+ }
60
+ },
61
+ "timeout": {
62
+ "type": "number",
63
+ "default": 3e4,
64
+ "description": "Query execution timeout in milliseconds"
65
+ }
66
+ },
67
+ "required": ["indexes"]
68
+ } }
69
+ };
70
+
71
+ //#endregion
72
+ export { manifest_default as default };
73
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","names":[],"sources":["../../../src/plugins/ai-search/manifest.json"],"sourcesContent":[""],"mappings":""}
@@ -0,0 +1,80 @@
1
+ import { BasePluginConfig } from "../../shared/src/plugin.js";
2
+ import "../../shared/src/index.js";
3
+
4
+ //#region src/plugins/ai-search/types.d.ts
5
+ /** Vector Search query mode: semantic (`ann`), keyword+semantic (`hybrid`), or keyword-only (`full_text`). */
6
+ type SearchQueryType = "ann" | "hybrid" | "full_text";
7
+ interface IAiSearchConfig extends BasePluginConfig {
8
+ timeout?: number;
9
+ indexes?: Record<string, IndexConfig>;
10
+ }
11
+ interface IndexConfig {
12
+ /**
13
+ * Three-level UC name: catalog.schema.index_name. Defaults to the
14
+ * `DATABRICKS_VS_INDEX_NAME` env var when omitted — so multiple aliases that
15
+ * omit it all resolve to that same physical index. Set it explicitly per
16
+ * alias when they should point at distinct indexes.
17
+ */
18
+ indexName?: string;
19
+ /**
20
+ * Columns to return in results. Optional: in development the plugin
21
+ * auto-discovers them from the index's source table when omitted (and warns
22
+ * that they should be set explicitly for production).
23
+ */
24
+ columns?: string[];
25
+ /** Default search mode */
26
+ queryType?: SearchQueryType;
27
+ /** Max results per query */
28
+ numResults?: number;
29
+ /** Enable built-in reranker. Pass true to rerank all non-id columns, or an object for fine control. */
30
+ reranker?: boolean | RerankerConfig;
31
+ /**
32
+ * Auth mode for the built-in HTTP routes — "service-principal" (default)
33
+ * uses the app's SP, "on-behalf-of-user" proxies the logged-in user's token.
34
+ * Programmatic callers select per call via `appkit.aiSearch.asUser(req)`.
35
+ */
36
+ auth?: "service-principal" | "on-behalf-of-user";
37
+ /** Enable cursor pagination */
38
+ pagination?: boolean;
39
+ /** VS endpoint name (required when pagination is true) */
40
+ endpointName?: string;
41
+ /**
42
+ * For self-managed embedding indexes: converts query text to an embedding vector.
43
+ * When provided, the plugin calls this function and sends query_vector to VS.
44
+ * When omitted, query_text is sent and VS computes embeddings server-side (managed mode).
45
+ */
46
+ embeddingFn?: (text: string) => Promise<number[]>;
47
+ }
48
+ interface RerankerConfig {
49
+ columnsToRerank: string[];
50
+ }
51
+ /** Public summary of a configured index, exposed to the client via `clientConfig()`. */
52
+ interface IndexSummary {
53
+ alias: string;
54
+ queryType: SearchQueryType;
55
+ pagination: boolean;
56
+ }
57
+ type SearchFilters = Record<string, string | number | boolean | (string | number)[]>;
58
+ interface SearchRequest {
59
+ queryText?: string;
60
+ queryVector?: number[];
61
+ columns?: string[];
62
+ numResults?: number;
63
+ queryType?: SearchQueryType;
64
+ filters?: SearchFilters;
65
+ reranker?: boolean;
66
+ }
67
+ interface SearchResponse<T extends Record<string, unknown> = Record<string, unknown>> {
68
+ results: SearchResult<T>[];
69
+ totalCount: number;
70
+ queryTimeMs: number;
71
+ queryType: SearchQueryType;
72
+ nextPageToken: string | null;
73
+ }
74
+ interface SearchResult<T extends Record<string, unknown> = Record<string, unknown>> {
75
+ score: number;
76
+ data: T;
77
+ }
78
+ //#endregion
79
+ export { IAiSearchConfig, IndexConfig, IndexSummary, RerankerConfig, SearchFilters, SearchQueryType, SearchRequest, SearchResponse, SearchResult };
80
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/ai-search/types.ts"],"mappings":";;;;;KAGY,eAAA;AAAA,UAEK,eAAA,SAAwB,gBAAA;EACvC,OAAA;EACA,OAAA,GAAU,MAAA,SAAe,WAAA;AAAA;AAAA,UAGV,WAAA;EAPU;AAE3B;;;;;EAYE,SAAA;EAZuD;;;;;EAkBvD,OAAA;EAhByB;EAkBzB,SAAA,GAAY,eAAA;EAlBwB;EAoBpC,UAAA;EAjB0B;EAmB1B,QAAA,aAAqB,cAAA;EAJT;;;;;EAUZ,IAAA;EAZA;EAcA,UAAA;EAZY;EAcZ,YAAA;EAVA;;;;;EAgBA,WAAA,IAAe,IAAA,aAAiB,OAAA;AAAA;AAAA,UAGjB,cAAA;EACf,eAAA;AAAA;AADF;AAAA,UAKiB,YAAA;EACf,KAAA;EACA,SAAA,EAAW,eAAA;EACX,UAAA;AAAA;AAAA,KAGU,aAAA,GAAgB,MAAA;AAAA,UAKX,aAAA;EACf,SAAA;EACA,WAAA;EACA,OAAA;EACA,UAAA;EACA,SAAA,GAAY,eAAA;EACZ,OAAA,GAAU,aAAA;EACV,QAAA;AAAA;AAAA,UAGe,cAAA,WACL,MAAA,oBAA0B,MAAA;EAEpC,OAAA,EAAS,YAAA,CAAa,CAAA;EACtB,UAAA;EACA,WAAA;EACA,SAAA,EAAW,eAAA;EACX,aAAA;AAAA;AAAA,UAGe,YAAA,WACL,MAAA,oBAA0B,MAAA;EAEpC,KAAA;EACA,IAAA,EAAM,CAAA;AAAA"}
@@ -1,2 +1,4 @@
1
1
  import { agents } from "./agents/agents.js";
2
- import "./agents/index.js";
2
+ import "./agents/index.js";
3
+ import { aiSearch } from "./ai-search/ai-search.js";
4
+ import "./ai-search/index.js";
@@ -1,4 +1,6 @@
1
1
  import { agents } from "./agents/agents.js";
2
2
  import "./agents/index.js";
3
+ import { aiSearch } from "./ai-search/ai-search.js";
4
+ import "./ai-search/index.js";
3
5
 
4
6
  export { };
@@ -1,2 +1,2 @@
1
- import { IJobsConfig, JobAPI, JobConfig, JobHandle, JobsExport } from "./types.js";
1
+ import { IJobsConfig, JobAPI, JobConfig, JobsExport } from "./types.js";
2
2
  import { jobs } from "./plugin.js";
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../src/plugins/jobs/plugin.ts"],"mappings":";;;;;;;;;cAsFM,UAAA,SAAmB,MAAA;EAAA,OAChB,QAAA,EAAuB,cAAA;EAAA,UAEZ,MAAA,EAAQ,WAAA;EAAA,QAClB,SAAA;EAAA,QACA,MAAA;EAAA,QACA,UAAA;EAAA,QACA,OAAA;;;;;SAMD,YAAA,CAAa,MAAA,EAAQ,WAAA,GAAc,MAAA,SAAe,SAAA;EAAf;;;;EAAA,OAgCnC,uBAAA,CAAwB,MAAA,EAAQ,WAAA,GAAc,mBAAA;cAqBzC,MAAA,EAAQ,WAAA;EA0Bd,KAAA,CAAA,GAAK,OAAA;EAAA,YAMC,MAAA,CAAA;EAAA,QAIJ,QAAA;EAAA,QAcA,aAAA;EAAA,QAYA,cAAA;EAhIqB;;;;EAAA,QA6IrB,eAAA;EA1IkB;;;;EAAA,UAoKhB,YAAA,CAAa,MAAA,WAAiB,MAAA;EA1JjC;;;;EAAA,QAgXC,WAAA;EAAA,QA0BA,gBAAA;EA1W+B;;;;;EAAA,QAsX/B,eAAA;EAAA,QAuCM,UAAA;EA4Cd,YAAA,CAAa,MAAA,EAAQ,UAAA;EA6IrB,OAAA,CAAA,GAAW,UAAA;EAsBX,YAAA,CAAA,GAAgB,MAAA;AAAA;;;;cAiBL,IAAA,EAAI,QAAA,QAAA,UAAA,EAAA,WAAA"}
1
+ {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../src/plugins/jobs/plugin.ts"],"mappings":";;;;;;;;;cAoFM,UAAA,SAAmB,MAAA;EAAA,OAChB,QAAA,EAAuB,cAAA;EAAA,UAEZ,MAAA,EAAQ,WAAA;EAAA,QAClB,SAAA;EAAA,QACA,MAAA;EAAA,QACA,UAAA;EAAA,QACA,OAAA;;;;;SAMD,YAAA,CAAa,MAAA,EAAQ,WAAA,GAAc,MAAA,SAAe,SAAA;EAAf;;;;EAAA,OAgCnC,uBAAA,CAAwB,MAAA,EAAQ,WAAA,GAAc,mBAAA;cAqBzC,MAAA,EAAQ,WAAA;EA0Bd,KAAA,CAAA,GAAK,OAAA;EAAA,YAMC,MAAA,CAAA;EAAA,QAIJ,QAAA;EAAA,QAcA,aAAA;EAAA,QAYA,cAAA;EAhIqB;;;;EAAA,QA6IrB,eAAA;EA1IkB;;;;EAAA,UAoKhB,YAAA,CAAa,MAAA,WAAiB,MAAA;EA1JjC;;;;EAAA,QA6WC,WAAA;EAAA,QA0BA,gBAAA;EAvW+B;;;;;EAAA,QAmX/B,eAAA;EAAA,QAuCM,UAAA;EA4Cd,YAAA,CAAa,MAAA,EAAQ,UAAA;EA6IrB,OAAA,CAAA,GAAW,UAAA;EAcX,YAAA,CAAA,GAAgB,MAAA;AAAA;;;;cAiBL,IAAA,EAAI,QAAA,QAAA,UAAA,EAAA,WAAA"}
@@ -498,12 +498,7 @@ var JobsPlugin = class JobsPlugin extends Plugin {
498
498
  exports() {
499
499
  const resolveJob = (jobKey) => {
500
500
  if (!this.jobKeys.includes(jobKey)) throw new Error(`Unknown job "${jobKey}". Available jobs: ${this.jobKeys.join(", ")}`);
501
- return {
502
- ...this.createJobAPI(jobKey),
503
- asUser: (req) => {
504
- return this.asUser(req).createJobAPI(jobKey);
505
- }
506
- };
501
+ return this.createJobAPI(jobKey);
507
502
  };
508
503
  return resolveJob;
509
504
  }