@databricks/appkit 0.68.0 → 0.69.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/dist/appkit/package.js +1 -1
- package/dist/core/agent/skills/types.d.ts +6 -1
- package/dist/core/agent/skills/types.d.ts.map +1 -1
- package/dist/plugins/agents/active-stream-tracker.js +52 -0
- package/dist/plugins/agents/active-stream-tracker.js.map +1 -0
- package/dist/plugins/agents/adapter-extensions.js +50 -0
- package/dist/plugins/agents/adapter-extensions.js.map +1 -0
- package/dist/plugins/agents/agents.d.ts +6 -77
- package/dist/plugins/agents/agents.d.ts.map +1 -1
- package/dist/plugins/agents/agents.js +57 -431
- package/dist/plugins/agents/agents.js.map +1 -1
- package/dist/plugins/agents/approval.js +28 -0
- package/dist/plugins/agents/approval.js.map +1 -0
- package/dist/plugins/agents/builtin-tools.js +39 -0
- package/dist/plugins/agents/builtin-tools.js.map +1 -0
- package/dist/plugins/agents/prompt.js +27 -0
- package/dist/plugins/agents/prompt.js.map +1 -0
- package/dist/plugins/agents/registry.js +56 -0
- package/dist/plugins/agents/registry.js.map +1 -0
- package/dist/plugins/agents/resolve-config.js +40 -0
- package/dist/plugins/agents/resolve-config.js.map +1 -0
- package/dist/plugins/agents/skill-loader.js +139 -0
- package/dist/plugins/agents/skill-loader.js.map +1 -0
- package/dist/plugins/agents/tool-dispatch.js +124 -0
- package/dist/plugins/agents/tool-dispatch.js.map +1 -0
- package/dist/plugins/server/index.js +2 -2
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js +3 -3
- package/dist/plugins/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
- package/dist/plugins/server/static-server.js +3 -3
- package/dist/plugins/server/static-server.js.map +1 -1
- package/dist/plugins/server/utils.js +3 -3
- package/dist/plugins/server/utils.js.map +1 -1
- package/dist/plugins/server/vite-dev-server.js +4 -4
- package/dist/plugins/server/vite-dev-server.js.map +1 -1
- package/dist/shared/src/schemas/manifest.d.ts +33 -33
- package/dist/type-generator/database/generate.js +3 -3
- package/dist/type-generator/database/generate.js.map +1 -1
- package/dist/type-generator/migration.js +2 -2
- package/dist/type-generator/migration.js.map +1 -1
- package/dist/type-generator/serving/server-file-extractor.js +3 -3
- package/dist/type-generator/serving/server-file-extractor.js.map +1 -1
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -6,12 +6,10 @@ import { toPlugin } from "../../plugin/to-plugin.js";
|
|
|
6
6
|
import "../../plugin/index.js";
|
|
7
7
|
import { defineManifest } from "../../registry/manifest-loader.js";
|
|
8
8
|
import "../../registry/index.js";
|
|
9
|
-
import { FilesConnector } from "../../connectors/files/client.js";
|
|
10
|
-
import "../../connectors/files/index.js";
|
|
11
9
|
import { buildMcpHostPolicy } from "../../connectors/mcp/host-policy.js";
|
|
12
10
|
import { AppKitMcpClient } from "../../connectors/mcp/client.js";
|
|
13
11
|
import "../../connectors/mcp/index.js";
|
|
14
|
-
import {
|
|
12
|
+
import { isSupervisorTool } from "../../agents/supervisor-api.js";
|
|
15
13
|
import { consumeAdapterStream } from "../../core/agent/consume-adapter-stream.js";
|
|
16
14
|
import { createPluginsProxy } from "../../core/agent/plugins-map.js";
|
|
17
15
|
import { resolveToolkitFromProvider } from "../../core/agent/toolkit-resolver.js";
|
|
@@ -20,68 +18,43 @@ import { isHostedTool, resolveHostedTools } from "../../core/agent/tools/hosted-
|
|
|
20
18
|
import { isToolkitEntry } from "../../core/agent/types.js";
|
|
21
19
|
import "../../core/agent/tools/index.js";
|
|
22
20
|
import { loadAgentsFromDir } from "../../core/agent/load-agents.js";
|
|
23
|
-
import { CODE_AGENTS_SOURCE_DIR
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import { renderLoadedSkill, renderSkillCatalog } from "../../core/agent/skills/render.js";
|
|
29
|
-
import { resolveSkill, resolveSkillCatalog } from "../../core/agent/skills/resolve-catalog.js";
|
|
30
|
-
import "../../core/agent/skills/index.js";
|
|
31
|
-
import { buildBaseSystemPrompt, composeSystemPrompt } from "../../core/agent/system-prompt.js";
|
|
21
|
+
import { CODE_AGENTS_SOURCE_DIR } from "../../core/agent/load-code-agents.js";
|
|
22
|
+
import { ActiveStreamTracker } from "./active-stream-tracker.js";
|
|
23
|
+
import { buildAdapterExtensions, supervisorToolDescription, warnOnCapabilityMismatch } from "./adapter-extensions.js";
|
|
24
|
+
import { requiresApproval } from "./approval.js";
|
|
25
|
+
import { LOAD_SKILL_TOOL_DEF, READ_SKILL_FILE_TOOL_DEF } from "./builtin-tools.js";
|
|
32
26
|
import { agentStreamDefaults } from "./defaults.js";
|
|
33
27
|
import { EventChannel } from "./event-channel.js";
|
|
34
28
|
import { AgentEventTranslator } from "./event-translator.js";
|
|
35
29
|
import manifest_default from "./manifest.js";
|
|
36
|
-
import { currentTraceId, initAgentTracing, linkTraceToRun, traceAgent
|
|
30
|
+
import { currentTraceId, initAgentTracing, linkTraceToRun, traceAgent } from "./mlflow.js";
|
|
31
|
+
import { composePromptForAgent } from "./prompt.js";
|
|
32
|
+
import { loadCodeAgents, resolveDefaultAgent } from "./registry.js";
|
|
33
|
+
import { resolveApprovalPolicy, resolveLimits } from "./resolve-config.js";
|
|
37
34
|
import { approvalRequestSchema, cancelRequestSchema, chatRequestSchema, invocationsRequestSchema } from "./schemas.js";
|
|
35
|
+
import { dispatchSkillTool, loadGlobalSkills, loadVolumeSkills, renderForcedSkill, resolveAgentSkills } from "./skill-loader.js";
|
|
38
36
|
import { InMemoryThreadStore } from "./thread-store.js";
|
|
39
37
|
import { ToolApprovalGate } from "./tool-approval-gate.js";
|
|
38
|
+
import { dispatchToolCall } from "./tool-dispatch.js";
|
|
40
39
|
import { randomUUID } from "node:crypto";
|
|
41
40
|
import pc from "picocolors";
|
|
42
41
|
import path from "node:path";
|
|
43
|
-
import { existsSync
|
|
42
|
+
import { existsSync } from "node:fs";
|
|
44
43
|
|
|
45
44
|
//#region src/plugins/agents/agents.ts
|
|
46
45
|
const logger = createLogger("agents");
|
|
47
46
|
/** Deprecated markdown location, read as a fallback with a one-time warning. */
|
|
48
47
|
const LEGACY_MARKDOWN_DIR = "config/agents";
|
|
49
|
-
/**
|
|
50
|
-
* Decide whether a tool call must traverse the approval gate. Honours both
|
|
51
|
-
* the modern `effect` field (mutating values: write / update / destructive)
|
|
52
|
-
* and the legacy `destructive: true` boolean. The contract is documented on
|
|
53
|
-
* `ToolAnnotations.effect` in shared/agent.ts.
|
|
54
|
-
*
|
|
55
|
-
* Without this, a tool authored only with `effect: "destructive"` (the
|
|
56
|
-
* preferred API) bypassed the gate entirely.
|
|
57
|
-
*/
|
|
58
|
-
function requiresApproval(annotations) {
|
|
59
|
-
if (!annotations) return false;
|
|
60
|
-
if (annotations.destructive === true) return true;
|
|
61
|
-
switch (annotations.effect) {
|
|
62
|
-
case "write":
|
|
63
|
-
case "update":
|
|
64
|
-
case "destructive": return true;
|
|
65
|
-
case "read":
|
|
66
|
-
case void 0: return false;
|
|
67
|
-
default:
|
|
68
|
-
annotations.effect;
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
48
|
var AgentsPlugin = class extends Plugin {
|
|
73
49
|
static manifest = defineManifest(manifest_default);
|
|
74
50
|
static phase = "deferred";
|
|
75
51
|
agents = /* @__PURE__ */ new Map();
|
|
76
52
|
defaultAgentName = null;
|
|
77
|
-
activeStreams = /* @__PURE__ */ new Map();
|
|
78
53
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* active stream on every request. Mutated only via {@link trackStream}
|
|
82
|
-
* and {@link untrackStream}.
|
|
54
|
+
* Active SSE streams + per-user counts (the O(1) concurrency-limit check).
|
|
55
|
+
* Mutated only via {@link trackStream} / {@link untrackStream}.
|
|
83
56
|
*/
|
|
84
|
-
|
|
57
|
+
streams = new ActiveStreamTracker();
|
|
85
58
|
mcpClient = null;
|
|
86
59
|
threadStore;
|
|
87
60
|
approvalGate = new ToolApprovalGate();
|
|
@@ -118,59 +91,22 @@ var AgentsPlugin = class extends Plugin {
|
|
|
118
91
|
cachedApprovalPolicy = null;
|
|
119
92
|
get resolvedApprovalPolicy() {
|
|
120
93
|
if (this.cachedApprovalPolicy) return this.cachedApprovalPolicy;
|
|
121
|
-
|
|
122
|
-
const APPROVAL_TIMEOUT_FLOOR_MS = 1e3;
|
|
123
|
-
const APPROVAL_TIMEOUT_DEFAULT_MS = 6e4;
|
|
124
|
-
let timeoutMs = cfg.timeoutMs ?? APPROVAL_TIMEOUT_DEFAULT_MS;
|
|
125
|
-
if (!Number.isFinite(timeoutMs) || timeoutMs < APPROVAL_TIMEOUT_FLOOR_MS) {
|
|
126
|
-
logger.warn("approval.timeoutMs=%s is below the %sms floor; using default %sms instead. Mutating tool calls would otherwise auto-deny before any UI could respond.", cfg.timeoutMs, APPROVAL_TIMEOUT_FLOOR_MS, APPROVAL_TIMEOUT_DEFAULT_MS);
|
|
127
|
-
timeoutMs = APPROVAL_TIMEOUT_DEFAULT_MS;
|
|
128
|
-
}
|
|
129
|
-
this.cachedApprovalPolicy = {
|
|
130
|
-
requireForDestructive: cfg.requireForDestructive ?? true,
|
|
131
|
-
timeoutMs
|
|
132
|
-
};
|
|
94
|
+
this.cachedApprovalPolicy = resolveApprovalPolicy(this.config);
|
|
133
95
|
return this.cachedApprovalPolicy;
|
|
134
96
|
}
|
|
135
97
|
/** Effective DoS limits with defaults applied. */
|
|
136
98
|
get resolvedLimits() {
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
maxConcurrentStreamsPerUser: cfg.maxConcurrentStreamsPerUser ?? 5,
|
|
140
|
-
maxToolCalls: cfg.maxToolCalls ?? 50,
|
|
141
|
-
maxSubAgentDepth: cfg.maxSubAgentDepth ?? 3,
|
|
142
|
-
toolCallTimeoutMs: cfg.toolCallTimeoutMs ?? 3e5
|
|
143
|
-
};
|
|
99
|
+
return resolveLimits(this.config);
|
|
144
100
|
}
|
|
145
101
|
/** Count active streams owned by a given user. O(1). */
|
|
146
102
|
countUserStreams(userId) {
|
|
147
|
-
return this.
|
|
103
|
+
return this.streams.count(userId);
|
|
148
104
|
}
|
|
149
|
-
/**
|
|
150
|
-
* Register a stream for `userId` and bump the per-user counter. Paired
|
|
151
|
-
* with {@link untrackStream}; the two helpers are the only writers to
|
|
152
|
-
* `activeStreams` + `userStreamCounts`, so the counter cannot drift from
|
|
153
|
-
* the map.
|
|
154
|
-
*/
|
|
155
105
|
trackStream(requestId, userId, controller) {
|
|
156
|
-
this.
|
|
157
|
-
controller,
|
|
158
|
-
userId
|
|
159
|
-
});
|
|
160
|
-
this.userStreamCounts.set(userId, (this.userStreamCounts.get(userId) ?? 0) + 1);
|
|
106
|
+
this.streams.track(requestId, userId, controller);
|
|
161
107
|
}
|
|
162
|
-
/**
|
|
163
|
-
* Remove a stream from the active map and decrement the per-user
|
|
164
|
-
* counter. Idempotent — calling twice for the same `requestId` is a
|
|
165
|
-
* no-op (the second call sees no entry and returns early).
|
|
166
|
-
*/
|
|
167
108
|
untrackStream(requestId) {
|
|
168
|
-
|
|
169
|
-
if (!entry) return;
|
|
170
|
-
this.activeStreams.delete(requestId);
|
|
171
|
-
const next = (this.userStreamCounts.get(entry.userId) ?? 0) - 1;
|
|
172
|
-
if (next <= 0) this.userStreamCounts.delete(entry.userId);
|
|
173
|
-
else this.userStreamCounts.set(entry.userId, next);
|
|
109
|
+
this.streams.untrack(requestId);
|
|
174
110
|
}
|
|
175
111
|
async setup() {
|
|
176
112
|
await initAgentTracing();
|
|
@@ -258,14 +194,7 @@ var AgentsPlugin = class extends Plugin {
|
|
|
258
194
|
* markdown `default: true` > first registered (insertion order).
|
|
259
195
|
*/
|
|
260
196
|
resolveDefaultAgent(agents, merged, fileDefault) {
|
|
261
|
-
|
|
262
|
-
if (!agents.has(this.config.defaultAgent)) throw new Error(`defaultAgent '${this.config.defaultAgent}' is not registered. Available: ${Array.from(agents.keys()).join(", ")}`);
|
|
263
|
-
return this.config.defaultAgent;
|
|
264
|
-
}
|
|
265
|
-
const codeDefault = Object.keys(merged).filter((id) => merged[id].src.origin === "code" && merged[id].def.default).sort()[0];
|
|
266
|
-
if (codeDefault) return codeDefault;
|
|
267
|
-
if (fileDefault && agents.has(fileDefault)) return fileDefault;
|
|
268
|
-
return agents.keys().next().value ?? null;
|
|
197
|
+
return resolveDefaultAgent(agents, merged, fileDefault, this.config.defaultAgent);
|
|
269
198
|
}
|
|
270
199
|
/**
|
|
271
200
|
* Emits the one-time deprecation warning for the `agents({ agents })` map.
|
|
@@ -288,37 +217,11 @@ var AgentsPlugin = class extends Plugin {
|
|
|
288
217
|
resolvedAgentsDir() {
|
|
289
218
|
return path.resolve(process.cwd(), CODE_AGENTS_SOURCE_DIR);
|
|
290
219
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
* the deprecated `agents({ agents })` map is carrying them instead.
|
|
296
|
-
*/
|
|
297
|
-
async loadCodeAgents() {
|
|
298
|
-
const resolved = resolveCodeAgentsDir({
|
|
299
|
-
cwd: process.cwd(),
|
|
300
|
-
exists: existsSync
|
|
220
|
+
loadCodeAgents() {
|
|
221
|
+
return loadCodeAgents({
|
|
222
|
+
agentsDir: this.resolvedAgentsDir(),
|
|
223
|
+
hasDeprecatedMap: Object.keys(this.config.agents ?? {}).length > 0
|
|
301
224
|
});
|
|
302
|
-
const discovered = await loadCodeAgentsFromDir(resolved.dir, { extensions: resolved.extensions });
|
|
303
|
-
const usingDeprecatedMap = Object.keys(this.config.agents ?? {}).length > 0;
|
|
304
|
-
const sourceDir = this.resolvedAgentsDir();
|
|
305
|
-
if (Object.keys(discovered).length === 0 && !usingDeprecatedMap && this.hasCodeAgentSources(sourceDir)) logger.warn("Found code-agent sources in %s but discovered no code agents (scanned %s). In a production build, ensure `<dir>/*/agent.ts` is included as tsdown entries so the compiled agents are emitted.", sourceDir, resolved.dir);
|
|
306
|
-
return discovered;
|
|
307
|
-
}
|
|
308
|
-
/** True when `dir` holds at least one `<id>/agent.ts` folder. */
|
|
309
|
-
hasCodeAgentSources(dir) {
|
|
310
|
-
try {
|
|
311
|
-
return readdirSync(dir, { withFileTypes: true }).some((e) => {
|
|
312
|
-
if (!e.isDirectory() && !e.isSymbolicLink()) return false;
|
|
313
|
-
try {
|
|
314
|
-
return readdirSync(path.join(dir, e.name)).includes("agent.ts");
|
|
315
|
-
} catch {
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
} catch {
|
|
320
|
-
return false;
|
|
321
|
-
}
|
|
322
225
|
}
|
|
323
226
|
async loadFileDefinitions(codeAgents) {
|
|
324
227
|
const primaryDir = this.resolvedAgentsDir();
|
|
@@ -387,15 +290,8 @@ var AgentsPlugin = class extends Plugin {
|
|
|
387
290
|
};
|
|
388
291
|
}
|
|
389
292
|
/** Loads the shared global skill pool from `<agentsDir>/skills/`. */
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (!dir) return [];
|
|
393
|
-
return loadSkillsFromDir(path.join(dir, "skills"), "bundle-global");
|
|
394
|
-
}
|
|
395
|
-
/** Configured catalog-skills volume path, or null when none is set. */
|
|
396
|
-
resolveSkillsVolume() {
|
|
397
|
-
const configured = this.config.skillsVolume ?? process.env.DATABRICKS_VOLUME_AGENT_SKILLS;
|
|
398
|
-
return configured && configured.trim() !== "" ? configured.trim() : null;
|
|
293
|
+
loadGlobalSkills() {
|
|
294
|
+
return loadGlobalSkills(this.resolvedAgentsDir());
|
|
399
295
|
}
|
|
400
296
|
/**
|
|
401
297
|
* Workspace client used to read catalog skills. v1 reads as the app service
|
|
@@ -406,82 +302,20 @@ var AgentsPlugin = class extends Plugin {
|
|
|
406
302
|
skillWorkspaceClient() {
|
|
407
303
|
return getWorkspaceClient();
|
|
408
304
|
}
|
|
409
|
-
/**
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
* folder with a `SKILL.md` becomes a `source: "volume"` skill. Best-effort:
|
|
413
|
-
* a missing volume, unavailable workspace client, or a malformed individual
|
|
414
|
-
* skill is logged and skipped rather than failing the whole registry build.
|
|
415
|
-
*/
|
|
416
|
-
async loadVolumeSkills() {
|
|
417
|
-
const volume = this.resolveSkillsVolume();
|
|
418
|
-
if (!volume) return [];
|
|
419
|
-
if ((this.config.skillCredentialMode ?? "sp") === "obo") logger.warn("skillCredentialMode 'obo' is not wired yet; reading catalog skills as the service principal.");
|
|
420
|
-
let client;
|
|
421
|
-
try {
|
|
422
|
-
client = this.skillWorkspaceClient();
|
|
423
|
-
} catch (err) {
|
|
424
|
-
logger.warn("Skipping catalog skills at '%s': no workspace client available (%s).", volume, err instanceof Error ? err.message : String(err));
|
|
425
|
-
return [];
|
|
426
|
-
}
|
|
427
|
-
const connector = new FilesConnector({ defaultVolume: volume });
|
|
428
|
-
let entries;
|
|
429
|
-
try {
|
|
430
|
-
entries = await connector.list(client, volume);
|
|
431
|
-
} catch (err) {
|
|
432
|
-
logger.warn("Failed to list catalog skills volume '%s': %s", volume, err instanceof Error ? err.message : String(err));
|
|
433
|
-
return [];
|
|
434
|
-
}
|
|
435
|
-
return (await Promise.all(entries.map(async (entry) => {
|
|
436
|
-
if (!entry.is_directory || !entry.name || !entry.path) return null;
|
|
437
|
-
const skillDir = entry.path;
|
|
438
|
-
const skillFile = `${skillDir}/SKILL.md`;
|
|
439
|
-
try {
|
|
440
|
-
const parsed = parseSkill(await connector.read(client, skillFile), skillFile);
|
|
441
|
-
const files = await this.listVolumeSkillFiles(connector, client, skillDir);
|
|
442
|
-
return {
|
|
443
|
-
name: parsed.name,
|
|
444
|
-
description: parsed.description,
|
|
445
|
-
body: parsed.body,
|
|
446
|
-
source: "volume",
|
|
447
|
-
dir: skillDir,
|
|
448
|
-
files,
|
|
449
|
-
allowedTools: parsed.allowedTools
|
|
450
|
-
};
|
|
451
|
-
} catch (err) {
|
|
452
|
-
logger.warn("Skipping catalog skill '%s': %s", skillDir, err instanceof Error ? err.message : String(err));
|
|
453
|
-
return null;
|
|
454
|
-
}
|
|
455
|
-
}))).filter((s) => s !== null);
|
|
456
|
-
}
|
|
457
|
-
/** Lists a volume skill's resource files (one level, excluding SKILL.md). */
|
|
458
|
-
async listVolumeSkillFiles(connector, client, skillDir) {
|
|
459
|
-
try {
|
|
460
|
-
return (await connector.list(client, skillDir)).filter((e) => !e.is_directory && e.name && e.name !== "SKILL.md").map((e) => e.name).sort();
|
|
461
|
-
} catch {
|
|
462
|
-
return [];
|
|
463
|
-
}
|
|
305
|
+
/** Discovers catalog skills from the configured UC Volume (best-effort). */
|
|
306
|
+
loadVolumeSkills() {
|
|
307
|
+
return loadVolumeSkills(this.config, () => this.skillWorkspaceClient());
|
|
464
308
|
}
|
|
465
|
-
/**
|
|
466
|
-
* Resolves the per-agent skill catalog: loads this agent's private skills
|
|
467
|
-
* (`<agentsDir>/<id>/skills/`, file-origin only), then applies visibility
|
|
468
|
-
* (opt-in via `def.skills` or `autoInheritSkills`) and collision rules
|
|
469
|
-
* against the shared global pool. Returns `undefined` when nothing is
|
|
470
|
-
* visible so the prompt catalog and dispatch can cheaply skip skills.
|
|
471
|
-
*/
|
|
472
309
|
async resolveAgentSkills(name, def, src) {
|
|
473
|
-
const dir = this.resolvedAgentsDir();
|
|
474
|
-
const perAgentSkills = src.origin === "file" && dir ? await loadSkillsFromDir(path.join(dir, name, "skills"), "bundle-agent") : [];
|
|
475
310
|
const inherit = normalizeAutoInherit(this.config.autoInheritSkills);
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
311
|
+
return resolveAgentSkills({
|
|
312
|
+
name,
|
|
313
|
+
def,
|
|
314
|
+
agentsDir: this.resolvedAgentsDir(),
|
|
315
|
+
isFileOrigin: src.origin === "file",
|
|
316
|
+
autoInherit: src.origin === "file" ? inherit.file : inherit.code,
|
|
317
|
+
globalSkills: this.globalSkills
|
|
483
318
|
});
|
|
484
|
-
return catalog.byAddress.size > 0 ? catalog : void 0;
|
|
485
319
|
}
|
|
486
320
|
async resolveAdapter(def, name) {
|
|
487
321
|
const source = def.model ?? this.config.defaultModel;
|
|
@@ -930,7 +764,8 @@ var AgentsPlugin = class extends Plugin {
|
|
|
930
764
|
outboundEvents,
|
|
931
765
|
toolCallsUsed: { count: 0 }
|
|
932
766
|
};
|
|
933
|
-
const
|
|
767
|
+
const deps = this.toolDispatchDeps();
|
|
768
|
+
const executeTool = (name, args) => dispatchToolCall(deps, runState, registered.toolIndex, name, args, 0);
|
|
934
769
|
const driver = (async () => {
|
|
935
770
|
try {
|
|
936
771
|
for (const evt of translator.translate({
|
|
@@ -1071,7 +906,8 @@ var AgentsPlugin = class extends Plugin {
|
|
|
1071
906
|
outboundEvents: new EventChannel(),
|
|
1072
907
|
toolCallsUsed: { count: 0 }
|
|
1073
908
|
};
|
|
1074
|
-
const
|
|
909
|
+
const deps = this.toolDispatchDeps();
|
|
910
|
+
const executeTool = (name, args) => dispatchToolCall(deps, runState, registered.toolIndex, name, args, 0);
|
|
1075
911
|
let fullContent = "";
|
|
1076
912
|
try {
|
|
1077
913
|
await traceAgent(registered.name ?? "agent", { messages: thread.messages.map((m) => ({
|
|
@@ -1152,82 +988,8 @@ var AgentsPlugin = class extends Plugin {
|
|
|
1152
988
|
output: [message]
|
|
1153
989
|
});
|
|
1154
990
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
* sub-agent. Centralising this in one method is what makes the budget
|
|
1158
|
-
* counter, approval gate, and abort signal observe sub-agent activity:
|
|
1159
|
-
* `runSubAgent` reuses the same `runState` and so increments the same
|
|
1160
|
-
* counter and emits approval events through the same channel.
|
|
1161
|
-
*
|
|
1162
|
-
* `depth` is the current sub-agent recursion depth (0 at the top level).
|
|
1163
|
-
* It is forwarded to `runSubAgent` when the dispatched entry is itself a
|
|
1164
|
-
* sub-agent, so depth limits remain enforced.
|
|
1165
|
-
*/
|
|
1166
|
-
async dispatchToolCall(runState, toolIndex, name, args, depth) {
|
|
1167
|
-
if (runState.toolCallsUsed.count >= runState.limits.maxToolCalls) {
|
|
1168
|
-
runState.abortController.abort(/* @__PURE__ */ new Error(`Tool-call budget exhausted (limit ${runState.limits.maxToolCalls}).`));
|
|
1169
|
-
throw new Error(`Tool-call budget exhausted (limit ${runState.limits.maxToolCalls}). Raise agents({ limits: { maxToolCalls } }) or review the agent's tool-selection logic.`);
|
|
1170
|
-
}
|
|
1171
|
-
runState.toolCallsUsed.count++;
|
|
1172
|
-
const entry = toolIndex.get(name);
|
|
1173
|
-
if (!entry) throw new Error(`Unknown tool: ${name}`);
|
|
1174
|
-
if (runState.approvalPolicy.requireForDestructive && requiresApproval(entry.def.annotations)) {
|
|
1175
|
-
const approvalId = randomUUID();
|
|
1176
|
-
for (const ev of runState.translator.translate({
|
|
1177
|
-
type: "approval_pending",
|
|
1178
|
-
approvalId,
|
|
1179
|
-
streamId: runState.requestId,
|
|
1180
|
-
toolName: name,
|
|
1181
|
-
args,
|
|
1182
|
-
annotations: entry.def.annotations
|
|
1183
|
-
})) runState.outboundEvents.push(ev);
|
|
1184
|
-
if (await this.approvalGate.wait({
|
|
1185
|
-
approvalId,
|
|
1186
|
-
streamId: runState.requestId,
|
|
1187
|
-
userId: runState.userId,
|
|
1188
|
-
timeoutMs: runState.approvalPolicy.timeoutMs
|
|
1189
|
-
}) === "deny") return `Tool execution denied by user approval gate (tool: ${name}).`;
|
|
1190
|
-
}
|
|
1191
|
-
return normalizeToolResult(await traceTool(name, args, async () => {
|
|
1192
|
-
let result;
|
|
1193
|
-
if (entry.source === "toolkit") {
|
|
1194
|
-
if (!this.context) throw new Error("Plugin tool execution requires PluginContext; this should never happen through createApp");
|
|
1195
|
-
result = await this.context.executeTool(runState.req, entry.pluginName, entry.localName, args, runState.signal, runState.limits.toolCallTimeoutMs);
|
|
1196
|
-
} else if (entry.source === "function") {
|
|
1197
|
-
if (typeof args !== "object" || args === null || Array.isArray(args)) throw new Error(`Function tool '${name}' received non-object arguments (got ${args === null ? "null" : Array.isArray(args) ? "array" : typeof args}); expected a JSON object.`);
|
|
1198
|
-
result = await entry.functionTool.execute(args);
|
|
1199
|
-
} else if (entry.source === "mcp") {
|
|
1200
|
-
if (!this.mcpClient) throw new Error("MCP client not connected");
|
|
1201
|
-
const oboToken = runState.req.headers["x-forwarded-access-token"];
|
|
1202
|
-
const mcpAuth = typeof oboToken === "string" ? { Authorization: `Bearer ${oboToken}` } : void 0;
|
|
1203
|
-
result = await this.mcpClient.callTool(entry.mcpToolName, args, mcpAuth);
|
|
1204
|
-
} else if (entry.source === "subagent") {
|
|
1205
|
-
const childAgent = this.agents.get(entry.agentName);
|
|
1206
|
-
if (!childAgent) throw new Error(`Sub-agent not found: ${entry.agentName}`);
|
|
1207
|
-
result = await this.runSubAgent(runState, childAgent, args, depth + 1);
|
|
1208
|
-
} else if (entry.source === "hosted-supervisor") throw new Error(`Tool '${name}' is a hosted-supervisor tool and cannot be invoked from the Node process. It is executed server-side by the Databricks AI Gateway and is only reachable when the agent's model is a Supervisor API adapter.`);
|
|
1209
|
-
else if (entry.source === "skill") result = await this.dispatchSkillTool(entry, args);
|
|
1210
|
-
return result;
|
|
1211
|
-
}));
|
|
1212
|
-
}
|
|
1213
|
-
/**
|
|
1214
|
-
* Executes the built-in `load_skill` / `read_skill_file` tools against the
|
|
1215
|
-
* agent's resolved skill catalog. `load_skill` returns a skill's body plus a
|
|
1216
|
-
* manifest of bundled files; `read_skill_file` returns the contents of one
|
|
1217
|
-
* of those files (bundle skills only in v1 — catalog-volume resource reads
|
|
1218
|
-
* arrive with the volume source).
|
|
1219
|
-
*/
|
|
1220
|
-
async dispatchSkillTool(entry, args) {
|
|
1221
|
-
const obj = typeof args === "object" && args !== null ? args : {};
|
|
1222
|
-
const skillName = typeof obj.skill === "string" ? obj.skill.trim() : "";
|
|
1223
|
-
if (!skillName) throw new Error(`'${entry.builtin}' requires a 'skill' argument naming the skill to use.`);
|
|
1224
|
-
const skill = resolveSkill(entry.catalog, skillName);
|
|
1225
|
-
if (entry.builtin === "load_skill") return renderLoadedSkill(skill);
|
|
1226
|
-
const filePath = typeof obj.path === "string" ? obj.path.trim() : "";
|
|
1227
|
-
if (!filePath) throw new Error("'read_skill_file' requires a 'path' argument.");
|
|
1228
|
-
if (!skill.files.includes(filePath)) throw new Error(`Skill '${skill.name}' has no bundled file '${filePath}'. Available: ${skill.files.join(", ") || "<none>"}.`);
|
|
1229
|
-
if (skill.source === "volume") return new FilesConnector({ defaultVolume: skill.dir }).read(this.skillWorkspaceClient(), `${skill.dir}/${filePath}`);
|
|
1230
|
-
return readSkillResource(skill.dir, filePath);
|
|
991
|
+
dispatchSkillTool(entry, args) {
|
|
992
|
+
return dispatchSkillTool(entry, args, () => this.skillWorkspaceClient());
|
|
1231
993
|
}
|
|
1232
994
|
/**
|
|
1233
995
|
* Renders the prompt addendum for a force-loaded skill (`/skill-name`).
|
|
@@ -1236,67 +998,19 @@ var AgentsPlugin = class extends Plugin {
|
|
|
1236
998
|
* model proceeds with the catalog + `load_skill` still available.
|
|
1237
999
|
*/
|
|
1238
1000
|
renderForcedSkill(registered, name) {
|
|
1239
|
-
|
|
1240
|
-
try {
|
|
1241
|
-
const skill = resolveSkill(registered.skills, name);
|
|
1242
|
-
return `The user explicitly requested the "${skill.name}" skill for this turn. Its instructions:\n\n${renderLoadedSkill(skill)}`;
|
|
1243
|
-
} catch (err) {
|
|
1244
|
-
logger.warn("Ignoring forced skill '%s': %s", name, err instanceof Error ? err.message : String(err));
|
|
1245
|
-
return null;
|
|
1246
|
-
}
|
|
1001
|
+
return renderForcedSkill(registered, name);
|
|
1247
1002
|
}
|
|
1248
|
-
/**
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
* Sub-agent tool calls run through `dispatchToolCall` with the same
|
|
1259
|
-
* `runState` as the parent — the budget counter and approval gate are
|
|
1260
|
-
* therefore enforced for every nested call, not only at the top level.
|
|
1261
|
-
*/
|
|
1262
|
-
async runSubAgent(runState, child, args, depth) {
|
|
1263
|
-
if (depth > runState.limits.maxSubAgentDepth) throw new Error(`Sub-agent depth exceeded (limit ${runState.limits.maxSubAgentDepth}). Raise agents({ limits: { maxSubAgentDepth } }) or break the delegation cycle.`);
|
|
1264
|
-
const input = typeof args === "object" && args !== null && typeof args.input === "string" ? args.input : JSON.stringify(args);
|
|
1265
|
-
const childTools = Array.from(child.toolIndex.values()).filter((e) => e.source !== "hosted-supervisor").map((e) => e.def);
|
|
1266
|
-
const childExecute = (name, childArgs) => this.dispatchToolCall(runState, child.toolIndex, name, childArgs, depth);
|
|
1267
|
-
const runContext = {
|
|
1268
|
-
executeTool: childExecute,
|
|
1269
|
-
signal: runState.signal
|
|
1003
|
+
/** Collaborators the tool-dispatch path needs, bound to this instance. */
|
|
1004
|
+
toolDispatchDeps() {
|
|
1005
|
+
return {
|
|
1006
|
+
approvalGate: this.approvalGate,
|
|
1007
|
+
context: this.context,
|
|
1008
|
+
getMcpClient: () => this.mcpClient,
|
|
1009
|
+
agents: this.agents,
|
|
1010
|
+
dispatchSkillTool: (entry, args) => this.dispatchSkillTool(entry, args),
|
|
1011
|
+
pluginName: this.name,
|
|
1012
|
+
baseSystemPrompt: this.config.baseSystemPrompt
|
|
1270
1013
|
};
|
|
1271
|
-
const pluginNames = this.context ? this.context.getPluginNames().filter((n) => n !== this.name && n !== "server") : [];
|
|
1272
|
-
const messages = [{
|
|
1273
|
-
id: "system",
|
|
1274
|
-
role: "system",
|
|
1275
|
-
content: composePromptForAgent(child, this.config.baseSystemPrompt, {
|
|
1276
|
-
agentName: child.name,
|
|
1277
|
-
pluginNames,
|
|
1278
|
-
toolNames: childTools.map((t) => t.name)
|
|
1279
|
-
}),
|
|
1280
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
1281
|
-
}, {
|
|
1282
|
-
id: randomUUID(),
|
|
1283
|
-
role: "user",
|
|
1284
|
-
content: input,
|
|
1285
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
1286
|
-
}];
|
|
1287
|
-
return consumeAdapterStream(child.adapter.run({
|
|
1288
|
-
messages,
|
|
1289
|
-
tools: childTools,
|
|
1290
|
-
threadId: randomUUID(),
|
|
1291
|
-
signal: runState.signal,
|
|
1292
|
-
extensions: buildAdapterExtensions(child.toolIndex)
|
|
1293
|
-
}, runContext), {
|
|
1294
|
-
signal: runState.signal,
|
|
1295
|
-
onEvent: (event) => {
|
|
1296
|
-
if (event.type === "metadata") return;
|
|
1297
|
-
for (const translated of runState.translator.translate(event)) runState.outboundEvents.push(translated);
|
|
1298
|
-
}
|
|
1299
|
-
});
|
|
1300
1014
|
}
|
|
1301
1015
|
async _handleCancel(req, res) {
|
|
1302
1016
|
const parsed = cancelRequestSchema.safeParse(req.body);
|
|
@@ -1308,7 +1022,7 @@ var AgentsPlugin = class extends Plugin {
|
|
|
1308
1022
|
return;
|
|
1309
1023
|
}
|
|
1310
1024
|
const { streamId } = parsed.data;
|
|
1311
|
-
const entry = this.
|
|
1025
|
+
const entry = this.streams.get(streamId);
|
|
1312
1026
|
if (!entry) {
|
|
1313
1027
|
res.json({ cancelled: true });
|
|
1314
1028
|
return;
|
|
@@ -1333,7 +1047,7 @@ var AgentsPlugin = class extends Plugin {
|
|
|
1333
1047
|
return;
|
|
1334
1048
|
}
|
|
1335
1049
|
const { streamId, approvalId, decision } = parsed.data;
|
|
1336
|
-
const streamEntry = this.
|
|
1050
|
+
const streamEntry = this.streams.get(streamId);
|
|
1337
1051
|
if (!streamEntry) {
|
|
1338
1052
|
res.status(404).json({ error: "Stream not found or already completed" });
|
|
1339
1053
|
return;
|
|
@@ -1436,94 +1150,6 @@ function normalizeAutoInherit(value) {
|
|
|
1436
1150
|
code: value.code ?? false
|
|
1437
1151
|
};
|
|
1438
1152
|
}
|
|
1439
|
-
/** Built-in tool the model calls to load a skill's full instructions on demand. */
|
|
1440
|
-
const LOAD_SKILL_TOOL_DEF = {
|
|
1441
|
-
name: "load_skill",
|
|
1442
|
-
description: "Load the full instructions for one of the available skills by name. Call this before acting on a task that matches a skill's description. Returns the skill's instructions plus a list of any bundled files you can read with read_skill_file.",
|
|
1443
|
-
parameters: {
|
|
1444
|
-
type: "object",
|
|
1445
|
-
properties: { skill: {
|
|
1446
|
-
type: "string",
|
|
1447
|
-
description: "The exact skill name to load, as shown in the available-skills list."
|
|
1448
|
-
} },
|
|
1449
|
-
required: ["skill"]
|
|
1450
|
-
},
|
|
1451
|
-
annotations: { effect: "read" }
|
|
1452
|
-
};
|
|
1453
|
-
/** Built-in tool for reading a resource file that a loaded skill references. */
|
|
1454
|
-
const READ_SKILL_FILE_TOOL_DEF = {
|
|
1455
|
-
name: "read_skill_file",
|
|
1456
|
-
description: "Read a bundled resource file that a loaded skill references (e.g. a reference doc). Only files listed by load_skill for that skill are readable.",
|
|
1457
|
-
parameters: {
|
|
1458
|
-
type: "object",
|
|
1459
|
-
properties: {
|
|
1460
|
-
skill: {
|
|
1461
|
-
type: "string",
|
|
1462
|
-
description: "The skill that owns the file."
|
|
1463
|
-
},
|
|
1464
|
-
path: {
|
|
1465
|
-
type: "string",
|
|
1466
|
-
description: "Relative path of the file within the skill, as listed by load_skill."
|
|
1467
|
-
}
|
|
1468
|
-
},
|
|
1469
|
-
required: ["skill", "path"]
|
|
1470
|
-
},
|
|
1471
|
-
annotations: { effect: "read" }
|
|
1472
|
-
};
|
|
1473
|
-
function composePromptForAgent(registered, pluginLevel, ctx) {
|
|
1474
|
-
const perAgent = registered.baseSystemPrompt;
|
|
1475
|
-
const resolved = perAgent !== void 0 ? perAgent : pluginLevel;
|
|
1476
|
-
let base = "";
|
|
1477
|
-
if (resolved === false) base = "";
|
|
1478
|
-
else if (typeof resolved === "string") base = resolved;
|
|
1479
|
-
else if (typeof resolved === "function") base = resolved(ctx);
|
|
1480
|
-
else base = buildBaseSystemPrompt(ctx);
|
|
1481
|
-
const composed = composeSystemPrompt(base, registered.instructions);
|
|
1482
|
-
const catalog = registered.skills?.catalog;
|
|
1483
|
-
if (!catalog || catalog.length === 0) return composed;
|
|
1484
|
-
return `${composed}\n\n${renderSkillCatalog(catalog)}`;
|
|
1485
|
-
}
|
|
1486
|
-
/**
|
|
1487
|
-
* Pulls the LLM-readable description off any {@link SupervisorTool} kind.
|
|
1488
|
-
* Used to populate the synthetic placeholder `def.description` on
|
|
1489
|
-
* hosted-supervisor tool-index entries.
|
|
1490
|
-
*/
|
|
1491
|
-
function supervisorToolDescription(spec) {
|
|
1492
|
-
switch (spec.type) {
|
|
1493
|
-
case "genie_space": return spec.genie_space.description;
|
|
1494
|
-
case "uc_function": return spec.uc_function.description;
|
|
1495
|
-
case "knowledge_assistant": return spec.knowledge_assistant.description;
|
|
1496
|
-
case "app": return spec.app.description;
|
|
1497
|
-
case "uc_connection": return spec.uc_connection.description;
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
/**
|
|
1501
|
-
* Builds the `AgentInput.extensions` payload from a tool index, aggregating
|
|
1502
|
-
* the hosted-supervisor specs under {@link SUPERVISOR_EXTENSION_KEY}. Returns
|
|
1503
|
-
* `undefined` when there are no adapter-side hosted tools so the field stays
|
|
1504
|
-
* absent on the wire — adapters that don't read extensions never see it.
|
|
1505
|
-
*/
|
|
1506
|
-
function buildAdapterExtensions(toolIndex) {
|
|
1507
|
-
const supervisorSpecs = [];
|
|
1508
|
-
for (const entry of toolIndex.values()) if (entry.source === "hosted-supervisor") supervisorSpecs.push(entry.spec);
|
|
1509
|
-
if (supervisorSpecs.length === 0) return void 0;
|
|
1510
|
-
return { [SUPERVISOR_EXTENSION_KEY]: { hostedTools: supervisorSpecs } };
|
|
1511
|
-
}
|
|
1512
|
-
/**
|
|
1513
|
-
* Compares the adapter's declared capabilities against the tool index and
|
|
1514
|
-
* logs a warning when the agent's tool declarations would be silently
|
|
1515
|
-
* dropped at runtime. Warn-not-throw: misconfiguration is loud enough to
|
|
1516
|
-
* notice without taking the whole app down.
|
|
1517
|
-
*/
|
|
1518
|
-
function warnOnCapabilityMismatch(agentName, adapter, toolIndex) {
|
|
1519
|
-
const accepted = new Set(adapter.acceptsExtensions ?? []);
|
|
1520
|
-
const hostedSupervisorKeys = [];
|
|
1521
|
-
const inputToolKeys = [];
|
|
1522
|
-
for (const [key, entry] of toolIndex) if (entry.source === "hosted-supervisor") hostedSupervisorKeys.push(key);
|
|
1523
|
-
else inputToolKeys.push(key);
|
|
1524
|
-
if (hostedSupervisorKeys.length > 0 && !accepted.has(SUPERVISOR_EXTENSION_KEY)) logger.warn(`Agent '${agentName}' declares hosted-supervisor tools (${hostedSupervisorKeys.join(", ")}) but its model adapter does not accept the 'databricks.supervisor' extension. These tools will not reach the model. Pair them with \`DatabricksAdapter.fromSupervisorApi(...)\`, or remove them.`);
|
|
1525
|
-
if (adapter.consumesInputTools === false && inputToolKeys.length > 0) logger.warn(`Agent '${agentName}' declares function tools / sub-agents / MCP tools (${inputToolKeys.join(", ")}) but its model adapter does not consume input.tools (Supervisor API owns its own tool loop). These tools will not be exposed to the model. See docs/plugins/agents.md.`);
|
|
1526
|
-
}
|
|
1527
1153
|
/**
|
|
1528
1154
|
* Plugin factory for the agents plugin. Discovers agents from
|
|
1529
1155
|
* `server/agents/<id>/agent.{ts,md}` by default (markdown still in
|