@dbx-tools/appkit-mastra 0.1.5 → 0.1.10
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 +394 -0
- package/index.ts +46 -37
- package/package.json +58 -47
- package/src/agents.ts +233 -62
- package/src/chart.ts +555 -285
- package/src/config.ts +282 -18
- package/src/filesystems.ts +1090 -0
- package/src/genie.ts +1004 -601
- package/src/history.ts +178 -79
- package/src/mcp.ts +105 -0
- package/src/memory.ts +129 -74
- package/src/mlflow.ts +149 -0
- package/src/model.ts +80 -408
- package/src/observability.ts +144 -0
- package/src/pagination.ts +34 -0
- package/src/plugin.ts +573 -59
- package/src/processors.ts +168 -0
- package/src/rest.ts +67 -0
- package/src/server.ts +243 -45
- package/src/serving-sanitize.ts +167 -0
- package/src/serving.ts +27 -224
- package/src/statement.ts +89 -0
- package/src/storage-schema.ts +41 -0
- package/src/summarize.ts +176 -0
- package/src/threads.ts +338 -0
- package/src/workspaces.ts +346 -0
- package/src/writer.ts +44 -0
- package/tsconfig.json +41 -0
- package/dist/index.d.ts +0 -19
- package/dist/index.js +0 -19
- package/dist/src/agents.d.ts +0 -306
- package/dist/src/agents.js +0 -393
- package/dist/src/chart.d.ts +0 -104
- package/dist/src/chart.js +0 -375
- package/dist/src/config.d.ts +0 -170
- package/dist/src/config.js +0 -12
- package/dist/src/genie.d.ts +0 -116
- package/dist/src/genie.js +0 -594
- package/dist/src/history.d.ts +0 -67
- package/dist/src/history.js +0 -158
- package/dist/src/memory.d.ts +0 -79
- package/dist/src/memory.js +0 -197
- package/dist/src/model.d.ts +0 -159
- package/dist/src/model.js +0 -423
- package/dist/src/plugin.d.ts +0 -130
- package/dist/src/plugin.js +0 -255
- package/dist/src/render-chart-route.d.ts +0 -33
- package/dist/src/render-chart-route.js +0 -120
- package/dist/src/server.d.ts +0 -46
- package/dist/src/server.js +0 -113
- package/dist/src/serving.d.ts +0 -156
- package/dist/src/serving.js +0 -214
- package/src/render-chart-route.ts +0 -141
package/dist/src/genie.js
DELETED
|
@@ -1,594 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mastra tool wrappers around the AppKit `genie` plugin's exports.
|
|
3
|
-
*
|
|
4
|
-
* One `sendMessage` tool is registered per configured space alias so
|
|
5
|
-
* the LLM picks the space by tool selection (the description bakes the
|
|
6
|
-
* alias in). `getConversation` is registered once, taking `alias` as a
|
|
7
|
-
* parameter.
|
|
8
|
-
*
|
|
9
|
-
* All Genie payload types are inferred from the public `genie` factory
|
|
10
|
-
* (`genie().plugin` constructor → `exports()` return type), so any
|
|
11
|
-
* upstream change in `@databricks/appkit` flows in automatically.
|
|
12
|
-
*
|
|
13
|
-
* As Genie streams its long-running events (`FETCHING_METADATA` →
|
|
14
|
-
* `ASKING_AI` → `EXECUTING_QUERY` → `COMPLETED`, plus SQL text and
|
|
15
|
-
* follow-ups in `message_result.attachments`), the tool forwards a
|
|
16
|
-
* normalised {@link GenieProgress} discriminated union out through
|
|
17
|
-
* `ctx.writer` so the client can render an incremental loading pill.
|
|
18
|
-
* Row payloads from `query_result` are intentionally discarded - the
|
|
19
|
-
* LLM never sees rows, and charts come from the separate
|
|
20
|
-
* `render_data` tool when the model decides one is useful.
|
|
21
|
-
*/
|
|
22
|
-
import { randomUUID } from "node:crypto";
|
|
23
|
-
import { genie } from "@databricks/appkit";
|
|
24
|
-
import { stringUtils } from "@dbx-tools/appkit-shared";
|
|
25
|
-
import { createTool } from "@mastra/core/tools";
|
|
26
|
-
import { z } from "zod";
|
|
27
|
-
/**
|
|
28
|
-
* Per-dataset metadata surfaced to the LLM. The actual rows are
|
|
29
|
-
* dispatched separately as a `kind: "chart"` writer event so the
|
|
30
|
-
* model never has the rows in its context (token cost stays flat
|
|
31
|
-
* regardless of dataset size). The model uses `chartId` to
|
|
32
|
-
* reference the chart inline via the `[[chart:<chartId>]]` marker.
|
|
33
|
-
*/
|
|
34
|
-
const datasetSchema = z.object({
|
|
35
|
-
chartId: z.string().describe(stringUtils.toDescription `
|
|
36
|
-
Short id (8 hex chars) for the chart-render slot the host UI
|
|
37
|
-
has staged for this dataset. Embed
|
|
38
|
-
\`[[chart:<chartId>]]\` on its own line in your reply at the
|
|
39
|
-
position you want the chart to appear; the client renders it
|
|
40
|
-
inline. Do not paraphrase the dataset's rows in prose - the
|
|
41
|
-
chart is the rendering.
|
|
42
|
-
`),
|
|
43
|
-
title: z.string().optional().describe(stringUtils.toDescription `
|
|
44
|
-
Genie's own title for the SQL that produced this dataset.
|
|
45
|
-
Useful as a label when you reference the chart in prose.
|
|
46
|
-
`),
|
|
47
|
-
description: z.string().optional().describe(stringUtils.toDescription `
|
|
48
|
-
Genie's prose description of the SQL, if any.
|
|
49
|
-
`),
|
|
50
|
-
columns: z.array(z.string()).describe(stringUtils.toDescription `
|
|
51
|
-
Column names in display order. Use these when describing what
|
|
52
|
-
is being charted (e.g. "trend of fill_rate over date").
|
|
53
|
-
`),
|
|
54
|
-
rowCount: z.number().describe(stringUtils.toDescription `
|
|
55
|
-
Total rows in this dataset. Mention only if it adds context
|
|
56
|
-
(e.g. "across the last 90 days").
|
|
57
|
-
`),
|
|
58
|
-
sql: z
|
|
59
|
-
.string()
|
|
60
|
-
.optional()
|
|
61
|
-
.describe(stringUtils.toDescription `
|
|
62
|
-
SQL Genie generated and executed. The host UI shows this on
|
|
63
|
-
demand; you do not need to repeat it.
|
|
64
|
-
`),
|
|
65
|
-
});
|
|
66
|
-
/**
|
|
67
|
-
* Top-level output schema returned to the LLM from a Genie tool
|
|
68
|
-
* call. The `datasets` array is intentionally metadata-only - row
|
|
69
|
-
* data rides a writer event the host UI consumes directly and is
|
|
70
|
-
* not in the model's context.
|
|
71
|
-
*/
|
|
72
|
-
const genieToolOutputSchema = z.object({
|
|
73
|
-
conversationId: z
|
|
74
|
-
.string()
|
|
75
|
-
.optional()
|
|
76
|
-
.describe(stringUtils.toDescription `
|
|
77
|
-
Pass back on the next call to continue the same Genie thread.
|
|
78
|
-
`),
|
|
79
|
-
genieAnswer: z
|
|
80
|
-
.string()
|
|
81
|
-
.optional()
|
|
82
|
-
.describe(stringUtils.toDescription `
|
|
83
|
-
Genie's natural-language answer to the question. Pass this
|
|
84
|
-
through to the user (verbatim, or as the basis of your
|
|
85
|
-
reply). Genie may have run multiple SQL queries and tools to
|
|
86
|
-
produce this; the full text is the answer.
|
|
87
|
-
`),
|
|
88
|
-
datasets: z
|
|
89
|
-
.array(datasetSchema)
|
|
90
|
-
.optional()
|
|
91
|
-
.describe(stringUtils.toDescription `
|
|
92
|
-
Datasets Genie produced for this turn (one per executed SQL
|
|
93
|
-
statement). Each entry is metadata only; the rows are
|
|
94
|
-
streamed to the host UI out-of-band. To render any of these
|
|
95
|
-
as a chart inline in your reply, embed
|
|
96
|
-
\`[[chart:<chartId>]]\` where you want the chart to appear.
|
|
97
|
-
Do not paraphrase the rows - the chart is what the user
|
|
98
|
-
should see; your prose should add interpretation
|
|
99
|
-
(highlights, deltas, anomalies) around the chart.
|
|
100
|
-
`),
|
|
101
|
-
suggestedFollowUps: z
|
|
102
|
-
.array(z.string())
|
|
103
|
-
.optional()
|
|
104
|
-
.describe(stringUtils.toDescription `
|
|
105
|
-
Follow-up question suggestions Genie produced. The host UI
|
|
106
|
-
renders these as clickable buttons; you do not need to list
|
|
107
|
-
them in your reply.
|
|
108
|
-
`),
|
|
109
|
-
error: z
|
|
110
|
-
.string()
|
|
111
|
-
.optional()
|
|
112
|
-
.describe(stringUtils.toDescription `
|
|
113
|
-
Genie-side error message if the request failed.
|
|
114
|
-
`),
|
|
115
|
-
});
|
|
116
|
-
const sendMessageSchema = z.object({
|
|
117
|
-
content: z.string().describe(stringUtils.toDescription `
|
|
118
|
-
Natural-language question to send to the Genie space.
|
|
119
|
-
`),
|
|
120
|
-
conversationId: z
|
|
121
|
-
.string()
|
|
122
|
-
.optional()
|
|
123
|
-
.describe(stringUtils.toDescription `
|
|
124
|
-
Optional Genie conversation id to continue an earlier thread.
|
|
125
|
-
Omit on the first call; pass the id returned in the previous
|
|
126
|
-
result's \`conversationId\` to follow up.
|
|
127
|
-
`),
|
|
128
|
-
});
|
|
129
|
-
const getConversationSchema = z.object({
|
|
130
|
-
alias: z.string().describe(stringUtils.toDescription `
|
|
131
|
-
Alias of the Genie space the conversation belongs to (matches
|
|
132
|
-
the key in the genie plugin's \`spaces\` config).
|
|
133
|
-
`),
|
|
134
|
-
conversationId: z.string().describe(stringUtils.toDescription `
|
|
135
|
-
Genie conversation id whose history to fetch.
|
|
136
|
-
`),
|
|
137
|
-
});
|
|
138
|
-
/** Per-attachment shape returned inside a stored Genie message. */
|
|
139
|
-
const genieAttachmentSchema = z.object({
|
|
140
|
-
attachmentId: z.string().optional().describe(stringUtils.toDescription `
|
|
141
|
-
Genie attachment id; internal bookkeeping.
|
|
142
|
-
`),
|
|
143
|
-
query: z
|
|
144
|
-
.object({
|
|
145
|
-
title: z.string().optional().describe(stringUtils.toDescription `
|
|
146
|
-
Genie's title for the SQL, if any.
|
|
147
|
-
`),
|
|
148
|
-
description: z.string().optional().describe(stringUtils.toDescription `
|
|
149
|
-
Genie's prose description of the SQL, if any.
|
|
150
|
-
`),
|
|
151
|
-
query: z.string().optional().describe(stringUtils.toDescription `
|
|
152
|
-
SQL Genie generated and executed.
|
|
153
|
-
`),
|
|
154
|
-
statementId: z.string().optional().describe(stringUtils.toDescription `
|
|
155
|
-
Statement-execution id; internal bookkeeping.
|
|
156
|
-
`),
|
|
157
|
-
})
|
|
158
|
-
.optional()
|
|
159
|
-
.describe(stringUtils.toDescription `
|
|
160
|
-
SQL Genie attached to this message, if it ran any.
|
|
161
|
-
`),
|
|
162
|
-
text: z
|
|
163
|
-
.object({
|
|
164
|
-
content: z.string().optional().describe(stringUtils.toDescription `
|
|
165
|
-
Genie's natural-language answer text for this attachment.
|
|
166
|
-
`),
|
|
167
|
-
})
|
|
168
|
-
.optional()
|
|
169
|
-
.describe(stringUtils.toDescription `
|
|
170
|
-
Per-attachment text content (independent of the message-level
|
|
171
|
-
\`content\` field).
|
|
172
|
-
`),
|
|
173
|
-
suggestedQuestions: z
|
|
174
|
-
.array(z.string())
|
|
175
|
-
.optional()
|
|
176
|
-
.describe(stringUtils.toDescription `
|
|
177
|
-
Follow-up question suggestions Genie generated for this turn.
|
|
178
|
-
`),
|
|
179
|
-
});
|
|
180
|
-
/** Single message inside a Genie conversation history page. */
|
|
181
|
-
const genieMessageSchema = z.object({
|
|
182
|
-
messageId: z.string().describe(stringUtils.toDescription `
|
|
183
|
-
Genie message id; internal bookkeeping.
|
|
184
|
-
`),
|
|
185
|
-
conversationId: z.string().describe(stringUtils.toDescription `
|
|
186
|
-
Conversation id this message belongs to.
|
|
187
|
-
`),
|
|
188
|
-
spaceId: z.string().describe(stringUtils.toDescription `
|
|
189
|
-
Genie space id this message belongs to.
|
|
190
|
-
`),
|
|
191
|
-
status: z.string().describe(stringUtils.toDescription `
|
|
192
|
-
Genie message status (\`COMPLETED\`, \`FAILED\`, etc.).
|
|
193
|
-
`),
|
|
194
|
-
content: z.string().describe(stringUtils.toDescription `
|
|
195
|
-
Outer message-level natural-language content Genie wrote.
|
|
196
|
-
`),
|
|
197
|
-
attachments: z
|
|
198
|
-
.array(genieAttachmentSchema)
|
|
199
|
-
.optional()
|
|
200
|
-
.describe(stringUtils.toDescription `
|
|
201
|
-
Attachments (SQL queries, text blocks, suggested follow-ups)
|
|
202
|
-
Genie produced for this message.
|
|
203
|
-
`),
|
|
204
|
-
error: z.string().optional().describe(stringUtils.toDescription `
|
|
205
|
-
Genie-side error attached to this message, if any.
|
|
206
|
-
`),
|
|
207
|
-
});
|
|
208
|
-
/**
|
|
209
|
-
* Output schema for the \`genie_get_conversation\` tool. Mirrors
|
|
210
|
-
* AppKit's \`GenieConversationHistoryResponse\` so the model gets a
|
|
211
|
-
* clear, typed view of prior messages instead of an opaque blob.
|
|
212
|
-
*/
|
|
213
|
-
const genieGetConversationOutputSchema = z.object({
|
|
214
|
-
conversationId: z.string().describe(stringUtils.toDescription `
|
|
215
|
-
Conversation id you fetched.
|
|
216
|
-
`),
|
|
217
|
-
spaceId: z.string().describe(stringUtils.toDescription `
|
|
218
|
-
Genie space the conversation belongs to.
|
|
219
|
-
`),
|
|
220
|
-
messages: z.array(genieMessageSchema).describe(stringUtils.toDescription `
|
|
221
|
-
Messages in the conversation, oldest to newest. Each
|
|
222
|
-
\`message.content\` is Genie's natural-language answer for
|
|
223
|
-
that turn; attachments carry the SQL and follow-ups Genie
|
|
224
|
-
produced.
|
|
225
|
-
`),
|
|
226
|
-
});
|
|
227
|
-
/**
|
|
228
|
-
* Default tool name for a wired Genie alias. The well-known `default`
|
|
229
|
-
* alias collapses to `genie`; everything else gets a `genie_` prefix so
|
|
230
|
-
* multiple spaces stay disambiguated when an agent has more than one
|
|
231
|
-
* wired. Matches the `genie` / `genie_<alias>` naming used elsewhere in
|
|
232
|
-
* dbx-tools AppKit demos.
|
|
233
|
-
*/
|
|
234
|
-
export function defaultGenieToolName(alias) {
|
|
235
|
-
if (alias === "default")
|
|
236
|
-
return "genie";
|
|
237
|
-
return stringUtils.toIdentifierWithOptions({ distinct: true }, "genie", alias);
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Build one `sendMessage` tool per configured Genie alias plus a single
|
|
241
|
-
* `getConversation` tool. Returns a record keyed by tool id, ready to
|
|
242
|
-
* spread into an `Agent`'s `tools` map.
|
|
243
|
-
*/
|
|
244
|
-
export function buildGenieTools(opts) {
|
|
245
|
-
const tools = {};
|
|
246
|
-
for (const alias of opts.aliases) {
|
|
247
|
-
const id = defaultGenieToolName(alias);
|
|
248
|
-
tools[id] = createTool({
|
|
249
|
-
id,
|
|
250
|
-
description: stringUtils.toDescription `
|
|
251
|
-
Ask the Databricks Genie space "${alias}" a single
|
|
252
|
-
natural-language question. Genie translates it to SQL,
|
|
253
|
-
runs the SQL against the configured datasets, and returns
|
|
254
|
-
\`genieAnswer\` (its prose answer) plus \`datasets[]\`
|
|
255
|
-
(one metadata entry per executed query). Each dataset
|
|
256
|
-
carries a short \`chartId\`; embed
|
|
257
|
-
\`[[chart:<chartId>]]\` on its own line in your reply at
|
|
258
|
-
the position where you want that data rendered as an
|
|
259
|
-
inline chart. Do not paraphrase row values - the chart is
|
|
260
|
-
the rendering. Add interpretation around the chart
|
|
261
|
-
(highlights, deltas, anomalies, takeaways) instead of
|
|
262
|
-
repeating numbers.
|
|
263
|
-
|
|
264
|
-
Calling this tool is expensive; issue **one** focused
|
|
265
|
-
question per user turn. If the first answer doesn't fit,
|
|
266
|
-
ask the user a clarifying question rather than
|
|
267
|
-
re-querying with rephrased intent. Prefer aggregated
|
|
268
|
-
questions over raw-row queries (e.g. ask for "monthly
|
|
269
|
-
averages" instead of "all rows" for time-series).
|
|
270
|
-
`,
|
|
271
|
-
inputSchema: sendMessageSchema,
|
|
272
|
-
outputSchema: genieToolOutputSchema,
|
|
273
|
-
execute: async ({ content, conversationId }, ctx) => {
|
|
274
|
-
const stream = opts.exports.sendMessage(alias, content, conversationId, {
|
|
275
|
-
signal: opts.signal,
|
|
276
|
-
});
|
|
277
|
-
return drainGenieStream(stream, ctx.writer);
|
|
278
|
-
},
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
tools.genie_get_conversation = createTool({
|
|
282
|
-
id: "genie_get_conversation",
|
|
283
|
-
description: stringUtils.toDescription `
|
|
284
|
-
Fetch the full message history of a prior Genie conversation
|
|
285
|
-
by id. Use when the user references an earlier Genie thread
|
|
286
|
-
by id, or to inspect attachments / SQL from previous turns.
|
|
287
|
-
`,
|
|
288
|
-
inputSchema: getConversationSchema,
|
|
289
|
-
outputSchema: genieGetConversationOutputSchema,
|
|
290
|
-
execute: async ({ alias, conversationId }) => {
|
|
291
|
-
return opts.exports.getConversation(alias, conversationId, opts.signal);
|
|
292
|
-
},
|
|
293
|
-
});
|
|
294
|
-
return tools;
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Drain the genie `sendMessage` AsyncGenerator into a flat result
|
|
298
|
-
* the agent's calling LLM can reason about, while forwarding
|
|
299
|
-
* progress and chart events to the host UI.
|
|
300
|
-
*
|
|
301
|
-
* Three streams of output happen in parallel:
|
|
302
|
-
*
|
|
303
|
-
* 1. {@link GenieProgress} pill events on the writer (`started`,
|
|
304
|
-
* `status`, `sql`, `suggested`, `error`) drive the loading
|
|
305
|
-
* pill in the chat bubble.
|
|
306
|
-
* 2. `kind: "chart"` events on the writer carry the row payload
|
|
307
|
-
* from each Genie SQL statement so the host UI's
|
|
308
|
-
* `<ChartSlot>` can render the chart inline at the marker
|
|
309
|
-
* position the model picked. The data never reaches the LLM.
|
|
310
|
-
* 3. The `DrainResult` returned to the LLM contains
|
|
311
|
-
* Genie's prose answer plus a `datasets[]` array of metadata
|
|
312
|
-
* (chartId, title, columns, rowCount, sql) the model uses to
|
|
313
|
-
* cite charts via `[[chart:<chartId>]]` markers.
|
|
314
|
-
*
|
|
315
|
-
* `query_result` and `message_result` events arrive in either
|
|
316
|
-
* order; we buffer per-statement metadata in
|
|
317
|
-
* {@link DatasetMeta} so each half can fill in the bits it knows
|
|
318
|
-
* about and we emit the chart event once `query_result` lands
|
|
319
|
-
* (with whatever title was already set, falling back to a
|
|
320
|
-
* generic label otherwise).
|
|
321
|
-
*/
|
|
322
|
-
async function drainGenieStream(stream, writer) {
|
|
323
|
-
let conversationId;
|
|
324
|
-
let genieAnswer;
|
|
325
|
-
let suggestedFollowUps;
|
|
326
|
-
let error;
|
|
327
|
-
// AppKit's `streamSendMessage` forwards every SDK `onProgress`
|
|
328
|
-
// callback verbatim - the same `EXECUTING_QUERY` can fire several
|
|
329
|
-
// times during a single poll loop. AppKit's other path,
|
|
330
|
-
// `streamGetMessage`, dedupes on the connector side; we mirror that
|
|
331
|
-
// behaviour here so the UI status pill doesn't flicker and we don't
|
|
332
|
-
// burn writer bytes on no-op events.
|
|
333
|
-
let lastStatus;
|
|
334
|
-
// Per-statement scratch keyed by Genie's `statementId`. Filled in
|
|
335
|
-
// by both `query_result` (rows + columns) and `message_result`
|
|
336
|
-
// (sql + title + description); the LLM-bound `datasets[]` is
|
|
337
|
-
// built from this at end-of-stream, and chart writer events fire
|
|
338
|
-
// when `query_result` lands.
|
|
339
|
-
const datasetsByStatementId = new Map();
|
|
340
|
-
// Best-effort progress emission. Awaited so the underlying agent
|
|
341
|
-
// stream sees events in order; write failures are swallowed so a
|
|
342
|
-
// dead writer (e.g. closed downstream) can't take the tool down.
|
|
343
|
-
const emit = async (event) => {
|
|
344
|
-
if (!writer)
|
|
345
|
-
return;
|
|
346
|
-
try {
|
|
347
|
-
await writer.write(event);
|
|
348
|
-
}
|
|
349
|
-
catch {
|
|
350
|
-
// ignore: downstream stream is no longer interested
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
for await (const event of stream) {
|
|
354
|
-
// Uncomment to log every raw Genie wire event before the switch
|
|
355
|
-
// routes it through the writer / DrainResult. Useful when tuning
|
|
356
|
-
// the pill / answer pipeline against real Genie payloads (status
|
|
357
|
-
// codes, attachment shapes, query_result manifests Genie surfaces
|
|
358
|
-
// only on certain question types, etc.).
|
|
359
|
-
// eslint-disable-next-line no-console
|
|
360
|
-
// console.log("[mastra/genie] event", event);
|
|
361
|
-
switch (event.type) {
|
|
362
|
-
case "message_start":
|
|
363
|
-
conversationId = event.conversationId;
|
|
364
|
-
await emit({
|
|
365
|
-
kind: "started",
|
|
366
|
-
conversationId: event.conversationId,
|
|
367
|
-
messageId: event.messageId,
|
|
368
|
-
spaceId: event.spaceId,
|
|
369
|
-
});
|
|
370
|
-
break;
|
|
371
|
-
case "status":
|
|
372
|
-
if (event.status === lastStatus)
|
|
373
|
-
break;
|
|
374
|
-
lastStatus = event.status;
|
|
375
|
-
await emit({
|
|
376
|
-
kind: "status",
|
|
377
|
-
status: event.status,
|
|
378
|
-
label: humanizeGenieStatus(event.status),
|
|
379
|
-
});
|
|
380
|
-
break;
|
|
381
|
-
case "query_result": {
|
|
382
|
-
const columns = (event.data?.manifest?.schema?.columns ?? []).map((c) => c.name);
|
|
383
|
-
const dataArray = (event.data?.result?.data_array ?? []);
|
|
384
|
-
const rows = genieRowsToObjects(columns, dataArray);
|
|
385
|
-
const meta = upsertDatasetMeta(datasetsByStatementId, event.statementId, {
|
|
386
|
-
columns,
|
|
387
|
-
rowCount: rows.length,
|
|
388
|
-
});
|
|
389
|
-
await emit({
|
|
390
|
-
kind: "chart",
|
|
391
|
-
chartId: meta.chartId,
|
|
392
|
-
title: meta.title ?? `Genie query`,
|
|
393
|
-
...(meta.description ? { description: meta.description } : {}),
|
|
394
|
-
data: rows,
|
|
395
|
-
});
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
398
|
-
case "message_result":
|
|
399
|
-
genieAnswer = event.message.content;
|
|
400
|
-
for (const attachment of event.message.attachments ?? []) {
|
|
401
|
-
const sqlText = attachment.query?.query;
|
|
402
|
-
const stmtId = attachment.query?.statementId;
|
|
403
|
-
if (sqlText && stmtId) {
|
|
404
|
-
upsertDatasetMeta(datasetsByStatementId, stmtId, {
|
|
405
|
-
sql: sqlText,
|
|
406
|
-
...(attachment.query?.title ? { title: attachment.query.title } : {}),
|
|
407
|
-
...(attachment.query?.description
|
|
408
|
-
? { description: attachment.query.description }
|
|
409
|
-
: {}),
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
if (sqlText) {
|
|
413
|
-
await emit({
|
|
414
|
-
kind: "sql",
|
|
415
|
-
sql: sqlText,
|
|
416
|
-
title: attachment.query?.title,
|
|
417
|
-
description: attachment.query?.description,
|
|
418
|
-
statementId: stmtId,
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
if (attachment.text?.content) {
|
|
422
|
-
await emit({ kind: "text", content: attachment.text.content });
|
|
423
|
-
}
|
|
424
|
-
if (attachment.suggestedQuestions?.length) {
|
|
425
|
-
// Last attachment with suggestions wins (same merge rule
|
|
426
|
-
// the UI uses via `collectSuggestions`); keeping just one
|
|
427
|
-
// copy per turn caps token usage.
|
|
428
|
-
suggestedFollowUps = attachment.suggestedQuestions;
|
|
429
|
-
await emit({
|
|
430
|
-
kind: "suggested",
|
|
431
|
-
questions: attachment.suggestedQuestions,
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
break;
|
|
436
|
-
case "error":
|
|
437
|
-
error = event.error;
|
|
438
|
-
await emit({ kind: "error", error: event.error });
|
|
439
|
-
break;
|
|
440
|
-
default:
|
|
441
|
-
break;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
// Strip statementId / row-only fields when handing the LLM the
|
|
445
|
-
// datasets - the model never references statementId, and the
|
|
446
|
-
// chartId is what the marker uses.
|
|
447
|
-
const datasets = [];
|
|
448
|
-
for (const meta of datasetsByStatementId.values()) {
|
|
449
|
-
datasets.push({
|
|
450
|
-
chartId: meta.chartId,
|
|
451
|
-
...(meta.title ? { title: meta.title } : {}),
|
|
452
|
-
...(meta.description ? { description: meta.description } : {}),
|
|
453
|
-
columns: meta.columns,
|
|
454
|
-
rowCount: meta.rowCount,
|
|
455
|
-
...(meta.sql ? { sql: meta.sql } : {}),
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
return {
|
|
459
|
-
...(conversationId ? { conversationId } : {}),
|
|
460
|
-
...(genieAnswer ? { genieAnswer } : {}),
|
|
461
|
-
...(datasets.length > 0 ? { datasets } : {}),
|
|
462
|
-
...(suggestedFollowUps ? { suggestedFollowUps } : {}),
|
|
463
|
-
...(error ? { error } : {}),
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Get-or-create-and-merge the per-statement scratch entry. Both
|
|
468
|
-
* `query_result` and `message_result` paths call this with their
|
|
469
|
-
* partial bag of fields; the resulting record is the union of
|
|
470
|
-
* everything we know about that statement so far.
|
|
471
|
-
*/
|
|
472
|
-
function upsertDatasetMeta(store, statementId, patch) {
|
|
473
|
-
const existing = store.get(statementId);
|
|
474
|
-
const merged = {
|
|
475
|
-
chartId: existing?.chartId ?? randomUUID().replace(/-/g, "").slice(0, 8),
|
|
476
|
-
statementId,
|
|
477
|
-
columns: patch.columns ?? existing?.columns ?? [],
|
|
478
|
-
rowCount: patch.rowCount ?? existing?.rowCount ?? 0,
|
|
479
|
-
...(patch.title ?? existing?.title
|
|
480
|
-
? { title: patch.title ?? existing?.title }
|
|
481
|
-
: {}),
|
|
482
|
-
...(patch.description ?? existing?.description
|
|
483
|
-
? { description: patch.description ?? existing?.description }
|
|
484
|
-
: {}),
|
|
485
|
-
...(patch.sql ?? existing?.sql ? { sql: patch.sql ?? existing?.sql } : {}),
|
|
486
|
-
};
|
|
487
|
-
store.set(statementId, merged);
|
|
488
|
-
return merged;
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Convert Genie's `data_array` (column-positional `string | null`
|
|
492
|
-
* tuples) into plain JS row objects keyed by column name. Numeric
|
|
493
|
-
* strings are coerced to numbers so the chart-planner picks
|
|
494
|
-
* `value` axes instead of `category` axes; everything else passes
|
|
495
|
-
* through verbatim. `null` becomes `null`.
|
|
496
|
-
*/
|
|
497
|
-
function genieRowsToObjects(columns, dataArray) {
|
|
498
|
-
const out = [];
|
|
499
|
-
for (const row of dataArray) {
|
|
500
|
-
const obj = {};
|
|
501
|
-
columns.forEach((col, i) => {
|
|
502
|
-
const cell = row[i] ?? null;
|
|
503
|
-
obj[col] = coerceCell(cell);
|
|
504
|
-
});
|
|
505
|
-
out.push(obj);
|
|
506
|
-
}
|
|
507
|
-
return out;
|
|
508
|
-
}
|
|
509
|
-
/** Best-effort numeric coercion for Genie's all-strings cells. */
|
|
510
|
-
function coerceCell(cell) {
|
|
511
|
-
if (cell === null)
|
|
512
|
-
return null;
|
|
513
|
-
// Anchored to keep `12.5px` / `123abc` as strings; only fully
|
|
514
|
-
// numeric values become JS numbers.
|
|
515
|
-
if (/^-?\d+(\.\d+)?$/.test(cell)) {
|
|
516
|
-
const n = Number(cell);
|
|
517
|
-
if (Number.isFinite(n))
|
|
518
|
-
return n;
|
|
519
|
-
}
|
|
520
|
-
return cell;
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Toolkit provider built from a live AppKit `GeniePlugin` instance.
|
|
524
|
-
* Returned by {@link buildGenieProvider} so that
|
|
525
|
-
* `plugins.genie?.toolkit()` inside an agent's `tools(plugins)` callback
|
|
526
|
-
* resolves to the streaming-aware {@link buildGenieTools} record instead
|
|
527
|
-
* of the AppKit default (which does one blocking call per tool with no
|
|
528
|
-
* mid-flight events).
|
|
529
|
-
*
|
|
530
|
-
* The returned `toolkit()` reads alias names off the plugin's
|
|
531
|
-
* `getAgentTools()` registry (each entry is `${alias}.sendMessage` or
|
|
532
|
-
* `${alias}.getConversation`), then mints one `sendMessage` tool per
|
|
533
|
-
* alias plus a shared `getConversation`. `sendMessage` / `getConversation`
|
|
534
|
-
* are bound back to the plugin instance so they keep their `this`
|
|
535
|
-
* (they are class methods, not free functions).
|
|
536
|
-
*
|
|
537
|
-
* `_opts` is accepted but unused for now - the streaming tools are an
|
|
538
|
-
* all-or-nothing bundle. Wire `only` / `except` / `prefix` / `rename`
|
|
539
|
-
* later if a caller needs them.
|
|
540
|
-
*/
|
|
541
|
-
export function buildGenieProvider(plugin) {
|
|
542
|
-
return {
|
|
543
|
-
toolkit(_opts) {
|
|
544
|
-
const aliases = extractGenieAliases(plugin);
|
|
545
|
-
return buildGenieTools({
|
|
546
|
-
aliases,
|
|
547
|
-
exports: {
|
|
548
|
-
sendMessage: plugin.sendMessage.bind(plugin),
|
|
549
|
-
getConversation: plugin.getConversation.bind(plugin),
|
|
550
|
-
},
|
|
551
|
-
});
|
|
552
|
-
},
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* Pull the configured space aliases out of a live AppKit `GeniePlugin`.
|
|
557
|
-
* Reads them off `getAgentTools()` (public API) so we don't poke at the
|
|
558
|
-
* `protected config.spaces` field: the plugin registers tools named
|
|
559
|
-
* `${alias}.sendMessage` / `${alias}.getConversation`, so the unique
|
|
560
|
-
* set of name prefixes is the alias list.
|
|
561
|
-
*/
|
|
562
|
-
function extractGenieAliases(plugin) {
|
|
563
|
-
const aliases = new Set();
|
|
564
|
-
for (const t of plugin.getAgentTools()) {
|
|
565
|
-
const dot = t.name.indexOf(".");
|
|
566
|
-
if (dot > 0)
|
|
567
|
-
aliases.add(t.name.slice(0, dot));
|
|
568
|
-
}
|
|
569
|
-
return [...aliases];
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Convert raw Genie status codes (`FETCHING_METADATA`, `ASKING_AI`,
|
|
573
|
-
* `EXECUTING_QUERY`, `COMPLETED`, ...) into short, sentence-cased
|
|
574
|
-
* labels safe to drop straight into a UI pill. Unknown codes are
|
|
575
|
-
* lower-cased with underscores stripped so new states still render.
|
|
576
|
-
*/
|
|
577
|
-
function humanizeGenieStatus(status) {
|
|
578
|
-
switch (status) {
|
|
579
|
-
case "FETCHING_METADATA":
|
|
580
|
-
return "Fetching metadata";
|
|
581
|
-
case "ASKING_AI":
|
|
582
|
-
return "Asking Genie";
|
|
583
|
-
case "EXECUTING_QUERY":
|
|
584
|
-
return "Running SQL query";
|
|
585
|
-
case "COMPLETED":
|
|
586
|
-
return "Completed";
|
|
587
|
-
case "FAILED":
|
|
588
|
-
return "Failed";
|
|
589
|
-
default:
|
|
590
|
-
return [
|
|
591
|
-
...stringUtils.tokenizeWithOptions({ capitalize: true, lowerCase: true }, status),
|
|
592
|
-
].join(" ");
|
|
593
|
-
}
|
|
594
|
-
}
|
package/dist/src/history.d.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Thread history loader exposed as a Mastra custom API route.
|
|
3
|
-
*
|
|
4
|
-
* Backed entirely by native Mastra: looks up the active agent by id,
|
|
5
|
-
* asks its `Memory` instance to `recall` a page of `MastraDBMessage`s,
|
|
6
|
-
* and converts the result to AI SDK V5 `UIMessage`s with the official
|
|
7
|
-
* {@link toAISdkV5Messages} helper from `@mastra/ai-sdk/ui`. No direct
|
|
8
|
-
* database reads.
|
|
9
|
-
*
|
|
10
|
-
* The route is registered through {@link historyRoute} as a Mastra
|
|
11
|
-
* `registerApiRoute` so it sits in the same dispatcher pipeline as
|
|
12
|
-
* `chatRoute`. That means the `MastraServer` auth middleware (in
|
|
13
|
-
* `./server.ts`) has already populated `RequestContext` with
|
|
14
|
-
* `MASTRA_THREAD_ID_KEY` and `MASTRA_RESOURCE_ID_KEY` by the time
|
|
15
|
-
* the handler runs - no cookie or user lookups happen here, and the
|
|
16
|
-
* session-cookie logic stays the single source of truth in `server.ts`.
|
|
17
|
-
*/
|
|
18
|
-
import type { Agent } from "@mastra/core/agent";
|
|
19
|
-
import type { MastraHistoryResponse } from "@dbx-tools/appkit-mastra-shared";
|
|
20
|
-
/** Inputs accepted by {@link loadHistory}. */
|
|
21
|
-
export interface LoadHistoryOptions {
|
|
22
|
-
agent: Agent;
|
|
23
|
-
threadId: string;
|
|
24
|
-
resourceId?: string;
|
|
25
|
-
page?: number;
|
|
26
|
-
perPage?: number;
|
|
27
|
-
/** When true, returns the *oldest* page first (chronological). */
|
|
28
|
-
ascending?: boolean;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Fetch a page of UI-formatted messages for a thread.
|
|
32
|
-
*
|
|
33
|
-
* Uses the agent's resolved `Memory` (`getMemory()`) so per-agent
|
|
34
|
-
* storage namespaces (`mastra_<agentId>` schemas) and any future
|
|
35
|
-
* memory-side filters apply automatically. When the agent has no
|
|
36
|
-
* memory configured the response is a successful empty page so
|
|
37
|
-
* callers don't have to special-case stateless agents.
|
|
38
|
-
*
|
|
39
|
-
* Pagination is descending-by-default: page 0 is the most recent
|
|
40
|
-
* page, page 1 the page before that, etc. The returned `uiMessages`
|
|
41
|
-
* are always re-sorted into chronological order (oldest -> newest)
|
|
42
|
-
* so the client can prepend them above the existing transcript
|
|
43
|
-
* without sorting locally.
|
|
44
|
-
*/
|
|
45
|
-
export declare function loadHistory(opts: LoadHistoryOptions): Promise<MastraHistoryResponse>;
|
|
46
|
-
/** Options accepted by {@link historyRoute}. */
|
|
47
|
-
export type HistoryRouteOptions = {
|
|
48
|
-
path: `${string}:agentId${string}`;
|
|
49
|
-
agent?: never;
|
|
50
|
-
} | {
|
|
51
|
-
path: string;
|
|
52
|
-
agent: string;
|
|
53
|
-
};
|
|
54
|
-
/**
|
|
55
|
-
* Register a `GET <path>` Mastra custom API route that returns a page
|
|
56
|
-
* of AI SDK V5 `UIMessage`s for the caller's current thread.
|
|
57
|
-
*
|
|
58
|
-
* Modeled after `chatRoute` from `@mastra/ai-sdk`: pass `agent` for a
|
|
59
|
-
* fixed-agent mount, or include `:agentId` in the path for dynamic
|
|
60
|
-
* routing. Pairs cleanly with the AppKit Mastra plugin's chat route
|
|
61
|
-
* layout (`/route/chat` + `/route/chat/:agentId`).
|
|
62
|
-
*
|
|
63
|
-
* The handler reads `threadId` and `resourceId` from `RequestContext`
|
|
64
|
-
* (populated upstream by `MastraServer.registerAuthMiddleware`), so
|
|
65
|
-
* no cookie or user lookups happen here.
|
|
66
|
-
*/
|
|
67
|
-
export declare function historyRoute(options: HistoryRouteOptions): import("@mastra/core/server").ApiRoute;
|