@dbx-tools/appkit-mastra 0.3.29 → 0.3.31
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/README.md +86 -15
- package/index.ts +3 -0
- package/package.json +10 -10
- package/src/agents.ts +12 -11
- package/src/chart.ts +69 -38
- package/src/config.ts +170 -18
- package/src/defaults.ts +133 -0
- package/src/filesystems.ts +17 -5
- package/src/genie.ts +80 -39
- package/src/history.ts +5 -2
- package/src/model.ts +7 -0
- package/src/plugin.ts +469 -194
- package/src/rest.ts +5 -2
- package/src/server.ts +2 -1
- package/src/serving-sanitize.ts +15 -5
- package/src/storage-schema.ts +4 -1
- package/src/threads.ts +10 -3
- package/src/validation.ts +22 -0
- package/test/config.test.ts +64 -0
package/src/rest.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { appkit } from "@dbx-tools/appkit";
|
|
2
|
-
import { json } from "@dbx-tools/shared-core";
|
|
3
1
|
/**
|
|
4
2
|
* Minimal authenticated Databricks REST helper. Pulls the workspace
|
|
5
3
|
* host and a fresh bearer header off an OBO-scoped `WorkspaceClient`
|
|
@@ -8,8 +6,13 @@ import { json } from "@dbx-tools/shared-core";
|
|
|
8
6
|
* method (e.g. the MLflow assessments API); returns the raw `Response`
|
|
9
7
|
* so callers decide how to treat status codes. Also carries the
|
|
10
8
|
* defensive `Response` body readers those callers share.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { appkit } from "@dbx-tools/appkit";
|
|
14
|
+
import { json } from "@dbx-tools/shared-core";
|
|
15
|
+
|
|
13
16
|
/** Workspace client carried on an AppKit execution context. */
|
|
14
17
|
type WorkspaceClient = appkit.WorkspaceClientLike;
|
|
15
18
|
|
package/src/server.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { trace } from "@opentelemetry/api";
|
|
|
20
20
|
import type express from "express";
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
|
+
executionContextUserId,
|
|
23
24
|
MASTRA_REQUEST_ID_KEY,
|
|
24
25
|
MASTRA_SCOPES_KEY,
|
|
25
26
|
MASTRA_USER_EMAIL_KEY,
|
|
@@ -94,7 +95,7 @@ export class MastraServer extends MastraServerExpress {
|
|
|
94
95
|
if ([MASTRA_USER_KEY, MASTRA_RESOURCE_ID_KEY].every((key) => requestContext.get(key))) return;
|
|
95
96
|
const executionContext = getExecutionContext();
|
|
96
97
|
const user: User = {
|
|
97
|
-
id:
|
|
98
|
+
id: executionContextUserId(executionContext),
|
|
98
99
|
executionContext,
|
|
99
100
|
};
|
|
100
101
|
requestContext.set(MASTRA_USER_KEY, user);
|
package/src/serving-sanitize.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repairs Mastra / AI SDK message replays sent to Databricks Model
|
|
3
|
+
* Serving before they hit the OpenAI-compatible `/chat/completions`
|
|
4
|
+
* route.
|
|
5
|
+
*
|
|
6
|
+
* Exists because the transcript Mastra persists is not always a transcript the
|
|
7
|
+
* provider will accept back: Databricks-hosted Claude rejects replayed
|
|
8
|
+
* extended-thinking blocks and reads a trailing assistant message as a prefill
|
|
9
|
+
* request. Both repairs are provider quirks, not schema violations, so they are
|
|
10
|
+
* applied on the wire (see the `globalThis.fetch` wrapper in `model.ts`) rather
|
|
11
|
+
* than by changing what the agent stores or what the UI shows.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
|
|
1
16
|
import { json, string } from "@dbx-tools/shared-core";
|
|
2
17
|
import {
|
|
3
18
|
type ChatMessage,
|
|
@@ -5,11 +20,6 @@ import {
|
|
|
5
20
|
openaiChat,
|
|
6
21
|
openaiResponses,
|
|
7
22
|
} from "@dbx-tools/shared-model";
|
|
8
|
-
/**
|
|
9
|
-
* Repairs Mastra / AI SDK message replays sent to Databricks Model
|
|
10
|
-
* Serving before they hit the OpenAI-compatible `/chat/completions`
|
|
11
|
-
* route.
|
|
12
|
-
*/
|
|
13
23
|
|
|
14
24
|
/**
|
|
15
25
|
* A chat message as it arrives on the serving wire, plus the extended-thinking
|
package/src/storage-schema.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { string } from "@dbx-tools/shared-core";
|
|
2
1
|
/**
|
|
3
2
|
* Derive a Postgres-safe schema name for per-agent Mastra storage.
|
|
4
3
|
*
|
|
5
4
|
* Agent ids are often kebab-case route segments (`data-mesh-book-assistant`)
|
|
6
5
|
* but {@link PostgresStore} validates `schemaName` with Mastra's
|
|
7
6
|
* `parseSqlIdentifier` (letter/underscore start, `[A-Za-z0-9_]` only, max 63).
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
8
9
|
*/
|
|
9
10
|
|
|
11
|
+
import { string } from "@dbx-tools/shared-core";
|
|
12
|
+
|
|
10
13
|
const SCHEMA_PREFIX = "mastra_";
|
|
11
14
|
const MAX_PG_IDENTIFIER_LEN = 63;
|
|
12
15
|
|
package/src/threads.ts
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* @module
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
import { ValidationError } from "@databricks/appkit";
|
|
24
25
|
import { log } from "@dbx-tools/shared-core";
|
|
25
26
|
import {
|
|
26
27
|
wire,
|
|
@@ -36,6 +37,7 @@ import type { ContextWithMastra } from "@mastra/core/server";
|
|
|
36
37
|
import { registerApiRoute } from "@mastra/core/server";
|
|
37
38
|
|
|
38
39
|
import { clampPerPage, parseIntParam } from "./pagination";
|
|
40
|
+
import { invalidFields } from "./validation";
|
|
39
41
|
|
|
40
42
|
const logger = log.logger("mastra/threads");
|
|
41
43
|
|
|
@@ -43,6 +45,8 @@ const logger = log.logger("mastra/threads");
|
|
|
43
45
|
const DEFAULT_PER_PAGE = 30;
|
|
44
46
|
/** Hard cap so a misbehaving client can't fetch every thread at once. */
|
|
45
47
|
const MAX_PER_PAGE = 200;
|
|
48
|
+
/** Stable client message for a rename body that fails schema validation. */
|
|
49
|
+
const INVALID_RENAME_MESSAGE = "Invalid thread rename request";
|
|
46
50
|
|
|
47
51
|
/** Inputs accepted by {@link listThreads}. */
|
|
48
52
|
export interface ListThreadsOptions {
|
|
@@ -220,8 +224,10 @@ export function threadsRoute(options: ThreadsRouteOptions) {
|
|
|
220
224
|
const { path } = options;
|
|
221
225
|
const fixedAgent = "agent" in options ? options.agent : undefined;
|
|
222
226
|
if (!fixedAgent && !path.includes(":agentId")) {
|
|
223
|
-
throw
|
|
224
|
-
"threadsRoute
|
|
227
|
+
throw ValidationError.invalidValue(
|
|
228
|
+
"threadsRoute.path",
|
|
229
|
+
path,
|
|
230
|
+
"a path containing `:agentId`, or an explicit `agent`",
|
|
225
231
|
);
|
|
226
232
|
}
|
|
227
233
|
// Shared by GET / DELETE: resolve the active agent and the caller's
|
|
@@ -298,7 +304,8 @@ export function threadsRoute(options: ThreadsRouteOptions) {
|
|
|
298
304
|
}
|
|
299
305
|
const body = wire.MastraUpdateThreadRequestSchema.safeParse(await c.req.json());
|
|
300
306
|
if (!body.success) {
|
|
301
|
-
|
|
307
|
+
logger.warn("rename:invalid", { error: body.error.message });
|
|
308
|
+
return c.json({ error: INVALID_RENAME_MESSAGE, fields: invalidFields(body.error) }, 400);
|
|
302
309
|
}
|
|
303
310
|
const thread = await renameThread({
|
|
304
311
|
agent: ctx.agent,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-body validation helpers shared by every route that parses a JSON
|
|
3
|
+
* body with a schema from `@dbx-tools/shared-mastra`.
|
|
4
|
+
*
|
|
5
|
+
* A schema library's own issue text quotes the received body back, so it is
|
|
6
|
+
* logged rather than returned. What a client gets instead is a stable message
|
|
7
|
+
* plus the field paths that failed, which is enough to highlight the offending
|
|
8
|
+
* inputs.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** The subset of a failed parse result these helpers read. */
|
|
14
|
+
export interface SchemaIssues {
|
|
15
|
+
issues: ReadonlyArray<{ path: PropertyKey[] }>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Distinct dot-joined field paths a failed parse flagged. */
|
|
19
|
+
export function invalidFields(error: SchemaIssues): string[] {
|
|
20
|
+
const fields = error.issues.map((issue) => issue.path.map(String).join(".")).filter(Boolean);
|
|
21
|
+
return [...new Set(fields)];
|
|
22
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { ConfigurationError } from "@databricks/appkit";
|
|
4
|
+
|
|
5
|
+
import { MASTRA_CONFIG_SCHEMA } from "../src/config";
|
|
6
|
+
import { normalizeGenieSpaces } from "../src/genie";
|
|
7
|
+
import { invalidFields } from "../src/validation";
|
|
8
|
+
|
|
9
|
+
describe("mastra config schema", () => {
|
|
10
|
+
it("describes every published property", () => {
|
|
11
|
+
const properties = Object.entries(MASTRA_CONFIG_SCHEMA.properties ?? {});
|
|
12
|
+
assert.ok(properties.length > 0);
|
|
13
|
+
for (const [name, schema] of properties) {
|
|
14
|
+
assert.equal(typeof schema, "object", `${name} must be a schema object`);
|
|
15
|
+
const description = (schema as { description?: unknown }).description;
|
|
16
|
+
assert.equal(typeof description, "string", `${name} must carry a description`);
|
|
17
|
+
assert.ok((description as string).length > 0, `${name} description must be non-empty`);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("genie space normalization", () => {
|
|
23
|
+
it("wraps bare space ids and passes objects through", () => {
|
|
24
|
+
assert.deepEqual(normalizeGenieSpaces({ default: "01ef", sales: { spaceId: "02ef" } }), {
|
|
25
|
+
default: { spaceId: "01ef" },
|
|
26
|
+
sales: { spaceId: "02ef" },
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("treats no config as no spaces", () => {
|
|
31
|
+
assert.deepEqual(normalizeGenieSpaces(undefined), {});
|
|
32
|
+
assert.deepEqual(normalizeGenieSpaces({}), {});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("fails loudly on an alias with no space id", () => {
|
|
36
|
+
for (const spaces of [
|
|
37
|
+
{ default: undefined },
|
|
38
|
+
{ default: "" },
|
|
39
|
+
{ sales: { spaceId: "" } },
|
|
40
|
+
] as Parameters<typeof normalizeGenieSpaces>[0][]) {
|
|
41
|
+
assert.throws(() => normalizeGenieSpaces(spaces), ConfigurationError);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("names the env var only for the default alias", () => {
|
|
46
|
+
assert.throws(() => normalizeGenieSpaces({ default: undefined }), /DATABRICKS_GENIE_SPACE_ID/);
|
|
47
|
+
assert.throws(() => normalizeGenieSpaces({ sales: undefined }), /genieSpaces\.sales/);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("request body validation", () => {
|
|
52
|
+
it("reports distinct dot-joined field paths", () => {
|
|
53
|
+
assert.deepEqual(
|
|
54
|
+
invalidFields({
|
|
55
|
+
issues: [{ path: ["traceId"] }, { path: ["value", 0] }, { path: ["traceId"] }],
|
|
56
|
+
}),
|
|
57
|
+
["traceId", "value.0"],
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("drops the root path, which names no field", () => {
|
|
62
|
+
assert.deepEqual(invalidFields({ issues: [{ path: [] }] }), []);
|
|
63
|
+
});
|
|
64
|
+
});
|