Skip to content

feat(appkit): ship the ai-search plugin at beta - #508

Open
MarioCadenas wants to merge 15 commits into
mainfrom
feat/export-vector-search-plugin
Open

feat(appkit): ship the ai-search plugin at beta#508
MarioCadenas wants to merge 15 commits into
mainfrom
feat/export-vector-search-plugin

Conversation

@MarioCadenas

@MarioCadenas MarioCadenas commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

What

Exports the vector-search plugin at beta, renames it to ai-search, and makes it cleanly consumable from @databricks/appkit/beta.

The plugin was fully implemented but unexported ("hidden": true, and the export barrels are auto-generated from each manifest's stability field). This PR ships it, renames it to ai-search to match its product positioning, and addresses a set of correctness/API issues found while reviewing it.

Changes

Export + rename

  • Flip the manifest to stability: beta; the barrel generator derives the camelCase binding from the kebab manifest name (ai-searchaiSearch).
  • Rename vector-searchai-search end to end: plugin + connector dirs/classes, config/binding symbols, telemetry scopes, docs, dev-playground + template consumers, and the HTTP route (/api/ai-search). Databricks-product identifiers stay as-is (the vector_search_index resource type, /api/2.0/vector-search REST paths, Vs* wire types, DATABRICKS_VS_* env vars).

API / consumability

  • appkit.aiSearch is the canonical SDK accessor: a shared kebabToCamel maps multi-word plugin names to camelCase handle keys (the kebab key is no longer exposed). The route prefix stays kebab.
  • query<T>() is generic over the result row type, so result.data is typed.
  • IndexConfig.indexName is optional and defaults to DATABRICKS_VS_INDEX_NAME; columns stays required (the VS query API requires it).
  • auth documented as the built-in-route OBO toggle; programmatic callers use asUser(req).

Correctness / security (from review)

  • embeddingFn no longer clears queryText for hybrid/full_text (was silently degrading hybrid to vector-only and breaking full_text).
  • Forward the execution's AbortSignal to the VS REST call via the SDK Context, so the configured timeout actually cancels in-flight queries. The abort→cancellation bridge is hoisted to a shared connectors/context.ts (also adopted by the serving connector).
  • _handleError gates the raw error message on NODE_ENV, matching Plugin.execute().
  • setup() no longer re-implements missing-resource policy — the framework's resource validation owns warn-in-dev / throw-in-prod; setup() keeps only the pagination→endpointName config check.
  • Fixed the docs /query response example to the real { results: [{ score, data }], totalCount, queryTimeMs, queryType, nextPageToken } shape.

Cleanup

  • Deduped the /query route vs programmatic query() payload; single-sourced kebabToCamel (dropped promote.ts's copy).

Consumers: import { aiSearch } from "@databricks/appkit/beta"appkit.aiSearch.query(...) → route /api/ai-search.

Verification

  • pnpm -r typecheck — all packages pass
  • Full test suite green (ai-search coverage: plugin ~49% → ~91%; route handlers, edge cases, env-default, and the generic all covered)
  • pnpm build clean, no generated-file drift; pnpm check and knip pass
  • Rebased onto main (v0.52.0), integrating the workspace-client wrapper (feat(appkit): introduce workspace-client wrapper over the Databricks SDK #475)

@MarioCadenas
MarioCadenas requested a review from a team as a code owner August 3, 2026 09:15
@MarioCadenas
MarioCadenas requested a review from pkosiec August 3, 2026 09:15
@MarioCadenas
MarioCadenas marked this pull request as draft August 3, 2026 09:17
@MarioCadenas
MarioCadenas force-pushed the feat/export-vector-search-plugin branch from 2fd6e3f to 380e668 Compare August 3, 2026 16:10
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 812 KB (+9.8 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 839 KB (+15 KB) 293 KB (+5.1 KB)
Type declarations 311 KB (+4.6 KB) 107 KB (+1.9 KB)
Source maps 1.6 MB (+22 KB) 547 KB (+7.4 KB)
Other 11 KB 3.7 KB
Total 2.8 MB (+42 KB) 951 KB (+14 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 86 KB (+20 B) 2.5 KB 89 KB (+20 B) external 282 KB (+94 B)
./beta 47 KB (+2.3 KB) 459 B (+1 B) 47 KB (+2.3 KB) external 138 KB (+8.3 KB)
./type-generator 19 KB 0 B 19 KB external 54 KB

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 82 KB
. utils.js initial 4.0 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 31 KB
./beta stream-manager.js initial 5.8 KB
./beta wide-event-emitter.js initial 3.2 KB
./beta databricks.js initial 3.0 KB
./beta configuration.js initial 2.1 KB
./beta service-context.js initial 1.3 KB
./beta client.js initial 431 B
./beta client-options.js initial 220 B
./beta supervisor-api.js lazy 194 B
./beta databricks.js lazy 142 B
./beta index.js lazy 123 B
./type-generator index.js initial 19 KB

@databricks/appkit-ui

npm tarball (packed): 305 KB — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 360 KB 119 KB
Type declarations 205 KB 74 KB
Source maps 686 KB 224 KB
CSS 16 KB 3.3 KB
Total 1.2 MB 422 KB
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
./js 4.3 KB 49 KB 54 KB 208 KB 12 KB
./js/beta 20 B 0 B 20 B 0 B 0 B
./react 429 KB 49 KB 478 KB 1.3 MB 168 KB
./react/beta 20 B 0 B 20 B 0 B 0 B

Chunks:

Entry Chunk Load Size (gz)
./js index.js initial 4.2 KB
./js chunk initial 120 B
./js apache-arrow lazy 49 KB
./js/beta beta.js initial 20 B
./react index.js initial 427 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 20 B

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🤖 AppKit PR bot

🔬 Run evals

Start an eval for this PR from the evals-monitor app: Go to Evals Monitor →

📦 Try this PR's app template

Scaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh auth login — and the Databricks CLI):

gh run download 30920811625 -R databricks/appkit -n appkit-template-0.52.0-pr.64fe161-feat-export-vector-search-plugin-508 -D appkit-pr-508 \
  && unzip -o "appkit-pr-508/appkit-template-0.52.0-pr.64fe161-feat-export-vector-search-plugin-508.zip" -d "appkit-pr-508" \
  && databricks apps init --template "appkit-pr-508"

The template pins @databricks/appkit and @databricks/appkit-ui to tarballs built from this branch, so the scaffolded app runs against this PR's code.

@MarioCadenas
MarioCadenas marked this pull request as ready for review August 3, 2026 16:21
The vector-search plugin was fully implemented but unexported: its
manifest had `hidden: true`, and the auto-generated export barrels are
driven by each manifest's `stability` field.

Flip the manifest to `stability: beta` and teach the barrel generator
to derive the camelCase binding from a kebab-case manifest name
(`vector-search` -> `vectorSearch`), mirroring `manifestNameToBinding`
in the plugin `promote` command. The folder-name check is relaxed to
the schema charset since it is only interpolated into a string path.

`vectorSearch` now ships from `@databricks/appkit/beta`, alongside its
public config and query types. The dev-playground consumer is
re-enabled (clearing its standing TODO). The runtime route stays
`/api/vector-search`, so docs and existing clients are unaffected.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Rename the beta plugin's public identity from `vectorSearch` to `aiSearch`
end to end: manifest name/displayName, folder and file names, the plugin
and connector classes, config/binding symbols, telemetry scopes, docs, and
the dev-playground + template consumers. The runtime route follows
`manifest.name`, so it moves from `/api/vector-search` to `/api/ai-search`.

Databricks-platform identifiers are intentionally left as-is since they name
the underlying product, not this plugin: the `vector_search_index` resource
type and its schema/permissions, the `/api/2.0/vector-search` REST paths,
`Vs*` wire-format types, `vs.*` span attributes, `DATABRICKS_VS_*` env vars,
and the "Vector Search Index" resource alias.

Regenerated the beta export barrel, template appkit.plugins.json, the
dev-playground route tree, and the typedoc API reference.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
The ai-search plugin ships from the /beta subpath, but the doc examples
imported it from the main entry. Split the imports so `aiSearch` comes
from `@databricks/appkit/beta` while `createApp`/`server` stay on the main
entry, matching the agents plugin docs. Also add the import header to the
programmatic-access example so it is copy-pasteable.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
The 'Check generated types are up to date' step diffs a hardcoded list of
plugin doc pages. After renaming the plugin, the doc page moved from
vector-search.md to ai-search.md, but the workflow still referenced the old
path — so `git diff` exited 128 (unknown path) and failed the check with a
misleading 'out of sync' message rather than a real drift.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…dling

Addresses issues found in review of the ai-search plugin:

- embeddingFn no longer clears queryText for hybrid/full_text queries. It
  was cleared unconditionally, which silently degraded hybrid to vector-only
  and produced invalid full_text requests. Now only ann drops the text, and
  full_text skips embedding entirely.
- Forward the execution's AbortSignal to the Vector Search REST call via the
  SDK Context/CancellationToken (mirroring the serving connector), so the
  configured timeout and client-disconnect actually cancel an in-flight
  query. Both query and queryNextPage were affected.
- _handleError now gates the raw error message on NODE_ENV, matching the
  base Plugin.execute() convention, so upstream error text (e.g. from a
  user-supplied embeddingFn) isn't leaked to clients in production.
- Fix the /query response example in the docs to the real nested shape
  ({ results: [{ score, data }], totalCount, queryTimeMs, queryType,
  nextPageToken }); it previously showed a flat row shape.
- Add tests for the three route handlers and their validation branches,
  queryNextPage wiring, _parseResponse edge cases (no score column, present
  next_page_token, latency_ms fallback), request-level overrides, object and
  request-suppressed reranker, and the embedding query-type matrix. Plugin
  line coverage 49% -> 91%.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
SearchResponse<T>/SearchResult<T> were parameterized over the row shape,
but query() returned the non-generic default and took no type parameter, so
consumers could never supply T — result.data was always Record<string,
unknown> and had to be cast. Thread the generic through query<T>() and
_parseResponse<T>() so `appkit.aiSearch.query<MyRow>(...)` yields a typed
result.data. The dynamic column-to-data mapping keeps one boundary cast
(data as T); the caller asserts T matches the configured columns.

Backward-compatible: T defaults to Record<string, unknown>, so existing
callers are unaffected.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Multi-word plugins were only reachable on the appkit handle by their
kebab-case manifest name (e.g. appkit["ai-search"]), because the accessor
key, the TS PluginMap type, and the HTTP route prefix all derive from
manifest.name. Add a camelCase alias derived from that name so ai-search is
usable as appkit.aiSearch.

- New kebabToCamel() runtime helper + KebabToCamel<S> type in shared.
- appkit.ts registers the plugin under its kebab name and, when the
  camelCase form differs, additively defines the same accessor under the
  alias. The kebab key keeps working; the HTTP route stays /api/ai-search.
- PluginMap gains the camelCase keys as an intersection, so both
  appkit.aiSearch and appkit["ai-search"] type-check. Single-word names are
  a no-op (camel === kebab), so no behavior change there.

This is uniform, so the GA ui-variants plugin also gains a uiVariants alias
alongside its existing ui-variants key — additive, nothing breaks.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…b alias

The previous change exposed multi-word plugins under both keys
(appkit.aiSearch and appkit["ai-search"]). Register the accessor under the
camelCase key only, so there is a single canonical handle. Internal lookups
(#pluginInstances, context.registerPlugin) and the HTTP route prefix still
use the kebab name — only the public property changes.

PluginMap maps solely to the camelCase keys to match, so appkit["ai-search"]
is now a type error as well as undefined at runtime. Single-word plugins are
unaffected (camel === kebab). The GA ui-variants plugin moves from
appkit["ui-variants"] to appkit.uiVariants (inert handle, no exports; route
/api/ui-variants unchanged); its tests are updated accordingly.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…bab-camel

Cleanups from a /simplify pass over the ai-search branch:

- _prepareQuery now returns Omit<VsQueryParams,"indexName"> (carries filters,
  renames rerankerConfig->reranker), so the /query route and programmatic
  query() collapse to `{ indexName, ...prepared }` instead of duplicating an
  8-field payload. Removes the accidental body.filters vs request.filters
  split.
- Hoist the AbortSignal->CancellationToken bridge into a shared
  connectors/context.ts (cancellationTokenFromAbortSignal +
  contextFromAbortSignal); ai-search and serving import it instead of each
  carrying a copy.
- promote.ts uses the shared kebabToCamel instead of its own
  manifestNameToBinding copy.
- _parseResponse skips the score column by index (i !== scoreIndex) rather
  than a per-cell string compare.
- Trim verbose comments introduced on the branch to the one-line bar.

Left alone (flagged, out of scope): the jobs connector's separate
_createContext variant (different listener semantics), scaffold.ts's
toCamelCase (different impl), and tools/generate-plugin-entries.ts (package
boundary).

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
setup() re-validated fields the TS types and manifest config.schema already
require (at least one index; columns non-empty), duplicating guarantees the
config layer provides. Drop those and keep only the two runtime checks types
can't express: an empty indexName (an env var populating it was unset) and
the pagination -> endpointName dependency. Also drops the stale
'Vector Search plugin configured' debug log (no sibling plugin logs this).

This does not overlap with the framework's ResourceRegistry, which validates
the manifest's declared resource env vars, not the indexes config object.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Make IndexConfig.indexName optional and resolve it from the
DATABRICKS_VS_INDEX_NAME env var when omitted, so the manifest's declared
resource env var actually feeds config. columns stays required (the VS query
API requires it and it can't be env-derived). Resolution happens once in
_resolveIndex, so both the HTTP routes and the programmatic query() pick up
the default; setup() now rejects only an index whose indexName resolves from
neither config nor env. Manifest config.schema drops indexName from required.

Also document that IndexConfig.auth governs the built-in HTTP routes;
programmatic callers select OBO per call via appkit.aiSearch.asUser(req).
auth is kept as the declarative route toggle, mirroring the files plugin
(the only other plugin with per-resource auth config).

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
setup() threw unconditionally on an index whose indexName resolves from
neither config nor DATABRICKS_VS_INDEX_NAME. That contradicted the framework's
resource-validation policy, which only warns for missing resources in dev
(NODE_ENV=development) and throws in prod. It also fired after that warning had
already been emitted, so a dev app crashed despite the graceful path.

Mirror the framework policy: warn and skip the unusable index in dev, throw
outside dev. The pagination -> endpointName check stays a hard throw in all
modes since it's a config logic error, not a missing resource. The
dev-playground demo drops its manual env fallback now that the plugin resolves
indexName from the env var itself.

The genie plugin has the same unconditional-throw pattern for a missing space
ID; left as-is (out of scope) but worth a follow-up.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…licy

setup()'s indexName-presence check duplicated the framework's resource
validation, which already warns-in-dev / throws-in-prod for a missing
DATABRICKS_VS_INDEX_NAME and runs before setup(). The inline NODE_ENV gate
was therefore redundant, its prod-throw branch was unreachable in the real
createApp flow, and it silently ignored APPKIT_STRICT_VALIDATION (which the
registry honors). Drop it and keep only the pagination -> endpointName check,
a config logic error the registry can't see. Unresolved aliases still 404 on
the routes and throw in query(). Removes the two now-obsolete setup() tests.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
@MarioCadenas MarioCadenas changed the title feat(appkit): export vector-search plugin at beta feat(appkit): ship the ai-search plugin at beta Aug 4, 2026
@MarioCadenas
MarioCadenas force-pushed the feat/export-vector-search-plugin branch from eda8ff2 to 60ebe31 Compare August 4, 2026 13:14
… can reuse it

The earlier dedup pointed promote.ts at kebabToCamel in plugin.ts. That
resolved in-repo but broke the published tarball: the CLI is copied to
appkit's dist/cli flattened, and its `../../../../plugin` import pointed at a
dist/plugin.js that dist-appkit.ts never copies — so `npm run typegen` in the
template failed with ERR_MODULE_NOT_FOUND (caught by the PR Template Artifact
check).

Move kebabToCamel + KebabToCamel into a dependency-free leaf module
shared/src/naming.ts. plugin.ts re-exports them (so `from "shared"` consumers
like appkit.ts are unchanged), promote.ts imports from ../../../../naming, and
dist-appkit.ts copies naming.js next to the CLI in the tarball — mirroring how
it already copies schemas/. Verified by building the prerelease tarball and
loading the bundled promote.js: it now resolves naming.js and imports cleanly.

Genuine single-source (no duplicated helper) that survives bundling, rather
than dragging the heavy plugin.ts graph into the CLI bundle.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…outes

Scaffolding an app with a multi-word plugin generated invalid server code
(`import { ai-search }` — a hyphen isn't a valid JS identifier) and the client
view didn't render (the template guards `.plugins.aiSearch` but the plugin-map
key was `ai-search`). Go templates can't express `.plugins.ai-search` at all.
ai-search is the first multi-word plugin, so it exposed that the whole system
assumes manifest.name is a valid JS identifier.

Make manifest.name camelCase (aiSearch, uiVariants) the single canonical
identifier — SDK accessor, JS export binding, and template plugin-map key.
Derive the kebab HTTP route (/api/ai-search) and doc filename via a new
camelToKebab; folders stay kebab. This inverts the earlier kebabToCamel
accessor approach: the accessor and PluginMap are now identity.

- naming.ts: add camelToKebab; plugin.ts re-exports it, PluginMap keys on
  P["name"] directly.
- server/index.ts + plugin.ts: route prefix uses camelToKebab(name).
- appkit.ts: accessor key is name verbatim.
- schema name regex loosened to ^[a-z][a-zA-Z0-9-]*$ at the two plugin-name
  sites only (resourceKey + project-name patterns untouched); create.ts and
  the two generators updated; generate-plugin-entries splits name (camel) vs
  folder (kebab) patterns; doc-banners derives the kebab .md basename.
- Rename ai-search->aiSearch and ui-variants->uiVariants manifests + type
  params + name-assertion test; regenerated schemas + template.

Verified end to end: scaffolding with --features=aiSearch now emits valid
`import { aiSearch }`, wires the AiSearchPage view, and keeps /api/ai-search
and /api/ui-variants/confirm routes. Full suite + typecheck + knip + tarball
load all pass.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant