@dbx-tools/appkit-mastra 0.1.92 → 0.1.94

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/index.d.ts CHANGED
@@ -17,6 +17,7 @@ import { Memory } from "@mastra/memory";
17
17
  import { PgVectorConfig, PostgresStore, PostgresStoreConfig } from "@mastra/pg";
18
18
  import { Pool } from "pg";
19
19
  import { MastraServer } from "@mastra/express";
20
+ import { Workspace } from "@mastra/core/workspace";
20
21
  import * as _mastra_core_workflows0 from "@mastra/core/workflows";
21
22
  import * as _mastra_core_vector0 from "@mastra/core/vector";
22
23
  import * as _mastra_core_tts0 from "@mastra/core/tts";
@@ -576,7 +577,7 @@ declare class MemoryBuilder {
576
577
  * the `requireApproval` flow) will not be available.
577
578
  *
578
579
  * The store lives in a dedicated `mastra_instance` schema so it
579
- * never collides with per-agent `mastra_<agentId>` namespaces.
580
+ * never collides with per-agent {@link agentStorageSchemaName} namespaces.
580
581
  * Workflow snapshots are not per-agent state; they belong to the
581
582
  * `Mastra` instance that owns the workflow execution.
582
583
  */
@@ -764,6 +765,8 @@ interface MastraPluginToolkitProvider {
764
765
  type MastraPlugins = Record<string, MastraPluginToolkitProvider>;
765
766
  /** Function form of {@link MastraAgentDefinition.tools}. */
766
767
  type MastraToolsFn = (plugins: MastraPlugins) => MastraTools | Promise<MastraTools>;
768
+ /** Function form of {@link MastraAgentDefinition.workspace}. */
769
+ type MastraAgentWorkspaceResolver = () => Workspace | undefined;
767
770
  /**
768
771
  * A code-defined Mastra agent. Mirrors the shape AppKit's `agents`
769
772
  * plugin uses for `AgentDefinition`. The registry key under
@@ -817,7 +820,7 @@ interface MastraAgentDefinition {
817
820
  *
818
821
  * - `undefined` (default): inherit `config.storage`. When that's
819
822
  * enabled, the agent gets its **own per-agent `PostgresStore`**
820
- * keyed by `schemaName: "mastra_<agentId>"` so threads and
823
+ * keyed by {@link agentStorageSchemaName} so threads and
821
824
  * messages stay isolated between agents in the same database.
822
825
  * - `false`: disable storage for this agent only (purely in-memory).
823
826
  * - `true`: enable with the per-agent default schema.
@@ -825,6 +828,16 @@ interface MastraAgentDefinition {
825
828
  * `PostgresStore` config (custom schema, connection, etc.).
826
829
  */
827
830
  storage?: boolean | MastraStorageConfigOverride;
831
+ /**
832
+ * Mastra {@link Workspace} for this agent. When it exposes skills,
833
+ * Mastra injects `<available_skills>` metadata each turn and mounts
834
+ * `skill` / `skill_search` / `skill_read` at runtime.
835
+ *
836
+ * Use a zero-arg function so resolution happens at agent registration
837
+ * (for example `skillWorkspace` from `@dbx-tools/appkit-skills`
838
+ * after `aiDevKit()` has primed the cache).
839
+ */
840
+ workspace?: Workspace | MastraAgentWorkspaceResolver;
828
841
  }
829
842
  /**
830
843
  * Distributive `Omit` so unions in `PostgresStoreConfig` /
@@ -1404,4 +1417,4 @@ declare function buildSummarizeTool(config: MastraPluginConfig): _mastra_core_to
1404
1417
  maxWords?: number | undefined;
1405
1418
  }, unknown, unknown, unknown, _mastra_core_tools0.ToolExecutionContext<unknown, unknown, unknown>, "summarize", unknown>;
1406
1419
  //#endregion
1407
- export { AppKitToolOptions, BuiltAgents, ChartPlannerRequest, DEFAULT_AGENT_MAX_STEPS, DEFAULT_GENIE_ALIAS, DEFAULT_STYLE_INSTRUCTIONS, FALLBACK_AGENT_ID, FetchChartOptions, GENIE_INSTRUCTIONS, GenieSpaceConfig, GenieSpacesConfig, MASTRA_MODEL_OVERRIDE_KEY, MASTRA_REQUEST_ID_KEY, MASTRA_USER_EMAIL_KEY, MASTRA_USER_KEY, MASTRA_USER_NAME_KEY, MastraAgentDefinition, MastraMcpConfig, MastraMemoryConfig, MastraMemoryConfigOverride, MastraPlugin, MastraPluginConfig, MastraPluginToolkitProvider, MastraPlugins, MastraStorageConfigOverride, MastraTools, MastraToolsFn, type ModelOverrideRequest, PrepareChartOptions, ResolvedMcp, type SummarizeOptions, TRACE_REQUEST_CONTEXT_KEYS, ToolkitOptions, User, approvalGatedToolIds, buildAgents, buildGenieToolkitProvider, buildGenieTools, buildMcpServer, buildRenderDataTool, buildSummarizeTool, chartPlannerRequestSchema, collectSpaceSuggestions, createAgent, createTool, extractModelOverride, fetchChart, mastra, normalizeGenieSpaces, prepareChart, resolveGenieSpaces, summarizeText, tool };
1420
+ export { AppKitToolOptions, BuiltAgents, ChartPlannerRequest, DEFAULT_AGENT_MAX_STEPS, DEFAULT_GENIE_ALIAS, DEFAULT_STYLE_INSTRUCTIONS, FALLBACK_AGENT_ID, FetchChartOptions, GENIE_INSTRUCTIONS, GenieSpaceConfig, GenieSpacesConfig, MASTRA_MODEL_OVERRIDE_KEY, MASTRA_REQUEST_ID_KEY, MASTRA_USER_EMAIL_KEY, MASTRA_USER_KEY, MASTRA_USER_NAME_KEY, MastraAgentDefinition, MastraAgentWorkspaceResolver, MastraMcpConfig, MastraMemoryConfig, MastraMemoryConfigOverride, MastraPlugin, MastraPluginConfig, MastraPluginToolkitProvider, MastraPlugins, MastraStorageConfigOverride, MastraTools, MastraToolsFn, type ModelOverrideRequest, PrepareChartOptions, ResolvedMcp, type SummarizeOptions, TRACE_REQUEST_CONTEXT_KEYS, ToolkitOptions, User, approvalGatedToolIds, buildAgents, buildGenieToolkitProvider, buildGenieTools, buildMcpServer, buildRenderDataTool, buildSummarizeTool, chartPlannerRequestSchema, collectSpaceSuggestions, createAgent, createTool, extractModelOverride, fetchChart, mastra, normalizeGenieSpaces, prepareChart, resolveGenieSpaces, summarizeText, tool };
package/dist/index.js CHANGED
@@ -76,6 +76,127 @@ const TRACE_REQUEST_CONTEXT_KEYS = [
76
76
  "mastra__model_override"
77
77
  ];
78
78
 
79
+ //#endregion
80
+ //#region packages/appkit-mastra/src/serving-sanitize.ts
81
+ /**
82
+ * Repairs Mastra / AI SDK message replays sent to Databricks Model
83
+ * Serving before they hit the OpenAI-compatible `/chat/completions`
84
+ * route.
85
+ */
86
+ const REASONING_PART_TYPES = new Set([
87
+ "reasoning",
88
+ "thinking",
89
+ "redacted_thinking"
90
+ ]);
91
+ /**
92
+ * Parse, sanitize, and re-serialize a `/serving-endpoints/...` POST
93
+ * body. Returns the original string verbatim when the body is not
94
+ * JSON, has no `messages`, or no rewrite was needed.
95
+ */
96
+ function rewriteServingBody(body) {
97
+ let parsed;
98
+ try {
99
+ parsed = JSON.parse(body);
100
+ } catch {
101
+ return body;
102
+ }
103
+ if (!Array.isArray(parsed.messages)) return body;
104
+ const messages = parsed.messages;
105
+ return stripReasoningFromServingMessages(messages) || repairAssistantPrefill(messages) ? JSON.stringify(parsed) : body;
106
+ }
107
+ /**
108
+ * Drop extended-thinking / reasoning blocks from a replayed transcript.
109
+ *
110
+ * Hybrid Claude endpoints (e.g. Sonnet 4.5+) may emit `reasoning` content
111
+ * parts on the first turn. Mastra persists and replays them on the next
112
+ * agent step, but Databricks-hosted Claude rejects those blocks on
113
+ * multi-turn tool continuations. The UI already captured reasoning for
114
+ * display; stripping here keeps provider replay compatible without
115
+ * changing what users see in the chat bubble.
116
+ */
117
+ function stripReasoningFromServingMessages(messages) {
118
+ let changed = false;
119
+ for (let i = messages.length - 1; i >= 0; i--) {
120
+ const msg = messages[i];
121
+ if (!msg) continue;
122
+ if (msg.role === "reasoning") {
123
+ messages.splice(i, 1);
124
+ changed = true;
125
+ continue;
126
+ }
127
+ if (msg.reasoning !== void 0) {
128
+ delete msg.reasoning;
129
+ changed = true;
130
+ }
131
+ if (msg.reasoning_content !== void 0) {
132
+ delete msg.reasoning_content;
133
+ changed = true;
134
+ }
135
+ if (!Array.isArray(msg.content)) continue;
136
+ const filtered = msg.content.filter((part) => {
137
+ const type = part?.type;
138
+ if (typeof type === "string" && REASONING_PART_TYPES.has(type)) {
139
+ changed = true;
140
+ return false;
141
+ }
142
+ return true;
143
+ });
144
+ if (filtered.length !== msg.content.length) msg.content = filtered;
145
+ const hasToolCalls = Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;
146
+ if (msg.role === "assistant" && !hasToolCalls && isEmptyServingContent(msg.content)) {
147
+ messages.splice(i, 1);
148
+ changed = true;
149
+ }
150
+ }
151
+ return changed;
152
+ }
153
+ /**
154
+ * Repair a Mastra/AI SDK message replay that Databricks-hosted Claude
155
+ * rejects with `"This model does not support assistant message
156
+ * prefill. The conversation must end with a user message."`.
157
+ *
158
+ * The bug pattern: when an assistant turn streams text *and* a
159
+ * `tool_call`, the AI SDK persists them as two separate assistant
160
+ * entries (text-only and tool-call-only). On the next agent step the
161
+ * tool-call entry is replayed *before* the tool result and the
162
+ * text entry is replayed *after* it, so the conversation ends with a
163
+ * trailing assistant text message. Anthropic interprets that as a
164
+ * prefill request and rejects it on Databricks (the upstream Bedrock
165
+ * route disallows prefill).
166
+ *
167
+ * Fix: when the last message is an assistant text with no `tool_calls`
168
+ * and the chain immediately before it is `assistant(tool_calls=...)`
169
+ * followed only by `tool(...)` results, fold the trailing text back
170
+ * into the `content` of that opening assistant and drop the duplicate.
171
+ */
172
+ function repairAssistantPrefill(messages) {
173
+ if (messages.length < 2) return false;
174
+ const last = messages[messages.length - 1];
175
+ if (!last || last.role !== "assistant" || last.tool_calls && last.tool_calls.length > 0) return false;
176
+ let i = messages.length - 2;
177
+ while (i >= 0 && messages[i]?.role === "tool") i--;
178
+ if (i < 0) return false;
179
+ const opener = messages[i];
180
+ if (!opener || opener.role !== "assistant" || !opener.tool_calls || opener.tool_calls.length === 0) return false;
181
+ opener.content = [stringUtils.trimToNull(textFromServingContent(opener.content)), stringUtils.trimToNull(textFromServingContent(last.content))].filter((s) => s !== null).join("\n\n");
182
+ messages.pop();
183
+ return true;
184
+ }
185
+ function textFromServingContent(content) {
186
+ if (typeof content === "string") return content;
187
+ if (!Array.isArray(content)) return "";
188
+ return content.filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n\n");
189
+ }
190
+ function isEmptyServingContent(content) {
191
+ if (content === void 0) return true;
192
+ if (typeof content === "string") return content.trim().length === 0;
193
+ if (!Array.isArray(content)) return true;
194
+ return content.every((part) => {
195
+ if (part?.type === "text") return typeof part.text !== "string" || part.text.trim().length === 0;
196
+ return false;
197
+ });
198
+ }
199
+
79
200
  //#endregion
80
201
  //#region packages/appkit-mastra/src/serving.ts
81
202
  /**
@@ -219,7 +340,7 @@ const SERVING_ENDPOINTS_PATH_PREFIX = "/serving-endpoints/";
219
340
  *
220
341
  * 1. Rewrites the outgoing `messages` array to repair Mastra/AI SDK
221
342
  * stream-replay quirks that Databricks-hosted Claude rejects (see
222
- * {@link sanitizeServingMessages}).
343
+ * {@link rewriteServingBody} in `./serving-sanitize.js`).
223
344
  * 2. At `LOG_LEVEL=debug`, dumps the (post-sanitize) JSON body so
224
345
  * 4xx debugging doesn't have to fight AI SDK's `[Array]`
225
346
  * formatter.
@@ -254,61 +375,6 @@ const setupFetchInterceptor = commonUtils.memoize(() => {
254
375
  return original(input, init);
255
376
  });
256
377
  });
257
- /**
258
- * Parse, sanitize, and re-serialize a `/serving-endpoints/...` POST
259
- * body. Returns the original string verbatim when the body is not
260
- * JSON, has no `messages`, or no rewrite was needed; this lets the
261
- * caller skip the allocation of a new `init` object in the common
262
- * pass-through case.
263
- */
264
- function rewriteServingBody(body) {
265
- let parsed;
266
- try {
267
- parsed = JSON.parse(body);
268
- } catch {
269
- return body;
270
- }
271
- if (!Array.isArray(parsed.messages)) return body;
272
- return sanitizeServingMessages(parsed.messages) ? JSON.stringify(parsed) : body;
273
- }
274
- /**
275
- * Repair a Mastra/AI SDK message replay that Databricks-hosted Claude
276
- * rejects with `"This model does not support assistant message
277
- * prefill. The conversation must end with a user message."`.
278
- *
279
- * The bug pattern: when an assistant turn streams text *and* a
280
- * `tool_call`, the AI SDK persists them as two separate assistant
281
- * entries (text-only and tool-call-only). On the next agent step the
282
- * tool-call entry is replayed *before* the tool result and the
283
- * text entry is replayed *after* it, so the conversation ends with a
284
- * trailing assistant text message. Anthropic interprets that as a
285
- * prefill request and rejects it on Databricks (the upstream Bedrock
286
- * route disallows prefill).
287
- *
288
- * Fix: when the last message is an assistant text with no `tool_calls`
289
- * and the chain immediately before it is `assistant(tool_calls=...)`
290
- * followed only by `tool(...)` results, fold the trailing text back
291
- * into the `content` of that opening assistant and drop the duplicate.
292
- * The result is the canonical OpenAI shape
293
- * `[..., user, assistant(text + tool_calls), tool(...)]` which both
294
- * Databricks Claude and every other endpoint accept.
295
- *
296
- * Mutates `messages` in place; returns `true` when something changed
297
- * so the caller knows whether to re-serialize.
298
- */
299
- function sanitizeServingMessages(messages) {
300
- if (messages.length < 2) return false;
301
- const last = messages[messages.length - 1];
302
- if (!last || last.role !== "assistant" || last.tool_calls && last.tool_calls.length > 0) return false;
303
- let i = messages.length - 2;
304
- while (i >= 0 && messages[i]?.role === "tool") i--;
305
- if (i < 0) return false;
306
- const opener = messages[i];
307
- if (!opener || opener.role !== "assistant" || !opener.tool_calls || opener.tool_calls.length === 0) return false;
308
- opener.content = [stringUtils.trimToNull(opener.content), stringUtils.trimToNull(last.content)].filter((s) => s !== null).join("\n\n");
309
- messages.pop();
310
- return true;
311
- }
312
378
 
313
379
  //#endregion
314
380
  //#region packages/appkit-mastra/src/chart.ts
@@ -2191,6 +2257,7 @@ async function buildAgents(opts) {
2191
2257
  const approvalGatedByAgent = [];
2192
2258
  for (const [id, def] of Object.entries(definitions)) {
2193
2259
  const tools = await resolveTools(def.tools, plugins, ambientTools);
2260
+ const workspace = resolveAgentWorkspace(def.workspace);
2194
2261
  const gated = approvalGatedToolIds(tools);
2195
2262
  if (gated.length > 0) approvalGatedByAgent.push({
2196
2263
  agentId: id,
@@ -2206,6 +2273,7 @@ async function buildAgents(opts) {
2206
2273
  defaultOptions: { maxSteps: config.agentMaxSteps ?? DEFAULT_AGENT_MAX_STEPS },
2207
2274
  tools,
2208
2275
  ...memory ? { memory } : {},
2276
+ ...workspace ? { workspace } : {},
2209
2277
  inputProcessors,
2210
2278
  outputProcessors
2211
2279
  });
@@ -2375,6 +2443,10 @@ async function resolveTools(defTools, plugins, ambientTools) {
2375
2443
  ...resolved
2376
2444
  };
2377
2445
  }
2446
+ function resolveAgentWorkspace(workspace) {
2447
+ if (!workspace) return void 0;
2448
+ return typeof workspace === "function" ? workspace() : workspace;
2449
+ }
2378
2450
  /**
2379
2451
  * Build the {@link MastraPlugins} runtime proxy handed to
2380
2452
  * `tools(plugins)` callbacks.
@@ -2758,6 +2830,36 @@ function toEpoch(value) {
2758
2830
  return 0;
2759
2831
  }
2760
2832
 
2833
+ //#endregion
2834
+ //#region packages/appkit-mastra/src/storage-schema.ts
2835
+ /**
2836
+ * Derive a Postgres-safe schema name for per-agent Mastra storage.
2837
+ *
2838
+ * Agent ids are often kebab-case route segments (`data-mesh-book-assistant`)
2839
+ * but {@link PostgresStore} validates `schemaName` with Mastra's
2840
+ * `parseSqlIdentifier` (letter/underscore start, `[A-Za-z0-9_]` only, max 63).
2841
+ */
2842
+ const SCHEMA_PREFIX = "mastra_";
2843
+ const MAX_PG_IDENTIFIER_LEN = 63;
2844
+ /**
2845
+ * Default Lakebase schema for one agent's thread/message store:
2846
+ * `mastra_<sanitized-agent-id>`.
2847
+ */
2848
+ function agentStorageSchemaName(agentId) {
2849
+ const maxSlugLen = MAX_PG_IDENTIFIER_LEN - 7;
2850
+ return `${SCHEMA_PREFIX}${stringUtils.toIdentifierWithOptions({
2851
+ delimiter: "_",
2852
+ maxLength: maxSlugLen,
2853
+ truncateStrategy: "hash",
2854
+ truncateHashLength: 6
2855
+ }, agentId) || stringUtils.toIdentifierWithOptions({
2856
+ delimiter: "_",
2857
+ maxLength: maxSlugLen,
2858
+ truncateStrategy: "hash",
2859
+ truncateHashLength: 6
2860
+ }, "agent", agentId)}`;
2861
+ }
2862
+
2761
2863
  //#endregion
2762
2864
  //#region packages/appkit-mastra/src/memory.ts
2763
2865
  /**
@@ -2767,7 +2869,7 @@ function toEpoch(value) {
2767
2869
  * with two independent knobs:
2768
2870
  *
2769
2871
  * - **Storage** (threads / messages via `PostgresStore`): defaults to
2770
- * **per-agent** namespacing via `schemaName: "mastra_<agentId>"` so
2872
+ * **per-agent** namespacing via {@link agentStorageSchemaName} so
2771
2873
  * conversation history stays isolated between agents in the same
2772
2874
  * database. `PostgresStore` auto-creates the schema with
2773
2875
  * `CREATE SCHEMA IF NOT EXISTS` on init.
@@ -2853,7 +2955,7 @@ var MemoryBuilder = class {
2853
2955
  * the `requireApproval` flow) will not be available.
2854
2956
  *
2855
2957
  * The store lives in a dedicated `mastra_instance` schema so it
2856
- * never collides with per-agent `mastra_<agentId>` namespaces.
2958
+ * never collides with per-agent {@link agentStorageSchemaName} namespaces.
2857
2959
  * Workflow snapshots are not per-agent state; they belong to the
2858
2960
  * `Mastra` instance that owns the workflow execution.
2859
2961
  */
@@ -2911,7 +3013,7 @@ var MemoryBuilder = class {
2911
3013
  if (!setting) return void 0;
2912
3014
  if (typeof setting === "boolean") return new PostgresStore({
2913
3015
  id: `mastra-store__${agentId}`,
2914
- schemaName: `mastra_${agentId}`,
3016
+ schemaName: agentStorageSchemaName(agentId),
2915
3017
  pool: this.servicePrincipalPool
2916
3018
  });
2917
3019
  return new PostgresStore(withId(setting, `mastra-store__${agentId}`));
@@ -3744,7 +3846,7 @@ function toIso(value) {
3744
3846
  * from `./memory.js`. Both auto-default to `true` when the
3745
3847
  * `lakebase` plugin is registered (unless the caller passed
3746
3848
  * `false` or a custom config). Storage namespaces per agent via
3747
- * `schemaName: "mastra_<agentId>"`; the vector store is a single
3849
+ * {@link agentStorageSchemaName} per agent; the vector store is a single
3748
3850
  * shared singleton across every agent.
3749
3851
  * - Server: the Express subapp wiring lives in `./server.js`.
3750
3852
  * - HTTP: AppKit mounts this plugin under `/api/mastra`. Alongside the
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@dbx-tools/appkit-mastra",
3
- "version": "0.1.92",
3
+ "version": "0.1.94",
4
4
  "dependencies": {
5
5
  "@databricks/sdk-experimental": "^0.17",
6
- "@dbx-tools/appkit-mastra-shared": "0.1.92",
7
- "@dbx-tools/genie": "0.1.92",
8
- "@dbx-tools/genie-shared": "0.1.92",
9
- "@dbx-tools/model": "0.1.92",
10
- "@dbx-tools/shared": "0.1.92",
6
+ "@dbx-tools/appkit-mastra-shared": "0.1.94",
7
+ "@dbx-tools/genie": "0.1.94",
8
+ "@dbx-tools/genie-shared": "0.1.94",
9
+ "@dbx-tools/model": "0.1.94",
10
+ "@dbx-tools/shared": "0.1.94",
11
11
  "@mastra/ai-sdk": "^1",
12
12
  "@mastra/core": "^1",
13
13
  "@mastra/express": "^1",