@dbx-tools/genie 0.1.57 → 0.1.67

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 CHANGED
@@ -104,16 +104,41 @@ Errors propagate by the generator throwing - there is no `error`
104
104
  variant. Wrap the `for await` in `try / catch` if you need to handle
105
105
  failures.
106
106
 
107
+ ## Space metadata
108
+
109
+ Beyond the chat drivers, the package exposes two helpers for reading a
110
+ space's definition (both re-exported from the package root):
111
+
112
+ - `getGenieSpace(spaceId, options?)` - fetch a `GenieSpace` via
113
+ `GET /api/2.0/genie/spaces/<id>`. Includes the `serialized_space`
114
+ blob (catalogs, tables, sample questions, prompts) by default; pass
115
+ `{ serialized: false }` for the lighter title/description-only
116
+ payload. Takes the same `workspaceClient` / `context` options as the
117
+ drivers.
118
+ - `genieSampleQuestions(space)` - pull the author-curated starter
119
+ questions out of a space's serialized blob
120
+ (`config.sample_questions[*].question`), order preserved and
121
+ duplicates dropped. Returns `[]` when the space carries no serialized
122
+ blob or no configured questions, so a missing/misconfigured space
123
+ degrades to "no suggestions" rather than throwing.
124
+
125
+ ```ts
126
+ import { getGenieSpace, genieSampleQuestions } from "@dbx-tools/genie";
127
+
128
+ const space = await getGenieSpace(spaceId);
129
+ const prompts = genieSampleQuestions(space); // string[]
130
+ ```
131
+
107
132
  ## Options
108
133
 
109
134
  `GenieChatOptions` is the same shape for both drivers:
110
135
 
111
- | Option | Default | Description |
112
- | ----------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
113
- | `conversationId` | `undefined` | Seed conversation id. When set, this turn appends to the existing conversation via `createMessage`. |
114
- | `workspaceClient` | resolved (see below) | Explicit `WorkspaceClient`. Defaults to AppKit's per-request client when available; otherwise env-var auth. |
115
- | `pollIntervalMs` | `500` | Cadence between successive `getMessage` calls. |
116
- | `context` | new internal `AbortController` per call | External cancellation. Accepts an `AbortSignal` or a fully-built SDK `Context` (`apiUtils.ContextLike`). |
136
+ | Option | Default | Description |
137
+ | ----------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
138
+ | `conversationId` | `undefined` | Seed conversation id. When set, this turn appends to the existing conversation via `createMessage`. |
139
+ | `workspaceClient` | resolved (see below) | Explicit `WorkspaceClient`. Defaults to AppKit's per-request client when available; otherwise env-var auth. |
140
+ | `pollIntervalMs` | `500` | Cadence between successive `getMessage` calls. |
141
+ | `context` | new internal `AbortController` per call | External cancellation. Accepts an `AbortSignal` or a fully-built SDK `Context` (`apiUtils.ContextLike`). |
117
142
 
118
143
  ### Workspace client resolution
119
144
 
package/dist/index.d.ts CHANGED
@@ -1,17 +1,157 @@
1
+ import { GenieChatEvent, GenieMessage, GenieSpace } from "@dbx-tools/genie-shared";
2
+ import { WorkspaceClient } from "@databricks/sdk-experimental";
3
+ import { apiUtils } from "@dbx-tools/shared";
4
+ export * from "@dbx-tools/genie-shared";
5
+
6
+ //#region packages/genie/src/chat.d.ts
7
+ /** Options accepted by both {@link genieChat} and {@link genieEventChat}. */
8
+ interface GenieChatOptions {
9
+ /**
10
+ * Seed conversation id. When set, this turn appends to the
11
+ * existing conversation (via `createMessage`) instead of opening
12
+ * a new one. Use it to thread a multi-turn conversation: read
13
+ * `conversation_id` off the prior turn's terminal `GenieMessage`
14
+ * (or the `result` event's `payload.conversation_id`) and pass
15
+ * it into the next call.
16
+ */
17
+ conversationId?: string;
18
+ /**
19
+ * Explicit `WorkspaceClient`. Defaults to AppKit's per-request
20
+ * execution-context client when AppKit is installed and we're
21
+ * inside a request; falls back to a fresh `new WorkspaceClient({})`
22
+ * (env-var auth) otherwise.
23
+ */
24
+ workspaceClient?: WorkspaceClient;
25
+ /** Poll cadence in milliseconds between successive `getMessage` calls (default 500). */
26
+ pollIntervalMs?: number;
27
+ /**
28
+ * External cancellation. Accepts a WHATWG `AbortSignal` or a
29
+ * fully-built SDK `Context` (see `apiUtils.ContextLike`).
30
+ * Aborting it cancels every in-flight SDK call and the next
31
+ * inter-poll sleep.
32
+ */
33
+ context?: apiUtils.ContextLike;
34
+ }
1
35
  /**
2
- * `@dbx-tools/genie` public surface.
3
- *
4
- * Bundles the package's Node-side drivers (live chat plus space
5
- * metadata / curated-question lookup) with a re-export of the pure
6
- * `@dbx-tools/genie-shared` wire vocabulary so a single
7
- * `from "@dbx-tools/genie"` import serves server-side consumers
8
- * that need both the live driver and the protocol types.
9
- *
10
- * Browser-side consumers should import `@dbx-tools/genie-shared`
11
- * directly: the chat driver pulls in `WorkspaceClient` and is
12
- * Node-only, whereas the re-exported protocol surface is pure
13
- * (types + sync functions) and safe for any runtime.
36
+ * One turn against a Genie space, yielded as a stream of
37
+ * `GenieMessage` snapshots.
38
+ *
39
+ * Turn lifecycle:
40
+ *
41
+ * - No `options.conversationId`: open a new conversation via
42
+ * `client.genie.startConversation`. The opened conversation id
43
+ * surfaces on every yielded `GenieMessage` (`.conversation_id`)
44
+ * so the caller can thread it into a follow-up call.
45
+ * - With `options.conversationId`: append to that conversation
46
+ * via `client.genie.createMessage`.
47
+ * - In both cases, after the create/start the driver polls
48
+ * `client.genie.getMessage` every `options.pollIntervalMs`
49
+ * (default 500ms) until the message reaches a terminal
50
+ * status, then yields the terminal snapshot and returns.
51
+ *
52
+ * Cancellation: a single internal `AbortController` covers the
53
+ * whole turn. `options.context` is tied into that controller so an
54
+ * external abort tears down every in-flight SDK call AND the
55
+ * inter-poll sleep. Breaking out of the `for await` does the same
56
+ * via the `try / finally`.
57
+ *
58
+ * @example
59
+ * // Single turn.
60
+ * for await (const m of genieChat(spaceId, "Top 5 stores?")) {
61
+ * render(m);
62
+ * }
63
+ *
64
+ * @example
65
+ * // Multi-turn: caller threads the conversation id.
66
+ * let conversationId: string | undefined;
67
+ * for (const question of questions) {
68
+ * for await (const m of genieChat(spaceId, question, { conversationId })) {
69
+ * conversationId = m.conversation_id ?? conversationId;
70
+ * render(m);
71
+ * }
72
+ * }
14
73
  */
15
- export * from "@dbx-tools/genie-shared";
16
- export * from "./src/chat.js";
17
- export * from "./src/space.js";
74
+ declare function genieChat(space_id: string, content: string, options?: GenieChatOptions): AsyncGenerator<GenieMessage, void, void>;
75
+ /**
76
+ * One turn against a Genie space, yielded as a typed
77
+ * {@link GenieChatEvent} stream. Drives {@link genieChat}
78
+ * underneath and decorates each snapshot with the derived events
79
+ * the field-level diff produced. Stream order:
80
+ *
81
+ * 1. `{ type: "message", message }` - the raw `GenieMessage`,
82
+ * once per poll yield.
83
+ * 2. `{ type: "question", content, message_id, ... }` fires
84
+ * exactly once, on the FIRST `message` yield. We read
85
+ * `content` and `message_id` straight off the snapshot so
86
+ * every downstream event for this turn shares the same
87
+ * `message_id` (the question included) - subscribers can
88
+ * group everything for one Genie call under that one key.
89
+ * 3. Any of `status` / `attachment` / `thinking` / `text` /
90
+ * `query` / `statement` / `rows` / `suggested_questions` the
91
+ * diff against the prior snapshot produced.
92
+ * 4. On the terminal snapshot, `{ type: "result", ... }` as
93
+ * the final yield.
94
+ *
95
+ * Errors propagate by the generator throwing - there's no
96
+ * `"error"` variant. Wrap the `for await` in `try/catch` if you
97
+ * need to handle failures.
98
+ *
99
+ * @example
100
+ * for await (const event of genieEventChat(spaceId, "Top stores?")) {
101
+ * switch (event.type) {
102
+ * case "thinking":
103
+ * console.log("[thinking]", event.thought_type, event.text);
104
+ * break;
105
+ * case "text":
106
+ * console.log("[text]", event.text);
107
+ * break;
108
+ * case "result":
109
+ * console.log("[done]", event.status);
110
+ * break;
111
+ * }
112
+ * }
113
+ */
114
+ declare function genieEventChat(space_id: string, content: string, options?: GenieChatOptions): AsyncGenerator<GenieChatEvent, void, void>;
115
+ //#endregion
116
+ //#region packages/genie/src/space.d.ts
117
+ /** Options for {@link getGenieSpace}. */
118
+ interface GetGenieSpaceOptions {
119
+ /**
120
+ * Explicit `WorkspaceClient`. Defaults to a fresh
121
+ * `new WorkspaceClient({})` (env-var auth). Server callers should
122
+ * pass their OBO-scoped client so the lookup runs as the user.
123
+ */
124
+ workspaceClient?: WorkspaceClient;
125
+ /**
126
+ * Request the `serialized_space` blob (catalogs, tables, sample
127
+ * questions, prompts). Defaults to `true` - the only reason to
128
+ * skip it is when the caller just needs title / description and
129
+ * wants the smaller payload.
130
+ */
131
+ serialized?: boolean;
132
+ /**
133
+ * External cancellation. Accepts a WHATWG `AbortSignal` or a
134
+ * fully-built SDK `Context` (see `apiUtils.ContextLike`).
135
+ */
136
+ context?: apiUtils.ContextLike;
137
+ }
138
+ /**
139
+ * Fetch a Genie space by id, optionally including its serialized
140
+ * definition. Hits `GET /api/2.0/genie/spaces/<id>` with
141
+ * `include_serialized_space=true` through the raw `apiClient`, then
142
+ * validates the response against {@link GenieSpaceSchema} (unknown
143
+ * fields like `etag` / `parent_path` are stripped).
144
+ */
145
+ declare function getGenieSpace(spaceId: string, options?: GetGenieSpaceOptions): Promise<GenieSpace>;
146
+ /**
147
+ * Extract the curated starter questions an author configured on a
148
+ * Genie space. Reads `serialized_space -> config.sample_questions[*]
149
+ * .question`. Returns `[]` when the space carries no serialized blob,
150
+ * the blob is unparseable, or no sample questions are configured -
151
+ * so a missing or misconfigured space degrades to "no suggestions"
152
+ * rather than throwing. Order is preserved (the author's ordering)
153
+ * and duplicates are dropped.
154
+ */
155
+ declare function genieSampleQuestions(space: GenieSpace): string[];
156
+ //#endregion
157
+ export { GenieChatOptions, GetGenieSpaceOptions, genieChat, genieEventChat, genieSampleQuestions, getGenieSpace };
package/dist/index.js CHANGED
@@ -1,17 +1,317 @@
1
+ import { GenieMessageSchema, GenieSpaceSchema, eventsFromMessage, isTerminalStatus } from "@dbx-tools/genie-shared";
2
+ import { WorkspaceClient } from "@databricks/sdk-experimental";
3
+ import { apiUtils, commonUtils, logUtils } from "@dbx-tools/shared";
4
+
5
+ export * from "@dbx-tools/genie-shared"
6
+
7
+ //#region packages/genie/src/chat.ts
1
8
  /**
2
- * `@dbx-tools/genie` public surface.
3
- *
4
- * Bundles the package's Node-side drivers (live chat plus space
5
- * metadata / curated-question lookup) with a re-export of the pure
6
- * `@dbx-tools/genie-shared` wire vocabulary so a single
7
- * `from "@dbx-tools/genie"` import serves server-side consumers
8
- * that need both the live driver and the protocol types.
9
- *
10
- * Browser-side consumers should import `@dbx-tools/genie-shared`
11
- * directly: the chat driver pulls in `WorkspaceClient` and is
12
- * Node-only, whereas the re-exported protocol surface is pure
13
- * (types + sync functions) and safe for any runtime.
14
- */
15
- export * from "@dbx-tools/genie-shared";
16
- export * from "./src/chat.js";
17
- export * from "./src/space.js";
9
+ * `@dbx-tools/genie` chat driver.
10
+ *
11
+ * Drives a single turn against a Genie space from one `content`
12
+ * string; multi-turn conversations are the caller's job (thread
13
+ * the `conversation_id` returned on each `GenieMessage` back into
14
+ * the next turn's `options.conversationId`).
15
+ *
16
+ * Two layers serve two kinds of consumer. The low-level layer
17
+ * yields every poll-observed `GenieMessage` (validated against
18
+ * `GenieMessageSchema`, falling back to the raw snapshot on a schema
19
+ * miss) and owns the messy parts - cancellation, conversation
20
+ * seeding, distinct-filtering, and SDK quirks (Waiter stripping);
21
+ * reach for it when you want the raw stream. The high-level layer wraps it
22
+ * and emits semantic, deduplicated `{ type, payload }` events
23
+ * (see {@link GenieChatEvent}), always closing a successful turn
24
+ * with a terminal `result` event carrying the final
25
+ * `GenieMessage`; errors propagate by throwing, with no `error`
26
+ * variant. Iterating UI / agent code that wants every message
27
+ * verbatim takes the low-level stream; subscribers reacting to
28
+ * "Genie is thinking about X" or "Genie produced text Y" take the
29
+ * event layer.
30
+ */
31
+ const log$1 = logUtils.logger("genie/chat");
32
+ /**
33
+ * Validate a polled wire snapshot against {@link GenieMessageSchema}
34
+ * and return the schema-normalized message. Genie's wire occasionally
35
+ * ships a shape the (SDK-derived) schema doesn't model exactly - e.g.
36
+ * an early poll that omits the SDK-required `message_id` - so a miss
37
+ * degrades to the raw snapshot rather than throwing, keeping a single
38
+ * odd poll from aborting the whole turn.
39
+ */
40
+ function validateMessage(raw) {
41
+ const result = GenieMessageSchema.safeParse(raw);
42
+ if (result.success) return result.data;
43
+ log$1.debug("wire-message:schema-miss", {
44
+ message_id: raw.message_id ?? raw.id,
45
+ issues: result.error.issues.length
46
+ });
47
+ return raw;
48
+ }
49
+ /**
50
+ * One turn against a Genie space, yielded as a stream of
51
+ * `GenieMessage` snapshots.
52
+ *
53
+ * Turn lifecycle:
54
+ *
55
+ * - No `options.conversationId`: open a new conversation via
56
+ * `client.genie.startConversation`. The opened conversation id
57
+ * surfaces on every yielded `GenieMessage` (`.conversation_id`)
58
+ * so the caller can thread it into a follow-up call.
59
+ * - With `options.conversationId`: append to that conversation
60
+ * via `client.genie.createMessage`.
61
+ * - In both cases, after the create/start the driver polls
62
+ * `client.genie.getMessage` every `options.pollIntervalMs`
63
+ * (default 500ms) until the message reaches a terminal
64
+ * status, then yields the terminal snapshot and returns.
65
+ *
66
+ * Cancellation: a single internal `AbortController` covers the
67
+ * whole turn. `options.context` is tied into that controller so an
68
+ * external abort tears down every in-flight SDK call AND the
69
+ * inter-poll sleep. Breaking out of the `for await` does the same
70
+ * via the `try / finally`.
71
+ *
72
+ * @example
73
+ * // Single turn.
74
+ * for await (const m of genieChat(spaceId, "Top 5 stores?")) {
75
+ * render(m);
76
+ * }
77
+ *
78
+ * @example
79
+ * // Multi-turn: caller threads the conversation id.
80
+ * let conversationId: string | undefined;
81
+ * for (const question of questions) {
82
+ * for await (const m of genieChat(spaceId, question, { conversationId })) {
83
+ * conversationId = m.conversation_id ?? conversationId;
84
+ * render(m);
85
+ * }
86
+ * }
87
+ */
88
+ async function* genieChat(space_id, content, options) {
89
+ const controller = new AbortController();
90
+ try {
91
+ const client = await getWorkspaceClient(options);
92
+ const context = apiUtils.toContext(controller, options?.context);
93
+ let conversationId = options?.conversationId;
94
+ let messageId;
95
+ const pollProducer = async (ctx) => {
96
+ if (!conversationId) {
97
+ if (ctx.attempt > 0) throw new Error("Genie did not return a conversation id; refusing to retry");
98
+ const startResponse = await client.genie.startConversation({
99
+ space_id,
100
+ content
101
+ }, context);
102
+ conversationId = startResponse.conversation_id;
103
+ messageId = startResponse.message_id;
104
+ return startResponse.message;
105
+ }
106
+ if (!messageId) {
107
+ const { wait: _wait, ...createResponse } = await client.genie.createMessage({
108
+ space_id,
109
+ conversation_id: conversationId,
110
+ content
111
+ }, context);
112
+ messageId = createResponse.message_id;
113
+ return createResponse;
114
+ }
115
+ return await client.genie.getMessage({
116
+ space_id,
117
+ conversation_id: conversationId,
118
+ message_id: messageId
119
+ }, context);
120
+ };
121
+ yield* commonUtils.poll(async (ctx) => validateMessage(await pollProducer(ctx)), {
122
+ intervalMs: options?.pollIntervalMs ?? 500,
123
+ filter: "distinct",
124
+ predicate: (m) => !isTerminalStatus(m.status),
125
+ signal: controller.signal
126
+ });
127
+ } finally {
128
+ controller.abort();
129
+ }
130
+ }
131
+ /**
132
+ * One turn against a Genie space, yielded as a typed
133
+ * {@link GenieChatEvent} stream. Drives {@link genieChat}
134
+ * underneath and decorates each snapshot with the derived events
135
+ * the field-level diff produced. Stream order:
136
+ *
137
+ * 1. `{ type: "message", message }` - the raw `GenieMessage`,
138
+ * once per poll yield.
139
+ * 2. `{ type: "question", content, message_id, ... }` fires
140
+ * exactly once, on the FIRST `message` yield. We read
141
+ * `content` and `message_id` straight off the snapshot so
142
+ * every downstream event for this turn shares the same
143
+ * `message_id` (the question included) - subscribers can
144
+ * group everything for one Genie call under that one key.
145
+ * 3. Any of `status` / `attachment` / `thinking` / `text` /
146
+ * `query` / `statement` / `rows` / `suggested_questions` the
147
+ * diff against the prior snapshot produced.
148
+ * 4. On the terminal snapshot, `{ type: "result", ... }` as
149
+ * the final yield.
150
+ *
151
+ * Errors propagate by the generator throwing - there's no
152
+ * `"error"` variant. Wrap the `for await` in `try/catch` if you
153
+ * need to handle failures.
154
+ *
155
+ * @example
156
+ * for await (const event of genieEventChat(spaceId, "Top stores?")) {
157
+ * switch (event.type) {
158
+ * case "thinking":
159
+ * console.log("[thinking]", event.thought_type, event.text);
160
+ * break;
161
+ * case "text":
162
+ * console.log("[text]", event.text);
163
+ * break;
164
+ * case "result":
165
+ * console.log("[done]", event.status);
166
+ * break;
167
+ * }
168
+ * }
169
+ */
170
+ async function* genieEventChat(space_id, content, options) {
171
+ let previous;
172
+ let questionEmitted = false;
173
+ for await (const rawMessage of genieChat(space_id, content, options)) {
174
+ const message = rawMessage.message_id ? rawMessage : {
175
+ ...rawMessage,
176
+ message_id: rawMessage.id
177
+ };
178
+ yield {
179
+ type: "message",
180
+ space_id: message.space_id,
181
+ message_id: message.message_id,
182
+ message
183
+ };
184
+ if (!questionEmitted) {
185
+ yield {
186
+ type: "question",
187
+ space_id: message.space_id,
188
+ ...message.conversation_id ? { conversation_id: message.conversation_id } : {},
189
+ ...message.message_id ? { message_id: message.message_id } : {},
190
+ content: message.content
191
+ };
192
+ questionEmitted = true;
193
+ }
194
+ yield* eventsFromMessage(message, previous, message.space_id);
195
+ if (isTerminalStatus(message.status)) yield {
196
+ type: "result",
197
+ space_id: message.space_id,
198
+ conversation_id: message.conversation_id,
199
+ message_id: message.message_id,
200
+ status: message.status,
201
+ message
202
+ };
203
+ previous = message;
204
+ }
205
+ }
206
+ /**
207
+ * Resolve a `WorkspaceClient` in this preference order:
208
+ *
209
+ * 1. Caller-supplied `options.workspaceClient`.
210
+ * 2. AppKit's per-request execution-context client, when AppKit
211
+ * is installed AND we're inside a request scope.
212
+ * 3. Fresh `new WorkspaceClient({})` (env-var auth via
213
+ * `DATABRICKS_CONFIG_PROFILE` / `DATABRICKS_HOST` /
214
+ * `DATABRICKS_TOKEN`).
215
+ *
216
+ * AppKit is loaded lazily so this package stays usable in
217
+ * non-AppKit environments (e.g. the `poll-chat` smoke test).
218
+ */
219
+ async function getWorkspaceClient(options) {
220
+ if (options?.workspaceClient) return options.workspaceClient;
221
+ const appkit = await getAppKit();
222
+ if (appkit) try {
223
+ return appkit.getExecutionContext().client;
224
+ } catch {}
225
+ return new WorkspaceClient({});
226
+ }
227
+ async function getAppKit() {
228
+ try {
229
+ return await import("@databricks/appkit");
230
+ } catch {
231
+ return;
232
+ }
233
+ }
234
+
235
+ //#endregion
236
+ //#region packages/genie/src/space.ts
237
+ /**
238
+ * `@dbx-tools/genie` space metadata helpers.
239
+ *
240
+ * Fetches a Genie space's definition (including the opt-in
241
+ * `serialized_space` blob) and extracts the curated starter
242
+ * questions an author configured on the space. The typed SDK
243
+ * `client.genie.getSpace` only returns the directory-listing surface
244
+ * (`title` / `description` / `warehouse_id`); the sample questions
245
+ * live inside `serialized_space`, which the REST API returns only
246
+ * when `include_serialized_space=true`. We hit that endpoint through
247
+ * the workspace client's raw `apiClient` since the typed request
248
+ * shape has no flag for it.
249
+ */
250
+ const log = logUtils.logger("genie/space");
251
+ /**
252
+ * Fetch a Genie space by id, optionally including its serialized
253
+ * definition. Hits `GET /api/2.0/genie/spaces/<id>` with
254
+ * `include_serialized_space=true` through the raw `apiClient`, then
255
+ * validates the response against {@link GenieSpaceSchema} (unknown
256
+ * fields like `etag` / `parent_path` are stripped).
257
+ */
258
+ async function getGenieSpace(spaceId, options) {
259
+ const client = options?.workspaceClient ?? new WorkspaceClient({});
260
+ const serialized = options?.serialized !== false;
261
+ const context = options?.context ? apiUtils.toContext(options.context) : void 0;
262
+ const raw = await client.apiClient.request({
263
+ path: `/api/2.0/genie/spaces/${encodeURIComponent(spaceId)}`,
264
+ method: "GET",
265
+ query: serialized ? { include_serialized_space: true } : {},
266
+ headers: new Headers(),
267
+ raw: false
268
+ }, context);
269
+ return GenieSpaceSchema.parse(raw);
270
+ }
271
+ /** Pull the first non-empty string out of a `question` field (string | string[]). */
272
+ function questionText(question) {
273
+ if (typeof question === "string") {
274
+ const trimmed = question.trim();
275
+ return trimmed.length > 0 ? trimmed : void 0;
276
+ }
277
+ if (Array.isArray(question)) {
278
+ for (const part of question) if (typeof part === "string" && part.trim().length > 0) return part.trim();
279
+ }
280
+ }
281
+ /**
282
+ * Extract the curated starter questions an author configured on a
283
+ * Genie space. Reads `serialized_space -> config.sample_questions[*]
284
+ * .question`. Returns `[]` when the space carries no serialized blob,
285
+ * the blob is unparseable, or no sample questions are configured -
286
+ * so a missing or misconfigured space degrades to "no suggestions"
287
+ * rather than throwing. Order is preserved (the author's ordering)
288
+ * and duplicates are dropped.
289
+ */
290
+ function genieSampleQuestions(space) {
291
+ const serialized = space.serialized_space;
292
+ if (!serialized) return [];
293
+ let parsed;
294
+ try {
295
+ parsed = JSON.parse(serialized);
296
+ } catch (err) {
297
+ log.warn("serialized-space:parse-error", {
298
+ spaceId: space.space_id,
299
+ error: commonUtils.errorMessage(err)
300
+ });
301
+ return [];
302
+ }
303
+ const sampleQuestions = parsed?.config?.sample_questions;
304
+ if (!Array.isArray(sampleQuestions)) return [];
305
+ const seen = /* @__PURE__ */ new Set();
306
+ const out = [];
307
+ for (const entry of sampleQuestions) {
308
+ const text = questionText(entry?.question);
309
+ if (!text || seen.has(text)) continue;
310
+ seen.add(text);
311
+ out.push(text);
312
+ }
313
+ return out;
314
+ }
315
+
316
+ //#endregion
317
+ export { genieChat, genieEventChat, genieSampleQuestions, getGenieSpace };
package/package.json CHANGED
@@ -1,35 +1,24 @@
1
1
  {
2
- "main": "./dist/index.js",
3
- "types": "./dist/index.d.ts",
2
+ "name": "@dbx-tools/genie",
3
+ "version": "0.1.67",
4
+ "dependencies": {
5
+ "@databricks/sdk-experimental": "^0.17",
6
+ "@dbx-tools/genie-shared": "0.1.67",
7
+ "@dbx-tools/shared": "0.1.67"
8
+ },
4
9
  "exports": {
5
10
  ".": {
6
- "source": "./index.ts",
11
+ "source": "./src/index.ts",
7
12
  "types": "./dist/index.d.ts",
8
13
  "default": "./dist/index.js"
9
14
  }
10
15
  },
11
- "name": "@dbx-tools/genie",
12
- "version": "0.1.57",
13
- "dependencies": {
14
- "@databricks/sdk-experimental": "^0.17",
15
- "@dbx-tools/genie-shared": "0.1.57",
16
- "@dbx-tools/shared": "0.1.57"
17
- },
18
- "module": "index.ts",
19
16
  "type": "module",
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
20
19
  "files": [
21
20
  "dist",
22
- "index*.ts",
23
21
  "src"
24
22
  ],
25
- "license": "Apache-2.0",
26
- "homepage": "https://github.com/reggie-db/dbx-tools-js#readme",
27
- "bugs": {
28
- "url": "https://github.com/reggie-db/dbx-tools-js/issues"
29
- },
30
- "repository": {
31
- "type": "git",
32
- "url": "git+https://github.com/reggie-db/dbx-tools-js.git",
33
- "directory": "packages/genie"
34
- }
23
+ "license": "Apache-2.0"
35
24
  }
@@ -14,5 +14,5 @@
14
14
  */
15
15
 
16
16
  export * from "@dbx-tools/genie-shared";
17
- export * from "./src/chat.js";
18
- export * from "./src/space.js";
17
+ export * from "./chat.js";
18
+ export * from "./space.js";