@databricks/appkit 0.31.0 → 0.33.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 (218) hide show
  1. package/CLAUDE.md +54 -1
  2. package/NOTICE.md +2 -0
  3. package/dist/agents/databricks.d.ts.map +1 -1
  4. package/dist/agents/databricks.js +8 -3
  5. package/dist/agents/databricks.js.map +1 -1
  6. package/dist/appkit/package.js +1 -1
  7. package/dist/beta.d.ts +16 -1
  8. package/dist/beta.js +14 -1
  9. package/dist/connectors/index.js +3 -0
  10. package/dist/connectors/mcp/client.d.ts +85 -0
  11. package/dist/connectors/mcp/client.d.ts.map +1 -0
  12. package/dist/connectors/mcp/client.js +296 -0
  13. package/dist/connectors/mcp/client.js.map +1 -0
  14. package/dist/connectors/mcp/host-policy.d.ts +51 -0
  15. package/dist/connectors/mcp/host-policy.d.ts.map +1 -0
  16. package/dist/connectors/mcp/host-policy.js +168 -0
  17. package/dist/connectors/mcp/host-policy.js.map +1 -0
  18. package/dist/connectors/mcp/index.d.ts +3 -0
  19. package/dist/connectors/mcp/index.js +4 -0
  20. package/dist/connectors/mcp/types.d.ts +16 -0
  21. package/dist/connectors/mcp/types.d.ts.map +1 -0
  22. package/dist/context/index.js +1 -1
  23. package/dist/core/agent/build-toolkit.d.ts +2 -0
  24. package/dist/core/agent/build-toolkit.js +45 -0
  25. package/dist/core/agent/build-toolkit.js.map +1 -0
  26. package/dist/core/agent/consume-adapter-stream.js +33 -0
  27. package/dist/core/agent/consume-adapter-stream.js.map +1 -0
  28. package/dist/core/agent/create-agent.d.ts +27 -0
  29. package/dist/core/agent/create-agent.d.ts.map +1 -0
  30. package/dist/core/agent/create-agent.js +50 -0
  31. package/dist/core/agent/create-agent.js.map +1 -0
  32. package/dist/core/agent/load-agents.d.ts +72 -0
  33. package/dist/core/agent/load-agents.d.ts.map +1 -0
  34. package/dist/core/agent/load-agents.js +268 -0
  35. package/dist/core/agent/load-agents.js.map +1 -0
  36. package/dist/core/agent/normalize-result.js +39 -0
  37. package/dist/core/agent/normalize-result.js.map +1 -0
  38. package/dist/core/agent/plugins-map.js +44 -0
  39. package/dist/core/agent/plugins-map.js.map +1 -0
  40. package/dist/core/agent/run-agent.d.ts +58 -0
  41. package/dist/core/agent/run-agent.d.ts.map +1 -0
  42. package/dist/core/agent/run-agent.js +257 -0
  43. package/dist/core/agent/run-agent.js.map +1 -0
  44. package/dist/core/agent/system-prompt.js +38 -0
  45. package/dist/core/agent/system-prompt.js.map +1 -0
  46. package/dist/core/agent/toolkit-options.js +28 -0
  47. package/dist/core/agent/toolkit-options.js.map +1 -0
  48. package/dist/core/agent/toolkit-resolver.js +44 -0
  49. package/dist/core/agent/toolkit-resolver.js.map +1 -0
  50. package/dist/core/agent/tools/define-tool.d.ts +66 -0
  51. package/dist/core/agent/tools/define-tool.d.ts.map +1 -0
  52. package/dist/core/agent/tools/define-tool.js +50 -0
  53. package/dist/core/agent/tools/define-tool.js.map +1 -0
  54. package/dist/core/agent/tools/function-tool.d.ts +38 -0
  55. package/dist/core/agent/tools/function-tool.d.ts.map +1 -0
  56. package/dist/core/agent/tools/function-tool.js +22 -0
  57. package/dist/core/agent/tools/function-tool.js.map +1 -0
  58. package/dist/core/agent/tools/hosted-tools.d.ts +47 -0
  59. package/dist/core/agent/tools/hosted-tools.d.ts.map +1 -0
  60. package/dist/core/agent/tools/hosted-tools.js +67 -0
  61. package/dist/core/agent/tools/hosted-tools.js.map +1 -0
  62. package/dist/core/agent/tools/index.d.ts +5 -0
  63. package/dist/core/agent/tools/index.js +7 -0
  64. package/dist/core/agent/tools/json-schema.js +24 -0
  65. package/dist/core/agent/tools/json-schema.js.map +1 -0
  66. package/dist/core/agent/tools/sql-policy.js +256 -0
  67. package/dist/core/agent/tools/sql-policy.js.map +1 -0
  68. package/dist/core/agent/tools/tool.d.ts +63 -0
  69. package/dist/core/agent/tools/tool.d.ts.map +1 -0
  70. package/dist/core/agent/tools/tool.js +42 -0
  71. package/dist/core/agent/tools/tool.js.map +1 -0
  72. package/dist/core/agent/types.d.ts +299 -0
  73. package/dist/core/agent/types.d.ts.map +1 -0
  74. package/dist/core/agent/types.js +12 -0
  75. package/dist/core/agent/types.js.map +1 -0
  76. package/dist/core/appkit.d.ts +1 -0
  77. package/dist/core/appkit.d.ts.map +1 -1
  78. package/dist/core/appkit.js +31 -4
  79. package/dist/core/appkit.js.map +1 -1
  80. package/dist/core/plugin-context.d.ts +133 -0
  81. package/dist/core/plugin-context.d.ts.map +1 -0
  82. package/dist/core/plugin-context.js +220 -0
  83. package/dist/core/plugin-context.js.map +1 -0
  84. package/dist/index.d.ts +11 -11
  85. package/dist/internal-telemetry/appkit-log.js +19 -0
  86. package/dist/internal-telemetry/appkit-log.js.map +1 -0
  87. package/dist/internal-telemetry/config.js +15 -0
  88. package/dist/internal-telemetry/config.js.map +1 -0
  89. package/dist/internal-telemetry/index.js +4 -0
  90. package/dist/internal-telemetry/reporter.js +132 -0
  91. package/dist/internal-telemetry/reporter.js.map +1 -0
  92. package/dist/plugin/plugin.d.ts +18 -3
  93. package/dist/plugin/plugin.d.ts.map +1 -1
  94. package/dist/plugin/plugin.js +26 -2
  95. package/dist/plugin/plugin.js.map +1 -1
  96. package/dist/plugin/to-plugin.d.ts +3 -2
  97. package/dist/plugin/to-plugin.d.ts.map +1 -1
  98. package/dist/plugin/to-plugin.js +7 -4
  99. package/dist/plugin/to-plugin.js.map +1 -1
  100. package/dist/plugins/agents/agents.d.ts +186 -0
  101. package/dist/plugins/agents/agents.d.ts.map +1 -0
  102. package/dist/plugins/agents/agents.js +979 -0
  103. package/dist/plugins/agents/agents.js.map +1 -0
  104. package/dist/plugins/agents/defaults.js +13 -0
  105. package/dist/plugins/agents/defaults.js.map +1 -0
  106. package/dist/plugins/agents/event-channel.js +64 -0
  107. package/dist/plugins/agents/event-channel.js.map +1 -0
  108. package/dist/plugins/agents/event-translator.js +224 -0
  109. package/dist/plugins/agents/event-translator.js.map +1 -0
  110. package/dist/plugins/agents/index.d.ts +4 -0
  111. package/dist/plugins/agents/index.js +6 -0
  112. package/dist/plugins/agents/manifest.js +26 -0
  113. package/dist/plugins/agents/manifest.js.map +1 -0
  114. package/dist/plugins/agents/schemas.js +51 -0
  115. package/dist/plugins/agents/schemas.js.map +1 -0
  116. package/dist/plugins/agents/thread-store.js +58 -0
  117. package/dist/plugins/agents/thread-store.js.map +1 -0
  118. package/dist/plugins/agents/tool-approval-gate.js +75 -0
  119. package/dist/plugins/agents/tool-approval-gate.js.map +1 -0
  120. package/dist/plugins/analytics/analytics.d.ts +15 -1
  121. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  122. package/dist/plugins/analytics/analytics.js +37 -2
  123. package/dist/plugins/analytics/analytics.js.map +1 -1
  124. package/dist/plugins/analytics/index.js +1 -0
  125. package/dist/plugins/analytics/types.js +15 -0
  126. package/dist/plugins/analytics/types.js.map +1 -0
  127. package/dist/plugins/beta-exports.generated.d.ts +2 -0
  128. package/dist/plugins/beta-exports.generated.js +4 -0
  129. package/dist/plugins/files/plugin.d.ts +20 -2
  130. package/dist/plugins/files/plugin.d.ts.map +1 -1
  131. package/dist/plugins/files/plugin.js +120 -2
  132. package/dist/plugins/files/plugin.js.map +1 -1
  133. package/dist/plugins/genie/genie.d.ts +17 -3
  134. package/dist/plugins/genie/genie.d.ts.map +1 -1
  135. package/dist/plugins/genie/genie.js +61 -2
  136. package/dist/plugins/genie/genie.js.map +1 -1
  137. package/dist/plugins/genie/types.d.ts +10 -2
  138. package/dist/plugins/genie/types.d.ts.map +1 -1
  139. package/dist/plugins/jobs/plugin.js +1 -1
  140. package/dist/plugins/lakebase/index.d.ts +2 -2
  141. package/dist/plugins/lakebase/index.js +1 -1
  142. package/dist/plugins/lakebase/lakebase.d.ts +31 -3
  143. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  144. package/dist/plugins/lakebase/lakebase.js +77 -5
  145. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  146. package/dist/plugins/lakebase/types.d.ts +39 -1
  147. package/dist/plugins/lakebase/types.d.ts.map +1 -1
  148. package/dist/plugins/server/index.d.ts +12 -0
  149. package/dist/plugins/server/index.d.ts.map +1 -1
  150. package/dist/plugins/server/index.js +47 -10
  151. package/dist/plugins/server/index.js.map +1 -1
  152. package/dist/plugins/server/types.d.ts +11 -3
  153. package/dist/plugins/server/types.d.ts.map +1 -1
  154. package/dist/shared/src/agent.d.ts +75 -1
  155. package/dist/shared/src/agent.d.ts.map +1 -1
  156. package/dist/shared/src/index.d.ts +1 -1
  157. package/dist/shared/src/plugin.d.ts +8 -0
  158. package/dist/shared/src/plugin.d.ts.map +1 -1
  159. package/docs/api/appkit/Class.AppKitMcpClient.md +157 -0
  160. package/docs/api/appkit/Class.DatabricksAdapter.md +151 -0
  161. package/docs/api/appkit/Class.Plugin.md +65 -23
  162. package/docs/api/appkit/Function.agentIdFromMarkdownPath.md +18 -0
  163. package/docs/api/appkit/Function.createAgent.md +33 -0
  164. package/docs/api/appkit/Function.createApp.md +10 -8
  165. package/docs/api/appkit/Function.defineTool.md +26 -0
  166. package/docs/api/appkit/Function.executeFromRegistry.md +25 -0
  167. package/docs/api/appkit/Function.functionToolToDefinition.md +16 -0
  168. package/docs/api/appkit/Function.isFunctionTool.md +16 -0
  169. package/docs/api/appkit/Function.isHostedTool.md +16 -0
  170. package/docs/api/appkit/Function.isToolkitEntry.md +18 -0
  171. package/docs/api/appkit/Function.loadAgentFromFile.md +21 -0
  172. package/docs/api/appkit/Function.loadAgentsFromDir.md +26 -0
  173. package/docs/api/appkit/Function.mcpServer.md +28 -0
  174. package/docs/api/appkit/Function.parseTextToolCalls.md +26 -0
  175. package/docs/api/appkit/Function.resolveHostedTools.md +16 -0
  176. package/docs/api/appkit/Function.runAgent.md +26 -0
  177. package/docs/api/appkit/Function.tool.md +28 -0
  178. package/docs/api/appkit/Function.toolsFromRegistry.md +20 -0
  179. package/docs/api/appkit/Interface.AgentAdapter.md +21 -0
  180. package/docs/api/appkit/Interface.AgentDefinition.md +112 -0
  181. package/docs/api/appkit/Interface.AgentInput.md +37 -0
  182. package/docs/api/appkit/Interface.AgentRunContext.md +32 -0
  183. package/docs/api/appkit/Interface.AgentToolDefinition.md +37 -0
  184. package/docs/api/appkit/Interface.AgentsPluginConfig.md +241 -0
  185. package/docs/api/appkit/Interface.AutoInheritToolsConfig.md +27 -0
  186. package/docs/api/appkit/Interface.BasePluginConfig.md +1 -0
  187. package/docs/api/appkit/Interface.FunctionTool.md +80 -0
  188. package/docs/api/appkit/Interface.McpConnectAllResult.md +38 -0
  189. package/docs/api/appkit/Interface.Message.md +55 -0
  190. package/docs/api/appkit/Interface.PluginToolkitProvider.md +22 -0
  191. package/docs/api/appkit/Interface.PromptContext.md +30 -0
  192. package/docs/api/appkit/Interface.RegisteredAgent.md +75 -0
  193. package/docs/api/appkit/Interface.RunAgentInput.md +34 -0
  194. package/docs/api/appkit/Interface.RunAgentResult.md +23 -0
  195. package/docs/api/appkit/Interface.Thread.md +46 -0
  196. package/docs/api/appkit/Interface.ThreadStore.md +103 -0
  197. package/docs/api/appkit/Interface.ToolAnnotations.md +56 -0
  198. package/docs/api/appkit/Interface.ToolConfig.md +72 -0
  199. package/docs/api/appkit/Interface.ToolEntry.md +73 -0
  200. package/docs/api/appkit/Interface.ToolProvider.md +38 -0
  201. package/docs/api/appkit/Interface.ToolkitEntry.md +59 -0
  202. package/docs/api/appkit/Interface.ToolkitOptions.md +45 -0
  203. package/docs/api/appkit/TypeAlias.AgentEvent.md +299 -0
  204. package/docs/api/appkit/TypeAlias.AgentTool.md +11 -0
  205. package/docs/api/appkit/TypeAlias.AgentTools.md +8 -0
  206. package/docs/api/appkit/TypeAlias.AgentToolsFn.md +20 -0
  207. package/docs/api/appkit/TypeAlias.BaseSystemPromptOption.md +9 -0
  208. package/docs/api/appkit/TypeAlias.HostedTool.md +10 -0
  209. package/docs/api/appkit/TypeAlias.Plugins.md +26 -0
  210. package/docs/api/appkit/TypeAlias.ResolvedToolEntry.md +29 -0
  211. package/docs/api/appkit/TypeAlias.ToolRegistry.md +6 -0
  212. package/docs/api/appkit/Variable.agents.md +19 -0
  213. package/docs/api/appkit.md +113 -62
  214. package/docs/plugins/agents.md +441 -0
  215. package/docs/privacy.md +41 -0
  216. package/llms.txt +54 -1
  217. package/package.json +4 -2
  218. package/sbom.cdx.json +1 -1
@@ -0,0 +1,58 @@
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ //#region src/plugins/agents/thread-store.ts
4
+ /**
5
+ * In-memory thread store backed by a nested Map.
6
+ *
7
+ * Outer key: userId, inner key: threadId. Thread history is retained for the
8
+ * lifetime of the process with no eviction, caps, or TTL — a chatty user will
9
+ * grow the in-memory footprint monotonically, and the server loses every
10
+ * thread on restart. **This implementation is intended for local development
11
+ * and single-process demos only.**
12
+ *
13
+ * For any real deployment, pass a persistent `ThreadStore` to `agents({ ... })`
14
+ * (e.g. a Lakebase- or Postgres-backed implementation). A bounded
15
+ * `InMemoryThreadStore` with eviction policies is tracked as a follow-up.
16
+ */
17
+ var InMemoryThreadStore = class {
18
+ store = /* @__PURE__ */ new Map();
19
+ async create(userId) {
20
+ const now = /* @__PURE__ */ new Date();
21
+ const thread = {
22
+ id: randomUUID(),
23
+ userId,
24
+ messages: [],
25
+ createdAt: now,
26
+ updatedAt: now
27
+ };
28
+ this.userMap(userId).set(thread.id, thread);
29
+ return thread;
30
+ }
31
+ async get(threadId, userId) {
32
+ return this.userMap(userId).get(threadId) ?? null;
33
+ }
34
+ async list(userId) {
35
+ return Array.from(this.userMap(userId).values()).sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
36
+ }
37
+ async addMessage(threadId, userId, message) {
38
+ const thread = this.userMap(userId).get(threadId);
39
+ if (!thread) throw new Error(`Thread ${threadId} not found`);
40
+ thread.messages.push(message);
41
+ thread.updatedAt = /* @__PURE__ */ new Date();
42
+ }
43
+ async delete(threadId, userId) {
44
+ return this.userMap(userId).delete(threadId);
45
+ }
46
+ userMap(userId) {
47
+ let map = this.store.get(userId);
48
+ if (!map) {
49
+ map = /* @__PURE__ */ new Map();
50
+ this.store.set(userId, map);
51
+ }
52
+ return map;
53
+ }
54
+ };
55
+
56
+ //#endregion
57
+ export { InMemoryThreadStore };
58
+ //# sourceMappingURL=thread-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thread-store.js","names":[],"sources":["../../../src/plugins/agents/thread-store.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { Message, Thread, ThreadStore } from \"shared\";\n\n/**\n * In-memory thread store backed by a nested Map.\n *\n * Outer key: userId, inner key: threadId. Thread history is retained for the\n * lifetime of the process with no eviction, caps, or TTL — a chatty user will\n * grow the in-memory footprint monotonically, and the server loses every\n * thread on restart. **This implementation is intended for local development\n * and single-process demos only.**\n *\n * For any real deployment, pass a persistent `ThreadStore` to `agents({ ... })`\n * (e.g. a Lakebase- or Postgres-backed implementation). A bounded\n * `InMemoryThreadStore` with eviction policies is tracked as a follow-up.\n */\nexport class InMemoryThreadStore implements ThreadStore {\n private store = new Map<string, Map<string, Thread>>();\n\n async create(userId: string): Promise<Thread> {\n const now = new Date();\n const thread: Thread = {\n id: randomUUID(),\n userId,\n messages: [],\n createdAt: now,\n updatedAt: now,\n };\n this.userMap(userId).set(thread.id, thread);\n return thread;\n }\n\n async get(threadId: string, userId: string): Promise<Thread | null> {\n return this.userMap(userId).get(threadId) ?? null;\n }\n\n async list(userId: string): Promise<Thread[]> {\n return Array.from(this.userMap(userId).values()).sort(\n (a, b) => b.updatedAt.getTime() - a.updatedAt.getTime(),\n );\n }\n\n async addMessage(\n threadId: string,\n userId: string,\n message: Message,\n ): Promise<void> {\n const thread = this.userMap(userId).get(threadId);\n if (!thread) throw new Error(`Thread ${threadId} not found`);\n thread.messages.push(message);\n thread.updatedAt = new Date();\n }\n\n async delete(threadId: string, userId: string): Promise<boolean> {\n return this.userMap(userId).delete(threadId);\n }\n\n private userMap(userId: string): Map<string, Thread> {\n let map = this.store.get(userId);\n if (!map) {\n map = new Map();\n this.store.set(userId, map);\n }\n return map;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,IAAa,sBAAb,MAAwD;CACtD,AAAQ,wBAAQ,IAAI,KAAkC;CAEtD,MAAM,OAAO,QAAiC;EAC5C,MAAM,sBAAM,IAAI,MAAM;EACtB,MAAM,SAAiB;GACrB,IAAI,YAAY;GAChB;GACA,UAAU,EAAE;GACZ,WAAW;GACX,WAAW;GACZ;AACD,OAAK,QAAQ,OAAO,CAAC,IAAI,OAAO,IAAI,OAAO;AAC3C,SAAO;;CAGT,MAAM,IAAI,UAAkB,QAAwC;AAClE,SAAO,KAAK,QAAQ,OAAO,CAAC,IAAI,SAAS,IAAI;;CAG/C,MAAM,KAAK,QAAmC;AAC5C,SAAO,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC,QAAQ,CAAC,CAAC,MAC9C,GAAG,MAAM,EAAE,UAAU,SAAS,GAAG,EAAE,UAAU,SAAS,CACxD;;CAGH,MAAM,WACJ,UACA,QACA,SACe;EACf,MAAM,SAAS,KAAK,QAAQ,OAAO,CAAC,IAAI,SAAS;AACjD,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,UAAU,SAAS,YAAY;AAC5D,SAAO,SAAS,KAAK,QAAQ;AAC7B,SAAO,4BAAY,IAAI,MAAM;;CAG/B,MAAM,OAAO,UAAkB,QAAkC;AAC/D,SAAO,KAAK,QAAQ,OAAO,CAAC,OAAO,SAAS;;CAG9C,AAAQ,QAAQ,QAAqC;EACnD,IAAI,MAAM,KAAK,MAAM,IAAI,OAAO;AAChC,MAAI,CAAC,KAAK;AACR,yBAAM,IAAI,KAAK;AACf,QAAK,MAAM,IAAI,QAAQ,IAAI;;AAE7B,SAAO"}
@@ -0,0 +1,75 @@
1
+ //#region src/plugins/agents/tool-approval-gate.ts
2
+ var ToolApprovalGate = class {
3
+ pending = /* @__PURE__ */ new Map();
4
+ /**
5
+ * Register a pending approval and return a promise that resolves with the
6
+ * user's decision or with `"deny"` when the timeout elapses. The returned
7
+ * promise never rejects.
8
+ */
9
+ wait(args) {
10
+ const { approvalId, streamId, userId, timeoutMs } = args;
11
+ return new Promise((resolve) => {
12
+ const timeout = setTimeout(() => {
13
+ if (this.pending.delete(approvalId)) resolve("deny");
14
+ }, timeoutMs);
15
+ this.pending.set(approvalId, {
16
+ resolve,
17
+ userId,
18
+ streamId,
19
+ timeout
20
+ });
21
+ });
22
+ }
23
+ /**
24
+ * Settle an approval with a user decision. Returns:
25
+ * - `{ ok: true }` if the pending record existed, the userId matched, and
26
+ * the promise was resolved.
27
+ * - `{ ok: false, reason: "unknown" }` if no pending record matches the id.
28
+ * - `{ ok: false, reason: "forbidden" }` if the userId does not match the
29
+ * user who initiated the stream.
30
+ */
31
+ submit(args) {
32
+ const { approvalId, userId, decision } = args;
33
+ const p = this.pending.get(approvalId);
34
+ if (!p) return {
35
+ ok: false,
36
+ reason: "unknown"
37
+ };
38
+ if (p.userId !== userId) return {
39
+ ok: false,
40
+ reason: "forbidden"
41
+ };
42
+ clearTimeout(p.timeout);
43
+ this.pending.delete(approvalId);
44
+ p.resolve(decision);
45
+ return { ok: true };
46
+ }
47
+ /**
48
+ * Cancel all pending gates for a specific stream (e.g., when the user
49
+ * cancels the stream). Each gate resolves with `"deny"` so the adapter
50
+ * unwinds cleanly.
51
+ */
52
+ abortStream(streamId) {
53
+ for (const [id, p] of this.pending) if (p.streamId === streamId) {
54
+ clearTimeout(p.timeout);
55
+ this.pending.delete(id);
56
+ p.resolve("deny");
57
+ }
58
+ }
59
+ /** Cancel every pending gate. Used at plugin shutdown. */
60
+ abortAll() {
61
+ for (const [id, p] of this.pending) {
62
+ clearTimeout(p.timeout);
63
+ this.pending.delete(id);
64
+ p.resolve("deny");
65
+ }
66
+ }
67
+ /** Number of pending approvals (test/diagnostic helper). */
68
+ get size() {
69
+ return this.pending.size;
70
+ }
71
+ };
72
+
73
+ //#endregion
74
+ export { ToolApprovalGate };
75
+ //# sourceMappingURL=tool-approval-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-approval-gate.js","names":[],"sources":["../../../src/plugins/agents/tool-approval-gate.ts"],"sourcesContent":["/**\n * Server-side state for the human-in-the-loop approval gate on mutating\n * agent tool calls — tools annotated with `effect: \"write\" | \"update\" |\n * \"destructive\"` (preferred) or the legacy `destructive: true` boolean.\n *\n * Lifecycle:\n *\n * 1. `wait(...)` is called from inside `executeTool` when a mutating tool\n * is about to execute. A `Pending` record is registered and a timer is\n * scheduled for auto-deny. The returned promise is what blocks the\n * adapter until the decision arrives.\n * 2. The client receives an `appkit.approval_pending` SSE event carrying the\n * `approvalId` + `streamId` and posts a decision to `POST /chat/approve`.\n * The route calls {@link ToolApprovalGate.submit} which resolves the\n * pending promise and clears the timer.\n * 3. If no submit arrives within `timeoutMs`, the timer fires and the\n * promise resolves with `\"deny\"`.\n *\n * Security invariants:\n *\n * - `submit` verifies that the decider's user id matches the user who\n * initiated the stream (set by `wait`). Mismatches are rejected without\n * resolving the pending promise — this prevents a second user from\n * approving (or denying) another user's destructive action.\n * - `abort(streamId)` cancels every pending gate for a stream and denies\n * each one. Used when the enclosing stream is cancelled or the plugin is\n * shutting down.\n */\ntype ApprovalDecision = \"approve\" | \"deny\";\n\ninterface Pending {\n resolve: (decision: ApprovalDecision) => void;\n userId: string;\n streamId: string;\n timeout: ReturnType<typeof setTimeout>;\n}\n\ntype ApprovalSubmitResult =\n | { ok: true }\n | { ok: false; reason: \"unknown\" | \"forbidden\" };\n\nexport class ToolApprovalGate {\n private pending = new Map<string, Pending>();\n\n /**\n * Register a pending approval and return a promise that resolves with the\n * user's decision or with `\"deny\"` when the timeout elapses. The returned\n * promise never rejects.\n */\n wait(args: {\n approvalId: string;\n streamId: string;\n userId: string;\n timeoutMs: number;\n }): Promise<ApprovalDecision> {\n const { approvalId, streamId, userId, timeoutMs } = args;\n return new Promise<ApprovalDecision>((resolve) => {\n const timeout = setTimeout(() => {\n if (this.pending.delete(approvalId)) {\n resolve(\"deny\");\n }\n }, timeoutMs);\n this.pending.set(approvalId, {\n resolve,\n userId,\n streamId,\n timeout,\n });\n });\n }\n\n /**\n * Settle an approval with a user decision. Returns:\n * - `{ ok: true }` if the pending record existed, the userId matched, and\n * the promise was resolved.\n * - `{ ok: false, reason: \"unknown\" }` if no pending record matches the id.\n * - `{ ok: false, reason: \"forbidden\" }` if the userId does not match the\n * user who initiated the stream.\n */\n submit(args: {\n approvalId: string;\n userId: string;\n decision: ApprovalDecision;\n }): ApprovalSubmitResult {\n const { approvalId, userId, decision } = args;\n const p = this.pending.get(approvalId);\n if (!p) return { ok: false, reason: \"unknown\" };\n if (p.userId !== userId) return { ok: false, reason: \"forbidden\" };\n clearTimeout(p.timeout);\n this.pending.delete(approvalId);\n p.resolve(decision);\n return { ok: true };\n }\n\n /**\n * Cancel all pending gates for a specific stream (e.g., when the user\n * cancels the stream). Each gate resolves with `\"deny\"` so the adapter\n * unwinds cleanly.\n */\n abortStream(streamId: string): void {\n for (const [id, p] of this.pending) {\n if (p.streamId === streamId) {\n clearTimeout(p.timeout);\n this.pending.delete(id);\n p.resolve(\"deny\");\n }\n }\n }\n\n /** Cancel every pending gate. Used at plugin shutdown. */\n abortAll(): void {\n for (const [id, p] of this.pending) {\n clearTimeout(p.timeout);\n this.pending.delete(id);\n p.resolve(\"deny\");\n }\n }\n\n /** Number of pending approvals (test/diagnostic helper). */\n get size(): number {\n return this.pending.size;\n }\n}\n"],"mappings":";AAyCA,IAAa,mBAAb,MAA8B;CAC5B,AAAQ,0BAAU,IAAI,KAAsB;;;;;;CAO5C,KAAK,MAKyB;EAC5B,MAAM,EAAE,YAAY,UAAU,QAAQ,cAAc;AACpD,SAAO,IAAI,SAA2B,YAAY;GAChD,MAAM,UAAU,iBAAiB;AAC/B,QAAI,KAAK,QAAQ,OAAO,WAAW,CACjC,SAAQ,OAAO;MAEhB,UAAU;AACb,QAAK,QAAQ,IAAI,YAAY;IAC3B;IACA;IACA;IACA;IACD,CAAC;IACF;;;;;;;;;;CAWJ,OAAO,MAIkB;EACvB,MAAM,EAAE,YAAY,QAAQ,aAAa;EACzC,MAAM,IAAI,KAAK,QAAQ,IAAI,WAAW;AACtC,MAAI,CAAC,EAAG,QAAO;GAAE,IAAI;GAAO,QAAQ;GAAW;AAC/C,MAAI,EAAE,WAAW,OAAQ,QAAO;GAAE,IAAI;GAAO,QAAQ;GAAa;AAClE,eAAa,EAAE,QAAQ;AACvB,OAAK,QAAQ,OAAO,WAAW;AAC/B,IAAE,QAAQ,SAAS;AACnB,SAAO,EAAE,IAAI,MAAM;;;;;;;CAQrB,YAAY,UAAwB;AAClC,OAAK,MAAM,CAAC,IAAI,MAAM,KAAK,QACzB,KAAI,EAAE,aAAa,UAAU;AAC3B,gBAAa,EAAE,QAAQ;AACvB,QAAK,QAAQ,OAAO,GAAG;AACvB,KAAE,QAAQ,OAAO;;;;CAMvB,WAAiB;AACf,OAAK,MAAM,CAAC,IAAI,MAAM,KAAK,SAAS;AAClC,gBAAa,EAAE,QAAQ;AACvB,QAAK,QAAQ,OAAO,GAAG;AACvB,KAAE,QAAQ,OAAO;;;;CAKrB,IAAI,OAAe;AACjB,SAAO,KAAK,QAAQ"}
@@ -1,16 +1,19 @@
1
+ import { AgentToolDefinition, ToolProvider } from "../../shared/src/agent.js";
1
2
  import { IAppRouter, ToPlugin } from "../../shared/src/plugin.js";
2
3
  import { SQLTypeMarker } from "../../shared/src/sql/types.js";
3
4
  import "../../shared/src/index.js";
5
+ import { ToolkitEntry, ToolkitOptions } from "../../core/agent/types.js";
4
6
  import { Plugin } from "../../plugin/plugin.js";
5
7
  import "../../plugin/index.js";
6
8
  import { PluginManifest } from "../../registry/types.js";
7
9
  import "../../registry/index.js";
10
+ import "../agents/index.js";
8
11
  import { IAnalyticsConfig } from "./types.js";
9
12
  import { WorkspaceClient } from "@databricks/sdk-experimental";
10
13
  import express from "express";
11
14
 
12
15
  //#region src/plugins/analytics/analytics.d.ts
13
- declare class AnalyticsPlugin extends Plugin {
16
+ declare class AnalyticsPlugin extends Plugin implements ToolProvider {
14
17
  /** Plugin manifest declaring metadata and resource requirements */
15
18
  static manifest: PluginManifest<"analytics">;
16
19
  protected static description: string;
@@ -50,6 +53,17 @@ declare class AnalyticsPlugin extends Plugin {
50
53
  */
51
54
  protected getArrowData(workspaceClient: WorkspaceClient, jobId: string, signal?: AbortSignal): Promise<ReturnType<typeof this.SQLClient.getArrowData>>;
52
55
  shutdown(): Promise<void>;
56
+ private tools;
57
+ getAgentTools(): AgentToolDefinition[];
58
+ executeAgentTool(name: string, args: unknown, signal?: AbortSignal): Promise<unknown>;
59
+ /**
60
+ * Returns the plugin's tools as a keyed record of `ToolkitEntry` markers.
61
+ * Called by the agents plugin (via `resolveToolkitFromProvider`) to spread
62
+ * a filtered, renamed view of the plugin's tools into an agent's tool
63
+ * index. Inside the function form of `AgentDefinition.tools`, callers
64
+ * reach this method via `plugins.analytics.toolkit(opts)`.
65
+ */
66
+ toolkit(opts?: ToolkitOptions): Record<string, ToolkitEntry>;
53
67
  /**
54
68
  * Returns the public exports for the analytics plugin.
55
69
  * Note: `asUser()` is automatically added by AppKit.
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","names":[],"sources":["../../../src/plugins/analytics/analytics.ts"],"mappings":";;;;;;;;;;;;cAwBa,eAAA,SAAwB,MAAA;;SAE5B,QAAA,EAAuB,cAAA;EAAA,iBAEb,WAAA;EAAA,UACC,MAAA,EAAQ,gBAAA;EAAA,QAGlB,SAAA;EAAA,QACA,cAAA;cAEI,MAAA,EAAQ,gBAAA;EAWpB,YAAA,CAAa,MAAA,EAAQ,UAAA;EApBS;;;;EAgDxB,iBAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,GAAA,EAAK,OAAA,CAAQ,QAAA,GACZ,OAAA;EADI;;;;EAuCD,iBAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,GAAA,EAAK,OAAA,CAAQ,QAAA,GACZ,OAAA;EA8G2B;;;;;;;;;;;;;;;EAFxB,KAAA,CACJ,KAAA,UACA,UAAA,GAAa,MAAA,SAAe,aAAA,sBAC5B,gBAAA,GAAmB,MAAA,eACnB,MAAA,GAAS,WAAA,GACR,OAAA;EA/MsC;;;EAAA,UAuOzB,YAAA,CACd,eAAA,EAAiB,eAAA,EACjB,KAAA,UACA,MAAA,GAAS,WAAA,GACR,OAAA,CAAQ,UAAA,aAAuB,SAAA,CAAU,YAAA;EAItC,QAAA,CAAA,GAAY,OAAA;EA3OD;;;;EAmPjB,OAAA,CAAA;;;;2BA5Ce,UAAA,GACA,MAAA,SAAe,aAAA,sBAAiC,gBAAA,GAC1C,MAAA,eAAmB,MAAA,GAC7B,WAAA,KACR,OAAA;EAAA;AAAA;;;;cAqDQ,SAAA,EAAS,QAAA,QAAA,eAAA,EAAA,gBAAA"}
1
+ {"version":3,"file":"analytics.d.ts","names":[],"sources":["../../../src/plugins/analytics/analytics.ts"],"mappings":";;;;;;;;;;;;;;;cAmCa,eAAA,SAAwB,MAAA,YAAkB,YAAA;;SAE9C,QAAA,EAAuB,cAAA;EAAA,iBAEb,WAAA;EAAA,UACC,MAAA,EAAQ,gBAAA;EAAA,QAGlB,SAAA;EAAA,QACA,cAAA;cAEI,MAAA,EAAQ,gBAAA;EAWpB,YAAA,CAAa,MAAA,EAAQ,UAAA;EAtBV;;;;EAkDL,iBAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,GAAA,EAAK,OAAA,CAAQ,QAAA,GACZ,OAAA;EA1CiB;;;;EAgFd,iBAAA,CACJ,GAAA,EAAK,OAAA,CAAQ,OAAA,EACb,GAAA,EAAK,OAAA,CAAQ,QAAA,GACZ,OAAA;EAFI;;;;;;;;;;;;;;;EAgHD,KAAA,CACJ,KAAA,UACA,UAAA,GAAa,MAAA,SAAe,aAAA,sBAC5B,gBAAA,GAAmB,MAAA,eACnB,MAAA,GAAS,WAAA,GACR,OAAA;EAmEO;;;EAAA,UA3CM,YAAA,CACd,eAAA,EAAiB,eAAA,EACjB,KAAA,UACA,MAAA,GAAS,WAAA,GACR,OAAA,CAAQ,UAAA,aAAuB,SAAA,CAAU,YAAA;EAItC,QAAA,CAAA,GAAY,OAAA;EAAA,QAIV,KAAA;EAuBR,aAAA,CAAA,GAAiB,mBAAA;EAIX,gBAAA,CACJ,IAAA,UACA,IAAA,WACA,MAAA,GAAS,WAAA,GACR,OAAA;EApRgC;;;;;;;EA+RnC,OAAA,CAAQ,IAAA,GAXE,cAAA,GAWoD,MAAA,SAAA,YAAA;EA3R7C;;;;EAmSjB,OAAA,CAAA;;;;2BA1Fe,UAAA,GACA,MAAA,SAAe,aAAA,sBAAiC,gBAAA,GAC1C,MAAA,eAAmB,MAAA,GAC7B,WAAA,KACR,OAAA;EAAA;AAAA;;;;cAmGQ,SAAA,EAAS,QAAA,QAAA,eAAA,EAAA,gBAAA"}
@@ -6,9 +6,14 @@ import { toPlugin } from "../../plugin/to-plugin.js";
6
6
  import "../../plugin/index.js";
7
7
  import { SQLWarehouseConnector } from "../../connectors/sql-warehouse/client.js";
8
8
  import "../../connectors/index.js";
9
+ import { buildToolkitEntries } from "../../core/agent/build-toolkit.js";
10
+ import { defineTool, executeFromRegistry, toolsFromRegistry } from "../../core/agent/tools/define-tool.js";
11
+ import { assertReadOnlySql } from "../../core/agent/tools/sql-policy.js";
9
12
  import { queryDefaults } from "./defaults.js";
10
13
  import manifest_default from "./manifest.js";
11
14
  import { QueryProcessor } from "./query.js";
15
+ import { normalizeAnalyticsFormat } from "./types.js";
16
+ import { z } from "zod";
12
17
 
13
18
  //#region src/plugins/analytics/analytics.ts
14
19
  init_context();
@@ -79,7 +84,8 @@ var AnalyticsPlugin = class extends Plugin {
79
84
  */
80
85
  async _handleQueryRoute(req, res) {
81
86
  const { query_key } = req.params;
82
- const { parameters, format = "JSON" } = req.body;
87
+ const { parameters, format: rawFormat = "JSON_ARRAY" } = req.body;
88
+ const format = normalizeAnalyticsFormat(rawFormat);
83
89
  logger.debug(req, "Executing query: %s (format=%s)", query_key, format);
84
90
  logger.event(req)?.setComponent("analytics", "executeQuery").setContext("analytics", {
85
91
  query_key,
@@ -99,7 +105,7 @@ var AnalyticsPlugin = class extends Plugin {
99
105
  const { query, isAsUser } = queryResult;
100
106
  const executor = isAsUser ? this.asUser(req) : this;
101
107
  const executorKey = isAsUser ? this.resolveUserId(req) : "global";
102
- const queryParameters = format === "ARROW" ? {
108
+ const queryParameters = format === "ARROW_STREAM" ? {
103
109
  formatParameters: {
104
110
  disposition: "EXTERNAL_LINKS",
105
111
  format: "ARROW_STREAM"
@@ -165,6 +171,35 @@ var AnalyticsPlugin = class extends Plugin {
165
171
  async shutdown() {
166
172
  this.streamManager.abortAll();
167
173
  }
174
+ tools = { query: defineTool({
175
+ description: "Execute a read-only SQL query against the Databricks SQL warehouse. Only SELECT, WITH, SHOW, EXPLAIN, and DESCRIBE statements are accepted; writes are rejected. Returns the query results as JSON.",
176
+ schema: z.object({ query: z.string().describe("The SQL query to execute. Must be a SELECT, WITH, SHOW, EXPLAIN, or DESCRIBE statement.") }),
177
+ annotations: {
178
+ effect: "read",
179
+ requiresUserContext: true
180
+ },
181
+ autoInheritable: true,
182
+ execute: (args, signal) => {
183
+ assertReadOnlySql(args.query);
184
+ return this.query(args.query, void 0, void 0, signal);
185
+ }
186
+ }) };
187
+ getAgentTools() {
188
+ return toolsFromRegistry(this.tools);
189
+ }
190
+ async executeAgentTool(name, args, signal) {
191
+ return executeFromRegistry(this.tools, name, args, signal);
192
+ }
193
+ /**
194
+ * Returns the plugin's tools as a keyed record of `ToolkitEntry` markers.
195
+ * Called by the agents plugin (via `resolveToolkitFromProvider`) to spread
196
+ * a filtered, renamed view of the plugin's tools into an agent's tool
197
+ * index. Inside the function form of `AgentDefinition.tools`, callers
198
+ * reach this method via `plugins.analytics.toolkit(opts)`.
199
+ */
200
+ toolkit(opts) {
201
+ return buildToolkitEntries(this.name, this.tools, opts);
202
+ }
168
203
  /**
169
204
  * Returns the public exports for the analytics plugin.
170
205
  * Note: `asUser()` is automatically added by AppKit.
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.js","names":["manifest"],"sources":["../../../src/plugins/analytics/analytics.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type express from \"express\";\nimport type {\n IAppRouter,\n PluginExecuteConfig,\n SQLTypeMarker,\n StreamExecutionSettings,\n} from \"shared\";\nimport { SQLWarehouseConnector } from \"../../connectors\";\nimport { getWarehouseId, getWorkspaceClient } from \"../../context\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { queryDefaults } from \"./defaults\";\nimport manifest from \"./manifest.json\";\nimport { QueryProcessor } from \"./query\";\nimport type {\n AnalyticsQueryResponse,\n IAnalyticsConfig,\n IAnalyticsQueryRequest,\n} from \"./types\";\n\nconst logger = createLogger(\"analytics\");\n\nexport class AnalyticsPlugin extends Plugin {\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = manifest as PluginManifest<\"analytics\">;\n\n protected static description = \"Analytics plugin for data analysis\";\n protected declare config: IAnalyticsConfig;\n\n // analytics services\n private SQLClient: SQLWarehouseConnector;\n private queryProcessor: QueryProcessor;\n\n constructor(config: IAnalyticsConfig) {\n super(config);\n this.config = config;\n this.queryProcessor = new QueryProcessor();\n\n this.SQLClient = new SQLWarehouseConnector({\n timeout: config.timeout,\n telemetry: config.telemetry,\n });\n }\n\n injectRoutes(router: IAppRouter) {\n // Arrow data downloads always run as service principal and bypass the\n // interceptor chain (execute/executeStream). The original query execution\n // handles OBO via executeStream(); this endpoint fetches pre-computed\n // results by job ID.\n this.route(router, {\n name: \"arrow\",\n method: \"get\",\n path: \"/arrow-result/:jobId\",\n handler: async (req: express.Request, res: express.Response) => {\n await this._handleArrowRoute(req, res);\n },\n });\n\n this.route<AnalyticsQueryResponse>(router, {\n name: \"query\",\n method: \"post\",\n path: \"/query/:query_key\",\n handler: async (req: express.Request, res: express.Response) => {\n await this._handleQueryRoute(req, res);\n },\n });\n }\n\n /**\n * Handle Arrow data download requests.\n * When called via asUser(req), uses the user's Databricks credentials.\n */\n async _handleArrowRoute(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n try {\n const { jobId } = req.params;\n const workspaceClient = getWorkspaceClient();\n\n logger.debug(\"Processing Arrow job request for jobId=%s\", jobId);\n\n const event = logger.event(req);\n event?.setComponent(\"analytics\", \"getArrowData\").setContext(\"analytics\", {\n job_id: jobId,\n plugin: this.name,\n });\n\n const result = await this.getArrowData(workspaceClient, jobId);\n\n res.setHeader(\"Content-Type\", \"application/octet-stream\");\n res.setHeader(\"Content-Length\", result.data.length.toString());\n res.setHeader(\"Cache-Control\", \"public, max-age=3600\");\n\n logger.debug(\n \"Sending Arrow buffer: %d bytes for job %s\",\n result.data.length,\n jobId,\n );\n res.send(Buffer.from(result.data));\n } catch (error) {\n logger.error(\"Arrow job error: %O\", error);\n res.status(404).json({\n error: error instanceof Error ? error.message : \"Arrow job not found\",\n plugin: this.name,\n });\n }\n }\n\n /**\n * Handle SQL query execution requests.\n * When called via asUser(req), uses the user's Databricks credentials.\n */\n async _handleQueryRoute(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { query_key } = req.params;\n const { parameters, format = \"JSON\" } = req.body as IAnalyticsQueryRequest;\n\n // Request-scoped logging with WideEvent tracking\n logger.debug(req, \"Executing query: %s (format=%s)\", query_key, format);\n\n const event = logger.event(req);\n event?.setComponent(\"analytics\", \"executeQuery\").setContext(\"analytics\", {\n query_key,\n format,\n parameter_count: parameters ? Object.keys(parameters).length : 0,\n plugin: this.name,\n });\n\n if (!query_key) {\n res.status(400).json({ error: \"query_key is required\" });\n return;\n }\n\n const queryResult = await this.app.getAppQuery(\n query_key,\n req,\n this.devFileReader,\n );\n\n if (!queryResult) {\n res.status(404).json({ error: \"Query not found\" });\n return;\n }\n\n const { query, isAsUser } = queryResult;\n\n // get execution context - user-scoped if .obo.sql, otherwise service principal\n const executor = isAsUser ? this.asUser(req) : this;\n const executorKey = isAsUser ? this.resolveUserId(req) : \"global\";\n\n const queryParameters =\n format === \"ARROW\"\n ? {\n formatParameters: {\n disposition: \"EXTERNAL_LINKS\",\n format: \"ARROW_STREAM\",\n },\n type: \"arrow\",\n }\n : {\n type: \"result\",\n };\n\n const hashedQuery = this.queryProcessor.hashQuery(query);\n\n const defaultConfig: PluginExecuteConfig = {\n ...queryDefaults,\n cache: {\n ...queryDefaults.cache,\n cacheKey: [\n \"analytics:query\",\n query_key,\n JSON.stringify(parameters),\n JSON.stringify(format),\n hashedQuery,\n executorKey,\n ],\n },\n };\n\n const streamExecutionSettings: StreamExecutionSettings = {\n default: defaultConfig,\n };\n\n await executor.executeStream(\n res,\n async (signal) => {\n const processedParams = await this.queryProcessor.processQueryParams(\n query,\n parameters,\n );\n\n const result = await executor.query(\n query,\n processedParams,\n queryParameters.formatParameters,\n signal,\n );\n\n return { type: queryParameters.type, ...result };\n },\n streamExecutionSettings,\n executorKey,\n );\n }\n\n /**\n * Execute a SQL query using the current execution context.\n *\n * When called directly: uses service principal credentials.\n * When called via asUser(req).query(...): uses user's credentials.\n *\n * @example\n * ```typescript\n * // Service principal execution\n * const result = await analytics.query(\"SELECT * FROM table\")\n *\n * // User context execution (in route handler)\n * const result = await this.asUser(req).query(\"SELECT * FROM table\")\n * ```\n */\n async query(\n query: string,\n parameters?: Record<string, SQLTypeMarker | null | undefined>,\n formatParameters?: Record<string, any>,\n signal?: AbortSignal,\n ): Promise<any> {\n const workspaceClient = getWorkspaceClient();\n const warehouseId = await getWarehouseId();\n\n const { statement, parameters: sqlParameters } =\n this.queryProcessor.convertToSQLParameters(query, parameters);\n\n const response = await this.SQLClient.executeStatement(\n workspaceClient,\n {\n statement,\n warehouse_id: warehouseId,\n parameters: sqlParameters,\n ...formatParameters,\n },\n signal,\n );\n\n return response.result;\n }\n\n /**\n * Get Arrow-formatted data for a completed query job.\n */\n protected async getArrowData(\n workspaceClient: WorkspaceClient,\n jobId: string,\n signal?: AbortSignal,\n ): Promise<ReturnType<typeof this.SQLClient.getArrowData>> {\n return await this.SQLClient.getArrowData(workspaceClient, jobId, signal);\n }\n\n async shutdown(): Promise<void> {\n this.streamManager.abortAll();\n }\n\n /**\n * Returns the public exports for the analytics plugin.\n * Note: `asUser()` is automatically added by AppKit.\n */\n exports() {\n return {\n /**\n * Execute a SQL query using service principal credentials.\n */\n query: this.query,\n };\n }\n}\n\n/**\n * @internal\n */\nexport const analytics = toPlugin(AnalyticsPlugin);\n"],"mappings":"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"}
1
+ {"version":3,"file":"analytics.js","names":["manifest"],"sources":["../../../src/plugins/analytics/analytics.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type express from \"express\";\nimport type {\n AgentToolDefinition,\n IAppRouter,\n PluginExecuteConfig,\n SQLTypeMarker,\n StreamExecutionSettings,\n ToolProvider,\n} from \"shared\";\nimport { z } from \"zod\";\nimport { SQLWarehouseConnector } from \"../../connectors\";\nimport { getWarehouseId, getWorkspaceClient } from \"../../context\";\nimport { buildToolkitEntries } from \"../../core/agent/build-toolkit\";\nimport {\n defineTool,\n executeFromRegistry,\n toolsFromRegistry,\n} from \"../../core/agent/tools/define-tool\";\nimport { assertReadOnlySql } from \"../../core/agent/tools/sql-policy\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { queryDefaults } from \"./defaults\";\nimport manifest from \"./manifest.json\";\nimport { QueryProcessor } from \"./query\";\nimport type {\n AnalyticsQueryResponse,\n IAnalyticsConfig,\n IAnalyticsQueryRequest,\n} from \"./types\";\nimport { normalizeAnalyticsFormat } from \"./types\";\n\nconst logger = createLogger(\"analytics\");\n\nexport class AnalyticsPlugin extends Plugin implements ToolProvider {\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = manifest as PluginManifest<\"analytics\">;\n\n protected static description = \"Analytics plugin for data analysis\";\n protected declare config: IAnalyticsConfig;\n\n // analytics services\n private SQLClient: SQLWarehouseConnector;\n private queryProcessor: QueryProcessor;\n\n constructor(config: IAnalyticsConfig) {\n super(config);\n this.config = config;\n this.queryProcessor = new QueryProcessor();\n\n this.SQLClient = new SQLWarehouseConnector({\n timeout: config.timeout,\n telemetry: config.telemetry,\n });\n }\n\n injectRoutes(router: IAppRouter) {\n // Arrow data downloads always run as service principal and bypass the\n // interceptor chain (execute/executeStream). The original query execution\n // handles OBO via executeStream(); this endpoint fetches pre-computed\n // results by job ID.\n this.route(router, {\n name: \"arrow\",\n method: \"get\",\n path: \"/arrow-result/:jobId\",\n handler: async (req: express.Request, res: express.Response) => {\n await this._handleArrowRoute(req, res);\n },\n });\n\n this.route<AnalyticsQueryResponse>(router, {\n name: \"query\",\n method: \"post\",\n path: \"/query/:query_key\",\n handler: async (req: express.Request, res: express.Response) => {\n await this._handleQueryRoute(req, res);\n },\n });\n }\n\n /**\n * Handle Arrow data download requests.\n * When called via asUser(req), uses the user's Databricks credentials.\n */\n async _handleArrowRoute(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n try {\n const { jobId } = req.params;\n const workspaceClient = getWorkspaceClient();\n\n logger.debug(\"Processing Arrow job request for jobId=%s\", jobId);\n\n const event = logger.event(req);\n event?.setComponent(\"analytics\", \"getArrowData\").setContext(\"analytics\", {\n job_id: jobId,\n plugin: this.name,\n });\n\n const result = await this.getArrowData(workspaceClient, jobId);\n\n res.setHeader(\"Content-Type\", \"application/octet-stream\");\n res.setHeader(\"Content-Length\", result.data.length.toString());\n res.setHeader(\"Cache-Control\", \"public, max-age=3600\");\n\n logger.debug(\n \"Sending Arrow buffer: %d bytes for job %s\",\n result.data.length,\n jobId,\n );\n res.send(Buffer.from(result.data));\n } catch (error) {\n logger.error(\"Arrow job error: %O\", error);\n res.status(404).json({\n error: error instanceof Error ? error.message : \"Arrow job not found\",\n plugin: this.name,\n });\n }\n }\n\n /**\n * Handle SQL query execution requests.\n * When called via asUser(req), uses the user's Databricks credentials.\n */\n async _handleQueryRoute(\n req: express.Request,\n res: express.Response,\n ): Promise<void> {\n const { query_key } = req.params;\n const { parameters, format: rawFormat = \"JSON_ARRAY\" } =\n req.body as IAnalyticsQueryRequest;\n const format = normalizeAnalyticsFormat(rawFormat);\n\n // Request-scoped logging with WideEvent tracking\n logger.debug(req, \"Executing query: %s (format=%s)\", query_key, format);\n\n const event = logger.event(req);\n event?.setComponent(\"analytics\", \"executeQuery\").setContext(\"analytics\", {\n query_key,\n format,\n parameter_count: parameters ? Object.keys(parameters).length : 0,\n plugin: this.name,\n });\n\n if (!query_key) {\n res.status(400).json({ error: \"query_key is required\" });\n return;\n }\n\n const queryResult = await this.app.getAppQuery(\n query_key,\n req,\n this.devFileReader,\n );\n\n if (!queryResult) {\n res.status(404).json({ error: \"Query not found\" });\n return;\n }\n\n const { query, isAsUser } = queryResult;\n\n // get execution context - user-scoped if .obo.sql, otherwise service principal\n const executor = isAsUser ? this.asUser(req) : this;\n const executorKey = isAsUser ? this.resolveUserId(req) : \"global\";\n\n const queryParameters =\n format === \"ARROW_STREAM\"\n ? {\n formatParameters: {\n disposition: \"EXTERNAL_LINKS\",\n format: \"ARROW_STREAM\",\n },\n type: \"arrow\",\n }\n : {\n type: \"result\",\n };\n\n const hashedQuery = this.queryProcessor.hashQuery(query);\n\n const defaultConfig: PluginExecuteConfig = {\n ...queryDefaults,\n cache: {\n ...queryDefaults.cache,\n cacheKey: [\n \"analytics:query\",\n query_key,\n JSON.stringify(parameters),\n JSON.stringify(format),\n hashedQuery,\n executorKey,\n ],\n },\n };\n\n const streamExecutionSettings: StreamExecutionSettings = {\n default: defaultConfig,\n };\n\n await executor.executeStream(\n res,\n async (signal) => {\n const processedParams = await this.queryProcessor.processQueryParams(\n query,\n parameters,\n );\n\n const result = await executor.query(\n query,\n processedParams,\n queryParameters.formatParameters,\n signal,\n );\n\n return { type: queryParameters.type, ...result };\n },\n streamExecutionSettings,\n executorKey,\n );\n }\n\n /**\n * Execute a SQL query using the current execution context.\n *\n * When called directly: uses service principal credentials.\n * When called via asUser(req).query(...): uses user's credentials.\n *\n * @example\n * ```typescript\n * // Service principal execution\n * const result = await analytics.query(\"SELECT * FROM table\")\n *\n * // User context execution (in route handler)\n * const result = await this.asUser(req).query(\"SELECT * FROM table\")\n * ```\n */\n async query(\n query: string,\n parameters?: Record<string, SQLTypeMarker | null | undefined>,\n formatParameters?: Record<string, any>,\n signal?: AbortSignal,\n ): Promise<any> {\n const workspaceClient = getWorkspaceClient();\n const warehouseId = await getWarehouseId();\n\n const { statement, parameters: sqlParameters } =\n this.queryProcessor.convertToSQLParameters(query, parameters);\n\n const response = await this.SQLClient.executeStatement(\n workspaceClient,\n {\n statement,\n warehouse_id: warehouseId,\n parameters: sqlParameters,\n ...formatParameters,\n },\n signal,\n );\n\n return response.result;\n }\n\n /**\n * Get Arrow-formatted data for a completed query job.\n */\n protected async getArrowData(\n workspaceClient: WorkspaceClient,\n jobId: string,\n signal?: AbortSignal,\n ): Promise<ReturnType<typeof this.SQLClient.getArrowData>> {\n return await this.SQLClient.getArrowData(workspaceClient, jobId, signal);\n }\n\n async shutdown(): Promise<void> {\n this.streamManager.abortAll();\n }\n\n private tools = {\n query: defineTool({\n description:\n \"Execute a read-only SQL query against the Databricks SQL warehouse. Only SELECT, WITH, SHOW, EXPLAIN, and DESCRIBE statements are accepted; writes are rejected. Returns the query results as JSON.\",\n schema: z.object({\n query: z\n .string()\n .describe(\n \"The SQL query to execute. Must be a SELECT, WITH, SHOW, EXPLAIN, or DESCRIBE statement.\",\n ),\n }),\n annotations: {\n effect: \"read\",\n requiresUserContext: true,\n },\n autoInheritable: true,\n execute: (args, signal) => {\n assertReadOnlySql(args.query);\n return this.query(args.query, undefined, undefined, signal);\n },\n }),\n };\n\n getAgentTools(): AgentToolDefinition[] {\n return toolsFromRegistry(this.tools);\n }\n\n async executeAgentTool(\n name: string,\n args: unknown,\n signal?: AbortSignal,\n ): Promise<unknown> {\n return executeFromRegistry(this.tools, name, args, signal);\n }\n\n /**\n * Returns the plugin's tools as a keyed record of `ToolkitEntry` markers.\n * Called by the agents plugin (via `resolveToolkitFromProvider`) to spread\n * a filtered, renamed view of the plugin's tools into an agent's tool\n * index. Inside the function form of `AgentDefinition.tools`, callers\n * reach this method via `plugins.analytics.toolkit(opts)`.\n */\n toolkit(opts?: import(\"../../core/agent/types\").ToolkitOptions) {\n return buildToolkitEntries(this.name, this.tools, opts);\n }\n\n /**\n * Returns the public exports for the analytics plugin.\n * Note: `asUser()` is automatically added by AppKit.\n */\n exports() {\n return {\n /**\n * Execute a SQL query using service principal credentials.\n */\n query: this.query,\n };\n }\n}\n\n/**\n * @internal\n */\nexport const analytics = toPlugin(AnalyticsPlugin);\n"],"mappings":";;;;;;;;;;;;;;;;;;cAYmE;AAqBnE,MAAM,SAAS,aAAa,YAAY;AAExC,IAAa,kBAAb,cAAqC,OAA+B;;CAElE,OAAO,WAAWA;CAElB,OAAiB,cAAc;CAI/B,AAAQ;CACR,AAAQ;CAER,YAAY,QAA0B;AACpC,QAAM,OAAO;AACb,OAAK,SAAS;AACd,OAAK,iBAAiB,IAAI,gBAAgB;AAE1C,OAAK,YAAY,IAAI,sBAAsB;GACzC,SAAS,OAAO;GAChB,WAAW,OAAO;GACnB,CAAC;;CAGJ,aAAa,QAAoB;AAK/B,OAAK,MAAM,QAAQ;GACjB,MAAM;GACN,QAAQ;GACR,MAAM;GACN,SAAS,OAAO,KAAsB,QAA0B;AAC9D,UAAM,KAAK,kBAAkB,KAAK,IAAI;;GAEzC,CAAC;AAEF,OAAK,MAA8B,QAAQ;GACzC,MAAM;GACN,QAAQ;GACR,MAAM;GACN,SAAS,OAAO,KAAsB,QAA0B;AAC9D,UAAM,KAAK,kBAAkB,KAAK,IAAI;;GAEzC,CAAC;;;;;;CAOJ,MAAM,kBACJ,KACA,KACe;AACf,MAAI;GACF,MAAM,EAAE,UAAU,IAAI;GACtB,MAAM,kBAAkB,oBAAoB;AAE5C,UAAO,MAAM,6CAA6C,MAAM;AAGhE,GADc,OAAO,MAAM,IAAI,EACxB,aAAa,aAAa,eAAe,CAAC,WAAW,aAAa;IACvE,QAAQ;IACR,QAAQ,KAAK;IACd,CAAC;GAEF,MAAM,SAAS,MAAM,KAAK,aAAa,iBAAiB,MAAM;AAE9D,OAAI,UAAU,gBAAgB,2BAA2B;AACzD,OAAI,UAAU,kBAAkB,OAAO,KAAK,OAAO,UAAU,CAAC;AAC9D,OAAI,UAAU,iBAAiB,uBAAuB;AAEtD,UAAO,MACL,6CACA,OAAO,KAAK,QACZ,MACD;AACD,OAAI,KAAK,OAAO,KAAK,OAAO,KAAK,CAAC;WAC3B,OAAO;AACd,UAAO,MAAM,uBAAuB,MAAM;AAC1C,OAAI,OAAO,IAAI,CAAC,KAAK;IACnB,OAAO,iBAAiB,QAAQ,MAAM,UAAU;IAChD,QAAQ,KAAK;IACd,CAAC;;;;;;;CAQN,MAAM,kBACJ,KACA,KACe;EACf,MAAM,EAAE,cAAc,IAAI;EAC1B,MAAM,EAAE,YAAY,QAAQ,YAAY,iBACtC,IAAI;EACN,MAAM,SAAS,yBAAyB,UAAU;AAGlD,SAAO,MAAM,KAAK,mCAAmC,WAAW,OAAO;AAGvE,EADc,OAAO,MAAM,IAAI,EACxB,aAAa,aAAa,eAAe,CAAC,WAAW,aAAa;GACvE;GACA;GACA,iBAAiB,aAAa,OAAO,KAAK,WAAW,CAAC,SAAS;GAC/D,QAAQ,KAAK;GACd,CAAC;AAEF,MAAI,CAAC,WAAW;AACd,OAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,yBAAyB,CAAC;AACxD;;EAGF,MAAM,cAAc,MAAM,KAAK,IAAI,YACjC,WACA,KACA,KAAK,cACN;AAED,MAAI,CAAC,aAAa;AAChB,OAAI,OAAO,IAAI,CAAC,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAClD;;EAGF,MAAM,EAAE,OAAO,aAAa;EAG5B,MAAM,WAAW,WAAW,KAAK,OAAO,IAAI,GAAG;EAC/C,MAAM,cAAc,WAAW,KAAK,cAAc,IAAI,GAAG;EAEzD,MAAM,kBACJ,WAAW,iBACP;GACE,kBAAkB;IAChB,aAAa;IACb,QAAQ;IACT;GACD,MAAM;GACP,GACD,EACE,MAAM,UACP;EAEP,MAAM,cAAc,KAAK,eAAe,UAAU,MAAM;EAiBxD,MAAM,0BAAmD,EACvD,SAhByC;GACzC,GAAG;GACH,OAAO;IACL,GAAG,cAAc;IACjB,UAAU;KACR;KACA;KACA,KAAK,UAAU,WAAW;KAC1B,KAAK,UAAU,OAAO;KACtB;KACA;KACD;IACF;GACF,EAIA;AAED,QAAM,SAAS,cACb,KACA,OAAO,WAAW;GAChB,MAAM,kBAAkB,MAAM,KAAK,eAAe,mBAChD,OACA,WACD;GAED,MAAM,SAAS,MAAM,SAAS,MAC5B,OACA,iBACA,gBAAgB,kBAChB,OACD;AAED,UAAO;IAAE,MAAM,gBAAgB;IAAM,GAAG;IAAQ;KAElD,yBACA,YACD;;;;;;;;;;;;;;;;;CAkBH,MAAM,MACJ,OACA,YACA,kBACA,QACc;EACd,MAAM,kBAAkB,oBAAoB;EAC5C,MAAM,cAAc,MAAM,gBAAgB;EAE1C,MAAM,EAAE,WAAW,YAAY,kBAC7B,KAAK,eAAe,uBAAuB,OAAO,WAAW;AAa/D,UAXiB,MAAM,KAAK,UAAU,iBACpC,iBACA;GACE;GACA,cAAc;GACd,YAAY;GACZ,GAAG;GACJ,EACD,OACD,EAEe;;;;;CAMlB,MAAgB,aACd,iBACA,OACA,QACyD;AACzD,SAAO,MAAM,KAAK,UAAU,aAAa,iBAAiB,OAAO,OAAO;;CAG1E,MAAM,WAA0B;AAC9B,OAAK,cAAc,UAAU;;CAG/B,AAAQ,QAAQ,EACd,OAAO,WAAW;EAChB,aACE;EACF,QAAQ,EAAE,OAAO,EACf,OAAO,EACJ,QAAQ,CACR,SACC,0FACD,EACJ,CAAC;EACF,aAAa;GACX,QAAQ;GACR,qBAAqB;GACtB;EACD,iBAAiB;EACjB,UAAU,MAAM,WAAW;AACzB,qBAAkB,KAAK,MAAM;AAC7B,UAAO,KAAK,MAAM,KAAK,OAAO,QAAW,QAAW,OAAO;;EAE9D,CAAC,EACH;CAED,gBAAuC;AACrC,SAAO,kBAAkB,KAAK,MAAM;;CAGtC,MAAM,iBACJ,MACA,MACA,QACkB;AAClB,SAAO,oBAAoB,KAAK,OAAO,MAAM,MAAM,OAAO;;;;;;;;;CAU5D,QAAQ,MAAwD;AAC9D,SAAO,oBAAoB,KAAK,MAAM,KAAK,OAAO,KAAK;;;;;;CAOzD,UAAU;AACR,SAAO,EAIL,OAAO,KAAK,OACb;;;;;;AAOL,MAAa,YAAY,SAAS,gBAAgB"}
@@ -1,3 +1,4 @@
1
+ import { normalizeAnalyticsFormat } from "./types.js";
1
2
  import { AnalyticsPlugin, analytics } from "./analytics.js";
2
3
 
3
4
  export { };
@@ -0,0 +1,15 @@
1
+ //#region src/plugins/analytics/types.ts
2
+ /**
3
+ * Map a (possibly legacy) AnalyticsFormat to its canonical form.
4
+ * Legacy values come from appkit/appkit-ui < 0.33.0 and can be removed
5
+ * along with the deprecated aliases once no such consumer remains.
6
+ */
7
+ function normalizeAnalyticsFormat(f) {
8
+ if (f === "JSON") return "JSON_ARRAY";
9
+ if (f === "ARROW") return "ARROW_STREAM";
10
+ return f;
11
+ }
12
+
13
+ //#endregion
14
+ export { normalizeAnalyticsFormat };
15
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../src/plugins/analytics/types.ts"],"sourcesContent":["import type { BasePluginConfig } from \"shared\";\n\nexport interface IAnalyticsConfig extends BasePluginConfig {\n timeout?: number;\n}\n\n/**\n * Supported response formats for analytics queries.\n *\n * \"JSON\" and \"ARROW\" are legacy aliases kept for backwards compatibility\n * with appkit/appkit-ui < 0.33.0 — safe to remove once no consumer is on\n * a pre-0.33.0 version. The route handler normalizes them to their\n * canonical equivalents before any downstream code reads the value.\n */\nexport type AnalyticsFormat =\n | \"JSON_ARRAY\"\n | \"ARROW_STREAM\"\n /** @deprecated Use \"JSON_ARRAY\". Safe to remove once no consumer is on appkit < 0.33.0. */\n | \"JSON\"\n /** @deprecated Use \"ARROW_STREAM\". Safe to remove once no consumer is on appkit < 0.33.0. */\n | \"ARROW\";\n\n/** Canonical (post-normalization) analytics format values. */\ntype CanonicalAnalyticsFormat = \"JSON_ARRAY\" | \"ARROW_STREAM\";\n\n/**\n * Map a (possibly legacy) AnalyticsFormat to its canonical form.\n * Legacy values come from appkit/appkit-ui < 0.33.0 and can be removed\n * along with the deprecated aliases once no such consumer remains.\n */\nexport function normalizeAnalyticsFormat(\n f: AnalyticsFormat,\n): CanonicalAnalyticsFormat {\n if (f === \"JSON\") return \"JSON_ARRAY\";\n if (f === \"ARROW\") return \"ARROW_STREAM\";\n return f;\n}\n\nexport interface IAnalyticsQueryRequest {\n parameters?: Record<string, any>;\n format?: AnalyticsFormat;\n}\n\nexport interface AnalyticsQueryResponse {\n chunk_index: number;\n row_offset: number;\n row_count: number;\n data: any[];\n}\n"],"mappings":";;;;;;AA8BA,SAAgB,yBACd,GAC0B;AAC1B,KAAI,MAAM,OAAQ,QAAO;AACzB,KAAI,MAAM,QAAS,QAAO;AAC1B,QAAO"}
@@ -0,0 +1,2 @@
1
+ import { agents } from "./agents/agents.js";
2
+ import "./agents/index.js";
@@ -0,0 +1,4 @@
1
+ import { agents } from "./agents/agents.js";
2
+ import "./agents/index.js";
3
+
4
+ export { };
@@ -1,14 +1,17 @@
1
+ import { AgentToolDefinition, ToolProvider } from "../../shared/src/agent.js";
1
2
  import { IAppRouter, ToPlugin } from "../../shared/src/plugin.js";
2
3
  import "../../shared/src/index.js";
4
+ import { ToolkitEntry, ToolkitOptions } from "../../core/agent/types.js";
3
5
  import { Plugin } from "../../plugin/plugin.js";
4
6
  import "../../plugin/index.js";
5
- import { FilePolicy, FilePolicyUser } from "./policy.js";
6
7
  import { PluginManifest, ResourceRequirement } from "../../registry/types.js";
7
8
  import "../../registry/index.js";
9
+ import "../agents/index.js";
10
+ import { FilePolicy, FilePolicyUser } from "./policy.js";
8
11
  import { FilesExport, IFilesConfig, VolumeAPI, VolumeConfig } from "./types.js";
9
12
 
10
13
  //#region src/plugins/files/plugin.d.ts
11
- declare class FilesPlugin extends Plugin {
14
+ declare class FilesPlugin extends Plugin implements ToolProvider {
12
15
  name: string;
13
16
  /** Plugin manifest declaring metadata and resource requirements. */
14
17
  static manifest: PluginManifest;
@@ -17,6 +20,7 @@ declare class FilesPlugin extends Plugin {
17
20
  private volumeConnectors;
18
21
  private volumeConfigs;
19
22
  private volumeKeys;
23
+ private tools;
20
24
  /**
21
25
  * Scans `process.env` for `DATABRICKS_VOLUME_*` keys and merges them with
22
26
  * any explicitly configured volumes. Explicit config wins for per-volume
@@ -98,9 +102,23 @@ declare class FilesPlugin extends Plugin {
98
102
  protected createVolumeAPI(volumeKey: string, user: FilePolicyUser, options?: {
99
103
  bypassPolicy?: boolean;
100
104
  }): VolumeAPI;
105
+ /**
106
+ * Builds the agent-tool registry entries for a single volume. One set of
107
+ * tools per configured volume, keyed by `${volumeKey}.${method}`.
108
+ *
109
+ * Each handler resolves the caller's identity from the current execution
110
+ * context (OBO user when the agent run is wrapped in `asUser(req)`, service
111
+ * principal otherwise in local dev) and dispatches through
112
+ * `createVolumeAPI(volumeKey, user)` so the volume's policy is enforced
113
+ * uniformly for agent and HTTP callers.
114
+ */
115
+ private _defineVolumeTools;
101
116
  private inflightWrites;
102
117
  private trackWrite;
103
118
  shutdown(): Promise<void>;
119
+ getAgentTools(): AgentToolDefinition[];
120
+ executeAgentTool(name: string, args: unknown, signal?: AbortSignal): Promise<unknown>;
121
+ toolkit(opts?: ToolkitOptions): Record<string, ToolkitEntry>;
104
122
  /**
105
123
  * Returns the programmatic API for the Files plugin.
106
124
  * Callable with a volume key to get a volume-scoped handle.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../src/plugins/files/plugin.ts"],"mappings":";;;;;;;;;;cA2Ca,WAAA,SAAoB,MAAA;EAC/B,IAAA;;SAGO,QAAA,EAAuB,cAAA;EAAA,iBACb,WAAA;EAAA,UACC,MAAA,EAAQ,YAAA;EAAA,QAElB,gBAAA;EAAA,QACA,aAAA;EAAA,QACA,UAAA;EAJkB;;;;;EAAA,OAWnB,eAAA,CAAgB,MAAA,EAAQ,YAAA,GAAe,MAAA,SAAe,YAAA;EAuIzC;;;;EAAA,OAhHb,uBAAA,CAAwB,MAAA,EAAQ,YAAA,GAAe,mBAAA;EAs9B3C;;;;EAAA,QAh8BH,YAAA;EA9DuB;;;;EAAA,QAiFjB,YAAA;EA3EI;;;;;EAAA,QA4GJ,cAAA;cAsCF,MAAA,EAAQ,YAAA;EA8CpB,YAAA,CAAa,MAAA,EAAQ,UAAA;EArLyB;;;;EAAA,QAmTtC,cAAA;EA5R8C;;;;EAAA,QAmT9C,YAAA;EAAA,QAQA,aAAA;EA3MI;;;;;;;;;EAAA,QA+NJ,oBAAA;EAAA,QAgBA,eAAA;EAAA,QAqCA,gBAAA;EAAA,QAOM,WAAA;EAAA,QA8BA,WAAA;EAAA,QAmCA,eAAA;EAAA,QAWA,UAAA;EAqIA;;;;;EAAA,QArHA,UAAA;EAAA,QAiFA,aAAA;EAAA,QAoCA,eAAA;EAAA,QAoCA,cAAA;EAAA,QAoCA,aAAA;EAAA,QAgHA,YAAA;EAAA,QA0CA,aAAA;EA4GN;;;;;;;;;;EAAA,UAxDE,eAAA,CACR,SAAA,UACA,IAAA,EAAM,cAAA,EACN,OAAA;IAAY,YAAA;EAAA,IACX,SAAA;EAAA,QAoDK,cAAA;EAAA,QAEA,UAAA;EAOF,QAAA,CAAA,GAAY,OAAA;EA6EF;;;;;;;;;;;;;;;;EA1ChB,OAAA,CAAA,GAAW,WAAA;EAkCX,YAAA,CAAA,GAAgB,MAAA;AAAA;;;;cAQL,KAAA,EAAK,QAAA,QAAA,WAAA,EAAA,YAAA;;gCAAA,UAAA"}
1
+ {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../src/plugins/files/plugin.ts"],"mappings":";;;;;;;;;;;;;cA4Da,WAAA,SAAoB,MAAA,YAAkB,YAAA;EACjD,IAAA;;SAGO,QAAA,EAAuB,cAAA;EAAA,iBACb,WAAA;EAAA,UACC,MAAA,EAAQ,YAAA;EAAA,QAElB,gBAAA;EAAA,QACA,aAAA;EAAA,QACA,UAAA;EAAA,QACA,KAAA;;;;;;SAOD,eAAA,CAAgB,MAAA,EAAQ,YAAA,GAAe,MAAA,SAAe,YAAA;EAuBtB;;;;EAAA,OAAhC,uBAAA,CAAwB,MAAA,EAAQ,YAAA,GAAe,mBAAA;EAs3BnD;;;;EAAA,QAh2BK,YAAA;EA8hCE;;;;EAAA,QA3gCI,YAAA;EAlFiB;;;;;EAAA,QAmHjB,cAAA;cAsCF,MAAA,EAAQ,YAAA;EA8CpB,YAAA,CAAa,MAAA,EAAQ,UAAA;EAnMS;;;;EAAA,QAiUtB,cAAA;EA5TA;;;;EAAA,QAmVA,YAAA;EAAA,QAQA,aAAA;EAlVsC;;;;;;;;;EAAA,QAsWtC,oBAAA;EAAA,QAgBA,eAAA;EAAA,QAqCA,gBAAA;EAAA,QAOM,WAAA;EAAA,QA8BA,WAAA;EAAA,QAmCA,eAAA;EAAA,QAWA,UAAA;EApKN;;;;;EAAA,QAoLM,UAAA;EAAA,QAiFA,aAAA;EAAA,QAoCA,eAAA;EAAA,QAoCA,cAAA;EAAA,QAoCA,aAAA;EAAA,QAgHA,YAAA;EAAA,QA0CA,aAAA;EA9LA;;;;;;;;;;EAAA,UAkPJ,eAAA,CACR,SAAA,UACA,IAAA,EAAM,cAAA,EACN,OAAA;IAAY,YAAA;EAAA,IACX,SAAA;EA4JK;;;;;;;;;;EAAA,QA9FA,kBAAA;EAAA,QA4FA,cAAA;EAAA,QAEA,UAAA;EAOF,QAAA,CAAA,GAAY,OAAA;EAmBlB,aAAA,CAAA,GAAiB,mBAAA;EAIX,gBAAA,CACJ,IAAA,UACA,IAAA,WACA,MAAA,GAAS,WAAA,GACR,OAAA;EAIH,OAAA,CAAQ,IAAA,GAJE,cAAA,GAIoD,MAAA,SAAA,YAAA;EAoBnD;;;;;AA0Cb;;;;;;;;;;;EA1CE,OAAA,CAAA,GAAW,WAAA;EAkCX,YAAA,CAAA,GAAgB,MAAA;AAAA;;;;cAQL,KAAA,EAAK,QAAA,QAAA,WAAA,EAAA,YAAA;;gCAAA,UAAA"}
@@ -1,7 +1,8 @@
1
1
  import { createLogger } from "../../logging/logger.js";
2
2
  import { AuthenticationError } from "../../errors/authentication.js";
3
3
  import { init_errors } from "../../errors/index.js";
4
- import { getCurrentUserId, getWorkspaceClient } from "../../context/execution-context.js";
4
+ import { init_user_context, isUserContext } from "../../context/user-context.js";
5
+ import { getCurrentUserId, getExecutionContext, getWorkspaceClient } from "../../context/execution-context.js";
5
6
  import { init_context } from "../../context/index.js";
6
7
  import { ResourceType } from "../../registry/types.generated.js";
7
8
  import "../../registry/index.js";
@@ -12,15 +13,19 @@ import { PolicyDeniedError, policy } from "./policy.js";
12
13
  import { contentTypeFromPath, isSafeInlineContentType, validateCustomContentTypes } from "../../connectors/files/defaults.js";
13
14
  import { FilesConnector } from "../../connectors/files/client.js";
14
15
  import "../../connectors/files/index.js";
16
+ import { buildToolkitEntries } from "../../core/agent/build-toolkit.js";
17
+ import { defineTool, executeFromRegistry, toolsFromRegistry } from "../../core/agent/tools/define-tool.js";
15
18
  import { FILES_DOWNLOAD_DEFAULTS, FILES_MAX_UPLOAD_SIZE, FILES_READ_DEFAULTS, FILES_WRITE_DEFAULTS } from "./defaults.js";
16
19
  import { parentDirectory, sanitizeFilename } from "./helpers.js";
17
20
  import manifest_default from "./manifest.js";
18
21
  import { ApiError } from "@databricks/sdk-experimental";
22
+ import { z } from "zod";
19
23
  import { STATUS_CODES } from "node:http";
20
24
  import { Readable } from "node:stream";
21
25
 
22
26
  //#region src/plugins/files/plugin.ts
23
27
  init_context();
28
+ init_user_context();
24
29
  init_errors();
25
30
  const logger = createLogger("files");
26
31
  var FilesPlugin = class FilesPlugin extends Plugin {
@@ -31,6 +36,7 @@ var FilesPlugin = class FilesPlugin extends Plugin {
31
36
  volumeConnectors = {};
32
37
  volumeConfigs = {};
33
38
  volumeKeys = [];
39
+ tools = {};
34
40
  /**
35
41
  * Scans `process.env` for `DATABRICKS_VOLUME_*` keys and merges them with
36
42
  * any explicitly configured volumes. Explicit config wins for per-volume
@@ -162,8 +168,9 @@ var FilesPlugin = class FilesPlugin extends Plugin {
162
168
  telemetry: config.telemetry,
163
169
  customContentTypes: mergedConfig.customContentTypes
164
170
  });
171
+ Object.assign(this.tools, this._defineVolumeTools(key));
172
+ if (!volumeCfg.policy) logger.warn("Volume \"%s\" has no explicit policy — defaulting to publicRead(). Set a policy in files({ volumes: { %s: { policy: ... } } }) to silence this warning.", key, key);
165
173
  }
166
- for (const key of this.volumeKeys) if (!volumes[key].policy) logger.warn("Volume \"%s\" has no explicit policy — defaulting to publicRead(). Set a policy in files({ volumes: { %s: { policy: ... } } }) to silence this warning.", key, key);
167
174
  }
168
175
  injectRoutes(router) {
169
176
  this.route(router, {
@@ -707,6 +714,108 @@ var FilesPlugin = class FilesPlugin extends Plugin {
707
714
  }
708
715
  };
709
716
  }
717
+ /**
718
+ * Builds the agent-tool registry entries for a single volume. One set of
719
+ * tools per configured volume, keyed by `${volumeKey}.${method}`.
720
+ *
721
+ * Each handler resolves the caller's identity from the current execution
722
+ * context (OBO user when the agent run is wrapped in `asUser(req)`, service
723
+ * principal otherwise in local dev) and dispatches through
724
+ * `createVolumeAPI(volumeKey, user)` so the volume's policy is enforced
725
+ * uniformly for agent and HTTP callers.
726
+ */
727
+ _defineVolumeTools(volumeKey) {
728
+ const buildUser = () => {
729
+ const ctx = getExecutionContext();
730
+ return isUserContext(ctx) ? { id: ctx.userId } : {
731
+ id: ctx.serviceUserId,
732
+ isServicePrincipal: true
733
+ };
734
+ };
735
+ const api = () => this.createVolumeAPI(volumeKey, buildUser());
736
+ return {
737
+ [`${volumeKey}.list`]: defineTool({
738
+ description: `List files and directories in the "${volumeKey}" volume`,
739
+ schema: z.object({ path: z.string().optional().describe("Directory path to list (optional, defaults to root)") }),
740
+ annotations: {
741
+ effect: "read",
742
+ requiresUserContext: true
743
+ },
744
+ autoInheritable: true,
745
+ execute: (args, signal) => {
746
+ signal?.throwIfAborted();
747
+ return api().list(args.path);
748
+ }
749
+ }),
750
+ [`${volumeKey}.read`]: defineTool({
751
+ description: `Read a text file from the "${volumeKey}" volume`,
752
+ schema: z.object({ path: z.string().describe("File path to read") }),
753
+ annotations: {
754
+ effect: "read",
755
+ requiresUserContext: true
756
+ },
757
+ autoInheritable: true,
758
+ execute: (args, signal) => {
759
+ signal?.throwIfAborted();
760
+ return api().read(args.path);
761
+ }
762
+ }),
763
+ [`${volumeKey}.exists`]: defineTool({
764
+ description: `Check if a file or directory exists in the "${volumeKey}" volume`,
765
+ schema: z.object({ path: z.string().describe("Path to check") }),
766
+ annotations: {
767
+ effect: "read",
768
+ requiresUserContext: true
769
+ },
770
+ autoInheritable: true,
771
+ execute: (args, signal) => {
772
+ signal?.throwIfAborted();
773
+ return api().exists(args.path);
774
+ }
775
+ }),
776
+ [`${volumeKey}.metadata`]: defineTool({
777
+ description: `Get metadata (size, type, last modified) for a file in the "${volumeKey}" volume`,
778
+ schema: z.object({ path: z.string().describe("File path") }),
779
+ annotations: {
780
+ effect: "read",
781
+ requiresUserContext: true
782
+ },
783
+ autoInheritable: true,
784
+ execute: (args, signal) => {
785
+ signal?.throwIfAborted();
786
+ return api().metadata(args.path);
787
+ }
788
+ }),
789
+ [`${volumeKey}.upload`]: defineTool({
790
+ description: `Upload a text file to the "${volumeKey}" volume`,
791
+ schema: z.object({
792
+ path: z.string().describe("Destination file path"),
793
+ contents: z.string().describe("File contents as a string"),
794
+ overwrite: z.boolean().optional().describe("Whether to overwrite existing file")
795
+ }),
796
+ annotations: {
797
+ effect: "destructive",
798
+ requiresUserContext: true
799
+ },
800
+ execute: (args, signal) => {
801
+ signal?.throwIfAborted();
802
+ return api().upload(args.path, args.contents, { overwrite: args.overwrite });
803
+ }
804
+ }),
805
+ [`${volumeKey}.delete`]: defineTool({
806
+ description: `Delete a file from the "${volumeKey}" volume`,
807
+ schema: z.object({ path: z.string().describe("File path to delete") }),
808
+ annotations: {
809
+ effect: "destructive",
810
+ requiresUserContext: true
811
+ },
812
+ execute: (args, signal) => {
813
+ signal?.throwIfAborted();
814
+ return api().delete(args.path);
815
+ }
816
+ })
817
+ };
818
+ }
710
819
  inflightWrites = 0;
711
820
  trackWrite(fn) {
712
821
  this.inflightWrites++;
@@ -723,6 +832,15 @@ var FilesPlugin = class FilesPlugin extends Plugin {
723
832
  if (this.inflightWrites > 0) logger.warn("Shutdown deadline reached with %d in-flight write(s) still pending.", this.inflightWrites);
724
833
  this.streamManager.abortAll();
725
834
  }
835
+ getAgentTools() {
836
+ return toolsFromRegistry(this.tools);
837
+ }
838
+ async executeAgentTool(name, args, signal) {
839
+ return executeFromRegistry(this.tools, name, args, signal);
840
+ }
841
+ toolkit(opts) {
842
+ return buildToolkitEntries(this.name, this.tools, opts);
843
+ }
726
844
  /**
727
845
  * Returns the programmatic API for the Files plugin.
728
846
  * Callable with a volume key to get a volume-scoped handle.