@dbx-tools/appkit-mastra 0.1.58 → 0.1.68

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 (51) hide show
  1. package/README.md +41 -1
  2. package/dist/index.d.ts +1229 -17
  3. package/dist/index.js +3348 -19
  4. package/package.json +17 -30
  5. package/dist/src/agents.d.ts +0 -316
  6. package/dist/src/agents.js +0 -470
  7. package/dist/src/chart.d.ts +0 -153
  8. package/dist/src/chart.js +0 -570
  9. package/dist/src/config.d.ts +0 -295
  10. package/dist/src/config.js +0 -55
  11. package/dist/src/genie.d.ts +0 -161
  12. package/dist/src/genie.js +0 -973
  13. package/dist/src/history.d.ts +0 -95
  14. package/dist/src/history.js +0 -278
  15. package/dist/src/memory.d.ts +0 -109
  16. package/dist/src/memory.js +0 -253
  17. package/dist/src/model.d.ts +0 -52
  18. package/dist/src/model.js +0 -198
  19. package/dist/src/observability.d.ts +0 -64
  20. package/dist/src/observability.js +0 -83
  21. package/dist/src/plugin.d.ts +0 -177
  22. package/dist/src/plugin.js +0 -554
  23. package/dist/src/processor.d.ts +0 -8
  24. package/dist/src/processor.js +0 -40
  25. package/dist/src/processors/strip-stale-charts.d.ts +0 -32
  26. package/dist/src/processors/strip-stale-charts.js +0 -98
  27. package/dist/src/server.d.ts +0 -51
  28. package/dist/src/server.js +0 -152
  29. package/dist/src/serving.d.ts +0 -65
  30. package/dist/src/serving.js +0 -79
  31. package/dist/src/statement.d.ts +0 -66
  32. package/dist/src/statement.js +0 -111
  33. package/dist/src/writer.d.ts +0 -23
  34. package/dist/src/writer.js +0 -37
  35. package/dist/tsconfig.build.tsbuildinfo +0 -1
  36. package/index.ts +0 -27
  37. package/src/agents.ts +0 -772
  38. package/src/chart.ts +0 -716
  39. package/src/config.ts +0 -320
  40. package/src/genie.ts +0 -1123
  41. package/src/history.ts +0 -322
  42. package/src/memory.ts +0 -293
  43. package/src/model.ts +0 -257
  44. package/src/observability.ts +0 -114
  45. package/src/plugin.ts +0 -623
  46. package/src/processor.ts +0 -46
  47. package/src/processors/strip-stale-charts.ts +0 -102
  48. package/src/server.ts +0 -195
  49. package/src/serving.ts +0 -104
  50. package/src/statement.ts +0 -120
  51. package/src/writer.ts +0 -44
package/src/model.ts DELETED
@@ -1,257 +0,0 @@
1
- /**
2
- * Databricks Model Serving resolver for Mastra agents.
3
- *
4
- * Each agent step calls {@link buildModel} with the active
5
- * `RequestContext`. The user stamped by `MastraServer` carries an
6
- * AppKit `WorkspaceClient`; we ask it for the workspace host and a
7
- * fresh bearer header, then point Mastra's OpenAI-compatible provider
8
- * at `/serving-endpoints` on that host.
9
- *
10
- * This module only adds the Mastra-specific glue. The actual model
11
- * selection - listing the workspace catalogue and resolving an
12
- * explicit name / class / fallback chain to a real endpoint id - lives
13
- * in `@dbx-tools/model` ({@link selectModel}) so non-Mastra consumers
14
- * (e.g. a job that just needs a model name) can reuse it. Here we
15
- * assemble the explicit ask from Mastra's request context (the
16
- * per-request override under {@link MASTRA_MODEL_OVERRIDE_KEY}, the
17
- * agent / plugin `modelId`, or `DATABRICKS_SERVING_ENDPOINT_NAME`),
18
- * pass the plugin's fuzzy / class / fallback knobs through, and wrap
19
- * the resolved id in the OpenAI-compatible provider config Mastra
20
- * expects. Catalogue fetches fail loud: network / auth errors
21
- * propagate so callers see the real SDK message.
22
- */
23
-
24
- import { type ModelClass, parseModelClass, selectModel } from "@dbx-tools/model";
25
- import { commonUtils, logUtils, netUtils, stringUtils } from "@dbx-tools/shared";
26
- import type { MastraModelConfig } from "@mastra/core/llm";
27
- import type { RequestContext } from "@mastra/core/request-context";
28
-
29
- import { MASTRA_USER_KEY, type MastraPluginConfig, type User } from "./config.js";
30
- import { MASTRA_MODEL_OVERRIDE_KEY, resolveServingConfig } from "./serving.js";
31
-
32
- export {
33
- classifyEndpoints,
34
- FALLBACK_MODEL_IDS,
35
- ModelClass,
36
- modelForClass,
37
- modelsForClass,
38
- } from "@dbx-tools/model";
39
-
40
- /** Optional overrides accepted by {@link buildModel}. */
41
- export interface BuildModelOverrides {
42
- /**
43
- * Static model id from the agent / plugin config (string sugar on
44
- * `def.model` or `config.defaultModel`). Loses to the per-request
45
- * override but wins over env / class / fallback.
46
- */
47
- modelId?: string;
48
- /**
49
- * Chat capability class to resolve when no explicit model id is
50
- * supplied. Used by internal agents (e.g. the chart planner asks for
51
- * {@link ModelClass.ChatFast}) to express intent without pinning an
52
- * endpoint name; the live catalogue is classified and the top
53
- * available model in the class is chosen, falling back to the
54
- * class's static list when the workspace has none.
55
- */
56
- modelClass?: ModelClass;
57
- }
58
-
59
- /**
60
- * Resolve a `MastraModelConfig` for the current agent step. Runs
61
- * while `agent.stream` is inside the `asUser(req)` scope so tokens
62
- * are user-scoped; outside an active user context the workspace
63
- * client falls back to the service principal.
64
- */
65
- export async function buildModel(
66
- config: MastraPluginConfig,
67
- requestContext: RequestContext,
68
- overrides: BuildModelOverrides = {},
69
- ): Promise<MastraModelConfig> {
70
- void setupFetchInterceptor();
71
- const user = requestContext.get(MASTRA_USER_KEY) as User;
72
- const clientConfig = user.executionContext.client.config;
73
- const host = (await clientConfig.getHost()).toString();
74
- const headers = new Headers();
75
- await clientConfig.authenticate(headers);
76
- // The OpenAI Node SDK appends paths like `/chat/completions` to whatever
77
- // URL we hand it. Drop the trailing slash so the resulting URL stays
78
- // well-formed (`/serving-endpoints/chat/completions`).
79
- const url = new URL("/serving-endpoints", host).toString().replace(/\/$/, "");
80
-
81
- const log = logUtils.logger(config);
82
- const serving = resolveServingConfig(config);
83
- const override = serving.allowOverride
84
- ? (requestContext.get(MASTRA_MODEL_OVERRIDE_KEY) as string | undefined)
85
- : undefined;
86
-
87
- // The override / agent default / env value can be either a concrete
88
- // endpoint name or a model class slug ("chat-thinking" /
89
- // "chat-balanced" / "chat-fast"). A class slug becomes a class intent
90
- // (let the live catalogue pick the best model in that band); anything
91
- // else is an explicit name fuzzy-matched against the catalogue. An
92
- // internal `overrides.modelClass` (e.g. the chart planner) is the
93
- // floor when nothing was requested.
94
- const requested =
95
- override ?? overrides.modelId ?? process.env.DATABRICKS_SERVING_ENDPOINT_NAME;
96
- const requestedClass = requested !== undefined ? parseModelClass(requested) : null;
97
- const explicit = requestedClass === null ? requested : undefined;
98
- const modelClass = requestedClass ?? overrides.modelClass;
99
-
100
- const { modelId, source } = await selectModel(user.executionContext.client, host, {
101
- ...(explicit !== undefined ? { explicit } : {}),
102
- fuzzy: serving.fuzzy,
103
- threshold: serving.threshold,
104
- ...(modelClass !== undefined ? { modelClass } : {}),
105
- fallbacks: serving.fallbacks,
106
- ttlMs: serving.ttlMs,
107
- });
108
- log.debug("model selected", { modelId, source, requested });
109
-
110
- return {
111
- providerId: config.providerId ?? "databricks",
112
- modelId,
113
- url,
114
- headers: Object.fromEntries(headers.entries()),
115
- };
116
- }
117
-
118
- /** Path prefix that identifies a Databricks Model Serving REST call. */
119
- const SERVING_ENDPOINTS_PATH_PREFIX = "/serving-endpoints/";
120
-
121
- /**
122
- * OpenAI-flavoured chat message shape we need to mutate. We do not
123
- * import the OpenAI / AI SDK types because both packages keep these
124
- * fields under internal namespaces; the wire payload is the contract
125
- * here and it's stable enough to inline.
126
- */
127
- interface ChatMessage {
128
- role: "system" | "user" | "assistant" | "tool";
129
- content?: string;
130
- tool_calls?: Array<{ id: string; type: string; function: unknown }>;
131
- tool_call_id?: string;
132
- }
133
-
134
- /**
135
- * Install a single shared `globalThis.fetch` wrapper for every POST to
136
- * `/serving-endpoints/...`. The wrapper does two things:
137
- *
138
- * 1. Rewrites the outgoing `messages` array to repair Mastra/AI SDK
139
- * stream-replay quirks that Databricks-hosted Claude rejects (see
140
- * {@link sanitizeServingMessages}).
141
- * 2. At `LOG_LEVEL=debug`, dumps the (post-sanitize) JSON body so
142
- * 4xx debugging doesn't have to fight AI SDK's `[Array]`
143
- * formatter.
144
- *
145
- * Safe to call from any hot path: {@link commonUtils.memoize} ensures
146
- * the wrapper is installed at most once per process, so subsequent
147
- * calls collapse to a single cached promise even when
148
- * {@link buildModel} fires on every agent step.
149
- */
150
- const setupFetchInterceptor = commonUtils.memoize((): void => {
151
- const log = logUtils.logger("mastra/llm");
152
- const original = globalThis.fetch.bind(globalThis);
153
- globalThis.fetch = (async (input, init) => {
154
- const url = netUtils.urlBuilder(input);
155
- if (
156
- !url ||
157
- !url.pathname.startsWith(SERVING_ENDPOINTS_PATH_PREFIX) ||
158
- typeof init?.body !== "string"
159
- ) {
160
- return original(input, init);
161
- }
162
- const rewritten = rewriteServingBody(init.body);
163
- if (rewritten !== init.body) {
164
- init = { ...init, body: rewritten };
165
- }
166
- try {
167
- log.debug("POST", { url: url.toString(), body: JSON.parse(rewritten) });
168
- } catch {
169
- log.debug("POST", { url: url.toString(), bodyType: "non-JSON" });
170
- }
171
- return original(input, init);
172
- }) as typeof globalThis.fetch;
173
- });
174
-
175
- /**
176
- * Parse, sanitize, and re-serialize a `/serving-endpoints/...` POST
177
- * body. Returns the original string verbatim when the body is not
178
- * JSON, has no `messages`, or no rewrite was needed; this lets the
179
- * caller skip the allocation of a new `init` object in the common
180
- * pass-through case.
181
- */
182
- function rewriteServingBody(body: string): string {
183
- let parsed: { messages?: unknown };
184
- try {
185
- parsed = JSON.parse(body);
186
- } catch {
187
- return body;
188
- }
189
- if (!Array.isArray(parsed.messages)) return body;
190
- const changed = sanitizeServingMessages(parsed.messages as ChatMessage[]);
191
- return changed ? JSON.stringify(parsed) : body;
192
- }
193
-
194
- /**
195
- * Repair a Mastra/AI SDK message replay that Databricks-hosted Claude
196
- * rejects with `"This model does not support assistant message
197
- * prefill. The conversation must end with a user message."`.
198
- *
199
- * The bug pattern: when an assistant turn streams text *and* a
200
- * `tool_call`, the AI SDK persists them as two separate assistant
201
- * entries (text-only and tool-call-only). On the next agent step the
202
- * tool-call entry is replayed *before* the tool result and the
203
- * text entry is replayed *after* it, so the conversation ends with a
204
- * trailing assistant text message. Anthropic interprets that as a
205
- * prefill request and rejects it on Databricks (the upstream Bedrock
206
- * route disallows prefill).
207
- *
208
- * Fix: when the last message is an assistant text with no `tool_calls`
209
- * and the chain immediately before it is `assistant(tool_calls=...)`
210
- * followed only by `tool(...)` results, fold the trailing text back
211
- * into the `content` of that opening assistant and drop the duplicate.
212
- * The result is the canonical OpenAI shape
213
- * `[..., user, assistant(text + tool_calls), tool(...)]` which both
214
- * Databricks Claude and every other endpoint accept.
215
- *
216
- * Mutates `messages` in place; returns `true` when something changed
217
- * so the caller knows whether to re-serialize.
218
- */
219
- function sanitizeServingMessages(messages: ChatMessage[]): boolean {
220
- if (messages.length < 2) return false;
221
- const last = messages[messages.length - 1];
222
- if (
223
- !last ||
224
- last.role !== "assistant" ||
225
- (last.tool_calls && last.tool_calls.length > 0)
226
- ) {
227
- return false;
228
- }
229
-
230
- // Walk back through any contiguous tool-result messages to find the
231
- // assistant turn that opened this tool sequence.
232
- let i = messages.length - 2;
233
- while (i >= 0 && messages[i]?.role === "tool") i--;
234
- if (i < 0) return false;
235
- const opener = messages[i];
236
- if (
237
- !opener ||
238
- opener.role !== "assistant" ||
239
- !opener.tool_calls ||
240
- opener.tool_calls.length === 0
241
- ) {
242
- return false;
243
- }
244
-
245
- // `trimToNull` collapses the `typeof string && trimmed` dance and
246
- // drops blank fragments before the `\n\n` join below, so the merge
247
- // never introduces stray leading / trailing whitespace.
248
- const merged = [
249
- stringUtils.trimToNull(opener.content),
250
- stringUtils.trimToNull(last.content),
251
- ]
252
- .filter((s): s is string => s !== null)
253
- .join("\n\n");
254
- opener.content = merged;
255
- messages.pop();
256
- return true;
257
- }
@@ -1,114 +0,0 @@
1
- /**
2
- * Mastra observability wired through the same OTel pipeline AppKit's
3
- * built-in plugins (e.g. `agents`) use, via `@mastra/otel-bridge`.
4
- *
5
- * How traces flow:
6
- *
7
- * 1. `@databricks/appkit` boots a global `NodeSDK` in
8
- * `TelemetryManager.initialize()` (during `createApp`) when
9
- * `OTEL_EXPORTER_OTLP_ENDPOINT` is set in the process env.
10
- * 2. Every AppKit plugin span (e.g. the `agents` plugin's
11
- * `executeStream`) is created via the global OTel tracer
12
- * (`trace.getTracer(<plugin>)`), so it lands on that NodeSDK and
13
- * is shipped through its OTLP exporter.
14
- * 3. The Mastra `OtelBridge` ALSO creates real OTel spans on the same
15
- * global tracer for every Mastra operation (agent runs, model
16
- * calls, tool invocations, workflow steps). They inherit the
17
- * ambient OTel context, so when Mastra is invoked from inside an
18
- * AppKit HTTP span the trace stays connected.
19
- *
20
- * Net effect: Mastra spans get exactly the treatment AppKit's
21
- * `agents` plugin gets. No custom OTLP pipeline lives in this
22
- * package; the OTLP endpoint, headers, and resource attributes are
23
- * driven by the standard OTel env vars
24
- * (`OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_HEADERS`,
25
- * `OTEL_SERVICE_NAME`, `OTEL_RESOURCE_ATTRIBUTES`, ...) and consumed
26
- * by AppKit's `TelemetryManager`. Set those once and both AppKit and
27
- * Mastra spans end up at the same backend.
28
- *
29
- * When `OTEL_EXPORTER_OTLP_ENDPOINT` is unset the bridge's spans go
30
- * to the global noop tracer, mirroring how the `agents` plugin
31
- * silently no-ops in the same situation.
32
- */
33
-
34
- import { logUtils, projectUtils } from "@dbx-tools/shared";
35
- import { Observability } from "@mastra/observability";
36
- import { OtelBridge } from "@mastra/otel-bridge";
37
-
38
- import { TRACE_REQUEST_CONTEXT_KEYS } from "./config.js";
39
-
40
- const log = logUtils.logger("mastra/observability");
41
-
42
- const DEFAULT_SERVICE_NAME = "mastra";
43
-
44
- export interface BuildObservabilityOptions {
45
- /**
46
- * Service name attached to the Mastra `Observability` config. Used
47
- * as the tracer scope name on bridged OTel spans (the `service.name`
48
- * resource attribute is owned by AppKit's `TelemetryManager` instead
49
- * - it reads `OTEL_SERVICE_NAME` / `DATABRICKS_APP_NAME` at
50
- * `createApp` time).
51
- *
52
- * Defaults to project name then `"mastra"`.
53
- */
54
- serviceName?: string;
55
- /**
56
- * `RequestContext` keys to extract as span metadata on every Mastra
57
- * trace. Defaults to {@link TRACE_REQUEST_CONTEXT_KEYS} (user id,
58
- * thread id, request id, environment, model override, ...).
59
- *
60
- * Supports dot notation for nested values per the Mastra docs.
61
- */
62
- requestContextKeys?: readonly string[];
63
- }
64
-
65
- /**
66
- * Build a Mastra `Observability` whose spans ride AppKit's global
67
- * OTel pipeline via `@mastra/otel-bridge`.
68
- *
69
- * Returns `undefined` only if someone explicitly opts out in the
70
- * future; today it always returns an `Observability` because the
71
- * bridge degrades gracefully (no-op tracer) when no global OTel SDK
72
- * is registered. Callers can spread `...(observability ? { observability } : {})`
73
- * either way to stay forward-compatible.
74
- */
75
- export async function buildObservability(
76
- options?: BuildObservabilityOptions,
77
- ): Promise<Observability | undefined> {
78
- const serviceName =
79
- options?.serviceName ?? (await projectUtils.name()) ?? DEFAULT_SERVICE_NAME;
80
- const requestContextKeys = [
81
- ...(options?.requestContextKeys ?? TRACE_REQUEST_CONTEXT_KEYS),
82
- ];
83
-
84
- // The OTel HTTP exporter treats `OTEL_EXPORTER_OTLP_ENDPOINT` as a
85
- // *base* URL and appends the signal path itself (e.g.
86
- // `http://localhost:6006` -> `http://localhost:6006/v1/traces`). Log
87
- // the resolved POST URL so misconfigurations (e.g. accidentally
88
- // setting the base var to a `/v1/traces`-suffixed URL, which makes
89
- // the SDK POST to `.../v1/traces/v1/traces` and Phoenix 404s) are
90
- // obvious in startup output.
91
- const otelBase = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
92
- const otelTracesOverride = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
93
- const resolvedTracesUrl = otelTracesOverride
94
- ? otelTracesOverride
95
- : otelBase
96
- ? `${otelBase.replace(/\/+$/, "")}/v1/traces`
97
- : undefined;
98
- log.info("Mastra observability wired through OTel bridge", {
99
- serviceName,
100
- requestContextKeys,
101
- otelBase: otelBase ?? "<unset>",
102
- resolvedTracesUrl: resolvedTracesUrl ?? "<noop; OTLP endpoint unset>",
103
- });
104
-
105
- return new Observability({
106
- configs: {
107
- serviceName: {
108
- serviceName,
109
- bridge: new OtelBridge(),
110
- requestContextKeys,
111
- },
112
- },
113
- });
114
- }