@databricks/appkit 0.45.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CLAUDE.md +1 -0
  2. package/dist/agents/databricks.d.ts +25 -1
  3. package/dist/agents/databricks.d.ts.map +1 -1
  4. package/dist/agents/databricks.js +20 -1
  5. package/dist/agents/databricks.js.map +1 -1
  6. package/dist/app/index.d.ts +49 -2
  7. package/dist/app/index.d.ts.map +1 -1
  8. package/dist/app/index.js +87 -10
  9. package/dist/app/index.js.map +1 -1
  10. package/dist/appkit/package.js +1 -1
  11. package/dist/beta.d.ts +2 -2
  12. package/dist/cli/commands/generate-types.js +9 -4
  13. package/dist/cli/commands/generate-types.js.map +1 -1
  14. package/dist/core/agent/load-agents.d.ts.map +1 -1
  15. package/dist/core/agent/load-agents.js +52 -0
  16. package/dist/core/agent/load-agents.js.map +1 -1
  17. package/dist/core/agent/types.d.ts +11 -0
  18. package/dist/core/agent/types.d.ts.map +1 -1
  19. package/dist/core/agent/types.js.map +1 -1
  20. package/dist/core/appkit.d.ts.map +1 -1
  21. package/dist/core/appkit.js +2 -0
  22. package/dist/core/appkit.js.map +1 -1
  23. package/dist/core/lifecycle-manager.js +183 -0
  24. package/dist/core/lifecycle-manager.js.map +1 -0
  25. package/dist/core/plugin-context.d.ts +19 -1
  26. package/dist/core/plugin-context.d.ts.map +1 -1
  27. package/dist/core/plugin-context.js +9 -3
  28. package/dist/core/plugin-context.js.map +1 -1
  29. package/dist/plugin/plugin.d.ts.map +1 -1
  30. package/dist/plugin/plugin.js +2 -1
  31. package/dist/plugin/plugin.js.map +1 -1
  32. package/dist/plugins/agents/agents.d.ts.map +1 -1
  33. package/dist/plugins/agents/agents.js +3 -3
  34. package/dist/plugins/agents/agents.js.map +1 -1
  35. package/dist/plugins/analytics/analytics.d.ts +16 -0
  36. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  37. package/dist/plugins/analytics/analytics.js +162 -1
  38. package/dist/plugins/analytics/analytics.js.map +1 -1
  39. package/dist/plugins/analytics/metric.js +7 -0
  40. package/dist/plugins/analytics/mv/cache.js +51 -0
  41. package/dist/plugins/analytics/mv/cache.js.map +1 -0
  42. package/dist/plugins/analytics/mv/constants.js +72 -0
  43. package/dist/plugins/analytics/mv/constants.js.map +1 -0
  44. package/dist/plugins/analytics/mv/formatters.js +151 -0
  45. package/dist/plugins/analytics/mv/formatters.js.map +1 -0
  46. package/dist/plugins/analytics/mv/index.js +6 -0
  47. package/dist/plugins/analytics/mv/registry.js +55 -0
  48. package/dist/plugins/analytics/mv/registry.js.map +1 -0
  49. package/dist/plugins/analytics/mv/schemas.js +178 -0
  50. package/dist/plugins/analytics/mv/schemas.js.map +1 -0
  51. package/dist/plugins/analytics/types.js.map +1 -1
  52. package/dist/plugins/lakebase/lakebase.d.ts +10 -2
  53. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  54. package/dist/plugins/lakebase/lakebase.js +18 -7
  55. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  56. package/dist/plugins/server/index.d.ts +51 -1
  57. package/dist/plugins/server/index.d.ts.map +1 -1
  58. package/dist/plugins/server/index.js +110 -23
  59. package/dist/plugins/server/index.js.map +1 -1
  60. package/dist/registry/resource-registry.d.ts +9 -1
  61. package/dist/registry/resource-registry.d.ts.map +1 -1
  62. package/dist/registry/resource-registry.js +22 -5
  63. package/dist/registry/resource-registry.js.map +1 -1
  64. package/dist/schemas/metric-fqn.js +14 -0
  65. package/dist/schemas/metric-fqn.js.map +1 -0
  66. package/dist/shared/src/execute.d.ts +1 -3
  67. package/dist/shared/src/execute.d.ts.map +1 -1
  68. package/dist/shared/src/plugin.d.ts +16 -0
  69. package/dist/shared/src/plugin.d.ts.map +1 -1
  70. package/dist/shared/src/schemas/metric-fqn.js +78 -46
  71. package/dist/shared/src/schemas/metric-fqn.js.map +1 -1
  72. package/dist/shared/src/schemas/metric-source.js +90 -0
  73. package/dist/shared/src/schemas/metric-source.js.map +1 -0
  74. package/dist/stream/buffers.js +1 -1
  75. package/dist/stream/buffers.js.map +1 -1
  76. package/dist/stream/defaults.js +0 -2
  77. package/dist/stream/defaults.js.map +1 -1
  78. package/dist/stream/stream-manager.d.ts +2 -2
  79. package/dist/stream/stream-manager.d.ts.map +1 -1
  80. package/dist/stream/stream-manager.js +26 -24
  81. package/dist/stream/stream-manager.js.map +1 -1
  82. package/dist/stream/stream-registry.js +30 -23
  83. package/dist/stream/stream-registry.js.map +1 -1
  84. package/dist/stream/timers.js +17 -0
  85. package/dist/stream/timers.js.map +1 -0
  86. package/dist/stream/types.js.map +1 -1
  87. package/dist/telemetry/telemetry-manager.js +19 -13
  88. package/dist/telemetry/telemetry-manager.js.map +1 -1
  89. package/dist/type-generator/index.js +14 -7
  90. package/dist/type-generator/index.js.map +1 -1
  91. package/dist/type-generator/mv-registry/config.js +13 -31
  92. package/dist/type-generator/mv-registry/config.js.map +1 -1
  93. package/dist/type-generator/mv-registry/describe.js +1 -31
  94. package/dist/type-generator/mv-registry/describe.js.map +1 -1
  95. package/dist/type-generator/mv-registry/sync.js +1 -1
  96. package/dist/type-generator/mv-registry/sync.js.map +1 -1
  97. package/dist/type-generator/vite-plugin.d.ts +5 -1
  98. package/dist/type-generator/vite-plugin.d.ts.map +1 -1
  99. package/dist/type-generator/vite-plugin.js +21 -4
  100. package/dist/type-generator/vite-plugin.js.map +1 -1
  101. package/dist/utils/safe-handler.js +28 -0
  102. package/dist/utils/safe-handler.js.map +1 -0
  103. package/docs/api/appkit/Class.Plugin.md +2 -0
  104. package/docs/api/appkit/Class.ResourceRegistry.md +1 -1
  105. package/docs/api/appkit/Interface.AgentDefinition.md +11 -0
  106. package/docs/api/appkit/Interface.GenerationParams.md +58 -0
  107. package/docs/api/appkit/Interface.RegisteredAgent.md +11 -0
  108. package/docs/api/appkit.md +1 -0
  109. package/docs/development/type-generation.md +3 -3
  110. package/docs/plugins/analytics.md +172 -23
  111. package/llms.txt +1 -0
  112. package/package.json +1 -1
  113. package/sbom.cdx.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { METRIC_CONFIG_FILE } from "../../schemas/metric-fqn.js";
1
2
  import { acquireSpawnLock, getSpawnLockPath, releaseSpawnLock } from "./spawn-lock.js";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
@@ -33,16 +34,20 @@ async function runGenerateTypes(rootDir, outFile, warehouseId, options) {
33
34
  if (resolvedWarehouseId) {
34
35
  const resolvedOutFile = outFile || path.join(process.cwd(), "shared/appkit-types/analytics.d.ts");
35
36
  const queryFolder = path.join(resolvedRootDir, "config/queries");
36
- if (fs.existsSync(queryFolder)) {
37
+ const metricViewsFolder = path.join(resolvedRootDir, "config/metric-views");
38
+ const hasQueries = fs.existsSync(queryFolder);
39
+ const hasMetricViews = fs.existsSync(metricViewsFolder);
40
+ if (hasQueries || hasMetricViews) {
37
41
  await typeGen.generateFromEntryPoint({
38
- queryFolder,
42
+ queryFolder: hasQueries ? queryFolder : void 0,
43
+ metricViewsFolder: hasMetricViews ? metricViewsFolder : void 0,
39
44
  outFile: resolvedOutFile,
40
45
  warehouseId: resolvedWarehouseId,
41
46
  noCache,
42
47
  mode
43
48
  });
44
- console.log(`Generated query types: ${resolvedOutFile}`);
45
- const metricConfig = path.join(queryFolder, "metric-views.json");
49
+ if (hasQueries) console.log(`Generated query types: ${resolvedOutFile}`);
50
+ const metricConfig = path.join(metricViewsFolder, METRIC_CONFIG_FILE);
46
51
  if (fs.existsSync(metricConfig)) {
47
52
  const typesDir = path.dirname(resolvedOutFile);
48
53
  console.log(`Generated metric types: ${path.join(typesDir, "metric-views.d.ts")}`);
@@ -1 +1 @@
1
- {"version":3,"file":"generate-types.js","names":[],"sources":["../../../src/cli/commands/generate-types.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Command, Option } from \"commander\";\nimport {\n acquireSpawnLock,\n getSpawnLockPath,\n releaseSpawnLock,\n} from \"./spawn-lock.js\";\n\n/**\n * Resolve the typegen pre-flight mode for the CLI. Defaults to \"non-blocking\" —\n * a one-shot CLI can't describe in the background, so by default it never\n * describes at all: it skips the warehouse probe AND every DESCRIBE, emits\n * best-available types (cache where the SQL hash matches, else `result: unknown`)\n * and returns immediately, never blocking on — or failing because of — a\n * warehouse, even a RUNNING one. Pass `--wait` (commander sets `wait: true`)\n * for a deliberate/CI invocation that should wait for a starting warehouse and\n * fail fast on a stopped one.\n */\nexport function resolveTypegenMode(options?: {\n wait?: boolean;\n}): \"non-blocking\" | \"blocking\" {\n return options?.wait ? \"blocking\" : \"non-blocking\";\n}\n\n/** Options parsed by commander for the generate-types command. */\ninterface GenerateTypesOptions {\n noCache?: boolean;\n wait?: boolean;\n /**\n * Internal: present only on the detached worker invocation. Carries the path\n * of the single-flight lock this worker must release when it finishes. Its\n * presence is what marks an invocation as \"the worker\" — workers always run\n * with `--wait`, so they never spawn another worker (only non-blocking runs\n * spawn), which terminates the recursion.\n */\n workerLock?: string;\n}\n\n/**\n * Generate types command implementation. Runs the library generate (which, in\n * non-blocking mode, writes degraded types and returns immediately). This is the\n * SAME work the worker performs in blocking mode in the background.\n */\nasync function runGenerateTypes(\n rootDir?: string,\n outFile?: string,\n warehouseId?: string,\n options?: GenerateTypesOptions,\n) {\n try {\n const resolvedRootDir = rootDir || process.cwd();\n const noCache = options?.noCache || false;\n const mode = resolveTypegenMode(options);\n\n const typeGen = await import(\"@databricks/appkit/type-generator\");\n\n // Generate analytics query types (requires warehouse ID)\n const resolvedWarehouseId =\n warehouseId || process.env.DATABRICKS_WAREHOUSE_ID;\n\n if (resolvedWarehouseId) {\n const resolvedOutFile =\n outFile ||\n path.join(process.cwd(), \"shared/appkit-types/analytics.d.ts\");\n\n const queryFolder = path.join(resolvedRootDir, \"config/queries\");\n if (fs.existsSync(queryFolder)) {\n await typeGen.generateFromEntryPoint({\n queryFolder,\n outFile: resolvedOutFile,\n warehouseId: resolvedWarehouseId,\n noCache,\n mode,\n });\n console.log(`Generated query types: ${resolvedOutFile}`);\n\n const metricConfig = path.join(queryFolder, \"metric-views.json\");\n if (fs.existsSync(metricConfig)) {\n const typesDir = path.dirname(resolvedOutFile);\n console.log(\n `Generated metric types: ${path.join(typesDir, \"metric-views.d.ts\")}`,\n );\n }\n }\n } else {\n console.error(\n \"Skipping query type generation: no warehouse ID. Set DATABRICKS_WAREHOUSE_ID or pass as argument.\",\n );\n }\n\n // Generate serving endpoint types (no warehouse required)\n const servingOutFile = path.join(\n process.cwd(),\n \"shared/appkit-types/serving.d.ts\",\n );\n await typeGen.generateServingTypes({\n outFile: servingOutFile,\n noCache,\n });\n console.log(`Generated serving types: ${servingOutFile}`);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(\"Cannot find module\")\n ) {\n console.error(\n \"Error: The 'generate-types' command is only available in @databricks/appkit.\",\n );\n console.error(\"Please install @databricks/appkit to use this command.\");\n process.exit(1);\n }\n // TypegenSyntaxError / TypegenFatalError carry a complete, actionable\n // message (which queries failed and how to debug them). The stack trace\n // points into appkit internals and is noise for app developers, so print\n // only the message and exit non-zero instead of letting it bubble up.\n if (\n error instanceof Error &&\n (error.name === \"TypegenSyntaxError\" ||\n error.name === \"TypegenFatalError\")\n ) {\n console.error(error.message);\n process.exit(1);\n }\n throw error;\n }\n}\n\n/**\n * Spawn the detached blocking worker that refreshes real types in the background\n * after the foreground non-blocking generate has already written degraded types.\n *\n * Re-invokes THIS CLI (`process.execPath` + `process.argv[1]` — the bin entry\n * that launched us) with `generate-types --wait --worker-lock <lockPath>` plus\n * the same positional target options the foreground used, so the worker writes\n * to the same out file / reads the same query folder. The worker is:\n * - `detached: true` + `.unref()` so it outlives this process (install/dev-setup\n * can finish and exit while the worker keeps warming the warehouse).\n * - `stdio: \"ignore\"` so it never holds the parent's pipes open or interleaves\n * output into the install/dev log.\n *\n * Spawning is wrapped so any failure is non-fatal: the caller still has degraded\n * types and exits 0.\n *\n * @param lockPath - the acquired single-flight lock; passed to the worker so it\n * releases the SAME lock when it finishes.\n * @param targets - the foreground's positional args, forwarded verbatim.\n * @returns true if the worker was spawned, false if spawning threw.\n */\nexport function spawnTypegenWorker(\n lockPath: string,\n targets: { rootDir?: string; outFile?: string; warehouseId?: string },\n): boolean {\n // The script the runtime launched us with (the `appkit` bin shim). Re-running\n // it under the same node binary reproduces this exact CLI in the worker.\n const cliEntry = process.argv[1];\n\n // Forward the positionals in declaration order (rootDir, outFile,\n // warehouseId). Stop at the first undefined so we never pass a literal\n // \"undefined\" — commander would treat it as a positional value. (rootDir is\n // effectively always set by commander's default, but guard anyway.)\n const positionals: string[] = [];\n for (const value of [targets.rootDir, targets.outFile, targets.warehouseId]) {\n if (value === undefined) break;\n positionals.push(value);\n }\n\n const args = [\n // Forward the parent's node/loader flags so the worker runs under the same\n // runtime. Critically this carries tsx's `--require`/`--import` when the CLI\n // is run from source (`tsx index.ts …`); without them the worker would be\n // `node index.ts …`, which can't parse TypeScript and dies silently — the\n // degraded types would then never refresh. Empty for the built bin (plain\n // `node bin/appkit.js`), so production behaviour is unchanged.\n ...process.execArgv,\n cliEntry,\n \"generate-types\",\n \"--wait\",\n \"--worker-lock\",\n lockPath,\n ...positionals,\n ];\n\n try {\n const child = spawn(process.execPath, args, {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n return true;\n } catch (error) {\n // Non-fatal: the foreground already wrote degraded types. Log and move on.\n console.error(\n `Could not start background type refresh: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return false;\n }\n}\n\n/**\n * The command action. Orchestrates the non-blocking foreground contract:\n * 1. Run the library generate (writes degraded types immediately in non-blocking\n * mode; does the full blocking lifecycle when this is the worker).\n * 2. If this is a non-blocking, non-worker invocation, try to spawn the detached\n * blocking worker behind the single-flight lock. If the lock is already held\n * by a live worker, skip (single-flight) with a one-line note. Either way the\n * foreground returns normally (exit 0).\n * 3. If this IS the worker (`--worker-lock` present), it ran blocking above and\n * releases the lock here (and via a process-exit guard, so a hard failure /\n * process.exit still frees it).\n */\nasync function generateTypesAction(\n rootDir: string | undefined,\n outFile: string | undefined,\n warehouseId: string | undefined,\n options: GenerateTypesOptions,\n) {\n const isWorker = typeof options.workerLock === \"string\";\n\n // A worker must always free its lock, even if the blocking generate throws or\n // calls process.exit (TypegenFatalError → exit 1). The exit handler covers the\n // process.exit / uncaught paths; the finally covers the normal return.\n if (isWorker && options.workerLock) {\n const lockPath = options.workerLock;\n process.once(\"exit\", () => releaseSpawnLock(lockPath));\n }\n\n try {\n await runGenerateTypes(rootDir, outFile, warehouseId, options);\n } finally {\n if (isWorker && options.workerLock) {\n releaseSpawnLock(options.workerLock);\n }\n }\n\n // Only a non-blocking, non-worker invocation spawns. A worker is always\n // --wait (so resolveTypegenMode → \"blocking\"), which both prevents recursion\n // and means we never get here for a worker.\n if (!isWorker && resolveTypegenMode(options) === \"non-blocking\") {\n const resolvedRootDir = rootDir || process.cwd();\n const lockPath = getSpawnLockPath(resolvedRootDir);\n\n if (acquireSpawnLock(lockPath)) {\n spawnTypegenWorker(lockPath, { rootDir, outFile, warehouseId });\n } else {\n console.log(\"Type refresh already in progress, skipping.\");\n }\n }\n}\n\nexport const generateTypesCommand = new Command(\"generate-types\")\n .description(\"Generate TypeScript types from SQL queries\")\n .argument(\"[rootDir]\", \"Root directory of the project\", process.cwd())\n .argument(\n \"[outFile]\",\n \"Output file path\",\n path.join(process.cwd(), \"shared/appkit-types/analytics.d.ts\"),\n )\n .argument(\"[warehouseId]\", \"Databricks warehouse ID\")\n .option(\"--no-cache\", \"Disable caching for type generation\")\n .option(\n \"--wait\",\n \"Wait for warehouse readiness instead of degrading (use for CI)\",\n )\n // Internal: marks the detached background worker and carries the lock it must\n // release. Hidden from --help; users should never pass it.\n .addOption(\n new Option(\n \"--worker-lock <path>\",\n \"Internal: detached worker lock path\",\n ).hideHelp(),\n )\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit generate-types\n $ appkit generate-types . shared/appkit-types/analytics.d.ts\n $ appkit generate-types . shared/appkit-types/analytics.d.ts my-warehouse-id\n $ appkit generate-types --no-cache\n $ appkit generate-types --wait # CI: wait for the warehouse and fail on a cold one`,\n )\n .action(generateTypesAction);\n"],"mappings":"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"}
1
+ {"version":3,"file":"generate-types.js","names":[],"sources":["../../../src/cli/commands/generate-types.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { Command, Option } from \"commander\";\nimport { METRIC_CONFIG_FILE } from \"../../schemas/metric-fqn\";\nimport {\n acquireSpawnLock,\n getSpawnLockPath,\n releaseSpawnLock,\n} from \"./spawn-lock.js\";\n\n/**\n * Resolve the typegen pre-flight mode for the CLI. Defaults to \"non-blocking\" —\n * a one-shot CLI can't describe in the background, so by default it never\n * describes at all: it skips the warehouse probe AND every DESCRIBE, emits\n * best-available types (cache where the SQL hash matches, else `result: unknown`)\n * and returns immediately, never blocking on — or failing because of — a\n * warehouse, even a RUNNING one. Pass `--wait` (commander sets `wait: true`)\n * for a deliberate/CI invocation that should wait for a starting warehouse and\n * fail fast on a stopped one.\n */\nexport function resolveTypegenMode(options?: {\n wait?: boolean;\n}): \"non-blocking\" | \"blocking\" {\n return options?.wait ? \"blocking\" : \"non-blocking\";\n}\n\n/** Options parsed by commander for the generate-types command. */\ninterface GenerateTypesOptions {\n noCache?: boolean;\n wait?: boolean;\n /**\n * Internal: present only on the detached worker invocation. Carries the path\n * of the single-flight lock this worker must release when it finishes. Its\n * presence is what marks an invocation as \"the worker\" — workers always run\n * with `--wait`, so they never spawn another worker (only non-blocking runs\n * spawn), which terminates the recursion.\n */\n workerLock?: string;\n}\n\n/**\n * Generate types command implementation. Runs the library generate (which, in\n * non-blocking mode, writes degraded types and returns immediately). This is the\n * SAME work the worker performs in blocking mode in the background.\n */\nasync function runGenerateTypes(\n rootDir?: string,\n outFile?: string,\n warehouseId?: string,\n options?: GenerateTypesOptions,\n) {\n try {\n const resolvedRootDir = rootDir || process.cwd();\n const noCache = options?.noCache || false;\n const mode = resolveTypegenMode(options);\n\n const typeGen = await import(\"@databricks/appkit/type-generator\");\n\n // Generate analytics query types (requires warehouse ID)\n const resolvedWarehouseId =\n warehouseId || process.env.DATABRICKS_WAREHOUSE_ID;\n\n if (resolvedWarehouseId) {\n const resolvedOutFile =\n outFile ||\n path.join(process.cwd(), \"shared/appkit-types/analytics.d.ts\");\n\n const queryFolder = path.join(resolvedRootDir, \"config/queries\");\n const metricViewsFolder = path.join(\n resolvedRootDir,\n \"config/metric-views\",\n );\n const hasQueries = fs.existsSync(queryFolder);\n const hasMetricViews = fs.existsSync(metricViewsFolder);\n\n // Generate when either config surface exists. Metric-view types are\n // independent of `.sql` queries — an app can declare metric views in\n // `config/metric-views/` without a `config/queries/` folder.\n if (hasQueries || hasMetricViews) {\n await typeGen.generateFromEntryPoint({\n queryFolder: hasQueries ? queryFolder : undefined,\n metricViewsFolder: hasMetricViews ? metricViewsFolder : undefined,\n outFile: resolvedOutFile,\n warehouseId: resolvedWarehouseId,\n noCache,\n mode,\n });\n\n if (hasQueries) {\n console.log(`Generated query types: ${resolvedOutFile}`);\n }\n\n const metricConfig = path.join(metricViewsFolder, METRIC_CONFIG_FILE);\n if (fs.existsSync(metricConfig)) {\n const typesDir = path.dirname(resolvedOutFile);\n console.log(\n `Generated metric types: ${path.join(typesDir, \"metric-views.d.ts\")}`,\n );\n }\n }\n } else {\n console.error(\n \"Skipping query type generation: no warehouse ID. Set DATABRICKS_WAREHOUSE_ID or pass as argument.\",\n );\n }\n\n // Generate serving endpoint types (no warehouse required)\n const servingOutFile = path.join(\n process.cwd(),\n \"shared/appkit-types/serving.d.ts\",\n );\n await typeGen.generateServingTypes({\n outFile: servingOutFile,\n noCache,\n });\n console.log(`Generated serving types: ${servingOutFile}`);\n } catch (error) {\n if (\n error instanceof Error &&\n error.message.includes(\"Cannot find module\")\n ) {\n console.error(\n \"Error: The 'generate-types' command is only available in @databricks/appkit.\",\n );\n console.error(\"Please install @databricks/appkit to use this command.\");\n process.exit(1);\n }\n // TypegenSyntaxError / TypegenFatalError carry a complete, actionable\n // message (which queries failed and how to debug them). The stack trace\n // points into appkit internals and is noise for app developers, so print\n // only the message and exit non-zero instead of letting it bubble up.\n if (\n error instanceof Error &&\n (error.name === \"TypegenSyntaxError\" ||\n error.name === \"TypegenFatalError\")\n ) {\n console.error(error.message);\n process.exit(1);\n }\n throw error;\n }\n}\n\n/**\n * Spawn the detached blocking worker that refreshes real types in the background\n * after the foreground non-blocking generate has already written degraded types.\n *\n * Re-invokes THIS CLI (`process.execPath` + `process.argv[1]` — the bin entry\n * that launched us) with `generate-types --wait --worker-lock <lockPath>` plus\n * the same positional target options the foreground used, so the worker writes\n * to the same out file / reads the same query folder. The worker is:\n * - `detached: true` + `.unref()` so it outlives this process (install/dev-setup\n * can finish and exit while the worker keeps warming the warehouse).\n * - `stdio: \"ignore\"` so it never holds the parent's pipes open or interleaves\n * output into the install/dev log.\n *\n * Spawning is wrapped so any failure is non-fatal: the caller still has degraded\n * types and exits 0.\n *\n * @param lockPath - the acquired single-flight lock; passed to the worker so it\n * releases the SAME lock when it finishes.\n * @param targets - the foreground's positional args, forwarded verbatim.\n * @returns true if the worker was spawned, false if spawning threw.\n */\nexport function spawnTypegenWorker(\n lockPath: string,\n targets: { rootDir?: string; outFile?: string; warehouseId?: string },\n): boolean {\n // The script the runtime launched us with (the `appkit` bin shim). Re-running\n // it under the same node binary reproduces this exact CLI in the worker.\n const cliEntry = process.argv[1];\n\n // Forward the positionals in declaration order (rootDir, outFile,\n // warehouseId). Stop at the first undefined so we never pass a literal\n // \"undefined\" — commander would treat it as a positional value. (rootDir is\n // effectively always set by commander's default, but guard anyway.)\n const positionals: string[] = [];\n for (const value of [targets.rootDir, targets.outFile, targets.warehouseId]) {\n if (value === undefined) break;\n positionals.push(value);\n }\n\n const args = [\n // Forward the parent's node/loader flags so the worker runs under the same\n // runtime. Critically this carries tsx's `--require`/`--import` when the CLI\n // is run from source (`tsx index.ts …`); without them the worker would be\n // `node index.ts …`, which can't parse TypeScript and dies silently — the\n // degraded types would then never refresh. Empty for the built bin (plain\n // `node bin/appkit.js`), so production behaviour is unchanged.\n ...process.execArgv,\n cliEntry,\n \"generate-types\",\n \"--wait\",\n \"--worker-lock\",\n lockPath,\n ...positionals,\n ];\n\n try {\n const child = spawn(process.execPath, args, {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n return true;\n } catch (error) {\n // Non-fatal: the foreground already wrote degraded types. Log and move on.\n console.error(\n `Could not start background type refresh: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n return false;\n }\n}\n\n/**\n * The command action. Orchestrates the non-blocking foreground contract:\n * 1. Run the library generate (writes degraded types immediately in non-blocking\n * mode; does the full blocking lifecycle when this is the worker).\n * 2. If this is a non-blocking, non-worker invocation, try to spawn the detached\n * blocking worker behind the single-flight lock. If the lock is already held\n * by a live worker, skip (single-flight) with a one-line note. Either way the\n * foreground returns normally (exit 0).\n * 3. If this IS the worker (`--worker-lock` present), it ran blocking above and\n * releases the lock here (and via a process-exit guard, so a hard failure /\n * process.exit still frees it).\n */\nasync function generateTypesAction(\n rootDir: string | undefined,\n outFile: string | undefined,\n warehouseId: string | undefined,\n options: GenerateTypesOptions,\n) {\n const isWorker = typeof options.workerLock === \"string\";\n\n // A worker must always free its lock, even if the blocking generate throws or\n // calls process.exit (TypegenFatalError → exit 1). The exit handler covers the\n // process.exit / uncaught paths; the finally covers the normal return.\n if (isWorker && options.workerLock) {\n const lockPath = options.workerLock;\n process.once(\"exit\", () => releaseSpawnLock(lockPath));\n }\n\n try {\n await runGenerateTypes(rootDir, outFile, warehouseId, options);\n } finally {\n if (isWorker && options.workerLock) {\n releaseSpawnLock(options.workerLock);\n }\n }\n\n // Only a non-blocking, non-worker invocation spawns. A worker is always\n // --wait (so resolveTypegenMode → \"blocking\"), which both prevents recursion\n // and means we never get here for a worker.\n if (!isWorker && resolveTypegenMode(options) === \"non-blocking\") {\n const resolvedRootDir = rootDir || process.cwd();\n const lockPath = getSpawnLockPath(resolvedRootDir);\n\n if (acquireSpawnLock(lockPath)) {\n spawnTypegenWorker(lockPath, { rootDir, outFile, warehouseId });\n } else {\n console.log(\"Type refresh already in progress, skipping.\");\n }\n }\n}\n\nexport const generateTypesCommand = new Command(\"generate-types\")\n .description(\"Generate TypeScript types from SQL queries\")\n .argument(\"[rootDir]\", \"Root directory of the project\", process.cwd())\n .argument(\n \"[outFile]\",\n \"Output file path\",\n path.join(process.cwd(), \"shared/appkit-types/analytics.d.ts\"),\n )\n .argument(\"[warehouseId]\", \"Databricks warehouse ID\")\n .option(\"--no-cache\", \"Disable caching for type generation\")\n .option(\n \"--wait\",\n \"Wait for warehouse readiness instead of degrading (use for CI)\",\n )\n // Internal: marks the detached background worker and carries the lock it must\n // release. Hidden from --help; users should never pass it.\n .addOption(\n new Option(\n \"--worker-lock <path>\",\n \"Internal: detached worker lock path\",\n ).hideHelp(),\n )\n .addHelpText(\n \"after\",\n `\nExamples:\n $ appkit generate-types\n $ appkit generate-types . shared/appkit-types/analytics.d.ts\n $ appkit generate-types . shared/appkit-types/analytics.d.ts my-warehouse-id\n $ appkit generate-types --no-cache\n $ appkit generate-types --wait # CI: wait for the warehouse and fail on a cold one`,\n )\n .action(generateTypesAction);\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"load-agents.d.ts","names":[],"sources":["../../../src/core/agent/load-agents.ts"],"mappings":";;;;;UAiBU,eAAA;EACR,OAAA,GAAU,IAAA,GAAO,cAAA,KAAmB,MAAA;AAAA;AAAA,UAGrB,WAAA;EAJQ;EAMvB,YAAA,GAAe,YAAA,GAAe,OAAA,CAAQ,YAAA;EALI;EAO1C,cAAA,GAAiB,MAAA,SAAe,SAAA;EAPf;;;;;AAGnB;EAWE,OAAA,GAAU,GAAA,SAAY,eAAA;;;;;;;;EAQtB,UAAA,GAAa,MAAA,SAAe,eAAA;AAAA;AAAA,UAGb,UAAA;EAHI;EAKnB,IAAA,EAAM,MAAA,SAAe,eAAA;EAtBrB;EAwBA,YAAA;AAAA;;;;;;iBA0Dc,uBAAA,CAAwB,QAAA;;;;;;;;;iBA8BlB,iBAAA,CACpB,QAAA,UACA,GAAA,EAAK,WAAA,GACJ,OAAA,CAAQ,eAAA;;;;;;;;;;;;;;;;;;;iBAgCW,iBAAA,CACpB,GAAA,UACA,GAAA,EAAK,WAAA,GACJ,OAAA,CAAQ,UAAA"}
1
+ {"version":3,"file":"load-agents.d.ts","names":[],"sources":["../../../src/core/agent/load-agents.ts"],"mappings":";;;;;UAkBU,eAAA;EACR,OAAA,GAAU,IAAA,GAAO,cAAA,KAAmB,MAAA;AAAA;AAAA,UAGrB,WAAA;EAJQ;EAMvB,YAAA,GAAe,YAAA,GAAe,OAAA,CAAQ,YAAA;EALI;EAO1C,cAAA,GAAiB,MAAA,SAAe,SAAA;EAPf;;;;;AAGnB;EAWE,OAAA,GAAU,GAAA,SAAY,eAAA;;;;;;;;EAQtB,UAAA,GAAa,MAAA,SAAe,eAAA;AAAA;AAAA,UAGb,UAAA;EAHI;EAKnB,IAAA,EAAM,MAAA,SAAe,eAAA;EAtBrB;EAwBA,YAAA;AAAA;;;;;;iBAgEc,uBAAA,CAAwB,QAAA;;;;;;;;;iBA+BlB,iBAAA,CACpB,QAAA,UACA,GAAA,EAAK,WAAA,GACJ,OAAA,CAAQ,eAAA;;;;;;;;;;;;;;;;;;;iBAgCW,iBAAA,CACpB,GAAA,UACA,GAAA,EAAK,WAAA,GACJ,OAAA,CAAQ,UAAA"}
@@ -26,6 +26,7 @@ const ALLOWED_KEYS = new Set([
26
26
  "agents",
27
27
  "maxSteps",
28
28
  "maxTokens",
29
+ "generationParams",
29
30
  "default",
30
31
  "baseSystemPrompt",
31
32
  "ephemeral"
@@ -169,6 +170,56 @@ function parseFrontmatter(raw, sourcePath) {
169
170
  content: match[2].trim()
170
171
  };
171
172
  }
173
+ const isNumber = (v) => typeof v === "number";
174
+ /**
175
+ * Per-key validators for {@link GenerationParams} frontmatter. Keyed by wire
176
+ * name and typed as `Record<keyof GenerationParams, ...>`, so a new param on
177
+ * the interface is a compile error until it gets an entry here — parsing,
178
+ * unknown-key detection, and warnings all derive from this one table.
179
+ */
180
+ const GENERATION_PARAM_SPECS = {
181
+ temperature: {
182
+ label: "number",
183
+ valid: isNumber
184
+ },
185
+ top_p: {
186
+ label: "number",
187
+ valid: isNumber
188
+ },
189
+ frequency_penalty: {
190
+ label: "number",
191
+ valid: isNumber
192
+ },
193
+ presence_penalty: {
194
+ label: "number",
195
+ valid: isNumber
196
+ },
197
+ stop: {
198
+ label: "string or string[]",
199
+ valid: (v) => typeof v === "string" || Array.isArray(v) && v.every((s) => typeof s === "string")
200
+ }
201
+ };
202
+ /**
203
+ * Defensively maps a frontmatter `generationParams` map to {@link GenerationParams}.
204
+ * Picks only known keys with the expected wire types. A key present with the
205
+ * wrong type (e.g. `temperature: "0.5"`) or an unknown key (e.g. `top-p`) is
206
+ * dropped and logged at warn level, so a silently-ignored param is visible
207
+ * rather than mistaken for "applied". Returns `undefined` when no valid key is
208
+ * present.
209
+ */
210
+ function parseGenerationParams(value, sourcePath) {
211
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return;
212
+ const raw = value;
213
+ const out = {};
214
+ const where = sourcePath ?? "<inline>";
215
+ for (const [key, v] of Object.entries(raw)) {
216
+ const spec = GENERATION_PARAM_SPECS[key];
217
+ if (!spec) logger.warn("Ignoring unknown generationParams key '%s' in %s", key, where);
218
+ else if (spec.valid(v)) out[key] = v;
219
+ else logger.warn("Ignoring generationParams.%s in %s: expected %s, got %s", key, where, spec.label, typeof v);
220
+ }
221
+ return Object.keys(out).length > 0 ? out : void 0;
222
+ }
172
223
  function buildDefinition(name, raw, filePath, ctx) {
173
224
  const { data, content } = parseFrontmatter(raw, filePath);
174
225
  const fm = data ?? {};
@@ -184,6 +235,7 @@ function buildDefinition(name, raw, filePath, ctx) {
184
235
  tools: Object.keys(tools).length > 0 ? tools : void 0,
185
236
  maxSteps: typeof fm.maxSteps === "number" ? fm.maxSteps : void 0,
186
237
  maxTokens: typeof fm.maxTokens === "number" ? fm.maxTokens : void 0,
238
+ generationParams: parseGenerationParams(fm.generationParams, filePath),
187
239
  baseSystemPrompt,
188
240
  ephemeral: typeof fm.ephemeral === "boolean" ? fm.ephemeral : void 0
189
241
  };
@@ -1 +1 @@
1
- {"version":3,"file":"load-agents.js","names":[],"sources":["../../../src/core/agent/load-agents.ts"],"sourcesContent":["import type { Dirent } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport yaml from \"js-yaml\";\nimport type { AgentAdapter } from \"shared\";\nimport type {\n AgentDefinition,\n AgentTool,\n BaseSystemPromptOption,\n ToolkitEntry,\n ToolkitOptions,\n} from \"../../core/agent/types\";\nimport { isToolkitEntry } from \"../../core/agent/types\";\nimport { createLogger } from \"../../logging/logger\";\n\nconst logger = createLogger(\"agents:loader\");\n\ninterface ToolkitProvider {\n toolkit: (opts?: ToolkitOptions) => Record<string, unknown>;\n}\n\nexport interface LoadContext {\n /** Default model when frontmatter has no `endpoint` and the def has no `model`. */\n defaultModel?: AgentAdapter | Promise<AgentAdapter> | string;\n /** Ambient tool library referenced by frontmatter `tools: [key1, key2]`. */\n availableTools?: Record<string, AgentTool>;\n /**\n * Registered plugin toolkits referenced by `plugin:NAME` entries in the\n * unified `tools:` frontmatter list. Keyed by plugin name; each value\n * exposes the same `toolkit(opts?)` surface as the `plugins` argument to\n * `tools(plugins) => Record<...>` in the code form.\n */\n plugins?: Map<string, ToolkitProvider>;\n /**\n * Code-defined agents contributed by `agents({ agents: { ... } })`. The\n * directory loader resolves `agents:` frontmatter references against\n * these alongside sibling markdown files, so a markdown parent can\n * delegate to a code-defined child. Code-defined names win on collision\n * with markdown names, matching the plugin's top-level merge precedence.\n */\n codeAgents?: Record<string, AgentDefinition>;\n}\n\nexport interface LoadResult {\n /** Agent definitions keyed by agent id (directory name under `dir`). */\n defs: Record<string, AgentDefinition>;\n /** First agent with `default: true` frontmatter (sorted id order), or `null`. */\n defaultAgent: string | null;\n}\n\ninterface Frontmatter {\n endpoint?: string;\n model?: string;\n /**\n * Unified tool list. Each entry is one of:\n *\n * - **`plugin:<name>`** (string) — pull every tool from the named plugin.\n * - **`plugin:<name>: [tool1, tool2]`** — pull only the listed tools\n * (shorthand for `{ only: [...] }`).\n * - **`plugin:<name>: { ...ToolkitOptions }`** — pass full\n * `prefix` / `only` / `except` / `rename` options.\n * - **`<key>`** (string, no `plugin:` prefix) — ambient tool name\n * resolved against the `agents({ tools: { ... } })` config.\n *\n * Mirrors the TS function form `tools(plugins) { ... }` where plugin\n * tools and inline tools live in the same record.\n */\n tools?: FrontmatterToolEntry[];\n /**\n * Other agent ids to expose as sub-agents. Each becomes an `agent-<id>`\n * tool at runtime. Resolution happens at directory-load time in\n * {@link loadAgentsFromDir}; the single-file {@link loadAgentFromFile} path\n * rejects non-empty values since there are no siblings to resolve against.\n */\n agents?: string[];\n maxSteps?: number;\n maxTokens?: number;\n default?: boolean;\n baseSystemPrompt?: false | string;\n ephemeral?: boolean;\n}\n\n/**\n * Each item in {@link Frontmatter.tools}. Strings are either ambient tool\n * names (no prefix) or bare plugin references (`plugin:NAME`). Objects are\n * single-key mappings whose key is `plugin:NAME` and whose value is either\n * an array of local tool names (sugar for `{ only: [...] }`) or a full\n * `ToolkitOptions` record.\n *\n * Named `FrontmatterToolEntry` to avoid colliding with the exported\n * `ToolEntry` from `tools/define-tool.ts` — that is the plugin-author API\n * surface (`defineTool({ ... }) : ToolEntry`); this is the frontmatter\n * parse type. They are unrelated and live in different layers.\n */\ntype FrontmatterToolEntry =\n | string\n | { [key: string]: ToolkitOptions | string[] };\n\nconst PLUGIN_PREFIX = \"plugin:\";\n\n/**\n * Derives the logical agent id from a markdown path. When the file is named\n * `agent.md`, the id is the parent directory name (folder-based layout);\n * otherwise the id is the file stem (e.g. legacy single-file paths).\n */\nexport function agentIdFromMarkdownPath(filePath: string): string {\n const normalized = path.normalize(filePath);\n const base = path.basename(normalized);\n const parent = path.basename(path.dirname(normalized));\n if (base === \"agent.md\" && parent && parent !== \".\" && parent !== \"..\") {\n return parent;\n }\n return path.basename(normalized, \".md\");\n}\n\nconst ALLOWED_KEYS = new Set([\n \"endpoint\",\n \"model\",\n \"tools\",\n \"agents\",\n \"maxSteps\",\n \"maxTokens\",\n \"default\",\n \"baseSystemPrompt\",\n \"ephemeral\",\n]);\n\n/**\n * Loads a single markdown agent file and resolves its frontmatter against\n * registered plugin toolkits + ambient tool library.\n *\n * Rejects non-empty `agents:` frontmatter because single-file loads have\n * no siblings to resolve sub-agent references against — callers must use\n * {@link loadAgentsFromDir} when markdown agents delegate to one another.\n */\nexport async function loadAgentFromFile(\n filePath: string,\n ctx: LoadContext,\n): Promise<AgentDefinition> {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const name = agentIdFromMarkdownPath(filePath);\n const { data } = parseFrontmatter(raw, filePath);\n if (Array.isArray(data?.agents) && data.agents.length > 0) {\n throw new Error(\n `Agent '${name}' (${filePath}) declares 'agents:' in frontmatter, ` +\n `which requires loadAgentsFromDir to resolve sibling references. ` +\n `Use loadAgentsFromDir, or wire sub-agents in code via createAgent({ agents: { ... } }).`,\n );\n }\n return buildDefinition(name, raw, filePath, ctx);\n}\n\n/**\n * Scans a directory for one subdirectory per agent, each containing\n * `agent.md` (frontmatter + body). Produces an `AgentDefinition` record keyed\n * by agent id (folder name). Throws on frontmatter errors or unresolved\n * references. Returns an empty map if the directory does not exist.\n *\n * Legacy top-level `*.md` files are rejected with an error — migrate each to\n * `<id>/agent.md` under a sibling folder named for the agent id.\n *\n * Runs in two passes so sub-agent references in frontmatter (`agents: [...]`)\n * can be resolved regardless of directory iteration order:\n *\n * 1. Build every agent's definition from its own `agent.md`.\n * 2. Walk `agents:` references and wire `def.agents = { child: childDef }`\n * by looking them up in the complete map. Dangling names and\n * self-references fail loudly; mutual delegation is allowed and bounded\n * at runtime by `limits.maxSubAgentDepth`.\n */\nexport async function loadAgentsFromDir(\n dir: string,\n ctx: LoadContext,\n): Promise<LoadResult> {\n let entries: Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { defs: {}, defaultAgent: null };\n }\n throw err;\n }\n const orphanMd = entries\n .filter((e) => e.isFile() && e.name.endsWith(\".md\"))\n .map((e) => e.name)\n .sort();\n\n if (orphanMd.length > 0) {\n const hint = orphanMd\n .map((f) => `${path.basename(f, \".md\")}/agent.md`)\n .join(\", \");\n throw new Error(\n `Agents directory contains unsupported top-level markdown file(s): ${orphanMd.join(\", \")}. ` +\n `Use one folder per agent with a fixed entry file, e.g. ${hint}.`,\n );\n }\n\n /** Reserved folder name until per-agent skills land; not an agent package. */\n const RESERVED_DIRS = new Set([\"skills\"]);\n\n const agentIds = entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .filter((name) => !RESERVED_DIRS.has(name))\n .sort();\n\n const defs: Record<string, AgentDefinition> = {};\n const subAgentRefs: Record<string, string[]> = {};\n let defaultAgent: string | null = null;\n\n // Pass 1: build every agent's definition; collect sub-agent refs.\n for (const id of agentIds) {\n const agentPath = path.join(dir, id, \"agent.md\");\n let raw: string;\n try {\n raw = await fs.readFile(agentPath, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new Error(\n `Agents subdirectory '${path.join(dir, id)}' must contain agent.md.`,\n );\n }\n throw err;\n }\n defs[id] = buildDefinition(id, raw, agentPath, ctx);\n const { data } = parseFrontmatter(raw, agentPath);\n if (data?.agents !== undefined) {\n subAgentRefs[id] = normalizeAgentsFrontmatter(data.agents, id, agentPath);\n }\n if (data?.default === true && !defaultAgent) {\n defaultAgent = id;\n }\n }\n\n // Pass 2: resolve sub-agent references against the complete defs map.\n // Code-defined agents (ctx.codeAgents) take precedence over markdown ones\n // with the same name, matching the plugin's top-level merge behaviour.\n for (const [name, refs] of Object.entries(subAgentRefs)) {\n if (refs.length === 0) continue;\n const children: Record<string, AgentDefinition> = {};\n const missing: string[] = [];\n for (const ref of refs) {\n if (ref === name) {\n throw new Error(\n `Agent '${name}' (${path.join(dir, name, \"agent.md\")}) cannot reference itself in 'agents:'.`,\n );\n }\n const sibling = ctx.codeAgents?.[ref] ?? defs[ref];\n if (!sibling) {\n missing.push(ref);\n continue;\n }\n children[ref] = sibling;\n }\n if (missing.length > 0) {\n const available =\n [...Object.keys(ctx.codeAgents ?? {}), ...Object.keys(defs)]\n .sort()\n .join(\", \") || \"<none>\";\n throw new Error(\n `Agent '${name}' references sub-agent(s) '${missing.join(\", \")}' in 'agents:', ` +\n `but no markdown or code agent(s) with those names exist. ` +\n `Available: ${available}.`,\n );\n }\n defs[name].agents = children;\n }\n\n return { defs, defaultAgent };\n}\n\n/**\n * Validates that `agents:` frontmatter is an array of non-empty strings and\n * returns it with duplicates removed. Throws with a clear per-file message\n * on malformed input rather than silently ignoring.\n */\nfunction normalizeAgentsFrontmatter(\n value: unknown,\n agentName: string,\n filePath: string,\n): string[] {\n if (!Array.isArray(value)) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has invalid 'agents:' frontmatter: ` +\n `expected an array of sibling agent ids, got ${typeof value}.`,\n );\n }\n const out: string[] = [];\n const seen = new Set<string>();\n for (const item of value) {\n if (typeof item !== \"string\" || item.trim() === \"\") {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has invalid 'agents:' entry: ` +\n `expected non-empty string, got ${JSON.stringify(item)}.`,\n );\n }\n if (seen.has(item)) continue;\n seen.add(item);\n out.push(item);\n }\n return out;\n}\n\n/** Exposed for tests. Parses `--- yaml ---\\nbody` and validates frontmatter keys. */\nexport function parseFrontmatter(\n raw: string,\n sourcePath?: string,\n): { data: Frontmatter | null; content: string } {\n const match = raw.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (!match) {\n return { data: null, content: raw.trim() };\n }\n let parsed: unknown;\n try {\n parsed = yaml.load(match[1]);\n } catch (err) {\n const src = sourcePath ? ` (${sourcePath})` : \"\";\n throw new Error(\n `Invalid YAML frontmatter${src}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (parsed === null || parsed === undefined) {\n return { data: {}, content: match[2].trim() };\n }\n if (typeof parsed !== \"object\" || Array.isArray(parsed)) {\n const src = sourcePath ? ` (${sourcePath})` : \"\";\n throw new Error(`Frontmatter must be a YAML object${src}`);\n }\n const data = parsed as Record<string, unknown>;\n for (const key of Object.keys(data)) {\n if (!ALLOWED_KEYS.has(key)) {\n logger.warn(\n \"Ignoring unknown frontmatter key '%s' in %s\",\n key,\n sourcePath ?? \"<inline>\",\n );\n }\n }\n return { data: data as Frontmatter, content: match[2].trim() };\n}\n\nfunction buildDefinition(\n name: string,\n raw: string,\n filePath: string,\n ctx: LoadContext,\n): AgentDefinition {\n const { data, content } = parseFrontmatter(raw, filePath);\n const fm: Frontmatter = data ?? {};\n\n const tools = resolveFrontmatterTools(name, fm, filePath, ctx);\n const model = fm.model ?? fm.endpoint ?? ctx.defaultModel;\n\n let baseSystemPrompt: BaseSystemPromptOption | undefined;\n if (fm.baseSystemPrompt === false) baseSystemPrompt = false;\n else if (typeof fm.baseSystemPrompt === \"string\")\n baseSystemPrompt = fm.baseSystemPrompt;\n\n return {\n name,\n instructions: content,\n model,\n tools: Object.keys(tools).length > 0 ? tools : undefined,\n maxSteps: typeof fm.maxSteps === \"number\" ? fm.maxSteps : undefined,\n maxTokens: typeof fm.maxTokens === \"number\" ? fm.maxTokens : undefined,\n baseSystemPrompt,\n ephemeral: typeof fm.ephemeral === \"boolean\" ? fm.ephemeral : undefined,\n };\n}\n\nfunction resolveFrontmatterTools(\n agentName: string,\n fm: Frontmatter,\n filePath: string,\n ctx: LoadContext,\n): Record<string, AgentTool> {\n const out: Record<string, AgentTool> = {};\n const pluginIdx = ctx.plugins ?? new Map<string, ToolkitProvider>();\n\n for (const entry of fm.tools ?? []) {\n const parsed = parseToolEntry(entry, filePath, agentName);\n if (parsed.kind === \"plugin\") {\n const provider = pluginIdx.get(parsed.pluginName);\n if (!provider) {\n const available =\n pluginIdx.size > 0\n ? Array.from(pluginIdx.keys()).join(\", \")\n : \"<none>\";\n throw new Error(\n `Agent '${agentName}' (${filePath}) references 'plugin:${parsed.pluginName}', but plugin '${parsed.pluginName}' is not registered. Available: ${available}`,\n );\n }\n const entries = provider.toolkit(parsed.opts) as Record<string, unknown>;\n for (const [key, value] of Object.entries(entries)) {\n if (!isToolkitEntry(value)) {\n throw new Error(\n `Plugin '${parsed.pluginName}'.toolkit() returned a value at key '${key}' that is not a ToolkitEntry`,\n );\n }\n out[key] = value as ToolkitEntry;\n }\n } else {\n const tool = ctx.availableTools?.[parsed.toolName];\n if (!tool) {\n const available = ctx.availableTools\n ? Object.keys(ctx.availableTools).join(\", \")\n : \"<none>\";\n throw new Error(\n `Agent '${agentName}' (${filePath}) references ambient tool '${parsed.toolName}', which is not in the agents() plugin's tools field. Available: ${available}. ` +\n \"If you meant to reference a plugin, use the 'plugin:NAME' prefix.\",\n );\n }\n out[parsed.toolName] = tool;\n }\n }\n\n return out;\n}\n\ntype ParsedToolEntry =\n | { kind: \"plugin\"; pluginName: string; opts: ToolkitOptions | undefined }\n | { kind: \"ambient\"; toolName: string };\n\n/**\n * Classify one item in the `tools:` frontmatter list into either a plugin\n * reference (with optional ToolkitOptions) or an ambient tool lookup.\n *\n * Strings starting with `plugin:` are bare plugin references. Strings\n * without the prefix are ambient tool names. Object entries are\n * single-key mappings keyed by `plugin:NAME`; the value is either an\n * array (sugar for `{ only: [...] }`) or a full `ToolkitOptions` record.\n */\nfunction parseToolEntry(\n entry: FrontmatterToolEntry,\n filePath: string,\n agentName: string,\n): ParsedToolEntry {\n if (typeof entry === \"string\") {\n if (entry.startsWith(PLUGIN_PREFIX)) {\n const pluginName = entry.slice(PLUGIN_PREFIX.length);\n if (pluginName.length === 0) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has an empty plugin name in 'plugin:'.`,\n );\n }\n return { kind: \"plugin\", pluginName, opts: undefined };\n }\n if (entry.length === 0) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has an empty string in 'tools:'.`,\n );\n }\n return { kind: \"ambient\", toolName: entry };\n }\n if (typeof entry !== \"object\" || entry === null) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has invalid 'tools:' entry: ${JSON.stringify(entry)}`,\n );\n }\n const keys = Object.keys(entry);\n if (keys.length !== 1) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) 'tools:' object entry must have exactly one key, got: ${keys.join(\", \")}`,\n );\n }\n const key = keys[0];\n // Bare `- plugin:` (no name after the colon) parses as a mapping with the\n // key `\"plugin\"`. Catch that as a friendly error rather than dumping it\n // through the generic \"expected key 'plugin:NAME'\" branch.\n if (key === \"plugin\") {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has an empty plugin name in 'plugin:'.`,\n );\n }\n if (!key.startsWith(PLUGIN_PREFIX)) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) 'tools:' object entries are reserved for plugin references; expected key 'plugin:NAME', got '${key}'. ` +\n \"Use a bare string for ambient tools (e.g. `- get_weather`).\",\n );\n }\n const pluginName = key.slice(PLUGIN_PREFIX.length);\n if (pluginName.length === 0) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has an empty plugin name in 'plugin:'.`,\n );\n }\n const value = entry[key];\n if (Array.isArray(value)) {\n return { kind: \"plugin\", pluginName, opts: { only: value } };\n }\n if (typeof value === \"object\" && value !== null) {\n return {\n kind: \"plugin\",\n pluginName,\n opts: value as ToolkitOptions,\n };\n }\n throw new Error(\n `Agent '${agentName}' (${filePath}) 'plugin:${pluginName}' options must be an array of tool names or a ToolkitOptions object.`,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"load-agents.js","names":[],"sources":["../../../src/core/agent/load-agents.ts"],"sourcesContent":["import type { Dirent } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport yaml from \"js-yaml\";\nimport type { AgentAdapter } from \"shared\";\nimport type { GenerationParams } from \"../../agents/databricks\";\nimport type {\n AgentDefinition,\n AgentTool,\n BaseSystemPromptOption,\n ToolkitEntry,\n ToolkitOptions,\n} from \"../../core/agent/types\";\nimport { isToolkitEntry } from \"../../core/agent/types\";\nimport { createLogger } from \"../../logging/logger\";\n\nconst logger = createLogger(\"agents:loader\");\n\ninterface ToolkitProvider {\n toolkit: (opts?: ToolkitOptions) => Record<string, unknown>;\n}\n\nexport interface LoadContext {\n /** Default model when frontmatter has no `endpoint` and the def has no `model`. */\n defaultModel?: AgentAdapter | Promise<AgentAdapter> | string;\n /** Ambient tool library referenced by frontmatter `tools: [key1, key2]`. */\n availableTools?: Record<string, AgentTool>;\n /**\n * Registered plugin toolkits referenced by `plugin:NAME` entries in the\n * unified `tools:` frontmatter list. Keyed by plugin name; each value\n * exposes the same `toolkit(opts?)` surface as the `plugins` argument to\n * `tools(plugins) => Record<...>` in the code form.\n */\n plugins?: Map<string, ToolkitProvider>;\n /**\n * Code-defined agents contributed by `agents({ agents: { ... } })`. The\n * directory loader resolves `agents:` frontmatter references against\n * these alongside sibling markdown files, so a markdown parent can\n * delegate to a code-defined child. Code-defined names win on collision\n * with markdown names, matching the plugin's top-level merge precedence.\n */\n codeAgents?: Record<string, AgentDefinition>;\n}\n\nexport interface LoadResult {\n /** Agent definitions keyed by agent id (directory name under `dir`). */\n defs: Record<string, AgentDefinition>;\n /** First agent with `default: true` frontmatter (sorted id order), or `null`. */\n defaultAgent: string | null;\n}\n\ninterface Frontmatter {\n endpoint?: string;\n model?: string;\n /**\n * Unified tool list. Each entry is one of:\n *\n * - **`plugin:<name>`** (string) — pull every tool from the named plugin.\n * - **`plugin:<name>: [tool1, tool2]`** — pull only the listed tools\n * (shorthand for `{ only: [...] }`).\n * - **`plugin:<name>: { ...ToolkitOptions }`** — pass full\n * `prefix` / `only` / `except` / `rename` options.\n * - **`<key>`** (string, no `plugin:` prefix) — ambient tool name\n * resolved against the `agents({ tools: { ... } })` config.\n *\n * Mirrors the TS function form `tools(plugins) { ... }` where plugin\n * tools and inline tools live in the same record.\n */\n tools?: FrontmatterToolEntry[];\n /**\n * Other agent ids to expose as sub-agents. Each becomes an `agent-<id>`\n * tool at runtime. Resolution happens at directory-load time in\n * {@link loadAgentsFromDir}; the single-file {@link loadAgentFromFile} path\n * rejects non-empty values since there are no siblings to resolve against.\n */\n agents?: string[];\n maxSteps?: number;\n maxTokens?: number;\n /**\n * Optional OpenAI-compatible generation params forwarded to the serving\n * request body (`temperature`, `top_p`, `stop`, `frequency_penalty`,\n * `presence_penalty`). Parsed defensively in {@link buildDefinition}.\n */\n generationParams?: Record<string, unknown>;\n default?: boolean;\n baseSystemPrompt?: false | string;\n ephemeral?: boolean;\n}\n\n/**\n * Each item in {@link Frontmatter.tools}. Strings are either ambient tool\n * names (no prefix) or bare plugin references (`plugin:NAME`). Objects are\n * single-key mappings whose key is `plugin:NAME` and whose value is either\n * an array of local tool names (sugar for `{ only: [...] }`) or a full\n * `ToolkitOptions` record.\n *\n * Named `FrontmatterToolEntry` to avoid colliding with the exported\n * `ToolEntry` from `tools/define-tool.ts` — that is the plugin-author API\n * surface (`defineTool({ ... }) : ToolEntry`); this is the frontmatter\n * parse type. They are unrelated and live in different layers.\n */\ntype FrontmatterToolEntry =\n | string\n | { [key: string]: ToolkitOptions | string[] };\n\nconst PLUGIN_PREFIX = \"plugin:\";\n\n/**\n * Derives the logical agent id from a markdown path. When the file is named\n * `agent.md`, the id is the parent directory name (folder-based layout);\n * otherwise the id is the file stem (e.g. legacy single-file paths).\n */\nexport function agentIdFromMarkdownPath(filePath: string): string {\n const normalized = path.normalize(filePath);\n const base = path.basename(normalized);\n const parent = path.basename(path.dirname(normalized));\n if (base === \"agent.md\" && parent && parent !== \".\" && parent !== \"..\") {\n return parent;\n }\n return path.basename(normalized, \".md\");\n}\n\nconst ALLOWED_KEYS = new Set([\n \"endpoint\",\n \"model\",\n \"tools\",\n \"agents\",\n \"maxSteps\",\n \"maxTokens\",\n \"generationParams\",\n \"default\",\n \"baseSystemPrompt\",\n \"ephemeral\",\n]);\n\n/**\n * Loads a single markdown agent file and resolves its frontmatter against\n * registered plugin toolkits + ambient tool library.\n *\n * Rejects non-empty `agents:` frontmatter because single-file loads have\n * no siblings to resolve sub-agent references against — callers must use\n * {@link loadAgentsFromDir} when markdown agents delegate to one another.\n */\nexport async function loadAgentFromFile(\n filePath: string,\n ctx: LoadContext,\n): Promise<AgentDefinition> {\n const raw = await fs.readFile(filePath, \"utf-8\");\n const name = agentIdFromMarkdownPath(filePath);\n const { data } = parseFrontmatter(raw, filePath);\n if (Array.isArray(data?.agents) && data.agents.length > 0) {\n throw new Error(\n `Agent '${name}' (${filePath}) declares 'agents:' in frontmatter, ` +\n `which requires loadAgentsFromDir to resolve sibling references. ` +\n `Use loadAgentsFromDir, or wire sub-agents in code via createAgent({ agents: { ... } }).`,\n );\n }\n return buildDefinition(name, raw, filePath, ctx);\n}\n\n/**\n * Scans a directory for one subdirectory per agent, each containing\n * `agent.md` (frontmatter + body). Produces an `AgentDefinition` record keyed\n * by agent id (folder name). Throws on frontmatter errors or unresolved\n * references. Returns an empty map if the directory does not exist.\n *\n * Legacy top-level `*.md` files are rejected with an error — migrate each to\n * `<id>/agent.md` under a sibling folder named for the agent id.\n *\n * Runs in two passes so sub-agent references in frontmatter (`agents: [...]`)\n * can be resolved regardless of directory iteration order:\n *\n * 1. Build every agent's definition from its own `agent.md`.\n * 2. Walk `agents:` references and wire `def.agents = { child: childDef }`\n * by looking them up in the complete map. Dangling names and\n * self-references fail loudly; mutual delegation is allowed and bounded\n * at runtime by `limits.maxSubAgentDepth`.\n */\nexport async function loadAgentsFromDir(\n dir: string,\n ctx: LoadContext,\n): Promise<LoadResult> {\n let entries: Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { defs: {}, defaultAgent: null };\n }\n throw err;\n }\n const orphanMd = entries\n .filter((e) => e.isFile() && e.name.endsWith(\".md\"))\n .map((e) => e.name)\n .sort();\n\n if (orphanMd.length > 0) {\n const hint = orphanMd\n .map((f) => `${path.basename(f, \".md\")}/agent.md`)\n .join(\", \");\n throw new Error(\n `Agents directory contains unsupported top-level markdown file(s): ${orphanMd.join(\", \")}. ` +\n `Use one folder per agent with a fixed entry file, e.g. ${hint}.`,\n );\n }\n\n /** Reserved folder name until per-agent skills land; not an agent package. */\n const RESERVED_DIRS = new Set([\"skills\"]);\n\n const agentIds = entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .filter((name) => !RESERVED_DIRS.has(name))\n .sort();\n\n const defs: Record<string, AgentDefinition> = {};\n const subAgentRefs: Record<string, string[]> = {};\n let defaultAgent: string | null = null;\n\n // Pass 1: build every agent's definition; collect sub-agent refs.\n for (const id of agentIds) {\n const agentPath = path.join(dir, id, \"agent.md\");\n let raw: string;\n try {\n raw = await fs.readFile(agentPath, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n throw new Error(\n `Agents subdirectory '${path.join(dir, id)}' must contain agent.md.`,\n );\n }\n throw err;\n }\n defs[id] = buildDefinition(id, raw, agentPath, ctx);\n const { data } = parseFrontmatter(raw, agentPath);\n if (data?.agents !== undefined) {\n subAgentRefs[id] = normalizeAgentsFrontmatter(data.agents, id, agentPath);\n }\n if (data?.default === true && !defaultAgent) {\n defaultAgent = id;\n }\n }\n\n // Pass 2: resolve sub-agent references against the complete defs map.\n // Code-defined agents (ctx.codeAgents) take precedence over markdown ones\n // with the same name, matching the plugin's top-level merge behaviour.\n for (const [name, refs] of Object.entries(subAgentRefs)) {\n if (refs.length === 0) continue;\n const children: Record<string, AgentDefinition> = {};\n const missing: string[] = [];\n for (const ref of refs) {\n if (ref === name) {\n throw new Error(\n `Agent '${name}' (${path.join(dir, name, \"agent.md\")}) cannot reference itself in 'agents:'.`,\n );\n }\n const sibling = ctx.codeAgents?.[ref] ?? defs[ref];\n if (!sibling) {\n missing.push(ref);\n continue;\n }\n children[ref] = sibling;\n }\n if (missing.length > 0) {\n const available =\n [...Object.keys(ctx.codeAgents ?? {}), ...Object.keys(defs)]\n .sort()\n .join(\", \") || \"<none>\";\n throw new Error(\n `Agent '${name}' references sub-agent(s) '${missing.join(\", \")}' in 'agents:', ` +\n `but no markdown or code agent(s) with those names exist. ` +\n `Available: ${available}.`,\n );\n }\n defs[name].agents = children;\n }\n\n return { defs, defaultAgent };\n}\n\n/**\n * Validates that `agents:` frontmatter is an array of non-empty strings and\n * returns it with duplicates removed. Throws with a clear per-file message\n * on malformed input rather than silently ignoring.\n */\nfunction normalizeAgentsFrontmatter(\n value: unknown,\n agentName: string,\n filePath: string,\n): string[] {\n if (!Array.isArray(value)) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has invalid 'agents:' frontmatter: ` +\n `expected an array of sibling agent ids, got ${typeof value}.`,\n );\n }\n const out: string[] = [];\n const seen = new Set<string>();\n for (const item of value) {\n if (typeof item !== \"string\" || item.trim() === \"\") {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has invalid 'agents:' entry: ` +\n `expected non-empty string, got ${JSON.stringify(item)}.`,\n );\n }\n if (seen.has(item)) continue;\n seen.add(item);\n out.push(item);\n }\n return out;\n}\n\n/** Exposed for tests. Parses `--- yaml ---\\nbody` and validates frontmatter keys. */\nexport function parseFrontmatter(\n raw: string,\n sourcePath?: string,\n): { data: Frontmatter | null; content: string } {\n const match = raw.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (!match) {\n return { data: null, content: raw.trim() };\n }\n let parsed: unknown;\n try {\n parsed = yaml.load(match[1]);\n } catch (err) {\n const src = sourcePath ? ` (${sourcePath})` : \"\";\n throw new Error(\n `Invalid YAML frontmatter${src}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (parsed === null || parsed === undefined) {\n return { data: {}, content: match[2].trim() };\n }\n if (typeof parsed !== \"object\" || Array.isArray(parsed)) {\n const src = sourcePath ? ` (${sourcePath})` : \"\";\n throw new Error(`Frontmatter must be a YAML object${src}`);\n }\n const data = parsed as Record<string, unknown>;\n for (const key of Object.keys(data)) {\n if (!ALLOWED_KEYS.has(key)) {\n logger.warn(\n \"Ignoring unknown frontmatter key '%s' in %s\",\n key,\n sourcePath ?? \"<inline>\",\n );\n }\n }\n return { data: data as Frontmatter, content: match[2].trim() };\n}\n\nconst isNumber = (v: unknown): v is number => typeof v === \"number\";\n\n/**\n * Per-key validators for {@link GenerationParams} frontmatter. Keyed by wire\n * name and typed as `Record<keyof GenerationParams, ...>`, so a new param on\n * the interface is a compile error until it gets an entry here — parsing,\n * unknown-key detection, and warnings all derive from this one table.\n */\nconst GENERATION_PARAM_SPECS: Record<\n keyof GenerationParams,\n { label: string; valid: (v: unknown) => boolean }\n> = {\n temperature: { label: \"number\", valid: isNumber },\n top_p: { label: \"number\", valid: isNumber },\n frequency_penalty: { label: \"number\", valid: isNumber },\n presence_penalty: { label: \"number\", valid: isNumber },\n stop: {\n label: \"string or string[]\",\n valid: (v) =>\n typeof v === \"string\" ||\n (Array.isArray(v) && v.every((s) => typeof s === \"string\")),\n },\n};\n\n/**\n * Defensively maps a frontmatter `generationParams` map to {@link GenerationParams}.\n * Picks only known keys with the expected wire types. A key present with the\n * wrong type (e.g. `temperature: \"0.5\"`) or an unknown key (e.g. `top-p`) is\n * dropped and logged at warn level, so a silently-ignored param is visible\n * rather than mistaken for \"applied\". Returns `undefined` when no valid key is\n * present.\n */\nfunction parseGenerationParams(\n value: unknown,\n sourcePath?: string,\n): GenerationParams | undefined {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return undefined;\n }\n const raw = value as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n const where = sourcePath ?? \"<inline>\";\n\n for (const [key, v] of Object.entries(raw)) {\n const spec = GENERATION_PARAM_SPECS[key as keyof GenerationParams];\n if (!spec) {\n logger.warn(\n \"Ignoring unknown generationParams key '%s' in %s\",\n key,\n where,\n );\n } else if (spec.valid(v)) {\n out[key] = v;\n } else {\n logger.warn(\n \"Ignoring generationParams.%s in %s: expected %s, got %s\",\n key,\n where,\n spec.label,\n typeof v,\n );\n }\n }\n\n return Object.keys(out).length > 0 ? (out as GenerationParams) : undefined;\n}\n\nfunction buildDefinition(\n name: string,\n raw: string,\n filePath: string,\n ctx: LoadContext,\n): AgentDefinition {\n const { data, content } = parseFrontmatter(raw, filePath);\n const fm: Frontmatter = data ?? {};\n\n const tools = resolveFrontmatterTools(name, fm, filePath, ctx);\n const model = fm.model ?? fm.endpoint ?? ctx.defaultModel;\n\n let baseSystemPrompt: BaseSystemPromptOption | undefined;\n if (fm.baseSystemPrompt === false) baseSystemPrompt = false;\n else if (typeof fm.baseSystemPrompt === \"string\")\n baseSystemPrompt = fm.baseSystemPrompt;\n\n return {\n name,\n instructions: content,\n model,\n tools: Object.keys(tools).length > 0 ? tools : undefined,\n maxSteps: typeof fm.maxSteps === \"number\" ? fm.maxSteps : undefined,\n maxTokens: typeof fm.maxTokens === \"number\" ? fm.maxTokens : undefined,\n generationParams: parseGenerationParams(fm.generationParams, filePath),\n baseSystemPrompt,\n ephemeral: typeof fm.ephemeral === \"boolean\" ? fm.ephemeral : undefined,\n };\n}\n\nfunction resolveFrontmatterTools(\n agentName: string,\n fm: Frontmatter,\n filePath: string,\n ctx: LoadContext,\n): Record<string, AgentTool> {\n const out: Record<string, AgentTool> = {};\n const pluginIdx = ctx.plugins ?? new Map<string, ToolkitProvider>();\n\n for (const entry of fm.tools ?? []) {\n const parsed = parseToolEntry(entry, filePath, agentName);\n if (parsed.kind === \"plugin\") {\n const provider = pluginIdx.get(parsed.pluginName);\n if (!provider) {\n const available =\n pluginIdx.size > 0\n ? Array.from(pluginIdx.keys()).join(\", \")\n : \"<none>\";\n throw new Error(\n `Agent '${agentName}' (${filePath}) references 'plugin:${parsed.pluginName}', but plugin '${parsed.pluginName}' is not registered. Available: ${available}`,\n );\n }\n const entries = provider.toolkit(parsed.opts) as Record<string, unknown>;\n for (const [key, value] of Object.entries(entries)) {\n if (!isToolkitEntry(value)) {\n throw new Error(\n `Plugin '${parsed.pluginName}'.toolkit() returned a value at key '${key}' that is not a ToolkitEntry`,\n );\n }\n out[key] = value as ToolkitEntry;\n }\n } else {\n const tool = ctx.availableTools?.[parsed.toolName];\n if (!tool) {\n const available = ctx.availableTools\n ? Object.keys(ctx.availableTools).join(\", \")\n : \"<none>\";\n throw new Error(\n `Agent '${agentName}' (${filePath}) references ambient tool '${parsed.toolName}', which is not in the agents() plugin's tools field. Available: ${available}. ` +\n \"If you meant to reference a plugin, use the 'plugin:NAME' prefix.\",\n );\n }\n out[parsed.toolName] = tool;\n }\n }\n\n return out;\n}\n\ntype ParsedToolEntry =\n | { kind: \"plugin\"; pluginName: string; opts: ToolkitOptions | undefined }\n | { kind: \"ambient\"; toolName: string };\n\n/**\n * Classify one item in the `tools:` frontmatter list into either a plugin\n * reference (with optional ToolkitOptions) or an ambient tool lookup.\n *\n * Strings starting with `plugin:` are bare plugin references. Strings\n * without the prefix are ambient tool names. Object entries are\n * single-key mappings keyed by `plugin:NAME`; the value is either an\n * array (sugar for `{ only: [...] }`) or a full `ToolkitOptions` record.\n */\nfunction parseToolEntry(\n entry: FrontmatterToolEntry,\n filePath: string,\n agentName: string,\n): ParsedToolEntry {\n if (typeof entry === \"string\") {\n if (entry.startsWith(PLUGIN_PREFIX)) {\n const pluginName = entry.slice(PLUGIN_PREFIX.length);\n if (pluginName.length === 0) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has an empty plugin name in 'plugin:'.`,\n );\n }\n return { kind: \"plugin\", pluginName, opts: undefined };\n }\n if (entry.length === 0) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has an empty string in 'tools:'.`,\n );\n }\n return { kind: \"ambient\", toolName: entry };\n }\n if (typeof entry !== \"object\" || entry === null) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has invalid 'tools:' entry: ${JSON.stringify(entry)}`,\n );\n }\n const keys = Object.keys(entry);\n if (keys.length !== 1) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) 'tools:' object entry must have exactly one key, got: ${keys.join(\", \")}`,\n );\n }\n const key = keys[0];\n // Bare `- plugin:` (no name after the colon) parses as a mapping with the\n // key `\"plugin\"`. Catch that as a friendly error rather than dumping it\n // through the generic \"expected key 'plugin:NAME'\" branch.\n if (key === \"plugin\") {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has an empty plugin name in 'plugin:'.`,\n );\n }\n if (!key.startsWith(PLUGIN_PREFIX)) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) 'tools:' object entries are reserved for plugin references; expected key 'plugin:NAME', got '${key}'. ` +\n \"Use a bare string for ambient tools (e.g. `- get_weather`).\",\n );\n }\n const pluginName = key.slice(PLUGIN_PREFIX.length);\n if (pluginName.length === 0) {\n throw new Error(\n `Agent '${agentName}' (${filePath}) has an empty plugin name in 'plugin:'.`,\n );\n }\n const value = entry[key];\n if (Array.isArray(value)) {\n return { kind: \"plugin\", pluginName, opts: { only: value } };\n }\n if (typeof value === \"object\" && value !== null) {\n return {\n kind: \"plugin\",\n pluginName,\n opts: value as ToolkitOptions,\n };\n }\n throw new Error(\n `Agent '${agentName}' (${filePath}) 'plugin:${pluginName}' options must be an array of tool names or a ToolkitOptions object.`,\n );\n}\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { AgentAdapter, AgentToolDefinition, ThreadStore, ToolAnnotations } from "../../shared/src/agent.js";
2
2
  import { BasePluginConfig } from "../../shared/src/plugin.js";
3
3
  import "../../shared/src/index.js";
4
+ import { GenerationParams } from "../../agents/databricks.js";
4
5
  import { McpHostPolicyConfig } from "../../connectors/mcp/host-policy.js";
5
6
  import "../../connectors/mcp/index.js";
6
7
  import { FunctionTool } from "./tools/function-tool.js";
@@ -148,6 +149,14 @@ interface AgentDefinition {
148
149
  baseSystemPrompt?: BaseSystemPromptOption;
149
150
  maxSteps?: number;
150
151
  maxTokens?: number;
152
+ /**
153
+ * Optional generation parameters (`temperature`, `top_p`, `stop`,
154
+ * `frequency_penalty`, `presence_penalty`) forwarded to the OpenAI-compatible
155
+ * serving request body. Only set keys are sent. Applied only when AppKit
156
+ * builds the adapter itself (string or omitted `model`); when you pass a
157
+ * pre-built `AgentAdapter`, configure generation params on it directly.
158
+ */
159
+ generationParams?: GenerationParams;
151
160
  /**
152
161
  * When true, the thread used for a chat request against this agent is
153
162
  * deleted from `ThreadStore` after the stream completes (success or
@@ -286,6 +295,8 @@ interface RegisteredAgent {
286
295
  baseSystemPrompt?: BaseSystemPromptOption;
287
296
  maxSteps?: number;
288
297
  maxTokens?: number;
298
+ /** Mirrors `AgentDefinition.generationParams`. */
299
+ generationParams?: GenerationParams;
289
300
  /** Mirrors `AgentDefinition.ephemeral` — skip thread persistence. */
290
301
  ephemeral?: boolean;
291
302
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/core/agent/types.ts"],"mappings":";;;;;;;;;;;;;;AAiBA;UAAiB,YAAA;EAAA,SACN,YAAA;EACT,UAAA;EACA,SAAA;EACA,GAAA,EAAK,mBAAA;EACL,WAAA,GAAc,eAAA;EADd;;;;;;EAQA,eAAA;AAAA;;;;;;KAQU,SAAA,GAAY,YAAA,GAAe,UAAA,GAAa,YAAA;AAAA,UAEnC,cAAA;EAFO;EAItB,MAAA;EAJkD;EAMlD,IAAA;EAN8D;EAQ9D,MAAA;EAN6B;EAQ7B,MAAA,GAAS,MAAA;AAAA;;;;;;;;UAUM,qBAAA;EACf,OAAA,CAAQ,IAAA,GAAO,cAAA,GAAiB,MAAA,SAAe,YAAA;AAAA;;;;;;;;;;;;;AA2BjD;;;;;AAKA;;;;;;;KALY,OAAA,GAAU,MAAA,SAAe,qBAAA;;AAWrC;;UANiB,aAAA;EACf,SAAA;EACA,WAAA;EACA,SAAA;AAAA;AAAA,KAGU,sBAAA,sBAGN,GAAA,EAAK,aAAA;;;AAiBX;;KAXY,UAAA,GAAa,MAAA,SAAe,SAAA;;;;;;;AAaxC;;;KAFY,YAAA,IAAgB,OAAA,EAAS,OAAA,KAAY,UAAA;AAAA,UAEhC,eAAA;EA2BQ;;;;;;;;;;;;;;;;;;EARvB,IAAA;EAsBwB;EApBxB,YAAA;EAsBmB;;;;;EAhBnB,KAAA,GAAQ,YAAA,GAAe,OAAA,CAAQ,YAAA;EAyChB;;;;;AAOjB;;;;;;EApCE,KAAA,GAAQ,UAAA,GAAa,YAAA;EA4CS;EA1C9B,MAAA,GAAS,MAAA,SAAe,eAAA;EA4ChB;EA1CR,gBAAA,GAAmB,sBAAA;EACnB,QAAA;EACA,SAAA;EAqDM;;;;;;;;EA5CN,SAAA;AAAA;;;;;;;;;;;;UAce,sBAAA;EAuBI;EArBnB,IAAA;EA4BM;EA1BN,IAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,gBAAA;EAmD1C;EAjDA,GAAA;EA6DE;EA3DF,MAAA,GAAS,MAAA,SAAe,eAAA;EA8EtB;EA5EF,YAAA;EA4EmB;EA1EnB,YAAA,GAAe,YAAA,GAAe,OAAA,CAAQ,YAAA;EA+EX;EA7E3B,KAAA,GAAQ,MAAA,SAAe,SAAA;EAkFd;EAhFT,gBAAA,aAA6B,sBAAA;EAqFpB;EAnFT,WAAA,GAAc,WAAA;EA6FL;EA3FT,gBAAA,GAAmB,sBAAA;EA2FS;;;;;;EApF5B,GAAA,GAAM,mBAAA;EAyEF;;;;;;;;;EA/DJ,QAAA;IA0EI;;;;AAGN;IAvEI,qBAAA,YAuE4B;IArE5B,SAAA;EAAA;EAyES;;;;;;;;EA/DX,MAAA;IA+DW;;;;;IAzDT,2BAAA;IA8DF;;;AAOF;;IA/DI,YAAA;IA+DiE;;;;;IAzDjE,gBAAA;;;;;;;;;;;;;IAaA,iBAAA;EAAA;AAAA;;KAKQ,iBAAA;EAEN,MAAA;EACA,UAAA;EACA,SAAA;EACA,GAAA,EAAK,mBAAA;AAAA;EAGL,MAAA;EACA,YAAA,EAAc,YAAA;EACd,GAAA,EAAK,mBAAA;AAAA;EAGL,MAAA;EACA,WAAA;EACA,GAAA,EAAK,mBAAA;AAAA;EAGL,MAAA;EACA,SAAA;EACA,GAAA,EAAK,mBAAA;AAAA;AAAA,UAGM,eAAA;EACf,IAAA;EACA,YAAA;EACA,OAAA,EAAS,YAAA;EACT,SAAA,EAAW,GAAA,SAAY,iBAAA;EACvB,gBAAA,GAAmB,sBAAA;EACnB,QAAA;EACA,SAAA;;EAEA,SAAA;AAAA;;;;;iBAOc,cAAA,CAAe,KAAA,YAAiB,KAAA,IAAS,YAAA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/core/agent/types.ts"],"mappings":";;;;;;;;;;;;;;;AAkBA;UAAiB,YAAA;EAAA,SACN,YAAA;EACT,UAAA;EACA,SAAA;EACA,GAAA,EAAK,mBAAA;EACL,WAAA,GAAc,eAAA;EADd;;;;;;EAQA,eAAA;AAAA;;;;;;KAQU,SAAA,GAAY,YAAA,GAAe,UAAA,GAAa,YAAA;AAAA,UAEnC,cAAA;EAFO;EAItB,MAAA;EAJkD;EAMlD,IAAA;EAN8D;EAQ9D,MAAA;EAN6B;EAQ7B,MAAA,GAAS,MAAA;AAAA;;;;;;;;UAUM,qBAAA;EACf,OAAA,CAAQ,IAAA,GAAO,cAAA,GAAiB,MAAA,SAAe,YAAA;AAAA;;;;;;;;;;;;;AA2BjD;;;;;AAKA;;;;;;;KALY,OAAA,GAAU,MAAA,SAAe,qBAAA;;AAWrC;;UANiB,aAAA;EACf,SAAA;EACA,WAAA;EACA,SAAA;AAAA;AAAA,KAGU,sBAAA,sBAGN,GAAA,EAAK,aAAA;;;AAiBX;;KAXY,UAAA,GAAa,MAAA,SAAe,SAAA;;;;;;;AAaxC;;;KAFY,YAAA,IAAgB,OAAA,EAAS,OAAA,KAAY,UAAA;AAAA,UAEhC,eAAA;EA2BQ;;;;;;;;;;;;;;;;;;EARvB,IAAA;EAsBS;EApBT,YAAA;EAsBA;;;;;EAhBA,KAAA,GAAQ,YAAA,GAAe,OAAA,CAAQ,YAAA;EAmC/B;;;AAcF;;;;;AAOA;;;EA5CE,KAAA,GAAQ,UAAA,GAAa,YAAA;EAgDZ;EA9CT,MAAA,GAAS,MAAA,SAAe,eAAA;EAkDc;EAhDtC,gBAAA,GAAmB,sBAAA;EACnB,QAAA;EACA,SAAA;EAkD6B;;;;;;;EA1C7B,gBAAA,GAAmB,gBAAA;EAgCnB;;;;;;;;EAvBA,SAAA;AAAA;;;;;;;;;;;;UAce,sBAAA;EAgDb;EA9CF,IAAA;EA8DE;EA5DF,IAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,gBAAA;EAkFvB;EAhFnB,GAAA;EAqFU;EAnFV,MAAA,GAAS,MAAA,SAAe,eAAA;;EAExB,YAAA;EA0FkB;EAxFlB,YAAA,GAAe,YAAA,GAAe,OAAA,CAAQ,YAAA;EA8F7B;EA5FT,KAAA,GAAQ,MAAA,SAAe,SAAA;EAiGK;EA/F5B,gBAAA,aAA6B,sBAAA;EA6EzB;EA3EJ,WAAA,GAAc,WAAA;EA6EV;EA3EJ,gBAAA,GAAmB,sBAAA;EA4EV;;;;;;EArET,GAAA,GAAM,mBAAA;EA8EF;;;;;;;;;EApEJ,QAAA;IA6E8B;;;;;IAvE5B,qBAAA,YAgFiB;IA9EjB,SAAA;EAAA;EAsEF;;;;;;;;EA5DA,MAAA;IAiEA;;;;;IA3DE,2BAAA;IAgEO;AAOX;;;;IAjEI,YAAA;IAiE4C;;;;;IA3D5C,gBAAA;;;;;;;;;;;;;IAaA,iBAAA;EAAA;AAAA;;KAKQ,iBAAA;EAEN,MAAA;EACA,UAAA;EACA,SAAA;EACA,GAAA,EAAK,mBAAA;AAAA;EAGL,MAAA;EACA,YAAA,EAAc,YAAA;EACd,GAAA,EAAK,mBAAA;AAAA;EAGL,MAAA;EACA,WAAA;EACA,GAAA,EAAK,mBAAA;AAAA;EAGL,MAAA;EACA,SAAA;EACA,GAAA,EAAK,mBAAA;AAAA;AAAA,UAGM,eAAA;EACf,IAAA;EACA,YAAA;EACA,OAAA,EAAS,YAAA;EACT,SAAA,EAAW,GAAA,SAAY,iBAAA;EACvB,gBAAA,GAAmB,sBAAA;EACnB,QAAA;EACA,SAAA;;EAEA,gBAAA,GAAmB,gBAAA;;EAEnB,SAAA;AAAA;;;;;iBAOc,cAAA,CAAe,KAAA,YAAiB,KAAA,IAAS,YAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../src/core/agent/types.ts"],"sourcesContent":["import type {\n AgentAdapter,\n AgentToolDefinition,\n BasePluginConfig,\n ThreadStore,\n ToolAnnotations,\n} from \"shared\";\nimport type { McpHostPolicyConfig } from \"../../connectors/mcp\";\nimport type { FunctionTool } from \"./tools/function-tool\";\nimport type { HostedTool } from \"./tools/hosted-tools\";\n\n/**\n * A tool reference produced by a plugin's `.toolkit()` call. The agents plugin\n * recognizes the `__toolkitRef` brand and dispatches tool invocations through\n * `PluginContext.executeTool(req, pluginName, localName, ...)`, preserving\n * OBO (asUser) and telemetry spans.\n */\nexport interface ToolkitEntry {\n readonly __toolkitRef: true;\n pluginName: string;\n localName: string;\n def: AgentToolDefinition;\n annotations?: ToolAnnotations;\n /**\n * Whether this tool is eligible for `autoInheritTools` spreading. Mirrors\n * {@link ToolEntry.autoInheritable} from the source registry so the agents\n * plugin can filter auto-inherited tools without re-walking the provider's\n * internal registry.\n */\n autoInheritable?: boolean;\n}\n\n/**\n * Any tool an agent can invoke: inline function tools (`tool()`), hosted MCP\n * tools (`mcpServer()` / raw hosted), or toolkit references from plugins\n * (`analytics().toolkit()`).\n */\nexport type AgentTool = FunctionTool | HostedTool | ToolkitEntry;\n\nexport interface ToolkitOptions {\n /** Key prefix to prepend to each tool's local name. Defaults to `${pluginName}.`. */\n prefix?: string;\n /** Only include tools whose local name matches one of these. */\n only?: string[];\n /** Exclude tools whose local name matches one of these. */\n except?: string[];\n /** Remap specific local names to different keys (applied after prefix). */\n rename?: Record<string, string>;\n}\n\n/**\n * Minimum shape every entry in the {@link Plugins} map must expose. Core\n * plugins (analytics, files, genie, lakebase) implement this directly via\n * their `.toolkit()` method. The agents plugin and standalone `runAgent`\n * synthesize this shape for any registered plugin that doesn't implement\n * `.toolkit()` directly (falling back to `getAgentTools()` walking).\n */\nexport interface PluginToolkitProvider {\n toolkit(opts?: ToolkitOptions): Record<string, ToolkitEntry>;\n}\n\n/**\n * Plugin map passed to the function form of {@link AgentDefinition.tools}.\n * Each entry exposes a `.toolkit(opts?)` method that returns a record of\n * {@link ToolkitEntry} markers ready to be spread into a tool record.\n *\n * AppKit does not statically know which plugins the surrounding\n * `createApp` will register, so this is a plain string-keyed record.\n * Refer to plugins by the name used in `createApp({ plugins: [...] })`;\n * unknown names resolve to `undefined` at runtime.\n *\n * @example\n * ```ts\n * const support = createAgent({\n * instructions: \"...\",\n * tools(plugins) {\n * return {\n * get_weather: tool({ ... }),\n * ...plugins.analytics.toolkit(),\n * ...plugins.files.toolkit({ only: [\"uploads.read\"] }),\n * };\n * },\n * });\n * ```\n */\nexport type Plugins = Record<string, PluginToolkitProvider>;\n\n/**\n * Context passed to `baseSystemPrompt` callbacks.\n */\nexport interface PromptContext {\n agentName: string;\n pluginNames: string[];\n toolNames: string[];\n}\n\nexport type BaseSystemPromptOption =\n | false\n | string\n | ((ctx: PromptContext) => string);\n\n/**\n * Per-agent tool record. String keys map to inline tools, toolkit entries,\n * hosted tools, etc.\n */\nexport type AgentTools = Record<string, AgentTool>;\n\n/**\n * Function form of `AgentDefinition.tools`. Receives the typed\n * {@link Plugins} map and returns a tool record. Invoked exactly once at\n * setup (or once per `runAgent` call in standalone mode); the result is\n * cached as the agent's resolved tool record.\n *\n * Use the function form when an agent needs tools from registered plugins.\n * The bare object form is fine when an agent only uses inline tools.\n */\nexport type AgentToolsFn = (plugins: Plugins) => AgentTools;\n\nexport interface AgentDefinition {\n /**\n * Stable identifier for the agent. **Optional and informational** —\n * when the definition is registered via `agents: { foo: def }` (code) or\n * lives at `config/agents/<id>/agent.md` (markdown), the **registry key\n * always wins** and `name` is ignored. The agent will be reachable as\n * `foo` (or `<id>`) regardless of what this field contains.\n *\n * Set `name` when:\n * - Running standalone via `runAgent({ agent: def })`, where there is\n * no enclosing key. The runtime uses it for the agent's slot in\n * error messages and OTel spans.\n * - Building a definition that may be passed to either form and you\n * want a consistent fallback label.\n *\n * Setting `name` to a value that differs from the registry key is\n * harmless but confusing — prefer keeping them aligned or omitting `name`\n * entirely.\n */\n name?: string;\n /** System prompt body. For markdown-loaded agents this is the file body. */\n instructions: string;\n /**\n * Model adapter (or endpoint-name string sugar for\n * `DatabricksAdapter.fromServingEndpoint({ endpointName })`). Optional —\n * falls back to the plugin's `defaultModel`.\n */\n model?: AgentAdapter | Promise<AgentAdapter> | string;\n /**\n * Per-agent tool record. Key is the LLM-visible tool-call name.\n *\n * Accepts either a plain record (for agents that only use inline tools)\n * or a function `(plugins) => Record<string, AgentTool>` that receives\n * the typed {@link Plugins} map and returns a tool record (for agents\n * that pull tools from registered plugins).\n *\n * The function is invoked once at agent setup; the result is cached.\n * Don't put per-request logic in there.\n */\n tools?: AgentTools | AgentToolsFn;\n /** Sub-agents, exposed as `agent-<key>` tools on this agent. */\n agents?: Record<string, AgentDefinition>;\n /** Override the plugin's baseSystemPrompt for this agent only. */\n baseSystemPrompt?: BaseSystemPromptOption;\n maxSteps?: number;\n maxTokens?: number;\n /**\n * When true, the thread used for a chat request against this agent is\n * deleted from `ThreadStore` after the stream completes (success or\n * failure). Use for stateless one-shot agents — e.g. autocomplete, where\n * each request is independent and retaining history would both poison\n * future calls and accumulate unbounded state in the default\n * `InMemoryThreadStore`. Defaults to `false`.\n */\n ephemeral?: boolean;\n}\n\n/**\n * Auto-inherit configuration. When enabled for a given agent origin, agents\n * with no explicit `tools:` declaration receive every registered ToolProvider\n * plugin tool whose author marked `autoInheritable: true`. Tools without that\n * flag — destructive, state-mutating, or privilege-sensitive — never spread\n * automatically and must be wired via `tools:` (object or function form in\n * code, `plugin:NAME` entries in markdown frontmatter).\n *\n * Defaults are `false` for both origins (safe-by-default): developers must\n * consciously opt an origin in to any auto-inherit behaviour.\n */\nexport interface AutoInheritToolsConfig {\n /** Default for agents loaded from markdown files. Default: `false`. */\n file?: boolean;\n /** Default for code-defined agents (via `agents: { foo: createAgent(...) }`). Default: `false`. */\n code?: boolean;\n}\n\nexport interface AgentsPluginConfig extends BasePluginConfig {\n /** Directory of agent packages (`<id>/agent.md` each). Default `./config/agents`. Set to `false` to disable. */\n dir?: string | false;\n /** Code-defined agents, merged with file-loaded ones (code wins on key collision). */\n agents?: Record<string, AgentDefinition>;\n /** Agent used when clients don't specify one. Defaults to the first-registered agent or the file with `default: true` frontmatter. */\n defaultAgent?: string;\n /** Default model for agents that don't specify their own (in code or frontmatter). */\n defaultModel?: AgentAdapter | Promise<AgentAdapter> | string;\n /** Ambient tool library. Keys may be referenced by markdown frontmatter via `tools: [key1, key2]`. */\n tools?: Record<string, AgentTool>;\n /** Whether to auto-inherit every ToolProvider plugin's toolkit. Accepts a boolean shorthand. */\n autoInheritTools?: boolean | AutoInheritToolsConfig;\n /** Persistent thread store. Default: in-memory. */\n threadStore?: ThreadStore;\n /** Customize or disable the AppKit base system prompt. */\n baseSystemPrompt?: BaseSystemPromptOption;\n /**\n * MCP server host policy. By default only same-origin Databricks workspace\n * URLs may be used as MCP endpoints; custom hosts must be explicitly\n * allowlisted here. Workspace credentials (SP / OBO) are never forwarded\n * to non-workspace hosts.\n */\n mcp?: McpHostPolicyConfig;\n /**\n * Human-in-the-loop approval gate for mutating tool calls. When enabled\n * (the default), the agents plugin emits an `appkit.approval_pending` SSE\n * event before executing any tool whose annotation flags it as mutating —\n * `effect: \"write\" | \"update\" | \"destructive\"` (preferred) or the legacy\n * `destructive: true` boolean — and waits for a `POST /chat/approve`\n * decision from the same user who initiated the stream. A missing decision\n * after `timeoutMs` auto-denies the call.\n */\n approval?: {\n /**\n * Require human approval for tools that mutate state. Triggered by\n * `effect: \"write\" | \"update\" | \"destructive\"` (preferred) or the legacy\n * `destructive: true` boolean. Default: `true`.\n */\n requireForDestructive?: boolean;\n /** Milliseconds to wait before auto-denying. Default: 60_000. */\n timeoutMs?: number;\n };\n /**\n * Runtime resource limits applied during agent execution. Defaults are\n * tuned to protect a single-instance deployment from a misbehaving user or\n * a runaway prompt injection; tighten or relax as appropriate for the\n * deployment's scale and trust model. Request-body caps (chat message\n * size, invocations input size / length) are enforced statically by the\n * Zod schemas and are not configurable here.\n */\n limits?: {\n /**\n * Max concurrent chat streams a single user may have open. Subsequent\n * `POST /chat` requests from that user while at-limit are rejected with\n * HTTP 429. Default: `5`.\n */\n maxConcurrentStreamsPerUser?: number;\n /**\n * Max tool invocations per agent run (across the full tool-call graph,\n * including sub-agent invocations). A run that exceeds the budget is\n * aborted with a terminal error event. Default: `50`.\n */\n maxToolCalls?: number;\n /**\n * Max sub-agent recursion depth. Protects against a prompt-injected\n * agent that delegates to a sub-agent which in turn delegates back to\n * itself (directly or transitively). Default: `3`.\n */\n maxSubAgentDepth?: number;\n /**\n * Per-call timeout for tools dispatched through `PluginContext`\n * (toolkit-routed tools — analytics SQL warehouse queries, Genie\n * messages, Lakebase queries). Independent of `maxToolCalls`: the\n * budget caps how many tools fire per run, this caps how long any\n * single tool call may run. The signal handed to plugin tool\n * implementations combines this timeout with the parent stream's\n * abort signal via `AbortSignal.any`. Function and MCP tools have\n * their own timeouts in their respective adapters and ignore this\n * setting. Default: `300_000` (5 minutes) — generous enough for cold\n * SQL Warehouse round-trips and long Genie conversations.\n */\n toolCallTimeoutMs?: number;\n };\n}\n\n/** Internal tool-index entry after a tool record has been resolved to a dispatchable form. */\nexport type ResolvedToolEntry =\n | {\n source: \"toolkit\";\n pluginName: string;\n localName: string;\n def: AgentToolDefinition;\n }\n | {\n source: \"function\";\n functionTool: FunctionTool;\n def: AgentToolDefinition;\n }\n | {\n source: \"mcp\";\n mcpToolName: string;\n def: AgentToolDefinition;\n }\n | {\n source: \"subagent\";\n agentName: string;\n def: AgentToolDefinition;\n };\n\nexport interface RegisteredAgent {\n name: string;\n instructions: string;\n adapter: AgentAdapter;\n toolIndex: Map<string, ResolvedToolEntry>;\n baseSystemPrompt?: BaseSystemPromptOption;\n maxSteps?: number;\n maxTokens?: number;\n /** Mirrors `AgentDefinition.ephemeral` — skip thread persistence. */\n ephemeral?: boolean;\n}\n\n/**\n * Type guard for `ToolkitEntry` — used by the agents plugin to differentiate\n * toolkit references from inline tools in a mixed `tools` record.\n */\nexport function isToolkitEntry(value: unknown): value is ToolkitEntry {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { __toolkitRef?: unknown }).__toolkitRef === true\n );\n}\n"],"mappings":";;;;;AA+TA,SAAgB,eAAe,OAAuC;AACpE,QACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../src/core/agent/types.ts"],"sourcesContent":["import type {\n AgentAdapter,\n AgentToolDefinition,\n BasePluginConfig,\n ThreadStore,\n ToolAnnotations,\n} from \"shared\";\nimport type { GenerationParams } from \"../../agents/databricks\";\nimport type { McpHostPolicyConfig } from \"../../connectors/mcp\";\nimport type { FunctionTool } from \"./tools/function-tool\";\nimport type { HostedTool } from \"./tools/hosted-tools\";\n\n/**\n * A tool reference produced by a plugin's `.toolkit()` call. The agents plugin\n * recognizes the `__toolkitRef` brand and dispatches tool invocations through\n * `PluginContext.executeTool(req, pluginName, localName, ...)`, preserving\n * OBO (asUser) and telemetry spans.\n */\nexport interface ToolkitEntry {\n readonly __toolkitRef: true;\n pluginName: string;\n localName: string;\n def: AgentToolDefinition;\n annotations?: ToolAnnotations;\n /**\n * Whether this tool is eligible for `autoInheritTools` spreading. Mirrors\n * {@link ToolEntry.autoInheritable} from the source registry so the agents\n * plugin can filter auto-inherited tools without re-walking the provider's\n * internal registry.\n */\n autoInheritable?: boolean;\n}\n\n/**\n * Any tool an agent can invoke: inline function tools (`tool()`), hosted MCP\n * tools (`mcpServer()` / raw hosted), or toolkit references from plugins\n * (`analytics().toolkit()`).\n */\nexport type AgentTool = FunctionTool | HostedTool | ToolkitEntry;\n\nexport interface ToolkitOptions {\n /** Key prefix to prepend to each tool's local name. Defaults to `${pluginName}.`. */\n prefix?: string;\n /** Only include tools whose local name matches one of these. */\n only?: string[];\n /** Exclude tools whose local name matches one of these. */\n except?: string[];\n /** Remap specific local names to different keys (applied after prefix). */\n rename?: Record<string, string>;\n}\n\n/**\n * Minimum shape every entry in the {@link Plugins} map must expose. Core\n * plugins (analytics, files, genie, lakebase) implement this directly via\n * their `.toolkit()` method. The agents plugin and standalone `runAgent`\n * synthesize this shape for any registered plugin that doesn't implement\n * `.toolkit()` directly (falling back to `getAgentTools()` walking).\n */\nexport interface PluginToolkitProvider {\n toolkit(opts?: ToolkitOptions): Record<string, ToolkitEntry>;\n}\n\n/**\n * Plugin map passed to the function form of {@link AgentDefinition.tools}.\n * Each entry exposes a `.toolkit(opts?)` method that returns a record of\n * {@link ToolkitEntry} markers ready to be spread into a tool record.\n *\n * AppKit does not statically know which plugins the surrounding\n * `createApp` will register, so this is a plain string-keyed record.\n * Refer to plugins by the name used in `createApp({ plugins: [...] })`;\n * unknown names resolve to `undefined` at runtime.\n *\n * @example\n * ```ts\n * const support = createAgent({\n * instructions: \"...\",\n * tools(plugins) {\n * return {\n * get_weather: tool({ ... }),\n * ...plugins.analytics.toolkit(),\n * ...plugins.files.toolkit({ only: [\"uploads.read\"] }),\n * };\n * },\n * });\n * ```\n */\nexport type Plugins = Record<string, PluginToolkitProvider>;\n\n/**\n * Context passed to `baseSystemPrompt` callbacks.\n */\nexport interface PromptContext {\n agentName: string;\n pluginNames: string[];\n toolNames: string[];\n}\n\nexport type BaseSystemPromptOption =\n | false\n | string\n | ((ctx: PromptContext) => string);\n\n/**\n * Per-agent tool record. String keys map to inline tools, toolkit entries,\n * hosted tools, etc.\n */\nexport type AgentTools = Record<string, AgentTool>;\n\n/**\n * Function form of `AgentDefinition.tools`. Receives the typed\n * {@link Plugins} map and returns a tool record. Invoked exactly once at\n * setup (or once per `runAgent` call in standalone mode); the result is\n * cached as the agent's resolved tool record.\n *\n * Use the function form when an agent needs tools from registered plugins.\n * The bare object form is fine when an agent only uses inline tools.\n */\nexport type AgentToolsFn = (plugins: Plugins) => AgentTools;\n\nexport interface AgentDefinition {\n /**\n * Stable identifier for the agent. **Optional and informational** —\n * when the definition is registered via `agents: { foo: def }` (code) or\n * lives at `config/agents/<id>/agent.md` (markdown), the **registry key\n * always wins** and `name` is ignored. The agent will be reachable as\n * `foo` (or `<id>`) regardless of what this field contains.\n *\n * Set `name` when:\n * - Running standalone via `runAgent({ agent: def })`, where there is\n * no enclosing key. The runtime uses it for the agent's slot in\n * error messages and OTel spans.\n * - Building a definition that may be passed to either form and you\n * want a consistent fallback label.\n *\n * Setting `name` to a value that differs from the registry key is\n * harmless but confusing — prefer keeping them aligned or omitting `name`\n * entirely.\n */\n name?: string;\n /** System prompt body. For markdown-loaded agents this is the file body. */\n instructions: string;\n /**\n * Model adapter (or endpoint-name string sugar for\n * `DatabricksAdapter.fromServingEndpoint({ endpointName })`). Optional —\n * falls back to the plugin's `defaultModel`.\n */\n model?: AgentAdapter | Promise<AgentAdapter> | string;\n /**\n * Per-agent tool record. Key is the LLM-visible tool-call name.\n *\n * Accepts either a plain record (for agents that only use inline tools)\n * or a function `(plugins) => Record<string, AgentTool>` that receives\n * the typed {@link Plugins} map and returns a tool record (for agents\n * that pull tools from registered plugins).\n *\n * The function is invoked once at agent setup; the result is cached.\n * Don't put per-request logic in there.\n */\n tools?: AgentTools | AgentToolsFn;\n /** Sub-agents, exposed as `agent-<key>` tools on this agent. */\n agents?: Record<string, AgentDefinition>;\n /** Override the plugin's baseSystemPrompt for this agent only. */\n baseSystemPrompt?: BaseSystemPromptOption;\n maxSteps?: number;\n maxTokens?: number;\n /**\n * Optional generation parameters (`temperature`, `top_p`, `stop`,\n * `frequency_penalty`, `presence_penalty`) forwarded to the OpenAI-compatible\n * serving request body. Only set keys are sent. Applied only when AppKit\n * builds the adapter itself (string or omitted `model`); when you pass a\n * pre-built `AgentAdapter`, configure generation params on it directly.\n */\n generationParams?: GenerationParams;\n /**\n * When true, the thread used for a chat request against this agent is\n * deleted from `ThreadStore` after the stream completes (success or\n * failure). Use for stateless one-shot agents — e.g. autocomplete, where\n * each request is independent and retaining history would both poison\n * future calls and accumulate unbounded state in the default\n * `InMemoryThreadStore`. Defaults to `false`.\n */\n ephemeral?: boolean;\n}\n\n/**\n * Auto-inherit configuration. When enabled for a given agent origin, agents\n * with no explicit `tools:` declaration receive every registered ToolProvider\n * plugin tool whose author marked `autoInheritable: true`. Tools without that\n * flag — destructive, state-mutating, or privilege-sensitive — never spread\n * automatically and must be wired via `tools:` (object or function form in\n * code, `plugin:NAME` entries in markdown frontmatter).\n *\n * Defaults are `false` for both origins (safe-by-default): developers must\n * consciously opt an origin in to any auto-inherit behaviour.\n */\nexport interface AutoInheritToolsConfig {\n /** Default for agents loaded from markdown files. Default: `false`. */\n file?: boolean;\n /** Default for code-defined agents (via `agents: { foo: createAgent(...) }`). Default: `false`. */\n code?: boolean;\n}\n\nexport interface AgentsPluginConfig extends BasePluginConfig {\n /** Directory of agent packages (`<id>/agent.md` each). Default `./config/agents`. Set to `false` to disable. */\n dir?: string | false;\n /** Code-defined agents, merged with file-loaded ones (code wins on key collision). */\n agents?: Record<string, AgentDefinition>;\n /** Agent used when clients don't specify one. Defaults to the first-registered agent or the file with `default: true` frontmatter. */\n defaultAgent?: string;\n /** Default model for agents that don't specify their own (in code or frontmatter). */\n defaultModel?: AgentAdapter | Promise<AgentAdapter> | string;\n /** Ambient tool library. Keys may be referenced by markdown frontmatter via `tools: [key1, key2]`. */\n tools?: Record<string, AgentTool>;\n /** Whether to auto-inherit every ToolProvider plugin's toolkit. Accepts a boolean shorthand. */\n autoInheritTools?: boolean | AutoInheritToolsConfig;\n /** Persistent thread store. Default: in-memory. */\n threadStore?: ThreadStore;\n /** Customize or disable the AppKit base system prompt. */\n baseSystemPrompt?: BaseSystemPromptOption;\n /**\n * MCP server host policy. By default only same-origin Databricks workspace\n * URLs may be used as MCP endpoints; custom hosts must be explicitly\n * allowlisted here. Workspace credentials (SP / OBO) are never forwarded\n * to non-workspace hosts.\n */\n mcp?: McpHostPolicyConfig;\n /**\n * Human-in-the-loop approval gate for mutating tool calls. When enabled\n * (the default), the agents plugin emits an `appkit.approval_pending` SSE\n * event before executing any tool whose annotation flags it as mutating —\n * `effect: \"write\" | \"update\" | \"destructive\"` (preferred) or the legacy\n * `destructive: true` boolean — and waits for a `POST /chat/approve`\n * decision from the same user who initiated the stream. A missing decision\n * after `timeoutMs` auto-denies the call.\n */\n approval?: {\n /**\n * Require human approval for tools that mutate state. Triggered by\n * `effect: \"write\" | \"update\" | \"destructive\"` (preferred) or the legacy\n * `destructive: true` boolean. Default: `true`.\n */\n requireForDestructive?: boolean;\n /** Milliseconds to wait before auto-denying. Default: 60_000. */\n timeoutMs?: number;\n };\n /**\n * Runtime resource limits applied during agent execution. Defaults are\n * tuned to protect a single-instance deployment from a misbehaving user or\n * a runaway prompt injection; tighten or relax as appropriate for the\n * deployment's scale and trust model. Request-body caps (chat message\n * size, invocations input size / length) are enforced statically by the\n * Zod schemas and are not configurable here.\n */\n limits?: {\n /**\n * Max concurrent chat streams a single user may have open. Subsequent\n * `POST /chat` requests from that user while at-limit are rejected with\n * HTTP 429. Default: `5`.\n */\n maxConcurrentStreamsPerUser?: number;\n /**\n * Max tool invocations per agent run (across the full tool-call graph,\n * including sub-agent invocations). A run that exceeds the budget is\n * aborted with a terminal error event. Default: `50`.\n */\n maxToolCalls?: number;\n /**\n * Max sub-agent recursion depth. Protects against a prompt-injected\n * agent that delegates to a sub-agent which in turn delegates back to\n * itself (directly or transitively). Default: `3`.\n */\n maxSubAgentDepth?: number;\n /**\n * Per-call timeout for tools dispatched through `PluginContext`\n * (toolkit-routed tools — analytics SQL warehouse queries, Genie\n * messages, Lakebase queries). Independent of `maxToolCalls`: the\n * budget caps how many tools fire per run, this caps how long any\n * single tool call may run. The signal handed to plugin tool\n * implementations combines this timeout with the parent stream's\n * abort signal via `AbortSignal.any`. Function and MCP tools have\n * their own timeouts in their respective adapters and ignore this\n * setting. Default: `300_000` (5 minutes) — generous enough for cold\n * SQL Warehouse round-trips and long Genie conversations.\n */\n toolCallTimeoutMs?: number;\n };\n}\n\n/** Internal tool-index entry after a tool record has been resolved to a dispatchable form. */\nexport type ResolvedToolEntry =\n | {\n source: \"toolkit\";\n pluginName: string;\n localName: string;\n def: AgentToolDefinition;\n }\n | {\n source: \"function\";\n functionTool: FunctionTool;\n def: AgentToolDefinition;\n }\n | {\n source: \"mcp\";\n mcpToolName: string;\n def: AgentToolDefinition;\n }\n | {\n source: \"subagent\";\n agentName: string;\n def: AgentToolDefinition;\n };\n\nexport interface RegisteredAgent {\n name: string;\n instructions: string;\n adapter: AgentAdapter;\n toolIndex: Map<string, ResolvedToolEntry>;\n baseSystemPrompt?: BaseSystemPromptOption;\n maxSteps?: number;\n maxTokens?: number;\n /** Mirrors `AgentDefinition.generationParams`. */\n generationParams?: GenerationParams;\n /** Mirrors `AgentDefinition.ephemeral` — skip thread persistence. */\n ephemeral?: boolean;\n}\n\n/**\n * Type guard for `ToolkitEntry` — used by the agents plugin to differentiate\n * toolkit references from inline tools in a mixed `tools` record.\n */\nexport function isToolkitEntry(value: unknown): value is ToolkitEntry {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as { __toolkitRef?: unknown }).__toolkitRef === true\n );\n}\n"],"mappings":";;;;;AA0UA,SAAgB,eAAe,OAAuC;AACpE,QACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAqC,iBAAiB"}
@@ -1 +1 @@
1
- {"version":3,"file":"appkit.d.ts","names":[],"sources":["../../src/core/appkit.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+SsB,SAAA,WACV,UAAA,CAAW,iBAAA,qBAAA,CAErB,MAAA;EACE,OAAA,GAAU,CAAA;EACV,SAAA,GAAY,eAAA;EACZ,KAAA,GAAQ,WAAA;EACR,MAAA,GAAS,eAAA;EACT,cAAA,IAAkB,MAAA,EAAQ,SAAA,CAAU,CAAA,aAAc,OAAA;EAClD,wBAAA;AAAA,IAED,OAAA,CAAQ,SAAA,CAAU,CAAA"}
1
+ {"version":3,"file":"appkit.d.ts","names":[],"sources":["../../src/core/appkit.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsTsB,SAAA,WACV,UAAA,CAAW,iBAAA,qBAAA,CAErB,MAAA;EACE,OAAA,GAAU,CAAA;EACV,SAAA,GAAY,eAAA;EACZ,KAAA,GAAQ,WAAA;EACR,MAAA,GAAS,eAAA;EACT,cAAA,IAAkB,MAAA,EAAQ,SAAA,CAAU,CAAA,aAAc,OAAA;EAClD,wBAAA;AAAA,IAED,OAAA,CAAQ,SAAA,CAAU,CAAA"}
@@ -12,6 +12,7 @@ import { isPlainObject } from "../plugin/plugin.js";
12
12
  import { ResourceType } from "../registry/types.generated.js";
13
13
  import { ResourceRegistry } from "../registry/resource-registry.js";
14
14
  import "../registry/index.js";
15
+ import { LifecycleManager } from "./lifecycle-manager.js";
15
16
  import { PluginContext, isToolProvider } from "./plugin-context.js";
16
17
 
17
18
  //#region src/core/appkit.ts
@@ -117,6 +118,7 @@ var AppKit = class AppKit {
117
118
  if (isInternalTelemetryEnabled(config)) AppKit.bootstrapInternalTelemetry();
118
119
  const serverPlugin = instance.#pluginInstances.server;
119
120
  if (serverPlugin && typeof serverPlugin.start === "function") await serverPlugin.start();
121
+ new LifecycleManager(instance.#context).installSignalHandlers();
120
122
  return handle;
121
123
  }
122
124
  static bootstrapInternalTelemetry() {
@@ -1 +1 @@
1
- {"version":3,"file":"appkit.js","names":["#context","#pluginInstances","#setupPromises","productVersion"],"sources":["../../src/core/appkit.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type {\n BasePlugin,\n CacheConfig,\n InputPluginMap,\n OptionalConfigPluginDef,\n PluginConstructor,\n PluginData,\n PluginMap,\n} from \"shared\";\nimport { version as productVersion } from \"../../package.json\";\nimport { CacheManager } from \"../cache\";\nimport { ServiceContext } from \"../context\";\nimport {\n isInternalTelemetryEnabled,\n TelemetryReporter,\n} from \"../internal-telemetry\";\nimport { createLogger } from \"../logging/logger\";\nimport { isPlainObject } from \"../plugin/plugin\";\nimport { ResourceRegistry, ResourceType } from \"../registry\";\nimport type { TelemetryConfig } from \"../telemetry\";\nimport { TelemetryManager } from \"../telemetry\";\nimport { isToolProvider, PluginContext } from \"./plugin-context\";\n\nconst logger = createLogger(\"appkit\");\n\nexport class AppKit<TPlugins extends InputPluginMap> {\n #pluginInstances: Record<string, BasePlugin> = {};\n #setupPromises: Promise<void>[] = [];\n #context: PluginContext;\n\n private constructor(config: { plugins: TPlugins }) {\n const { plugins, ...globalConfig } = config;\n\n this.#context = new PluginContext();\n\n const pluginEntries = Object.entries(plugins);\n\n const corePlugins = pluginEntries.filter(([_, p]) => {\n return (p?.plugin?.phase ?? \"normal\") === \"core\";\n });\n const normalPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"normal\",\n );\n const deferredPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"deferred\",\n );\n\n for (const [name, pluginData] of corePlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of normalPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of deferredPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n }\n\n private createAndRegisterPlugin<T extends PluginConstructor>(\n config: Omit<{ plugins: TPlugins }, \"plugins\">,\n name: string,\n pluginData: OptionalConfigPluginDef<T>,\n extraData?: Record<string, unknown>,\n ) {\n const { plugin: Plugin, config: pluginConfig } = pluginData;\n const baseConfig = {\n ...config,\n ...Plugin.DEFAULT_CONFIG,\n ...pluginConfig,\n name,\n ...extraData,\n };\n const pluginInstance = new Plugin(baseConfig);\n\n if (typeof pluginInstance.attachContext === \"function\") {\n pluginInstance.attachContext({\n context: this.#context,\n telemetryConfig: baseConfig.telemetry,\n });\n }\n\n this.#pluginInstances[name] = pluginInstance;\n\n this.#context.registerPlugin(name, pluginInstance);\n if (isToolProvider(pluginInstance)) {\n this.#context.registerToolProvider(name, pluginInstance);\n }\n\n this.#setupPromises.push(pluginInstance.setup());\n\n const self = this;\n\n Object.defineProperty(this, name, {\n get() {\n const plugin = self.#pluginInstances[name];\n return self.wrapWithAsUser(plugin);\n },\n enumerable: true,\n });\n }\n\n /**\n * Binds all function properties in an exports object to the given context.\n * Recurses into plain objects to handle nested APIs (e.g., volume APIs).\n */\n private bindExportMethods(\n exports: Record<string, unknown>,\n context: BasePlugin,\n ) {\n for (const key in exports) {\n if (!Object.hasOwn(exports, key)) continue;\n const val = exports[key];\n if (typeof val === \"function\") {\n exports[key] = (val as (...args: unknown[]) => unknown).bind(context);\n } else if (isPlainObject(val)) {\n this.bindExportMethods(val as Record<string, unknown>, context);\n }\n }\n }\n\n /**\n * Wraps a plugin's exports with an `asUser` method that returns\n * a user-scoped version of the exports.\n *\n * When `exports()` returns a callable (function), it is returned as-is\n * since the plugin manages its own `asUser` per-call (e.g. files plugin).\n * When it returns a plain object, the standard `asUser` wrapper is added.\n *\n * The OBO-side wrapping lives inside `Plugin.asUser` — calling\n * `plugin.asUser(req).exports()` returns exports whose functions already\n * run inside the user's AsyncLocalStorage scope. AppKit only adapts the\n * shape; it does not own the user-context concept.\n */\n private wrapWithAsUser<T extends BasePlugin>(plugin: T) {\n // If plugin doesn't implement exports(), return empty object\n const pluginExports = plugin.exports?.() ?? {};\n\n // If exports is a function, the plugin manages its own asUser pattern\n if (typeof pluginExports === \"function\") {\n return pluginExports;\n }\n\n const objExports = pluginExports as Record<string, unknown>;\n this.bindExportMethods(objExports, plugin);\n\n // If plugin doesn't support asUser (no asUser method), return exports as-is\n if (typeof (plugin as any).asUser !== \"function\") {\n return objExports;\n }\n\n return {\n ...objExports,\n /**\n * Execute operations using the user's identity from the request.\n * Returns user-scoped exports where all methods execute with the\n * user's Databricks credentials instead of the service principal.\n */\n asUser: (req: import(\"express\").Request) =>\n (plugin as any).asUser(req).exports() as Record<string, unknown>,\n };\n }\n\n static async _createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n ): Promise<PluginMap<T>> {\n // Initialize core services\n TelemetryManager.initialize(config?.telemetry);\n await CacheManager.getInstance(config?.cache);\n\n const rawPlugins = config.plugins as T;\n\n // Collect manifest resources via registry\n const registry = new ResourceRegistry();\n registry.collectResources(rawPlugins);\n\n // Derive ServiceContext needs from what manifests declared\n const needsWarehouse = registry\n .getRequired()\n .some((r) => r.type === ResourceType.SQL_WAREHOUSE);\n await ServiceContext.initialize(\n { warehouseId: needsWarehouse },\n config?.client,\n );\n\n // Validate env vars\n registry.enforceValidation();\n\n const preparedPlugins = AppKit.preparePlugins(rawPlugins);\n const mergedConfig = {\n plugins: preparedPlugins,\n };\n\n const instance = new AppKit(mergedConfig);\n\n await Promise.all(instance.#setupPromises);\n await instance.#context.emitLifecycle(\"setup:complete\");\n\n const handle = instance as unknown as PluginMap<T>;\n\n if (config.onPluginsReady) {\n logger.debug(\"Running onPluginsReady hook\");\n await config.onPluginsReady(handle);\n logger.debug(\"onPluginsReady hook completed\");\n }\n\n if (isInternalTelemetryEnabled(config)) {\n AppKit.bootstrapInternalTelemetry();\n }\n\n const serverPlugin = instance.#pluginInstances.server;\n if (serverPlugin && typeof (serverPlugin as any).start === \"function\") {\n await (serverPlugin as any).start();\n }\n\n return handle;\n }\n\n private static bootstrapInternalTelemetry(): void {\n const serviceCtx = ServiceContext.get();\n const reporter = TelemetryReporter.initialize({\n workspaceId: serviceCtx.workspaceId,\n client: serviceCtx.client,\n appId: process.env.DATABRICKS_CLIENT_ID || \"\",\n appkitVersion: productVersion,\n });\n reporter.start();\n reporter.sendStartup().catch(() => {});\n }\n\n private static preparePlugins(\n plugins: PluginData<PluginConstructor, unknown, string>[],\n ) {\n const result: InputPluginMap = {};\n for (const currentPlugin of plugins) {\n result[currentPlugin.name] = {\n plugin: currentPlugin.plugin,\n config: currentPlugin.config as Record<string, unknown>,\n };\n }\n return result;\n }\n}\n\n/**\n * Bootstraps AppKit with the provided configuration.\n *\n * Initializes telemetry, cache, and service context, then registers plugins\n * in phase order (core, normal, deferred) and awaits their setup.\n * If a `onPluginsReady` callback is provided it runs after plugin setup but\n * before the server starts, giving you access to the full appkit handle\n * for registering custom routes or performing async setup.\n * The returned object maps each plugin name to its `exports()` API,\n * with an `asUser(req)` method for user-scoped execution.\n *\n * @returns A `PluginMap` keyed by plugin name with typed exports\n *\n * @example Minimal server\n * ```ts\n * import { createApp, server } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server()],\n * });\n * ```\n *\n * @example Server with custom routes via onPluginsReady\n * ```ts\n * import { createApp, server, analytics } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server(), analytics({})],\n * onPluginsReady(appkit) {\n * appkit.server.extend((app) => {\n * app.get(\"/custom\", (_req, res) => res.json({ ok: true }));\n * });\n * },\n * });\n * ```\n */\nexport async function createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n>(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n): Promise<PluginMap<T>> {\n return AppKit._createApp(config);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"appkit.js","names":["#context","#pluginInstances","#setupPromises","productVersion"],"sources":["../../src/core/appkit.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type {\n BasePlugin,\n CacheConfig,\n InputPluginMap,\n OptionalConfigPluginDef,\n PluginConstructor,\n PluginData,\n PluginMap,\n} from \"shared\";\nimport { version as productVersion } from \"../../package.json\";\nimport { CacheManager } from \"../cache\";\nimport { ServiceContext } from \"../context\";\nimport {\n isInternalTelemetryEnabled,\n TelemetryReporter,\n} from \"../internal-telemetry\";\nimport { createLogger } from \"../logging/logger\";\nimport { isPlainObject } from \"../plugin/plugin\";\nimport { ResourceRegistry, ResourceType } from \"../registry\";\nimport type { TelemetryConfig } from \"../telemetry\";\nimport { TelemetryManager } from \"../telemetry\";\nimport { LifecycleManager } from \"./lifecycle-manager\";\nimport { isToolProvider, PluginContext } from \"./plugin-context\";\n\nconst logger = createLogger(\"appkit\");\n\nexport class AppKit<TPlugins extends InputPluginMap> {\n #pluginInstances: Record<string, BasePlugin> = {};\n #setupPromises: Promise<void>[] = [];\n #context: PluginContext;\n\n private constructor(config: { plugins: TPlugins }) {\n const { plugins, ...globalConfig } = config;\n\n this.#context = new PluginContext();\n\n const pluginEntries = Object.entries(plugins);\n\n const corePlugins = pluginEntries.filter(([_, p]) => {\n return (p?.plugin?.phase ?? \"normal\") === \"core\";\n });\n const normalPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"normal\",\n );\n const deferredPlugins = pluginEntries.filter(\n ([_, p]) => (p?.plugin?.phase ?? \"normal\") === \"deferred\",\n );\n\n for (const [name, pluginData] of corePlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of normalPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n\n for (const [name, pluginData] of deferredPlugins) {\n if (pluginData) {\n this.createAndRegisterPlugin(globalConfig, name, pluginData, {\n context: this.#context,\n });\n }\n }\n }\n\n private createAndRegisterPlugin<T extends PluginConstructor>(\n config: Omit<{ plugins: TPlugins }, \"plugins\">,\n name: string,\n pluginData: OptionalConfigPluginDef<T>,\n extraData?: Record<string, unknown>,\n ) {\n const { plugin: Plugin, config: pluginConfig } = pluginData;\n const baseConfig = {\n ...config,\n ...Plugin.DEFAULT_CONFIG,\n ...pluginConfig,\n name,\n ...extraData,\n };\n const pluginInstance = new Plugin(baseConfig);\n\n if (typeof pluginInstance.attachContext === \"function\") {\n pluginInstance.attachContext({\n context: this.#context,\n telemetryConfig: baseConfig.telemetry,\n });\n }\n\n this.#pluginInstances[name] = pluginInstance;\n\n this.#context.registerPlugin(name, pluginInstance);\n if (isToolProvider(pluginInstance)) {\n this.#context.registerToolProvider(name, pluginInstance);\n }\n\n this.#setupPromises.push(pluginInstance.setup());\n\n const self = this;\n\n Object.defineProperty(this, name, {\n get() {\n const plugin = self.#pluginInstances[name];\n return self.wrapWithAsUser(plugin);\n },\n enumerable: true,\n });\n }\n\n /**\n * Binds all function properties in an exports object to the given context.\n * Recurses into plain objects to handle nested APIs (e.g., volume APIs).\n */\n private bindExportMethods(\n exports: Record<string, unknown>,\n context: BasePlugin,\n ) {\n for (const key in exports) {\n if (!Object.hasOwn(exports, key)) continue;\n const val = exports[key];\n if (typeof val === \"function\") {\n exports[key] = (val as (...args: unknown[]) => unknown).bind(context);\n } else if (isPlainObject(val)) {\n this.bindExportMethods(val as Record<string, unknown>, context);\n }\n }\n }\n\n /**\n * Wraps a plugin's exports with an `asUser` method that returns\n * a user-scoped version of the exports.\n *\n * When `exports()` returns a callable (function), it is returned as-is\n * since the plugin manages its own `asUser` per-call (e.g. files plugin).\n * When it returns a plain object, the standard `asUser` wrapper is added.\n *\n * The OBO-side wrapping lives inside `Plugin.asUser` — calling\n * `plugin.asUser(req).exports()` returns exports whose functions already\n * run inside the user's AsyncLocalStorage scope. AppKit only adapts the\n * shape; it does not own the user-context concept.\n */\n private wrapWithAsUser<T extends BasePlugin>(plugin: T) {\n // If plugin doesn't implement exports(), return empty object\n const pluginExports = plugin.exports?.() ?? {};\n\n // If exports is a function, the plugin manages its own asUser pattern\n if (typeof pluginExports === \"function\") {\n return pluginExports;\n }\n\n const objExports = pluginExports as Record<string, unknown>;\n this.bindExportMethods(objExports, plugin);\n\n // If plugin doesn't support asUser (no asUser method), return exports as-is\n if (typeof (plugin as any).asUser !== \"function\") {\n return objExports;\n }\n\n return {\n ...objExports,\n /**\n * Execute operations using the user's identity from the request.\n * Returns user-scoped exports where all methods execute with the\n * user's Databricks credentials instead of the service principal.\n */\n asUser: (req: import(\"express\").Request) =>\n (plugin as any).asUser(req).exports() as Record<string, unknown>,\n };\n }\n\n static async _createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n >(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n ): Promise<PluginMap<T>> {\n // Initialize core services\n TelemetryManager.initialize(config?.telemetry);\n await CacheManager.getInstance(config?.cache);\n\n const rawPlugins = config.plugins as T;\n\n // Collect manifest resources via registry\n const registry = new ResourceRegistry();\n registry.collectResources(rawPlugins);\n\n // Derive ServiceContext needs from what manifests declared\n const needsWarehouse = registry\n .getRequired()\n .some((r) => r.type === ResourceType.SQL_WAREHOUSE);\n await ServiceContext.initialize(\n { warehouseId: needsWarehouse },\n config?.client,\n );\n\n // Validate env vars\n registry.enforceValidation();\n\n const preparedPlugins = AppKit.preparePlugins(rawPlugins);\n const mergedConfig = {\n plugins: preparedPlugins,\n };\n\n const instance = new AppKit(mergedConfig);\n\n await Promise.all(instance.#setupPromises);\n await instance.#context.emitLifecycle(\"setup:complete\");\n\n const handle = instance as unknown as PluginMap<T>;\n\n if (config.onPluginsReady) {\n logger.debug(\"Running onPluginsReady hook\");\n await config.onPluginsReady(handle);\n logger.debug(\"onPluginsReady hook completed\");\n }\n\n if (isInternalTelemetryEnabled(config)) {\n AppKit.bootstrapInternalTelemetry();\n }\n\n const serverPlugin = instance.#pluginInstances.server;\n if (serverPlugin && typeof (serverPlugin as any).start === \"function\") {\n await (serverPlugin as any).start();\n }\n\n // Core owns graceful shutdown: install the signal handlers once every\n // plugin has started. Applies uniformly whether or not a server plugin\n // is present — server-less apps still get their telemetry flushed and\n // plugin shutdown() hooks run.\n new LifecycleManager(instance.#context).installSignalHandlers();\n\n return handle;\n }\n\n private static bootstrapInternalTelemetry(): void {\n const serviceCtx = ServiceContext.get();\n const reporter = TelemetryReporter.initialize({\n workspaceId: serviceCtx.workspaceId,\n client: serviceCtx.client,\n appId: process.env.DATABRICKS_CLIENT_ID || \"\",\n appkitVersion: productVersion,\n });\n reporter.start();\n reporter.sendStartup().catch(() => {});\n }\n\n private static preparePlugins(\n plugins: PluginData<PluginConstructor, unknown, string>[],\n ) {\n const result: InputPluginMap = {};\n for (const currentPlugin of plugins) {\n result[currentPlugin.name] = {\n plugin: currentPlugin.plugin,\n config: currentPlugin.config as Record<string, unknown>,\n };\n }\n return result;\n }\n}\n\n/**\n * Bootstraps AppKit with the provided configuration.\n *\n * Initializes telemetry, cache, and service context, then registers plugins\n * in phase order (core, normal, deferred) and awaits their setup.\n * If a `onPluginsReady` callback is provided it runs after plugin setup but\n * before the server starts, giving you access to the full appkit handle\n * for registering custom routes or performing async setup.\n * The returned object maps each plugin name to its `exports()` API,\n * with an `asUser(req)` method for user-scoped execution.\n *\n * @returns A `PluginMap` keyed by plugin name with typed exports\n *\n * @example Minimal server\n * ```ts\n * import { createApp, server } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server()],\n * });\n * ```\n *\n * @example Server with custom routes via onPluginsReady\n * ```ts\n * import { createApp, server, analytics } from \"@databricks/appkit\";\n *\n * await createApp({\n * plugins: [server(), analytics({})],\n * onPluginsReady(appkit) {\n * appkit.server.extend((app) => {\n * app.get(\"/custom\", (_req, res) => res.json({ ok: true }));\n * });\n * },\n * });\n * ```\n */\nexport async function createApp<\n T extends PluginData<PluginConstructor, unknown, string>[],\n>(\n config: {\n plugins?: T;\n telemetry?: TelemetryConfig;\n cache?: CacheConfig;\n client?: WorkspaceClient;\n onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;\n disableInternalTelemetry?: boolean;\n } = {},\n): Promise<PluginMap<T>> {\n return AppKit._createApp(config);\n}\n"],"mappings":"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"}