@dbx-tools/appkit-mastra 0.1.92 → 0.1.93
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 +2 -2
- package/dist/index.js +156 -60
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -576,7 +576,7 @@ declare class MemoryBuilder {
|
|
|
576
576
|
* the `requireApproval` flow) will not be available.
|
|
577
577
|
*
|
|
578
578
|
* The store lives in a dedicated `mastra_instance` schema so it
|
|
579
|
-
* never collides with per-agent
|
|
579
|
+
* never collides with per-agent {@link agentStorageSchemaName} namespaces.
|
|
580
580
|
* Workflow snapshots are not per-agent state; they belong to the
|
|
581
581
|
* `Mastra` instance that owns the workflow execution.
|
|
582
582
|
*/
|
|
@@ -817,7 +817,7 @@ interface MastraAgentDefinition {
|
|
|
817
817
|
*
|
|
818
818
|
* - `undefined` (default): inherit `config.storage`. When that's
|
|
819
819
|
* enabled, the agent gets its **own per-agent `PostgresStore`**
|
|
820
|
-
* keyed by
|
|
820
|
+
* keyed by {@link agentStorageSchemaName} so threads and
|
|
821
821
|
* messages stay isolated between agents in the same database.
|
|
822
822
|
* - `false`: disable storage for this agent only (purely in-memory).
|
|
823
823
|
* - `true`: enable with the per-agent default schema.
|
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
|
|
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
|
|
@@ -2758,6 +2824,36 @@ function toEpoch(value) {
|
|
|
2758
2824
|
return 0;
|
|
2759
2825
|
}
|
|
2760
2826
|
|
|
2827
|
+
//#endregion
|
|
2828
|
+
//#region packages/appkit-mastra/src/storage-schema.ts
|
|
2829
|
+
/**
|
|
2830
|
+
* Derive a Postgres-safe schema name for per-agent Mastra storage.
|
|
2831
|
+
*
|
|
2832
|
+
* Agent ids are often kebab-case route segments (`data-mesh-book-assistant`)
|
|
2833
|
+
* but {@link PostgresStore} validates `schemaName` with Mastra's
|
|
2834
|
+
* `parseSqlIdentifier` (letter/underscore start, `[A-Za-z0-9_]` only, max 63).
|
|
2835
|
+
*/
|
|
2836
|
+
const SCHEMA_PREFIX = "mastra_";
|
|
2837
|
+
const MAX_PG_IDENTIFIER_LEN = 63;
|
|
2838
|
+
/**
|
|
2839
|
+
* Default Lakebase schema for one agent's thread/message store:
|
|
2840
|
+
* `mastra_<sanitized-agent-id>`.
|
|
2841
|
+
*/
|
|
2842
|
+
function agentStorageSchemaName(agentId) {
|
|
2843
|
+
const maxSlugLen = MAX_PG_IDENTIFIER_LEN - 7;
|
|
2844
|
+
return `${SCHEMA_PREFIX}${stringUtils.toIdentifierWithOptions({
|
|
2845
|
+
delimiter: "_",
|
|
2846
|
+
maxLength: maxSlugLen,
|
|
2847
|
+
truncateStrategy: "hash",
|
|
2848
|
+
truncateHashLength: 6
|
|
2849
|
+
}, agentId) || stringUtils.toIdentifierWithOptions({
|
|
2850
|
+
delimiter: "_",
|
|
2851
|
+
maxLength: maxSlugLen,
|
|
2852
|
+
truncateStrategy: "hash",
|
|
2853
|
+
truncateHashLength: 6
|
|
2854
|
+
}, "agent", agentId)}`;
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2761
2857
|
//#endregion
|
|
2762
2858
|
//#region packages/appkit-mastra/src/memory.ts
|
|
2763
2859
|
/**
|
|
@@ -2767,7 +2863,7 @@ function toEpoch(value) {
|
|
|
2767
2863
|
* with two independent knobs:
|
|
2768
2864
|
*
|
|
2769
2865
|
* - **Storage** (threads / messages via `PostgresStore`): defaults to
|
|
2770
|
-
* **per-agent** namespacing via
|
|
2866
|
+
* **per-agent** namespacing via {@link agentStorageSchemaName} so
|
|
2771
2867
|
* conversation history stays isolated between agents in the same
|
|
2772
2868
|
* database. `PostgresStore` auto-creates the schema with
|
|
2773
2869
|
* `CREATE SCHEMA IF NOT EXISTS` on init.
|
|
@@ -2853,7 +2949,7 @@ var MemoryBuilder = class {
|
|
|
2853
2949
|
* the `requireApproval` flow) will not be available.
|
|
2854
2950
|
*
|
|
2855
2951
|
* The store lives in a dedicated `mastra_instance` schema so it
|
|
2856
|
-
* never collides with per-agent
|
|
2952
|
+
* never collides with per-agent {@link agentStorageSchemaName} namespaces.
|
|
2857
2953
|
* Workflow snapshots are not per-agent state; they belong to the
|
|
2858
2954
|
* `Mastra` instance that owns the workflow execution.
|
|
2859
2955
|
*/
|
|
@@ -2911,7 +3007,7 @@ var MemoryBuilder = class {
|
|
|
2911
3007
|
if (!setting) return void 0;
|
|
2912
3008
|
if (typeof setting === "boolean") return new PostgresStore({
|
|
2913
3009
|
id: `mastra-store__${agentId}`,
|
|
2914
|
-
schemaName:
|
|
3010
|
+
schemaName: agentStorageSchemaName(agentId),
|
|
2915
3011
|
pool: this.servicePrincipalPool
|
|
2916
3012
|
});
|
|
2917
3013
|
return new PostgresStore(withId(setting, `mastra-store__${agentId}`));
|
|
@@ -3744,7 +3840,7 @@ function toIso(value) {
|
|
|
3744
3840
|
* from `./memory.js`. Both auto-default to `true` when the
|
|
3745
3841
|
* `lakebase` plugin is registered (unless the caller passed
|
|
3746
3842
|
* `false` or a custom config). Storage namespaces per agent via
|
|
3747
|
-
*
|
|
3843
|
+
* {@link agentStorageSchemaName} per agent; the vector store is a single
|
|
3748
3844
|
* shared singleton across every agent.
|
|
3749
3845
|
* - Server: the Express subapp wiring lives in `./server.js`.
|
|
3750
3846
|
* - 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.
|
|
3
|
+
"version": "0.1.93",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@databricks/sdk-experimental": "^0.17",
|
|
6
|
-
"@dbx-tools/appkit-mastra-shared": "0.1.
|
|
7
|
-
"@dbx-tools/genie": "0.1.
|
|
8
|
-
"@dbx-tools/genie-shared": "0.1.
|
|
9
|
-
"@dbx-tools/model": "0.1.
|
|
10
|
-
"@dbx-tools/shared": "0.1.
|
|
6
|
+
"@dbx-tools/appkit-mastra-shared": "0.1.93",
|
|
7
|
+
"@dbx-tools/genie": "0.1.93",
|
|
8
|
+
"@dbx-tools/genie-shared": "0.1.93",
|
|
9
|
+
"@dbx-tools/model": "0.1.93",
|
|
10
|
+
"@dbx-tools/shared": "0.1.93",
|
|
11
11
|
"@mastra/ai-sdk": "^1",
|
|
12
12
|
"@mastra/core": "^1",
|
|
13
13
|
"@mastra/express": "^1",
|