@databricks/appkit 0.32.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/CLAUDE.md +53 -1
  2. package/NOTICE.md +1 -0
  3. package/dist/agents/databricks.d.ts.map +1 -1
  4. package/dist/agents/databricks.js +8 -3
  5. package/dist/agents/databricks.js.map +1 -1
  6. package/dist/appkit/package.js +1 -1
  7. package/dist/beta.d.ts +5 -3
  8. package/dist/beta.js +3 -1
  9. package/dist/connectors/mcp/client.d.ts +27 -2
  10. package/dist/connectors/mcp/client.d.ts.map +1 -1
  11. package/dist/connectors/mcp/client.js +117 -18
  12. package/dist/connectors/mcp/client.js.map +1 -1
  13. package/dist/connectors/mcp/index.d.ts +1 -1
  14. package/dist/connectors/mcp/types.d.ts +1 -1
  15. package/dist/core/agent/build-toolkit.js +3 -8
  16. package/dist/core/agent/build-toolkit.js.map +1 -1
  17. package/dist/core/agent/load-agents.d.ts +6 -1
  18. package/dist/core/agent/load-agents.d.ts.map +1 -1
  19. package/dist/core/agent/load-agents.js +67 -27
  20. package/dist/core/agent/load-agents.js.map +1 -1
  21. package/dist/core/agent/plugins-map.js +44 -0
  22. package/dist/core/agent/plugins-map.js.map +1 -0
  23. package/dist/core/agent/run-agent.d.ts +31 -7
  24. package/dist/core/agent/run-agent.d.ts.map +1 -1
  25. package/dist/core/agent/run-agent.js +138 -27
  26. package/dist/core/agent/run-agent.js.map +1 -1
  27. package/dist/core/agent/toolkit-options.js +28 -0
  28. package/dist/core/agent/toolkit-options.js.map +1 -0
  29. package/dist/core/agent/toolkit-resolver.js +44 -0
  30. package/dist/core/agent/toolkit-resolver.js.map +1 -0
  31. package/dist/core/agent/tools/define-tool.d.ts +14 -2
  32. package/dist/core/agent/tools/define-tool.d.ts.map +1 -1
  33. package/dist/core/agent/tools/define-tool.js +1 -1
  34. package/dist/core/agent/tools/define-tool.js.map +1 -1
  35. package/dist/core/agent/tools/function-tool.d.ts +13 -2
  36. package/dist/core/agent/tools/function-tool.d.ts.map +1 -1
  37. package/dist/core/agent/tools/function-tool.js +4 -3
  38. package/dist/core/agent/tools/function-tool.js.map +1 -1
  39. package/dist/core/agent/tools/index.d.ts +1 -1
  40. package/dist/core/agent/tools/tool.d.ts +32 -3
  41. package/dist/core/agent/tools/tool.d.ts.map +1 -1
  42. package/dist/core/agent/tools/tool.js +4 -3
  43. package/dist/core/agent/tools/tool.js.map +1 -1
  44. package/dist/core/agent/types.d.ts +95 -10
  45. package/dist/core/agent/types.d.ts.map +1 -1
  46. package/dist/core/agent/types.js.map +1 -1
  47. package/dist/plugin/index.d.ts +1 -1
  48. package/dist/plugin/to-plugin.d.ts +3 -13
  49. package/dist/plugin/to-plugin.d.ts.map +1 -1
  50. package/dist/plugin/to-plugin.js +1 -8
  51. package/dist/plugin/to-plugin.js.map +1 -1
  52. package/dist/plugins/agents/agents.d.ts +184 -2
  53. package/dist/plugins/agents/agents.d.ts.map +1 -0
  54. package/dist/plugins/agents/agents.js +169 -72
  55. package/dist/plugins/agents/agents.js.map +1 -1
  56. package/dist/plugins/agents/index.d.ts +2 -2
  57. package/dist/plugins/agents/index.js +1 -1
  58. package/dist/plugins/agents/manifest.js +4 -5
  59. package/dist/plugins/agents/tool-approval-gate.js.map +1 -1
  60. package/dist/plugins/analytics/analytics.d.ts +3 -4
  61. package/dist/plugins/analytics/analytics.d.ts.map +1 -1
  62. package/dist/plugins/analytics/analytics.js +8 -6
  63. package/dist/plugins/analytics/analytics.js.map +1 -1
  64. package/dist/plugins/analytics/index.js +1 -0
  65. package/dist/plugins/analytics/types.js +15 -0
  66. package/dist/plugins/analytics/types.js.map +1 -0
  67. package/dist/plugins/beta-exports.generated.d.ts +2 -0
  68. package/dist/plugins/beta-exports.generated.js +4 -0
  69. package/dist/plugins/files/plugin.d.ts +1 -2
  70. package/dist/plugins/files/plugin.d.ts.map +1 -1
  71. package/dist/plugins/files/plugin.js +30 -12
  72. package/dist/plugins/files/plugin.js.map +1 -1
  73. package/dist/plugins/genie/genie.d.ts +5 -4
  74. package/dist/plugins/genie/genie.d.ts.map +1 -1
  75. package/dist/plugins/genie/genie.js +22 -8
  76. package/dist/plugins/genie/genie.js.map +1 -1
  77. package/dist/plugins/genie/types.d.ts +10 -2
  78. package/dist/plugins/genie/types.d.ts.map +1 -1
  79. package/dist/plugins/jobs/plugin.d.ts +1 -2
  80. package/dist/plugins/jobs/plugin.d.ts.map +1 -1
  81. package/dist/plugins/lakebase/lakebase.d.ts +1 -2
  82. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  83. package/dist/plugins/lakebase/lakebase.js +3 -3
  84. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  85. package/dist/plugins/lakebase/types.d.ts +5 -4
  86. package/dist/plugins/lakebase/types.d.ts.map +1 -1
  87. package/dist/plugins/server/index.d.ts +3 -2
  88. package/dist/plugins/server/index.d.ts.map +1 -1
  89. package/dist/plugins/server/index.js +8 -5
  90. package/dist/plugins/server/index.js.map +1 -1
  91. package/dist/plugins/server/types.d.ts +11 -0
  92. package/dist/plugins/server/types.d.ts.map +1 -1
  93. package/dist/plugins/serving/serving.d.ts +1 -2
  94. package/dist/plugins/serving/serving.d.ts.map +1 -1
  95. package/dist/shared/src/agent.d.ts +16 -4
  96. package/dist/shared/src/agent.d.ts.map +1 -1
  97. package/docs/api/appkit/Class.AppKitMcpClient.md +157 -0
  98. package/docs/api/appkit/Class.DatabricksAdapter.md +151 -0
  99. package/docs/api/appkit/Function.agentIdFromMarkdownPath.md +18 -0
  100. package/docs/api/appkit/Function.createAgent.md +33 -0
  101. package/docs/api/appkit/Function.defineTool.md +26 -0
  102. package/docs/api/appkit/Function.executeFromRegistry.md +25 -0
  103. package/docs/api/appkit/Function.functionToolToDefinition.md +16 -0
  104. package/docs/api/appkit/Function.isFunctionTool.md +16 -0
  105. package/docs/api/appkit/Function.isHostedTool.md +16 -0
  106. package/docs/api/appkit/Function.isToolkitEntry.md +18 -0
  107. package/docs/api/appkit/Function.loadAgentFromFile.md +21 -0
  108. package/docs/api/appkit/Function.loadAgentsFromDir.md +26 -0
  109. package/docs/api/appkit/Function.mcpServer.md +28 -0
  110. package/docs/api/appkit/Function.parseTextToolCalls.md +26 -0
  111. package/docs/api/appkit/Function.resolveHostedTools.md +16 -0
  112. package/docs/api/appkit/Function.runAgent.md +26 -0
  113. package/docs/api/appkit/Function.tool.md +28 -0
  114. package/docs/api/appkit/Function.toolsFromRegistry.md +20 -0
  115. package/docs/api/appkit/Interface.AgentAdapter.md +21 -0
  116. package/docs/api/appkit/Interface.AgentDefinition.md +112 -0
  117. package/docs/api/appkit/Interface.AgentInput.md +37 -0
  118. package/docs/api/appkit/Interface.AgentRunContext.md +32 -0
  119. package/docs/api/appkit/Interface.AgentToolDefinition.md +37 -0
  120. package/docs/api/appkit/Interface.AgentsPluginConfig.md +241 -0
  121. package/docs/api/appkit/Interface.AutoInheritToolsConfig.md +27 -0
  122. package/docs/api/appkit/Interface.BasePluginConfig.md +1 -0
  123. package/docs/api/appkit/Interface.FunctionTool.md +80 -0
  124. package/docs/api/appkit/Interface.McpConnectAllResult.md +38 -0
  125. package/docs/api/appkit/Interface.Message.md +55 -0
  126. package/docs/api/appkit/Interface.PluginToolkitProvider.md +22 -0
  127. package/docs/api/appkit/Interface.PromptContext.md +30 -0
  128. package/docs/api/appkit/Interface.RegisteredAgent.md +75 -0
  129. package/docs/api/appkit/Interface.RunAgentInput.md +34 -0
  130. package/docs/api/appkit/Interface.RunAgentResult.md +23 -0
  131. package/docs/api/appkit/Interface.Thread.md +46 -0
  132. package/docs/api/appkit/Interface.ThreadStore.md +103 -0
  133. package/docs/api/appkit/Interface.ToolAnnotations.md +56 -0
  134. package/docs/api/appkit/Interface.ToolConfig.md +72 -0
  135. package/docs/api/appkit/Interface.ToolEntry.md +73 -0
  136. package/docs/api/appkit/Interface.ToolProvider.md +38 -0
  137. package/docs/api/appkit/Interface.ToolkitEntry.md +59 -0
  138. package/docs/api/appkit/Interface.ToolkitOptions.md +45 -0
  139. package/docs/api/appkit/TypeAlias.AgentEvent.md +299 -0
  140. package/docs/api/appkit/TypeAlias.AgentTool.md +11 -0
  141. package/docs/api/appkit/TypeAlias.AgentTools.md +8 -0
  142. package/docs/api/appkit/TypeAlias.AgentToolsFn.md +20 -0
  143. package/docs/api/appkit/TypeAlias.BaseSystemPromptOption.md +9 -0
  144. package/docs/api/appkit/TypeAlias.HostedTool.md +10 -0
  145. package/docs/api/appkit/TypeAlias.Plugins.md +26 -0
  146. package/docs/api/appkit/TypeAlias.ResolvedToolEntry.md +29 -0
  147. package/docs/api/appkit/TypeAlias.ToolRegistry.md +6 -0
  148. package/docs/api/appkit/Variable.agents.md +19 -0
  149. package/docs/api/appkit.md +113 -62
  150. package/docs/plugins/agents.md +441 -0
  151. package/llms.txt +53 -1
  152. package/package.json +1 -1
  153. package/sbom.cdx.json +1 -1
@@ -6,6 +6,8 @@ import { buildMcpHostPolicy } from "../../connectors/mcp/host-policy.js";
6
6
  import { AppKitMcpClient } from "../../connectors/mcp/client.js";
7
7
  import "../../connectors/mcp/index.js";
8
8
  import { consumeAdapterStream } from "../../core/agent/consume-adapter-stream.js";
9
+ import { createPluginsProxy } from "../../core/agent/plugins-map.js";
10
+ import { resolveToolkitFromProvider } from "../../core/agent/toolkit-resolver.js";
9
11
  import { functionToolToDefinition, isFunctionTool } from "../../core/agent/tools/function-tool.js";
10
12
  import { isHostedTool, resolveHostedTools } from "../../core/agent/tools/hosted-tools.js";
11
13
  import { isToolkitEntry } from "../../core/agent/types.js";
@@ -56,6 +58,13 @@ var AgentsPlugin = class extends Plugin {
56
58
  agents = /* @__PURE__ */ new Map();
57
59
  defaultAgentName = null;
58
60
  activeStreams = /* @__PURE__ */ new Map();
61
+ /**
62
+ * Per-user stream count, kept in sync with `activeStreams` so the
63
+ * concurrent-stream rate limit check is O(1) instead of O(n) over every
64
+ * active stream on every request. Mutated only via {@link trackStream}
65
+ * and {@link untrackStream}.
66
+ */
67
+ userStreamCounts = /* @__PURE__ */ new Map();
59
68
  mcpClient = null;
60
69
  threadStore;
61
70
  approvalGate = new ToolApprovalGate();
@@ -69,13 +78,32 @@ var AgentsPlugin = class extends Plugin {
69
78
  else logger.info("Using default InMemoryThreadStore (dev-only — threads are lost on restart and grow without bound).");
70
79
  }
71
80
  }
72
- /** Effective approval policy with defaults applied. */
81
+ /**
82
+ * Effective approval policy with defaults applied. Memoised so the
83
+ * `timeoutMs` validation warning fires at most once per plugin instance —
84
+ * `resolvedApprovalPolicy` gets hit on every chat stream and a noisy
85
+ * misconfig would otherwise spam the logs.
86
+ *
87
+ * `timeoutMs` is clamped to a 1s floor so a misconfigured value (`0`,
88
+ * negative, or `NaN`) can't degrade into immediate auto-denial of every
89
+ * mutating tool call.
90
+ */
91
+ cachedApprovalPolicy = null;
73
92
  get resolvedApprovalPolicy() {
93
+ if (this.cachedApprovalPolicy) return this.cachedApprovalPolicy;
74
94
  const cfg = this.config.approval ?? {};
75
- return {
95
+ const APPROVAL_TIMEOUT_FLOOR_MS = 1e3;
96
+ const APPROVAL_TIMEOUT_DEFAULT_MS = 6e4;
97
+ let timeoutMs = cfg.timeoutMs ?? APPROVAL_TIMEOUT_DEFAULT_MS;
98
+ if (!Number.isFinite(timeoutMs) || timeoutMs < APPROVAL_TIMEOUT_FLOOR_MS) {
99
+ 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);
100
+ timeoutMs = APPROVAL_TIMEOUT_DEFAULT_MS;
101
+ }
102
+ this.cachedApprovalPolicy = {
76
103
  requireForDestructive: cfg.requireForDestructive ?? true,
77
- timeoutMs: cfg.timeoutMs ?? 6e4
104
+ timeoutMs
78
105
  };
106
+ return this.cachedApprovalPolicy;
79
107
  }
80
108
  /** Effective DoS limits with defaults applied. */
81
109
  get resolvedLimits() {
@@ -87,11 +115,35 @@ var AgentsPlugin = class extends Plugin {
87
115
  toolCallTimeoutMs: cfg.toolCallTimeoutMs ?? 3e5
88
116
  };
89
117
  }
90
- /** Count active streams owned by a given user. */
118
+ /** Count active streams owned by a given user. O(1). */
91
119
  countUserStreams(userId) {
92
- let n = 0;
93
- for (const entry of this.activeStreams.values()) if (entry.userId === userId) n++;
94
- return n;
120
+ return this.userStreamCounts.get(userId) ?? 0;
121
+ }
122
+ /**
123
+ * Register a stream for `userId` and bump the per-user counter. Paired
124
+ * with {@link untrackStream}; the two helpers are the only writers to
125
+ * `activeStreams` + `userStreamCounts`, so the counter cannot drift from
126
+ * the map.
127
+ */
128
+ trackStream(requestId, userId, controller) {
129
+ this.activeStreams.set(requestId, {
130
+ controller,
131
+ userId
132
+ });
133
+ this.userStreamCounts.set(userId, (this.userStreamCounts.get(userId) ?? 0) + 1);
134
+ }
135
+ /**
136
+ * Remove a stream from the active map and decrement the per-user
137
+ * counter. Idempotent — calling twice for the same `requestId` is a
138
+ * no-op (the second call sees no entry and returns early).
139
+ */
140
+ untrackStream(requestId) {
141
+ const entry = this.activeStreams.get(requestId);
142
+ if (!entry) return;
143
+ this.activeStreams.delete(requestId);
144
+ const next = (this.userStreamCounts.get(entry.userId) ?? 0) - 1;
145
+ if (next <= 0) this.userStreamCounts.delete(entry.userId);
146
+ else this.userStreamCounts.set(entry.userId, next);
95
147
  }
96
148
  async setup() {
97
149
  const { agents, defaultAgentName } = await this.buildAgentRegistry();
@@ -108,10 +160,6 @@ var AgentsPlugin = class extends Plugin {
108
160
  */
109
161
  async reload() {
110
162
  const next = await this.buildAgentRegistry();
111
- if (this.mcpClient) {
112
- await this.mcpClient.close();
113
- this.mcpClient = null;
114
- }
115
163
  this.agents = next.agents;
116
164
  this.defaultAgentName = next.defaultAgentName;
117
165
  }
@@ -180,7 +228,9 @@ var AgentsPlugin = class extends Plugin {
180
228
  }
181
229
  /**
182
230
  * Builds the map of plugin-name → toolkit that the markdown loader consults
183
- * when resolving `toolkits:` frontmatter entries.
231
+ * when resolving `plugin:NAME` entries in the unified `tools:` frontmatter
232
+ * list (and, equivalently, that the code form passes as the `plugins`
233
+ * argument to `tools(plugins) => Record<...>`).
184
234
  */
185
235
  pluginProviderIndex() {
186
236
  const out = /* @__PURE__ */ new Map();
@@ -215,7 +265,7 @@ var AgentsPlugin = class extends Plugin {
215
265
  try {
216
266
  return await DatabricksAdapter.fromModelServing(void 0, adapterOptions);
217
267
  } catch (err) {
218
- throw new Error(`Agent '${name}' has no model configured and no DATABRICKS_AGENT_ENDPOINT default available`, { cause: err instanceof Error ? err : void 0 });
268
+ throw new Error(`Agent '${name}' has no model configured and no DATABRICKS_SERVING_ENDPOINT_NAME default available`, { cause: err instanceof Error ? err : void 0 });
219
269
  }
220
270
  }
221
271
  if (typeof source === "string") {
@@ -231,10 +281,11 @@ var AgentsPlugin = class extends Plugin {
231
281
  */
232
282
  async buildToolIndex(agentName, def, src) {
233
283
  const index = /* @__PURE__ */ new Map();
234
- const hasExplicitTools = def.tools && Object.keys(def.tools).length > 0;
284
+ const hasDeclaredTools = def.tools !== void 0;
285
+ const toolsRecord = this.resolveDefTools(agentName, def);
235
286
  const hasExplicitSubAgents = def.agents && Object.keys(def.agents).length > 0;
236
287
  const inheritDefaults = normalizeAutoInherit(this.config.autoInheritTools);
237
- if (!hasExplicitTools && !hasExplicitSubAgents && (src.origin === "file" ? inheritDefaults.file : inheritDefaults.code)) await this.applyAutoInherit(agentName, index);
288
+ if (!hasDeclaredTools && !hasExplicitSubAgents && (src.origin === "file" ? inheritDefaults.file : inheritDefaults.code)) await this.applyAutoInherit(agentName, index);
238
289
  for (const [childKey, childDef] of Object.entries(def.agents ?? {})) {
239
290
  const toolName = `agent-${childKey}`;
240
291
  index.set(toolName, {
@@ -255,7 +306,7 @@ var AgentsPlugin = class extends Plugin {
255
306
  });
256
307
  }
257
308
  const hostedToCollect = [];
258
- for (const [key, tool] of Object.entries(def.tools ?? {})) {
309
+ for (const [key, tool] of Object.entries(toolsRecord)) {
259
310
  if (isToolkitEntry(tool)) {
260
311
  index.set(key, {
261
312
  source: "toolkit",
@@ -288,6 +339,44 @@ var AgentsPlugin = class extends Plugin {
288
339
  if (hostedToCollect.length > 0) await this.connectHostedTools(hostedToCollect, index);
289
340
  return index;
290
341
  }
342
+ /**
343
+ * Resolves an `AgentDefinition.tools` field to a plain tool record. The
344
+ * function form is invoked exactly once at agent setup with the typed
345
+ * {@link Plugins} map; the result replaces the function reference for the
346
+ * remainder of the registered agent's lifetime.
347
+ *
348
+ * Plain object form is returned as-is; an undefined `tools` returns an
349
+ * empty record. The function form is wrapped in a try/catch so a thrown
350
+ * callback fails registration with a useful message instead of leaking
351
+ * the raw stack.
352
+ */
353
+ resolveDefTools(agentName, def) {
354
+ if (typeof def.tools !== "function") return def.tools ?? {};
355
+ try {
356
+ return def.tools(this.buildPluginsMap());
357
+ } catch (err) {
358
+ throw new Error(`Agent '${agentName}': tools(plugins) callback threw: ${err instanceof Error ? err.message : String(err)}`, { cause: err instanceof Error ? err : void 0 });
359
+ }
360
+ }
361
+ /**
362
+ * Builds the typed {@link Plugins} map passed to the function form of
363
+ * `AgentDefinition.tools`. Each entry exposes the plugin instance directly
364
+ * (so user code can call typed instance methods including `.toolkit()`);
365
+ * plugins missing `.toolkit()` get a synthesized fallback that walks
366
+ * `getAgentTools()` via `resolveToolkitFromProvider`.
367
+ *
368
+ * Wrapped in {@link createPluginsProxy} so that accessing an unknown
369
+ * plugin name throws a named "not registered, Available: ..." error
370
+ * instead of bubbling up a generic `Cannot read properties of undefined`
371
+ * from the agent's `tools(plugins)` callback.
372
+ */
373
+ buildPluginsMap() {
374
+ const out = {};
375
+ if (!this.context) return createPluginsProxy(out, `Agent '${this.name}': tools(plugins)`);
376
+ for (const { name, provider } of this.context.getToolProviders()) if (typeof provider.toolkit === "function") out[name] = provider;
377
+ else out[name] = { toolkit: (opts) => resolveToolkitFromProvider(name, provider, opts) };
378
+ return createPluginsProxy(out, `Agent '${this.name}': tools(plugins)`);
379
+ }
291
380
  async applyAutoInherit(agentName, index) {
292
381
  if (!this.context) return;
293
382
  const inherited = [];
@@ -299,29 +388,23 @@ var AgentsPlugin = class extends Plugin {
299
388
  };
300
389
  for (const { name: pluginName, provider } of this.context.getToolProviders()) {
301
390
  if (pluginName === this.name) continue;
302
- const withToolkit = provider;
303
- if (typeof withToolkit.toolkit === "function") {
304
- const entries = withToolkit.toolkit();
305
- for (const [key, maybeEntry] of Object.entries(entries)) {
306
- if (!isToolkitEntry(maybeEntry)) continue;
307
- if (maybeEntry.autoInheritable !== true) {
308
- recordSkip(maybeEntry.pluginName, maybeEntry.localName);
309
- continue;
310
- }
311
- index.set(key, {
312
- source: "toolkit",
313
- pluginName: maybeEntry.pluginName,
314
- localName: maybeEntry.localName,
315
- def: {
316
- ...maybeEntry.def,
317
- name: key
318
- }
319
- });
320
- inherited.push(key);
391
+ const entries = resolveToolkitFromProvider(pluginName, provider);
392
+ for (const [key, entry] of Object.entries(entries)) {
393
+ if (entry.autoInheritable !== true) {
394
+ recordSkip(entry.pluginName, entry.localName);
395
+ continue;
321
396
  }
322
- continue;
397
+ index.set(key, {
398
+ source: "toolkit",
399
+ pluginName: entry.pluginName,
400
+ localName: entry.localName,
401
+ def: {
402
+ ...entry.def,
403
+ name: key
404
+ }
405
+ });
406
+ inherited.push(key);
323
407
  }
324
- for (const tool of provider.getAgentTools()) recordSkip(pluginName, tool.name);
325
408
  }
326
409
  if (inherited.length > 0) logger.info("[agent %s] auto-inherited %d tool(s): %s", agentName, inherited.length, inherited.join(", "));
327
410
  if (skippedByPlugin.size > 0) {
@@ -358,7 +441,8 @@ var AgentsPlugin = class extends Plugin {
358
441
  this.mcpClient = new AppKitMcpClient(host, authenticate, policy);
359
442
  }
360
443
  const endpoints = resolveHostedTools(hostedTools);
361
- await this.mcpClient.connectAll(endpoints);
444
+ const result = await this.mcpClient.connectAll(endpoints);
445
+ if (result.failed.length > 0) logger.warn("MCP: %s of %s endpoints failed to connect (%s). Agents that reference these endpoints will boot without their hosted tools.", result.failed.length, endpoints.length, result.failed.map((f) => f.name).join(", "));
362
446
  for (const def of this.mcpClient.getAllToolDefinitions()) index.set(def.name, {
363
447
  source: "mcp",
364
448
  mcpToolName: def.name,
@@ -454,19 +538,26 @@ var AgentsPlugin = class extends Plugin {
454
538
  res.status(429).json({ error: `Too many concurrent streams for this user (limit ${limits.maxConcurrentStreamsPerUser}). Wait for an existing stream to complete before starting another.` });
455
539
  return;
456
540
  }
457
- let thread = threadId ? await this.threadStore.get(threadId, userId) : null;
458
- if (threadId && !thread) {
459
- res.status(404).json({ error: `Thread ${threadId} not found` });
541
+ let thread;
542
+ try {
543
+ const existing = threadId ? await this.threadStore.get(threadId, userId) : null;
544
+ if (threadId && !existing) {
545
+ res.status(404).json({ error: `Thread ${threadId} not found` });
546
+ return;
547
+ }
548
+ thread = existing ?? await this.threadStore.create(userId);
549
+ const userMessage = {
550
+ id: randomUUID(),
551
+ role: "user",
552
+ content: message,
553
+ createdAt: /* @__PURE__ */ new Date()
554
+ };
555
+ await this.threadStore.addMessage(thread.id, userId, userMessage);
556
+ } catch (err) {
557
+ logger.error("threadStore failed in /chat: %O", err);
558
+ res.status(500).json({ error: "Thread operation failed" });
460
559
  return;
461
560
  }
462
- if (!thread) thread = await this.threadStore.create(userId);
463
- const userMessage = {
464
- id: randomUUID(),
465
- role: "user",
466
- content: message,
467
- createdAt: /* @__PURE__ */ new Date()
468
- };
469
- await this.threadStore.addMessage(thread.id, userId, userMessage);
470
561
  return this._streamAgent(req, res, registered, thread, userId);
471
562
  }
472
563
  async _handleInvocations(req, res) {
@@ -491,23 +582,30 @@ var AgentsPlugin = class extends Plugin {
491
582
  res.status(429).json({ error: `Too many concurrent streams for this user (limit ${limits.maxConcurrentStreamsPerUser}). Wait for an existing stream to complete before starting another.` });
492
583
  return;
493
584
  }
494
- const thread = await this.threadStore.create(userId);
495
- if (typeof input === "string") await this.threadStore.addMessage(thread.id, userId, {
496
- id: randomUUID(),
497
- role: "user",
498
- content: input,
499
- createdAt: /* @__PURE__ */ new Date()
500
- });
501
- else for (const item of input) {
502
- const role = item.role ?? "user";
503
- const content = typeof item.content === "string" ? item.content : JSON.stringify(item.content ?? "");
504
- if (!content) continue;
505
- await this.threadStore.addMessage(thread.id, userId, {
585
+ let thread;
586
+ try {
587
+ thread = await this.threadStore.create(userId);
588
+ if (typeof input === "string") await this.threadStore.addMessage(thread.id, userId, {
506
589
  id: randomUUID(),
507
- role,
508
- content,
590
+ role: "user",
591
+ content: input,
509
592
  createdAt: /* @__PURE__ */ new Date()
510
593
  });
594
+ else for (const item of input) {
595
+ const role = item.role ?? "user";
596
+ const content = typeof item.content === "string" ? item.content : JSON.stringify(item.content ?? "");
597
+ if (!content) continue;
598
+ await this.threadStore.addMessage(thread.id, userId, {
599
+ id: randomUUID(),
600
+ role,
601
+ content,
602
+ createdAt: /* @__PURE__ */ new Date()
603
+ });
604
+ }
605
+ } catch (err) {
606
+ logger.error("threadStore failed in /invocations: %O", err);
607
+ res.status(500).json({ error: "Thread operation failed" });
608
+ return;
511
609
  }
512
610
  return this._streamAgent(req, res, registered, thread, userId);
513
611
  }
@@ -515,10 +613,7 @@ var AgentsPlugin = class extends Plugin {
515
613
  const abortController = new AbortController();
516
614
  const signal = abortController.signal;
517
615
  const requestId = randomUUID();
518
- this.activeStreams.set(requestId, {
519
- controller: abortController,
520
- userId
521
- });
616
+ this.trackStream(requestId, userId, abortController);
522
617
  const tools = Array.from(registered.toolIndex.values()).map((e) => e.def);
523
618
  const approvalPolicy = this.resolvedApprovalPolicy;
524
619
  const limits = this.resolvedLimits;
@@ -585,7 +680,7 @@ var AgentsPlugin = class extends Plugin {
585
680
  return;
586
681
  } finally {
587
682
  this.approvalGate.abortStream(requestId);
588
- this.activeStreams.delete(requestId);
683
+ this.untrackStream(requestId);
589
684
  if (registered.ephemeral) try {
590
685
  await this.threadStore.delete(thread.id, userId);
591
686
  } catch (err) {
@@ -648,8 +743,10 @@ var AgentsPlugin = class extends Plugin {
648
743
  if (entry.source === "toolkit") {
649
744
  if (!this.context) throw new Error("Plugin tool execution requires PluginContext; this should never happen through createApp");
650
745
  result = await this.context.executeTool(runState.req, entry.pluginName, entry.localName, args, runState.signal, runState.limits.toolCallTimeoutMs);
651
- } else if (entry.source === "function") result = await entry.functionTool.execute(args);
652
- else if (entry.source === "mcp") {
746
+ } else if (entry.source === "function") {
747
+ 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.`);
748
+ result = await entry.functionTool.execute(args);
749
+ } else if (entry.source === "mcp") {
653
750
  if (!this.mcpClient) throw new Error("MCP client not connected");
654
751
  const oboToken = runState.req.headers["x-forwarded-access-token"];
655
752
  const mcpAuth = typeof oboToken === "string" ? { Authorization: `Bearer ${oboToken}` } : void 0;
@@ -734,7 +831,7 @@ var AgentsPlugin = class extends Plugin {
734
831
  return;
735
832
  }
736
833
  entry.controller.abort("Cancelled by user");
737
- this.activeStreams.delete(streamId);
834
+ this.untrackStream(streamId);
738
835
  this.approvalGate.abortStream(streamId);
739
836
  res.json({ cancelled: true });
740
837
  }
@@ -878,5 +975,5 @@ function composePromptForAgent(registered, pluginLevel, ctx) {
878
975
  const agents = toPlugin(AgentsPlugin);
879
976
 
880
977
  //#endregion
881
- export { AgentsPlugin };
978
+ export { AgentsPlugin, agents };
882
979
  //# sourceMappingURL=agents.js.map