@dbx-tools/appkit-mastra 0.1.13 → 0.1.14

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 (59) hide show
  1. package/README.md +304 -645
  2. package/index.ts +46 -38
  3. package/package.json +58 -45
  4. package/src/agents.ts +224 -66
  5. package/src/chart.ts +531 -429
  6. package/src/config.ts +270 -19
  7. package/src/filesystems.ts +1090 -0
  8. package/src/genie.ts +1000 -660
  9. package/src/history.ts +166 -79
  10. package/src/mcp.ts +105 -0
  11. package/src/memory.ts +94 -92
  12. package/src/mlflow.ts +149 -0
  13. package/src/model.ts +75 -408
  14. package/src/observability.ts +121 -69
  15. package/src/pagination.ts +34 -0
  16. package/src/plugin.ts +552 -67
  17. package/src/processors.ts +168 -0
  18. package/src/rest.ts +67 -0
  19. package/src/server.ts +232 -45
  20. package/src/serving-sanitize.ts +167 -0
  21. package/src/serving.ts +27 -243
  22. package/src/statement.ts +89 -0
  23. package/src/storage-schema.ts +41 -0
  24. package/src/summarize.ts +176 -0
  25. package/src/threads.ts +338 -0
  26. package/src/workspaces.ts +346 -0
  27. package/src/writer.ts +44 -0
  28. package/tsconfig.json +41 -0
  29. package/dist/index.d.ts +0 -20
  30. package/dist/index.js +0 -20
  31. package/dist/src/agents.d.ts +0 -306
  32. package/dist/src/agents.js +0 -403
  33. package/dist/src/chart.d.ts +0 -170
  34. package/dist/src/chart.js +0 -491
  35. package/dist/src/config.d.ts +0 -183
  36. package/dist/src/config.js +0 -12
  37. package/dist/src/genie.d.ts +0 -131
  38. package/dist/src/genie.js +0 -630
  39. package/dist/src/history.d.ts +0 -67
  40. package/dist/src/history.js +0 -172
  41. package/dist/src/memory.d.ts +0 -100
  42. package/dist/src/memory.js +0 -242
  43. package/dist/src/model.d.ts +0 -159
  44. package/dist/src/model.js +0 -427
  45. package/dist/src/observability.d.ts +0 -33
  46. package/dist/src/observability.js +0 -71
  47. package/dist/src/plugin.d.ts +0 -130
  48. package/dist/src/plugin.js +0 -283
  49. package/dist/src/processors/strip-stale-charts.d.ts +0 -29
  50. package/dist/src/processors/strip-stale-charts.js +0 -96
  51. package/dist/src/server.d.ts +0 -46
  52. package/dist/src/server.js +0 -123
  53. package/dist/src/serving.d.ts +0 -156
  54. package/dist/src/serving.js +0 -231
  55. package/dist/src/tools/email.d.ts +0 -74
  56. package/dist/src/tools/email.js +0 -122
  57. package/dist/tsconfig.build.tsbuildinfo +0 -1
  58. package/src/processors/strip-stale-charts.ts +0 -105
  59. package/src/tools/email.ts +0 -147
@@ -1,92 +1,144 @@
1
1
  /**
2
- * Mastra observability wiring for the `@dbx-tools/appkit-phoenix`
3
- * sibling plugin.
2
+ * Mastra observability wired through the same OTel pipeline AppKit's
3
+ * built-in plugins (e.g. `agents`) use, via `@mastra/otel-bridge`.
4
4
  *
5
- * Mastra's `Observability` registry accepts any
6
- * `@mastra/observability` `BaseExporter`. We use `OtelExporter` from
7
- * `@mastra/otel-exporter` (Mastra's first-party OTLP shim) with the
8
- * `custom` provider pointed at Phoenix's local collector URL. No
9
- * Arize-specific wrapper is needed - Phoenix is a vanilla
10
- * OpenInference-compatible OTLP/HTTP receiver.
5
+ * How traces flow:
11
6
  *
12
- * Discovery is structural so this module doesn't depend on
13
- * `@dbx-tools/appkit-phoenix` at compile time: we look up the
14
- * registered plugin by its registered name (`"phoenix"`) and read its
15
- * `exports().collectorEndpoint()` if it is shaped like the phoenix
16
- * plugin. The phoenix package is therefore an *optional* sibling -
17
- * apps that don't install it just get an undefined observability
18
- * config and Mastra runs without OTLP export.
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 is not
30
+ * registered at all (unless `observability: true` forces it on), so
31
+ * Mastra does not emit `[OtelBridge] No OTEL span found` warnings.
19
32
  */
20
33
 
21
- import type { pluginUtils } from "@dbx-tools/appkit-shared";
22
34
  import { Observability } from "@mastra/observability";
23
- import { OtelExporter } from "@mastra/otel-exporter";
35
+ import { OtelBridge } from "@mastra/otel-bridge";
36
+
37
+ import { TRACE_REQUEST_CONTEXT_KEYS } from "./config";
38
+ import { log } from "@dbx-tools/shared-core";
39
+ import { project } from "@dbx-tools/core";
24
40
 
25
- /** Plugin name the phoenix plugin registers under (matches `phoenix()`). */
26
- const PHOENIX_PLUGIN_NAME = "phoenix";
41
+ const logger = log.logger("mastra/observability");
27
42
 
28
- /** Structural shape of the bits of `phoenix().exports()` we touch. */
29
- interface PhoenixExportsLike {
30
- collectorEndpoint?(): string | undefined;
43
+ const DEFAULT_SERVICE_NAME = "mastra";
44
+
45
+ export interface BuildObservabilityOptions {
46
+ /**
47
+ * Whether to wire the Mastra `OtelBridge`. Mirrors
48
+ * {@link MastraPluginConfig.observability}:
49
+ *
50
+ * - `undefined` (auto): on only when `OTEL_EXPORTER_OTLP_ENDPOINT` or
51
+ * `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` is set (same signal AppKit's
52
+ * `TelemetryManager` uses to register a real tracer). When unset,
53
+ * skip the bridge entirely so Mastra does not emit
54
+ * `[OtelBridge] No OTEL span found` warnings on the noop tracer.
55
+ * - `true`: force on even without an OTLP endpoint.
56
+ * - `false`: force off.
57
+ */
58
+ enabled?: boolean;
59
+ /**
60
+ * Service name attached to the Mastra `Observability` config. Used
61
+ * as the tracer scope name on bridged OTel spans (the `service.name`
62
+ * resource attribute is owned by AppKit's `TelemetryManager` instead
63
+ * - it reads `OTEL_SERVICE_NAME` / `DATABRICKS_APP_NAME` at
64
+ * `createApp` time).
65
+ *
66
+ * Defaults to project name then `"mastra"`.
67
+ */
68
+ serviceName?: string;
69
+ /**
70
+ * `RequestContext` keys to extract as span metadata on every Mastra
71
+ * trace. Defaults to {@link TRACE_REQUEST_CONTEXT_KEYS} (user id,
72
+ * thread id, request id, environment, model override, ...).
73
+ *
74
+ * Supports dot notation for nested values per the Mastra docs.
75
+ */
76
+ requestContextKeys?: readonly string[];
31
77
  }
32
78
 
33
- /** Structural shape of an AppKit plugin instance with `exports()`. */
34
- interface PluginWithExports {
35
- exports?(): unknown;
79
+ /** True when AppKit's global OTel pipeline is configured to export traces. */
80
+ export function isOtlpTracingConfigured(): boolean {
81
+ return Boolean(
82
+ process.env.OTEL_EXPORTER_OTLP_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
83
+ );
36
84
  }
37
85
 
38
86
  /**
39
- * If the sibling `phoenix` plugin is registered AND has booted with a
40
- * usable collector URL, return a Mastra `Observability` configured to
41
- * stream traces + logs there. Otherwise return `undefined` so the
42
- * caller can omit the field on the `new Mastra({...})` constructor.
87
+ * Build a Mastra `Observability` whose spans ride AppKit's global
88
+ * OTel pipeline via `@mastra/otel-bridge`.
43
89
  *
44
- * The exporter uses `provider.custom` with `http/protobuf`, which is
45
- * what Phoenix's `/v1/traces` endpoint speaks natively. Switching
46
- * Phoenix to gRPC would be a one-line `protocol: "grpc"` change and
47
- * a different exported URL.
90
+ * Returns `undefined` when tracing is off: either the caller passed
91
+ * `enabled: false`, or auto mode finds no OTLP endpoint (the default).
92
+ * Skipping the bridge in that case avoids `[OtelBridge] No OTEL span
93
+ * found` log spam from Mastra spans that have no parent on the noop
94
+ * tracer.
48
95
  */
49
- export function buildPhoenixObservability(
50
- context: pluginUtils.PluginContextLike | undefined,
51
- serviceName: string,
52
- ): Observability | undefined {
53
- const endpoint = readPhoenixEndpoint(context);
54
- if (!endpoint) return undefined;
96
+ export async function buildObservability(
97
+ options?: BuildObservabilityOptions,
98
+ ): Promise<Observability | undefined> {
99
+ const otelBase = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
100
+ const otelTracesOverride = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
101
+ const otlpConfigured = isOtlpTracingConfigured();
102
+
103
+ if (options?.enabled === false || (options?.enabled !== true && !otlpConfigured)) {
104
+ logger.info("Mastra observability off", {
105
+ reason:
106
+ options?.enabled === false
107
+ ? "disabled in plugin config"
108
+ : "OTEL_EXPORTER_OTLP_ENDPOINT unset",
109
+ });
110
+ return undefined;
111
+ }
112
+
113
+ const serviceName = options?.serviceName ?? (await project.name()) ?? DEFAULT_SERVICE_NAME;
114
+ const requestContextKeys = [...(options?.requestContextKeys ?? TRACE_REQUEST_CONTEXT_KEYS)];
115
+
116
+ // The OTel HTTP exporter treats `OTEL_EXPORTER_OTLP_ENDPOINT` as a
117
+ // *base* URL and appends the signal path itself (e.g.
118
+ // `http://localhost:6006` -> `http://localhost:6006/v1/traces`). Log
119
+ // the resolved POST URL so misconfigurations (e.g. accidentally
120
+ // setting the base var to a `/v1/traces`-suffixed URL, which makes
121
+ // the SDK POST to `.../v1/traces/v1/traces` and Phoenix 404s) are
122
+ // obvious in startup output.
123
+ const resolvedTracesUrl = otelTracesOverride
124
+ ? otelTracesOverride
125
+ : otelBase
126
+ ? `${otelBase.replace(/\/+$/, "")}/v1/traces`
127
+ : undefined;
128
+ logger.info("Mastra observability wired through OTel bridge", {
129
+ serviceName,
130
+ requestContextKeys,
131
+ otelBase: otelBase ?? "<unset>",
132
+ resolvedTracesUrl: resolvedTracesUrl ?? "<unset>",
133
+ });
55
134
 
56
135
  return new Observability({
57
136
  configs: {
58
- phoenix: {
137
+ serviceName: {
59
138
  serviceName,
60
- exporters: [
61
- new OtelExporter({
62
- provider: {
63
- custom: {
64
- endpoint,
65
- protocol: "http/protobuf",
66
- },
67
- },
68
- }),
69
- ],
139
+ bridge: new OtelBridge(),
140
+ requestContextKeys,
70
141
  },
71
142
  },
72
143
  });
73
144
  }
74
-
75
- /**
76
- * Pull the OTLP collector URL out of the registered `phoenix` plugin.
77
- * Tolerant of the plugin being absent (returns `undefined`) and of a
78
- * future shape change in its exports (anything that's not a string
79
- * is ignored). The lookup is keyed off the registered plugin *name*
80
- * so this file does not depend on `@dbx-tools/appkit-phoenix`.
81
- */
82
- function readPhoenixEndpoint(
83
- context: pluginUtils.PluginContextLike | undefined,
84
- ): string | undefined {
85
- if (!context) return undefined;
86
- const plugin = context.getPlugins().get(PHOENIX_PLUGIN_NAME) as
87
- | PluginWithExports
88
- | undefined;
89
- const exports_ = plugin?.exports?.() as PhoenixExportsLike | undefined;
90
- const url = exports_?.collectorEndpoint?.();
91
- return typeof url === "string" ? url : undefined;
92
- }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Query-parameter coercion shared by the paginated custom routes
3
+ * (`history` / `threads`). Each route keeps its own page-size default
4
+ * and hard cap and passes them in, so the coercion rules stay in one
5
+ * place without collapsing the routes' distinct sizing.
6
+ */
7
+
8
+ /** Bounds for {@link clampPerPage}. */
9
+ export interface PerPageBounds {
10
+ /** Page size used for empty / non-positive / non-numeric inputs. */
11
+ fallback: number;
12
+ /** Hard cap so a misbehaving client can't fetch everything at once. */
13
+ max: number;
14
+ }
15
+
16
+ /** Coerce / clamp a `perPage` value, falling back to `bounds.fallback`. */
17
+ export function clampPerPage(value: number | undefined, bounds: PerPageBounds): number {
18
+ if (value === undefined || Number.isNaN(value)) return bounds.fallback;
19
+ const n = Math.trunc(value);
20
+ if (n <= 0) return bounds.fallback;
21
+ return Math.min(n, bounds.max);
22
+ }
23
+
24
+ /**
25
+ * Coerce a Hono query value into a non-negative integer. Returns
26
+ * `undefined` for empty / non-numeric / negative inputs so the caller
27
+ * can apply its built-in defaults.
28
+ */
29
+ export function parseIntParam(value: string | undefined): number | undefined {
30
+ if (!value) return undefined;
31
+ const n = Number(value);
32
+ if (!Number.isFinite(n) || n < 0) return undefined;
33
+ return Math.trunc(n);
34
+ }