@dbx-tools/genie 0.1.34 → 0.1.35

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/index.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * `@dbx-tools/genie` public surface.
3
3
  *
4
- * Bundles the package's Node-side chat driver with a re-export of
5
- * the pure `@dbx-tools/genie-shared` wire vocabulary so a single
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
6
7
  * `from "@dbx-tools/genie"` import serves server-side consumers
7
8
  * that need both the live driver and the protocol types.
8
9
  *
@@ -14,3 +15,4 @@
14
15
 
15
16
  export * from "@dbx-tools/genie-shared";
16
17
  export * from "./src/chat.js";
18
+ export * from "./src/space.js";
package/package.json CHANGED
@@ -9,11 +9,11 @@
9
9
  }
10
10
  },
11
11
  "name": "@dbx-tools/genie",
12
- "version": "0.1.34",
12
+ "version": "0.1.35",
13
13
  "dependencies": {
14
14
  "@databricks/sdk-experimental": "^0.17",
15
- "@dbx-tools/genie-shared": "0.1.34",
16
- "@dbx-tools/shared": "0.1.34"
15
+ "@dbx-tools/genie-shared": "0.1.35",
16
+ "@dbx-tools/shared": "0.1.35"
17
17
  },
18
18
  "module": "index.ts",
19
19
  "type": "module",
package/src/space.ts ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * `@dbx-tools/genie` space metadata helpers.
3
+ *
4
+ * Fetches a Genie space's definition (including the opt-in
5
+ * `serialized_space` blob) and extracts the curated starter
6
+ * questions an author configured on the space. The typed SDK
7
+ * `client.genie.getSpace` only returns the directory-listing surface
8
+ * (`title` / `description` / `warehouse_id`); the sample questions
9
+ * live inside `serialized_space`, which the REST API returns only
10
+ * when `include_serialized_space=true`. We hit that endpoint through
11
+ * the workspace client's raw `apiClient` since the typed request
12
+ * shape has no flag for it.
13
+ */
14
+
15
+ import { WorkspaceClient } from "@databricks/sdk-experimental";
16
+ import { GenieSpaceSchema, type GenieSpace } from "@dbx-tools/genie-shared";
17
+ import { apiUtils, commonUtils, logUtils } from "@dbx-tools/shared";
18
+
19
+ const log = logUtils.logger("genie/space");
20
+
21
+ /** Options for {@link getGenieSpace}. */
22
+ export interface GetGenieSpaceOptions {
23
+ /**
24
+ * Explicit `WorkspaceClient`. Defaults to a fresh
25
+ * `new WorkspaceClient({})` (env-var auth). Server callers should
26
+ * pass their OBO-scoped client so the lookup runs as the user.
27
+ */
28
+ workspaceClient?: WorkspaceClient;
29
+ /**
30
+ * Request the `serialized_space` blob (catalogs, tables, sample
31
+ * questions, prompts). Defaults to `true` - the only reason to
32
+ * skip it is when the caller just needs title / description and
33
+ * wants the smaller payload.
34
+ */
35
+ serialized?: boolean;
36
+ /**
37
+ * External cancellation. Accepts a WHATWG `AbortSignal` or a
38
+ * fully-built SDK `Context` (see `apiUtils.ContextLike`).
39
+ */
40
+ context?: apiUtils.ContextLike;
41
+ }
42
+
43
+ /**
44
+ * Fetch a Genie space by id, optionally including its serialized
45
+ * definition. Hits `GET /api/2.0/genie/spaces/<id>` with
46
+ * `include_serialized_space=true` through the raw `apiClient`, then
47
+ * validates the response against {@link GenieSpaceSchema} (unknown
48
+ * fields like `etag` / `parent_path` are stripped).
49
+ */
50
+ export async function getGenieSpace(
51
+ spaceId: string,
52
+ options?: GetGenieSpaceOptions,
53
+ ): Promise<GenieSpace> {
54
+ const client = options?.workspaceClient ?? new WorkspaceClient({});
55
+ const serialized = options?.serialized !== false;
56
+ const context = options?.context ? apiUtils.toContext(options.context) : undefined;
57
+ const raw = await client.apiClient.request(
58
+ {
59
+ path: `/api/2.0/genie/spaces/${encodeURIComponent(spaceId)}`,
60
+ method: "GET",
61
+ query: serialized ? { include_serialized_space: true } : {},
62
+ headers: new Headers(),
63
+ raw: false,
64
+ },
65
+ context,
66
+ );
67
+ return GenieSpaceSchema.parse(raw);
68
+ }
69
+
70
+ /**
71
+ * One entry in a serialized space's `config.sample_questions`. The
72
+ * author-facing field is `question`, which the wire format models as
73
+ * a string array (a single multi-line question is split across
74
+ * entries); we treat the first non-empty entry as the displayable
75
+ * question text.
76
+ */
77
+ interface SerializedSampleQuestion {
78
+ question?: unknown;
79
+ }
80
+
81
+ /** Pull the first non-empty string out of a `question` field (string | string[]). */
82
+ function questionText(question: unknown): string | undefined {
83
+ if (typeof question === "string") {
84
+ const trimmed = question.trim();
85
+ return trimmed.length > 0 ? trimmed : undefined;
86
+ }
87
+ if (Array.isArray(question)) {
88
+ for (const part of question) {
89
+ if (typeof part === "string" && part.trim().length > 0) return part.trim();
90
+ }
91
+ }
92
+ return undefined;
93
+ }
94
+
95
+ /**
96
+ * Extract the curated starter questions an author configured on a
97
+ * Genie space. Reads `serialized_space -> config.sample_questions[*]
98
+ * .question`. Returns `[]` when the space carries no serialized blob,
99
+ * the blob is unparseable, or no sample questions are configured -
100
+ * so a missing or misconfigured space degrades to "no suggestions"
101
+ * rather than throwing. Order is preserved (the author's ordering)
102
+ * and duplicates are dropped.
103
+ */
104
+ export function genieSampleQuestions(space: GenieSpace): string[] {
105
+ const serialized = space.serialized_space;
106
+ if (!serialized) return [];
107
+ let parsed: unknown;
108
+ try {
109
+ parsed = JSON.parse(serialized);
110
+ } catch (err) {
111
+ log.warn("serialized-space:parse-error", {
112
+ spaceId: space.space_id,
113
+ error: commonUtils.errorMessage(err),
114
+ });
115
+ return [];
116
+ }
117
+ const sampleQuestions = (parsed as { config?: { sample_questions?: unknown } } | null)
118
+ ?.config?.sample_questions;
119
+ if (!Array.isArray(sampleQuestions)) return [];
120
+
121
+ const seen = new Set<string>();
122
+ const out: string[] = [];
123
+ for (const entry of sampleQuestions as SerializedSampleQuestion[]) {
124
+ const text = questionText(entry?.question);
125
+ if (!text || seen.has(text)) continue;
126
+ seen.add(text);
127
+ out.push(text);
128
+ }
129
+ return out;
130
+ }