@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.
- package/CLAUDE.md +54 -1
- package/NOTICE.md +2 -0
- package/dist/agents/databricks.d.ts.map +1 -1
- package/dist/agents/databricks.js +8 -3
- package/dist/agents/databricks.js.map +1 -1
- package/dist/appkit/package.js +1 -1
- package/dist/beta.d.ts +16 -1
- package/dist/beta.js +14 -1
- package/dist/connectors/index.js +3 -0
- package/dist/connectors/mcp/client.d.ts +85 -0
- package/dist/connectors/mcp/client.d.ts.map +1 -0
- package/dist/connectors/mcp/client.js +296 -0
- package/dist/connectors/mcp/client.js.map +1 -0
- package/dist/connectors/mcp/host-policy.d.ts +51 -0
- package/dist/connectors/mcp/host-policy.d.ts.map +1 -0
- package/dist/connectors/mcp/host-policy.js +168 -0
- package/dist/connectors/mcp/host-policy.js.map +1 -0
- package/dist/connectors/mcp/index.d.ts +3 -0
- package/dist/connectors/mcp/index.js +4 -0
- package/dist/connectors/mcp/types.d.ts +16 -0
- package/dist/connectors/mcp/types.d.ts.map +1 -0
- package/dist/context/index.js +1 -1
- package/dist/core/agent/build-toolkit.d.ts +2 -0
- package/dist/core/agent/build-toolkit.js +45 -0
- package/dist/core/agent/build-toolkit.js.map +1 -0
- package/dist/core/agent/consume-adapter-stream.js +33 -0
- package/dist/core/agent/consume-adapter-stream.js.map +1 -0
- package/dist/core/agent/create-agent.d.ts +27 -0
- package/dist/core/agent/create-agent.d.ts.map +1 -0
- package/dist/core/agent/create-agent.js +50 -0
- package/dist/core/agent/create-agent.js.map +1 -0
- package/dist/core/agent/load-agents.d.ts +72 -0
- package/dist/core/agent/load-agents.d.ts.map +1 -0
- package/dist/core/agent/load-agents.js +268 -0
- package/dist/core/agent/load-agents.js.map +1 -0
- package/dist/core/agent/normalize-result.js +39 -0
- package/dist/core/agent/normalize-result.js.map +1 -0
- package/dist/core/agent/plugins-map.js +44 -0
- package/dist/core/agent/plugins-map.js.map +1 -0
- package/dist/core/agent/run-agent.d.ts +58 -0
- package/dist/core/agent/run-agent.d.ts.map +1 -0
- package/dist/core/agent/run-agent.js +257 -0
- package/dist/core/agent/run-agent.js.map +1 -0
- package/dist/core/agent/system-prompt.js +38 -0
- package/dist/core/agent/system-prompt.js.map +1 -0
- package/dist/core/agent/toolkit-options.js +28 -0
- package/dist/core/agent/toolkit-options.js.map +1 -0
- package/dist/core/agent/toolkit-resolver.js +44 -0
- package/dist/core/agent/toolkit-resolver.js.map +1 -0
- package/dist/core/agent/tools/define-tool.d.ts +66 -0
- package/dist/core/agent/tools/define-tool.d.ts.map +1 -0
- package/dist/core/agent/tools/define-tool.js +50 -0
- package/dist/core/agent/tools/define-tool.js.map +1 -0
- package/dist/core/agent/tools/function-tool.d.ts +38 -0
- package/dist/core/agent/tools/function-tool.d.ts.map +1 -0
- package/dist/core/agent/tools/function-tool.js +22 -0
- package/dist/core/agent/tools/function-tool.js.map +1 -0
- package/dist/core/agent/tools/hosted-tools.d.ts +47 -0
- package/dist/core/agent/tools/hosted-tools.d.ts.map +1 -0
- package/dist/core/agent/tools/hosted-tools.js +67 -0
- package/dist/core/agent/tools/hosted-tools.js.map +1 -0
- package/dist/core/agent/tools/index.d.ts +5 -0
- package/dist/core/agent/tools/index.js +7 -0
- package/dist/core/agent/tools/json-schema.js +24 -0
- package/dist/core/agent/tools/json-schema.js.map +1 -0
- package/dist/core/agent/tools/sql-policy.js +256 -0
- package/dist/core/agent/tools/sql-policy.js.map +1 -0
- package/dist/core/agent/tools/tool.d.ts +63 -0
- package/dist/core/agent/tools/tool.d.ts.map +1 -0
- package/dist/core/agent/tools/tool.js +42 -0
- package/dist/core/agent/tools/tool.js.map +1 -0
- package/dist/core/agent/types.d.ts +299 -0
- package/dist/core/agent/types.d.ts.map +1 -0
- package/dist/core/agent/types.js +12 -0
- package/dist/core/agent/types.js.map +1 -0
- package/dist/core/appkit.d.ts +1 -0
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js +31 -4
- package/dist/core/appkit.js.map +1 -1
- package/dist/core/plugin-context.d.ts +133 -0
- package/dist/core/plugin-context.d.ts.map +1 -0
- package/dist/core/plugin-context.js +220 -0
- package/dist/core/plugin-context.js.map +1 -0
- package/dist/index.d.ts +11 -11
- package/dist/internal-telemetry/appkit-log.js +19 -0
- package/dist/internal-telemetry/appkit-log.js.map +1 -0
- package/dist/internal-telemetry/config.js +15 -0
- package/dist/internal-telemetry/config.js.map +1 -0
- package/dist/internal-telemetry/index.js +4 -0
- package/dist/internal-telemetry/reporter.js +132 -0
- package/dist/internal-telemetry/reporter.js.map +1 -0
- package/dist/plugin/plugin.d.ts +18 -3
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +26 -2
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugin/to-plugin.d.ts +3 -2
- package/dist/plugin/to-plugin.d.ts.map +1 -1
- package/dist/plugin/to-plugin.js +7 -4
- package/dist/plugin/to-plugin.js.map +1 -1
- package/dist/plugins/agents/agents.d.ts +186 -0
- package/dist/plugins/agents/agents.d.ts.map +1 -0
- package/dist/plugins/agents/agents.js +979 -0
- package/dist/plugins/agents/agents.js.map +1 -0
- package/dist/plugins/agents/defaults.js +13 -0
- package/dist/plugins/agents/defaults.js.map +1 -0
- package/dist/plugins/agents/event-channel.js +64 -0
- package/dist/plugins/agents/event-channel.js.map +1 -0
- package/dist/plugins/agents/event-translator.js +224 -0
- package/dist/plugins/agents/event-translator.js.map +1 -0
- package/dist/plugins/agents/index.d.ts +4 -0
- package/dist/plugins/agents/index.js +6 -0
- package/dist/plugins/agents/manifest.js +26 -0
- package/dist/plugins/agents/manifest.js.map +1 -0
- package/dist/plugins/agents/schemas.js +51 -0
- package/dist/plugins/agents/schemas.js.map +1 -0
- package/dist/plugins/agents/thread-store.js +58 -0
- package/dist/plugins/agents/thread-store.js.map +1 -0
- package/dist/plugins/agents/tool-approval-gate.js +75 -0
- package/dist/plugins/agents/tool-approval-gate.js.map +1 -0
- package/dist/plugins/analytics/analytics.d.ts +15 -1
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js +37 -2
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/analytics/index.js +1 -0
- package/dist/plugins/analytics/types.js +15 -0
- package/dist/plugins/analytics/types.js.map +1 -0
- package/dist/plugins/beta-exports.generated.d.ts +2 -0
- package/dist/plugins/beta-exports.generated.js +4 -0
- package/dist/plugins/files/plugin.d.ts +20 -2
- package/dist/plugins/files/plugin.d.ts.map +1 -1
- package/dist/plugins/files/plugin.js +120 -2
- package/dist/plugins/files/plugin.js.map +1 -1
- package/dist/plugins/genie/genie.d.ts +17 -3
- package/dist/plugins/genie/genie.d.ts.map +1 -1
- package/dist/plugins/genie/genie.js +61 -2
- package/dist/plugins/genie/genie.js.map +1 -1
- package/dist/plugins/genie/types.d.ts +10 -2
- package/dist/plugins/genie/types.d.ts.map +1 -1
- package/dist/plugins/jobs/plugin.js +1 -1
- package/dist/plugins/lakebase/index.d.ts +2 -2
- package/dist/plugins/lakebase/index.js +1 -1
- package/dist/plugins/lakebase/lakebase.d.ts +31 -3
- package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
- package/dist/plugins/lakebase/lakebase.js +77 -5
- package/dist/plugins/lakebase/lakebase.js.map +1 -1
- package/dist/plugins/lakebase/types.d.ts +39 -1
- package/dist/plugins/lakebase/types.d.ts.map +1 -1
- package/dist/plugins/server/index.d.ts +12 -0
- package/dist/plugins/server/index.d.ts.map +1 -1
- package/dist/plugins/server/index.js +47 -10
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/types.d.ts +11 -3
- package/dist/plugins/server/types.d.ts.map +1 -1
- package/dist/shared/src/agent.d.ts +75 -1
- package/dist/shared/src/agent.d.ts.map +1 -1
- package/dist/shared/src/index.d.ts +1 -1
- package/dist/shared/src/plugin.d.ts +8 -0
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/docs/api/appkit/Class.AppKitMcpClient.md +157 -0
- package/docs/api/appkit/Class.DatabricksAdapter.md +151 -0
- package/docs/api/appkit/Class.Plugin.md +65 -23
- package/docs/api/appkit/Function.agentIdFromMarkdownPath.md +18 -0
- package/docs/api/appkit/Function.createAgent.md +33 -0
- package/docs/api/appkit/Function.createApp.md +10 -8
- package/docs/api/appkit/Function.defineTool.md +26 -0
- package/docs/api/appkit/Function.executeFromRegistry.md +25 -0
- package/docs/api/appkit/Function.functionToolToDefinition.md +16 -0
- package/docs/api/appkit/Function.isFunctionTool.md +16 -0
- package/docs/api/appkit/Function.isHostedTool.md +16 -0
- package/docs/api/appkit/Function.isToolkitEntry.md +18 -0
- package/docs/api/appkit/Function.loadAgentFromFile.md +21 -0
- package/docs/api/appkit/Function.loadAgentsFromDir.md +26 -0
- package/docs/api/appkit/Function.mcpServer.md +28 -0
- package/docs/api/appkit/Function.parseTextToolCalls.md +26 -0
- package/docs/api/appkit/Function.resolveHostedTools.md +16 -0
- package/docs/api/appkit/Function.runAgent.md +26 -0
- package/docs/api/appkit/Function.tool.md +28 -0
- package/docs/api/appkit/Function.toolsFromRegistry.md +20 -0
- package/docs/api/appkit/Interface.AgentAdapter.md +21 -0
- package/docs/api/appkit/Interface.AgentDefinition.md +112 -0
- package/docs/api/appkit/Interface.AgentInput.md +37 -0
- package/docs/api/appkit/Interface.AgentRunContext.md +32 -0
- package/docs/api/appkit/Interface.AgentToolDefinition.md +37 -0
- package/docs/api/appkit/Interface.AgentsPluginConfig.md +241 -0
- package/docs/api/appkit/Interface.AutoInheritToolsConfig.md +27 -0
- package/docs/api/appkit/Interface.BasePluginConfig.md +1 -0
- package/docs/api/appkit/Interface.FunctionTool.md +80 -0
- package/docs/api/appkit/Interface.McpConnectAllResult.md +38 -0
- package/docs/api/appkit/Interface.Message.md +55 -0
- package/docs/api/appkit/Interface.PluginToolkitProvider.md +22 -0
- package/docs/api/appkit/Interface.PromptContext.md +30 -0
- package/docs/api/appkit/Interface.RegisteredAgent.md +75 -0
- package/docs/api/appkit/Interface.RunAgentInput.md +34 -0
- package/docs/api/appkit/Interface.RunAgentResult.md +23 -0
- package/docs/api/appkit/Interface.Thread.md +46 -0
- package/docs/api/appkit/Interface.ThreadStore.md +103 -0
- package/docs/api/appkit/Interface.ToolAnnotations.md +56 -0
- package/docs/api/appkit/Interface.ToolConfig.md +72 -0
- package/docs/api/appkit/Interface.ToolEntry.md +73 -0
- package/docs/api/appkit/Interface.ToolProvider.md +38 -0
- package/docs/api/appkit/Interface.ToolkitEntry.md +59 -0
- package/docs/api/appkit/Interface.ToolkitOptions.md +45 -0
- package/docs/api/appkit/TypeAlias.AgentEvent.md +299 -0
- package/docs/api/appkit/TypeAlias.AgentTool.md +11 -0
- package/docs/api/appkit/TypeAlias.AgentTools.md +8 -0
- package/docs/api/appkit/TypeAlias.AgentToolsFn.md +20 -0
- package/docs/api/appkit/TypeAlias.BaseSystemPromptOption.md +9 -0
- package/docs/api/appkit/TypeAlias.HostedTool.md +10 -0
- package/docs/api/appkit/TypeAlias.Plugins.md +26 -0
- package/docs/api/appkit/TypeAlias.ResolvedToolEntry.md +29 -0
- package/docs/api/appkit/TypeAlias.ToolRegistry.md +6 -0
- package/docs/api/appkit/Variable.agents.md +19 -0
- package/docs/api/appkit.md +113 -62
- package/docs/plugins/agents.md +441 -0
- package/docs/privacy.md +41 -0
- package/llms.txt +54 -1
- package/package.json +4 -2
- 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":"
|
|
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 = "
|
|
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 === "
|
|
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":"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"}
|
|
@@ -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"}
|
|
@@ -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":"
|
|
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 {
|
|
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.
|