@deepagents/text2sql 6.0.0 → 6.1.2

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 (43) hide show
  1. package/README.md +7 -1
  2. package/dist/bin/sql.js +1 -0
  3. package/dist/index.js +6 -1
  4. package/dist/index.js.map +2 -2
  5. package/dist/lib/adapters/adapter.d.ts +2 -1
  6. package/dist/lib/adapters/adapter.d.ts.map +1 -1
  7. package/dist/lib/adapters/bigquery/index.js +6 -1
  8. package/dist/lib/adapters/bigquery/index.js.map +2 -2
  9. package/dist/lib/adapters/clickhouse/index.js +6 -1
  10. package/dist/lib/adapters/clickhouse/index.js.map +2 -2
  11. package/dist/lib/adapters/groundings/context.d.ts +3 -0
  12. package/dist/lib/adapters/groundings/context.d.ts.map +1 -1
  13. package/dist/lib/adapters/groundings/index.js +1 -0
  14. package/dist/lib/adapters/groundings/index.js.map +2 -2
  15. package/dist/lib/adapters/mysql/index.js +6 -1
  16. package/dist/lib/adapters/mysql/index.js.map +2 -2
  17. package/dist/lib/adapters/postgres/index.js +6 -1
  18. package/dist/lib/adapters/postgres/index.js.map +2 -2
  19. package/dist/lib/adapters/posthog/definitions.posthog.grounding.d.ts +19 -0
  20. package/dist/lib/adapters/posthog/definitions.posthog.grounding.d.ts.map +1 -0
  21. package/dist/lib/adapters/posthog/index.d.ts +22 -0
  22. package/dist/lib/adapters/posthog/index.d.ts.map +1 -0
  23. package/dist/lib/adapters/posthog/index.js +1317 -0
  24. package/dist/lib/adapters/posthog/index.js.map +7 -0
  25. package/dist/lib/adapters/posthog/info.posthog.grounding.d.ts +7 -0
  26. package/dist/lib/adapters/posthog/info.posthog.grounding.d.ts.map +1 -0
  27. package/dist/lib/adapters/posthog/posthog.d.ts +24 -0
  28. package/dist/lib/adapters/posthog/posthog.d.ts.map +1 -0
  29. package/dist/lib/adapters/posthog/posthog.sql-policy.d.ts +8 -0
  30. package/dist/lib/adapters/posthog/posthog.sql-policy.d.ts.map +1 -0
  31. package/dist/lib/adapters/posthog/schema.posthog.grounding.d.ts +16 -0
  32. package/dist/lib/adapters/posthog/schema.posthog.grounding.d.ts.map +1 -0
  33. package/dist/lib/adapters/posthog/transport.d.ts +16 -0
  34. package/dist/lib/adapters/posthog/transport.d.ts.map +1 -0
  35. package/dist/lib/adapters/posthog/types.d.ts +97 -0
  36. package/dist/lib/adapters/posthog/types.d.ts.map +1 -0
  37. package/dist/lib/adapters/spreadsheet/index.js +6 -1
  38. package/dist/lib/adapters/spreadsheet/index.js.map +2 -2
  39. package/dist/lib/adapters/sqlite/index.js +6 -1
  40. package/dist/lib/adapters/sqlite/index.js.map +2 -2
  41. package/dist/lib/adapters/sqlserver/index.js +6 -1
  42. package/dist/lib/adapters/sqlserver/index.js.map +2 -2
  43. package/package.json +11 -5
@@ -0,0 +1,1317 @@
1
+ // packages/text2sql/src/lib/adapters/groundings/abstract.grounding.ts
2
+ function filterColumns(columns, filter) {
3
+ if (Array.isArray(filter)) {
4
+ return columns.filter((col) => filter.includes(col.name));
5
+ }
6
+ if (filter instanceof RegExp) {
7
+ return columns.filter((col) => filter.test(col.name));
8
+ }
9
+ return columns.filter((col) => filter(col.name));
10
+ }
11
+ function applyColumnFilter(entity, columnsConfig) {
12
+ if (!columnsConfig) return entity;
13
+ const filter = columnsConfig[entity.name];
14
+ if (!filter) return entity;
15
+ return { ...entity, columns: filterColumns(entity.columns, filter) };
16
+ }
17
+ var AbstractGrounding = class {
18
+ /**
19
+ * Grounding identifier for debugging/logging.
20
+ */
21
+ name;
22
+ phase;
23
+ constructor(name, phase) {
24
+ this.name = name;
25
+ this.phase = phase;
26
+ }
27
+ /**
28
+ * Populate ctx with the tables/views this grounding makes available for scope
29
+ * resolution. Annotators decorate entities other groundings produced and add
30
+ * no membership, so the default is a no-op; entity-producing groundings
31
+ * override it. This lets scope resolution skip the annotators' metadata scans.
32
+ *
33
+ * @param _ctx - Shared context for accumulating entity membership
34
+ */
35
+ async contributeEntities(_ctx) {
36
+ }
37
+ };
38
+
39
+ // packages/text2sql/src/lib/adapters/posthog/definitions.posthog.grounding.ts
40
+ var PostHogDefinitionsGrounding = class extends AbstractGrounding {
41
+ #adapter;
42
+ #config;
43
+ constructor(adapter, config = {}) {
44
+ super("definitions", "definitions");
45
+ this.#adapter = adapter;
46
+ this.#config = config;
47
+ }
48
+ async execute(ctx) {
49
+ const fragments = ctx.fragments ??= [];
50
+ const configuredPropertyTypes = this.#config.propertyTypes ?? ["event", "person", "session"];
51
+ const propertyTypes = [
52
+ ...new Set(configuredPropertyTypes)
53
+ ];
54
+ validatePropertyTypes(propertyTypes);
55
+ const groupTypeIndexes = [...new Set(this.#config.groupTypeIndexes ?? [])];
56
+ if (groupTypeIndexes.some((index2) => !Number.isInteger(index2) || index2 < 0)) {
57
+ throw new Error(
58
+ "PostHog groupTypeIndexes must contain non-negative integers."
59
+ );
60
+ }
61
+ const propertyRequests = [
62
+ ...propertyTypes.map(async (type) => ({
63
+ type,
64
+ definitions: await this.#adapter.transport.listPropertyDefinitions({
65
+ type
66
+ })
67
+ })),
68
+ ...groupTypeIndexes.map(async (groupTypeIndex) => ({
69
+ type: `group:${groupTypeIndex}`,
70
+ definitions: await this.#adapter.transport.listPropertyDefinitions({
71
+ type: "group",
72
+ groupTypeIndex
73
+ })
74
+ }))
75
+ ];
76
+ const [events, properties] = await Promise.all([
77
+ this.#adapter.transport.listEventDefinitions(),
78
+ Promise.all(propertyRequests)
79
+ ]);
80
+ const eventData = readEvents(events, this.#config.events);
81
+ if (eventData.length > 0) {
82
+ fragments.push({ name: "posthogEvents", data: eventData });
83
+ }
84
+ const propertyData = {};
85
+ for (const group of properties) {
86
+ const definitions2 = readProperties(
87
+ group.definitions,
88
+ this.#config.properties
89
+ );
90
+ if (definitions2.length > 0) propertyData[group.type] = definitions2;
91
+ }
92
+ if (Object.keys(propertyData).length > 0) {
93
+ fragments.push({
94
+ name: "posthogProperties",
95
+ data: propertyData
96
+ });
97
+ }
98
+ }
99
+ };
100
+ function readEvents(values, filter) {
101
+ return values.map(validateEvent).filter((event) => matchesFilter(event.name, filter)).toSorted((left, right) => left.name.localeCompare(right.name)).map(
102
+ (event) => compact({
103
+ name: event.name,
104
+ description: event.description ?? void 0,
105
+ tags: event.tags?.length ? event.tags : void 0,
106
+ verified: event.verified ?? void 0
107
+ })
108
+ );
109
+ }
110
+ function readProperties(values, filter) {
111
+ return values.map(validateProperty).filter((property) => matchesFilter(property.name, filter)).toSorted((left, right) => left.name.localeCompare(right.name)).map(
112
+ (property) => compact({
113
+ name: property.name,
114
+ description: property.description ?? void 0,
115
+ propertyType: property.property_type ?? void 0,
116
+ numerical: property.is_numerical ?? void 0,
117
+ verified: property.verified ?? void 0
118
+ })
119
+ );
120
+ }
121
+ function validateEvent(value) {
122
+ if (!isRecord(value) || !readNonEmptyString(value.name) || !isOptionalString(value.description) || !isOptionalBoolean(value.verified) || !isOptionalStringArray(value.tags)) {
123
+ throw new Error("PostHog returned a malformed event definition.");
124
+ }
125
+ return value;
126
+ }
127
+ function validateProperty(value) {
128
+ if (!isRecord(value) || !readNonEmptyString(value.name) || !isOptionalString(value.description) || !isOptionalString(value.property_type) || !isOptionalBoolean(value.is_numerical) || !isOptionalBoolean(value.verified)) {
129
+ throw new Error("PostHog returned a malformed property definition.");
130
+ }
131
+ return value;
132
+ }
133
+ function validatePropertyTypes(values) {
134
+ const allowed = /* @__PURE__ */ new Set(["event", "person", "session"]);
135
+ if (values.some((value) => !allowed.has(value))) {
136
+ throw new Error("PostHog propertyTypes contains an unsupported type.");
137
+ }
138
+ }
139
+ function matchesFilter(name, filter) {
140
+ if (!filter) return true;
141
+ if (Array.isArray(filter)) return filter.includes(name);
142
+ if (filter instanceof RegExp) {
143
+ filter.lastIndex = 0;
144
+ return filter.test(name);
145
+ }
146
+ return filter(name);
147
+ }
148
+ function compact(value) {
149
+ return Object.fromEntries(
150
+ Object.entries(value).filter((entry) => entry[1] !== void 0)
151
+ );
152
+ }
153
+ function isOptionalString(value) {
154
+ return value === void 0 || value === null || typeof value === "string";
155
+ }
156
+ function isOptionalBoolean(value) {
157
+ return value === void 0 || value === null || typeof value === "boolean";
158
+ }
159
+ function isOptionalStringArray(value) {
160
+ return value === void 0 || value === null || Array.isArray(value) && value.every((item) => typeof item === "string");
161
+ }
162
+ function readNonEmptyString(value) {
163
+ return typeof value === "string" && value.length > 0 ? value : void 0;
164
+ }
165
+ function isRecord(value) {
166
+ return typeof value === "object" && value !== null && !Array.isArray(value);
167
+ }
168
+
169
+ // packages/text2sql/src/lib/adapters/posthog/info.posthog.grounding.ts
170
+ var PostHogInfoGrounding = class extends AbstractGrounding {
171
+ constructor() {
172
+ super("info", "info");
173
+ }
174
+ async execute(ctx) {
175
+ ctx.info = {
176
+ dialect: "HogQL",
177
+ details: {
178
+ identifierQuoting: "Use backticks for identifiers that require quoting.",
179
+ propertyAccess: "Use PostHog HogQL property access and the grounded event/person/session property definitions. Do not guess property names.",
180
+ performance: "Prefer bounded time ranges and LIMIT for non-aggregate event queries. OFFSET is not supported by the Query API.",
181
+ execution: "Queries run through PostHog as read-only HogQLQuery nodes, not through a PostgreSQL connection."
182
+ }
183
+ };
184
+ }
185
+ };
186
+
187
+ // packages/text2sql/src/lib/adapters/adapter.ts
188
+ import { format as formatSql } from "sql-formatter";
189
+
190
+ // packages/text2sql/src/lib/agents/exceptions.ts
191
+ var sqlValidationMarker = /* @__PURE__ */ Symbol("SQLValidationError");
192
+ var unanswerableSqlMarker = /* @__PURE__ */ Symbol("UnanswerableSQLError");
193
+ var sqlScopeMarker = /* @__PURE__ */ Symbol("SQLScopeError");
194
+ var sqlReadOnlyMarker = /* @__PURE__ */ Symbol("SQLReadOnlyError");
195
+ var SQLValidationError = class _SQLValidationError extends Error {
196
+ [sqlValidationMarker];
197
+ constructor(message) {
198
+ super(message);
199
+ this.name = "SQLValidationError";
200
+ this[sqlValidationMarker] = true;
201
+ }
202
+ static isInstance(error) {
203
+ return error instanceof _SQLValidationError && error[sqlValidationMarker] === true;
204
+ }
205
+ };
206
+ var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
207
+ [unanswerableSqlMarker];
208
+ constructor(message) {
209
+ super(message);
210
+ this.name = "UnanswerableSQLError";
211
+ this[unanswerableSqlMarker] = true;
212
+ }
213
+ static isInstance(error) {
214
+ return error instanceof _UnanswerableSQLError && error[unanswerableSqlMarker] === true;
215
+ }
216
+ };
217
+ var SQLScopeError = class _SQLScopeError extends Error {
218
+ [sqlScopeMarker];
219
+ payload;
220
+ errorType;
221
+ constructor(payload) {
222
+ super(JSON.stringify(payload));
223
+ this.name = "SQLScopeError";
224
+ this.payload = payload;
225
+ this.errorType = payload.error_type;
226
+ this[sqlScopeMarker] = true;
227
+ }
228
+ static isInstance(error) {
229
+ return error instanceof _SQLScopeError && error[sqlScopeMarker] === true;
230
+ }
231
+ };
232
+ var SQLReadOnlyError = class _SQLReadOnlyError extends Error {
233
+ [sqlReadOnlyMarker];
234
+ constructor(message) {
235
+ super(message);
236
+ this.name = "SQLReadOnlyError";
237
+ this[sqlReadOnlyMarker] = true;
238
+ }
239
+ static isInstance(error) {
240
+ return error instanceof _SQLReadOnlyError && error[sqlReadOnlyMarker] === true;
241
+ }
242
+ };
243
+
244
+ // packages/text2sql/src/lib/fragments/schema.ts
245
+ function dialectInfo(input) {
246
+ return {
247
+ name: "dialectInfo",
248
+ data: {
249
+ dialect: input.dialect,
250
+ ...input.version && { version: input.version },
251
+ ...input.database && { database: input.database },
252
+ ...input.details && { details: input.details }
253
+ }
254
+ };
255
+ }
256
+ function table(input) {
257
+ return {
258
+ name: "table",
259
+ data: {
260
+ name: input.name,
261
+ ...input.schema && { schema: input.schema },
262
+ ...input.rowCount != null && { rowCount: input.rowCount },
263
+ ...input.sizeHint && { sizeHint: input.sizeHint },
264
+ columns: input.columns,
265
+ ...input.indexes?.length && { indexes: input.indexes },
266
+ ...input.constraints?.length && { constraints: input.constraints }
267
+ }
268
+ };
269
+ }
270
+ function column(input) {
271
+ return {
272
+ name: "column",
273
+ data: {
274
+ name: input.name,
275
+ type: input.type,
276
+ ...input.pk && { pk: true },
277
+ ...input.fk && { fk: input.fk },
278
+ ...input.unique && { unique: true },
279
+ ...input.notNull && { notNull: true },
280
+ ...input.default && { default: input.default },
281
+ ...input.indexed && { indexed: true },
282
+ ...input.values?.length && { values: input.values },
283
+ ...input.stats && { stats: input.stats }
284
+ }
285
+ };
286
+ }
287
+ function index(input) {
288
+ return {
289
+ name: "index",
290
+ data: {
291
+ name: input.name,
292
+ columns: input.columns,
293
+ ...input.unique && { unique: true },
294
+ ...input.type && { type: input.type }
295
+ }
296
+ };
297
+ }
298
+ function constraint(input) {
299
+ return {
300
+ name: "constraint",
301
+ data: {
302
+ name: input.name,
303
+ type: input.type,
304
+ ...input.columns?.length && { columns: input.columns },
305
+ ...input.definition && { definition: input.definition },
306
+ ...input.defaultValue && { defaultValue: input.defaultValue },
307
+ ...input.referencedTable && { referencedTable: input.referencedTable },
308
+ ...input.referencedColumns?.length && {
309
+ referencedColumns: input.referencedColumns
310
+ }
311
+ }
312
+ };
313
+ }
314
+ function view(input) {
315
+ return {
316
+ name: "view",
317
+ data: {
318
+ name: input.name,
319
+ ...input.schema && { schema: input.schema },
320
+ columns: input.columns,
321
+ ...input.definition && { definition: input.definition }
322
+ }
323
+ };
324
+ }
325
+ function relationship(input) {
326
+ return {
327
+ name: "relationship",
328
+ data: {
329
+ from: input.from,
330
+ to: input.to,
331
+ ...input.cardinality && { cardinality: input.cardinality }
332
+ }
333
+ };
334
+ }
335
+
336
+ // packages/text2sql/src/lib/adapters/groundings/context.ts
337
+ var noopProgress = () => {
338
+ };
339
+ function timestampProgress(onProgress) {
340
+ if (!onProgress) return noopProgress;
341
+ return (progress) => {
342
+ onProgress({
343
+ ...progress,
344
+ timestampMs: progress.timestampMs ?? Date.now()
345
+ });
346
+ };
347
+ }
348
+ function createGroundingContext(options = {}) {
349
+ return {
350
+ tables: [],
351
+ views: [],
352
+ relationships: [],
353
+ info: void 0,
354
+ fragments: [],
355
+ cache: /* @__PURE__ */ new Map(),
356
+ onProgress: timestampProgress(options.onProgress)
357
+ };
358
+ }
359
+
360
+ // packages/text2sql/src/lib/adapters/adapter.ts
361
+ var Adapter = class {
362
+ #policyAnalyzer;
363
+ /**
364
+ * Injects the strategy that guards validate() and execute().
365
+ */
366
+ constructor(policyAnalyzer) {
367
+ this.#policyAnalyzer = policyAnalyzer;
368
+ }
369
+ /**
370
+ * Introspect the database schema and return context fragments.
371
+ *
372
+ * Executes all configured groundings to populate the context, then
373
+ * generates fragments from the complete context data.
374
+ *
375
+ * @param ctx - Optional grounding context for sharing state between groundings
376
+ * @returns Array of context fragments representing the database schema
377
+ */
378
+ async introspect(ctx = createGroundingContext()) {
379
+ for (const fn of this.grounding) {
380
+ const grounding = fn(this);
381
+ if (grounding.phase) {
382
+ ctx.onProgress({
383
+ type: "phase:start",
384
+ phase: grounding.phase,
385
+ message: `Starting ${grounding.name} introspection...`
386
+ });
387
+ }
388
+ try {
389
+ await grounding.execute(ctx);
390
+ } finally {
391
+ if (grounding.phase) {
392
+ ctx.onProgress({
393
+ type: "phase:end",
394
+ phase: grounding.phase,
395
+ message: `Finished ${grounding.name} introspection.`
396
+ });
397
+ }
398
+ }
399
+ }
400
+ return this.#toSchemaFragments(ctx);
401
+ }
402
+ /**
403
+ * Resolve the allowed entity names (tables + views) from grounding config.
404
+ * Asks each grounding to contribute its entities; annotators contribute
405
+ * nothing, so their metadata scans are skipped.
406
+ * Results are NOT cached — call once and store the result.
407
+ */
408
+ async resolveAllowedEntities() {
409
+ const ctx = createGroundingContext();
410
+ for (const fn of this.grounding) {
411
+ await fn(this).contributeEntities(ctx);
412
+ }
413
+ return [...ctx.tables.map((t) => t.name), ...ctx.views.map((v) => v.name)];
414
+ }
415
+ /**
416
+ * Convert complete grounding context to schema fragments.
417
+ * Called after all groundings have populated ctx with data.
418
+ */
419
+ #toSchemaFragments(ctx) {
420
+ const fragments = [];
421
+ if (ctx.info) {
422
+ fragments.push(
423
+ dialectInfo({
424
+ dialect: ctx.info.dialect,
425
+ version: ctx.info.version,
426
+ database: ctx.info.database,
427
+ details: ctx.info.details
428
+ })
429
+ );
430
+ }
431
+ for (const t of ctx.tables) {
432
+ fragments.push(this.#tableToFragment(t));
433
+ }
434
+ for (const v of ctx.views) {
435
+ fragments.push(this.#viewToFragment(v));
436
+ }
437
+ const tableMap = new Map(ctx.tables.map((t) => [t.name, t]));
438
+ const tableColumnSets = new Map(
439
+ ctx.tables.map((t) => [t.name, new Set(t.columns.map((c) => c.name))])
440
+ );
441
+ for (const rel of ctx.relationships) {
442
+ const sourceColumns = tableColumnSets.get(rel.table);
443
+ const targetColumns = tableColumnSets.get(rel.referenced_table);
444
+ if (sourceColumns && rel.from.some((column2) => !sourceColumns.has(column2))) {
445
+ continue;
446
+ }
447
+ if (targetColumns && rel.to.some((column2) => !targetColumns.has(column2))) {
448
+ continue;
449
+ }
450
+ const sourceTable = tableMap.get(rel.table);
451
+ const targetTable = tableMap.get(rel.referenced_table);
452
+ fragments.push(
453
+ this.#relationshipToFragment(rel, sourceTable, targetTable)
454
+ );
455
+ }
456
+ fragments.push(...ctx.fragments ?? []);
457
+ return fragments;
458
+ }
459
+ /**
460
+ * Convert a Table to a table fragment with nested column, index, and constraint fragments.
461
+ */
462
+ #tableToFragment(t) {
463
+ const pkConstraint = t.constraints?.find((c) => c.type === "PRIMARY_KEY");
464
+ const pkColumns = new Set(pkConstraint?.columns ?? []);
465
+ const notNullColumns = new Set(
466
+ t.constraints?.filter((c) => c.type === "NOT_NULL").flatMap((c) => c.columns ?? []) ?? []
467
+ );
468
+ const defaultByColumn = /* @__PURE__ */ new Map();
469
+ for (const c of t.constraints?.filter((c2) => c2.type === "DEFAULT") ?? []) {
470
+ for (const col of c.columns ?? []) {
471
+ if (c.defaultValue != null) {
472
+ defaultByColumn.set(col, c.defaultValue);
473
+ }
474
+ }
475
+ }
476
+ const uniqueColumns = new Set(
477
+ t.constraints?.filter((c) => c.type === "UNIQUE" && c.columns?.length === 1).flatMap((c) => c.columns ?? []) ?? []
478
+ );
479
+ const fkByColumn = /* @__PURE__ */ new Map();
480
+ for (const c of t.constraints?.filter((c2) => c2.type === "FOREIGN_KEY") ?? []) {
481
+ const cols = c.columns ?? [];
482
+ const refCols = c.referencedColumns ?? [];
483
+ for (let i = 0; i < cols.length; i++) {
484
+ const refCol = refCols[i] ?? refCols[0] ?? cols[i];
485
+ fkByColumn.set(cols[i], `${c.referencedTable}.${refCol}`);
486
+ }
487
+ }
488
+ const columnFragments = t.columns.map(
489
+ (col) => column({
490
+ name: col.name,
491
+ type: col.type,
492
+ pk: pkColumns.has(col.name) || void 0,
493
+ fk: fkByColumn.get(col.name),
494
+ unique: uniqueColumns.has(col.name) || void 0,
495
+ notNull: notNullColumns.has(col.name) || void 0,
496
+ default: defaultByColumn.get(col.name),
497
+ indexed: col.isIndexed || void 0,
498
+ values: col.values,
499
+ stats: col.stats
500
+ })
501
+ );
502
+ const presentColumns = new Set(t.columns.map((c) => c.name));
503
+ const indexFragments = (t.indexes ?? []).filter(
504
+ (idx) => idx.columns.every((column2) => presentColumns.has(column2))
505
+ ).map(
506
+ (idx) => index({
507
+ name: idx.name,
508
+ columns: idx.columns,
509
+ unique: idx.unique,
510
+ type: idx.type
511
+ })
512
+ );
513
+ const constraintFragments = (t.constraints ?? []).filter(
514
+ (c) => c.type === "CHECK" || c.type === "UNIQUE" && (c.columns?.length ?? 0) > 1
515
+ ).filter(
516
+ (c) => !c.columns?.length || c.columns.every((column2) => presentColumns.has(column2))
517
+ ).map(
518
+ (c) => constraint({
519
+ name: c.name,
520
+ type: c.type,
521
+ columns: c.columns,
522
+ definition: c.definition
523
+ })
524
+ );
525
+ return table({
526
+ name: t.name,
527
+ schema: t.schema,
528
+ rowCount: t.rowCount,
529
+ sizeHint: t.sizeHint,
530
+ columns: columnFragments,
531
+ indexes: indexFragments.length > 0 ? indexFragments : void 0,
532
+ constraints: constraintFragments.length > 0 ? constraintFragments : void 0
533
+ });
534
+ }
535
+ /**
536
+ * Convert a View to a view fragment with nested column fragments.
537
+ */
538
+ #viewToFragment(v) {
539
+ const columnFragments = v.columns.map(
540
+ (col) => column({
541
+ name: col.name,
542
+ type: col.type,
543
+ values: col.values,
544
+ stats: col.stats
545
+ })
546
+ );
547
+ return view({
548
+ name: v.name,
549
+ schema: v.schema,
550
+ columns: columnFragments,
551
+ definition: v.definition
552
+ });
553
+ }
554
+ /**
555
+ * Convert a Relationship to a relationship fragment.
556
+ * Infers cardinality from row counts if available.
557
+ */
558
+ #relationshipToFragment(rel, sourceTable, targetTable) {
559
+ const sourceCount = sourceTable?.rowCount;
560
+ const targetCount = targetTable?.rowCount;
561
+ let cardinality;
562
+ if (sourceCount != null && targetCount != null && targetCount > 0) {
563
+ const ratio = sourceCount / targetCount;
564
+ if (ratio > 5) {
565
+ cardinality = "many-to-one";
566
+ } else if (ratio < 1.2 && ratio > 0.8) {
567
+ cardinality = "one-to-one";
568
+ } else if (ratio < 0.2) {
569
+ cardinality = "one-to-many";
570
+ }
571
+ }
572
+ return relationship({
573
+ from: { table: rel.table, columns: rel.from },
574
+ to: { table: rel.referenced_table, columns: rel.to },
575
+ cardinality
576
+ });
577
+ }
578
+ format(sql) {
579
+ const decoded = this.#decodeShellEscapes(sql);
580
+ try {
581
+ return formatSql(decoded, { language: this.formatterLanguage });
582
+ } catch {
583
+ return decoded;
584
+ }
585
+ }
586
+ #decodeShellEscapes(sql) {
587
+ return sql.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\([().*])/g, "$1").replace(/\\(?=$)/gm, "");
588
+ }
589
+ #cachedAllowedEntities = null;
590
+ async #resolveScope() {
591
+ if (this.#cachedAllowedEntities) return this.#cachedAllowedEntities;
592
+ this.#cachedAllowedEntities = await this.resolveAllowedEntities();
593
+ return this.#cachedAllowedEntities;
594
+ }
595
+ async #analyzePolicy(sql) {
596
+ return this.#policyAnalyzer.analyze(sql, {
597
+ resolveAllowedEntities: () => this.#resolveScope()
598
+ });
599
+ }
600
+ async validate(sql) {
601
+ const decoded = this.#decodeShellEscapes(sql);
602
+ const violation = await this.#analyzePolicy(decoded);
603
+ if (violation?.kind === "read-only") return violation.message;
604
+ if (violation?.kind === "scope") return JSON.stringify(violation.payload);
605
+ return this.validateImpl(decoded);
606
+ }
607
+ async execute(sql) {
608
+ return this.executeImpl(await this.enforceExecutionPolicy(sql));
609
+ }
610
+ async enforceExecutionPolicy(sql) {
611
+ const decoded = this.#decodeShellEscapes(sql);
612
+ const violation = await this.#analyzePolicy(decoded);
613
+ if (violation?.kind === "read-only") {
614
+ throw new SQLReadOnlyError(violation.message);
615
+ }
616
+ if (violation?.kind === "scope") {
617
+ throw new SQLScopeError(violation.payload);
618
+ }
619
+ return decoded;
620
+ }
621
+ /**
622
+ * Convert unknown database value to number.
623
+ * Handles number, bigint, and string types.
624
+ */
625
+ toNumber(value) {
626
+ if (typeof value === "number" && Number.isFinite(value)) {
627
+ return value;
628
+ }
629
+ if (typeof value === "bigint") {
630
+ return Number(value);
631
+ }
632
+ if (typeof value === "string" && value.trim() !== "") {
633
+ const parsed = Number(value);
634
+ return Number.isFinite(parsed) ? parsed : void 0;
635
+ }
636
+ return void 0;
637
+ }
638
+ /**
639
+ * Parse a potentially qualified table name into schema and table parts.
640
+ */
641
+ parseTableName(name) {
642
+ if (name.includes(".")) {
643
+ const [schema2, ...rest] = name.split(".");
644
+ return { schema: schema2, table: rest.join(".") };
645
+ }
646
+ return { schema: this.defaultSchema ?? "", table: name };
647
+ }
648
+ /**
649
+ * Escape a string value for use in SQL string literals (single quotes).
650
+ * Used in WHERE clauses like: WHERE name = '${escapeString(value)}'
651
+ */
652
+ escapeString(value) {
653
+ return value.replace(/'/g, "''");
654
+ }
655
+ /**
656
+ * Build a SQL filter clause to include/exclude schemas.
657
+ * @param columnName - The schema column name (e.g., 'TABLE_SCHEMA')
658
+ * @param allowedSchemas - If provided, filter to these schemas only
659
+ */
660
+ buildSchemaFilter(columnName, allowedSchemas) {
661
+ if (allowedSchemas && allowedSchemas.length > 0) {
662
+ const values = allowedSchemas.map((s) => `'${this.escapeString(s)}'`).join(", ");
663
+ return `AND ${columnName} IN (${values})`;
664
+ }
665
+ if (this.systemSchemas.length > 0) {
666
+ const values = this.systemSchemas.map((s) => `'${this.escapeString(s)}'`).join(", ");
667
+ return `AND ${columnName} NOT IN (${values})`;
668
+ }
669
+ return "";
670
+ }
671
+ };
672
+
673
+ // packages/text2sql/src/lib/sql-scope-error.ts
674
+ function buildScopeParseErrorPayload(sql, parserDialect, error) {
675
+ const parserError = error instanceof Error ? error.message : String(error ?? "Unknown error");
676
+ return {
677
+ error: `SQL scope analysis failed before validation/execution: ${parserError}`,
678
+ error_type: "SQL_SCOPE_PARSE_ERROR",
679
+ suggestion: "Rewrite the query into simpler SQL that can be analyzed safely, or extend parser coverage for this dialect feature.",
680
+ sql_attempted: sql,
681
+ parser_dialect: parserDialect,
682
+ parser_error: parserError
683
+ };
684
+ }
685
+
686
+ // packages/text2sql/src/lib/adapters/posthog/posthog.sql-policy.ts
687
+ var PostHogSqlPolicyAnalyzer = class {
688
+ #transport;
689
+ constructor(transport) {
690
+ this.#transport = transport;
691
+ }
692
+ async analyze(sql, context) {
693
+ const response = await this.#transport.query({
694
+ query: { kind: "HogQLMetadata", language: "hogQL", query: sql },
695
+ name: "deepagents_text2sql_validate"
696
+ });
697
+ let metadata;
698
+ try {
699
+ metadata = validateMetadataResponse(response);
700
+ } catch (error) {
701
+ return {
702
+ kind: "scope",
703
+ payload: buildScopeParseErrorPayload(sql, "hogql", error)
704
+ };
705
+ }
706
+ const allowedEntities = await context.resolveAllowedEntities();
707
+ const allowed = new Set(allowedEntities.map(caseFold));
708
+ const rejected = metadata.table_names.filter(
709
+ (entity) => !allowed.has(caseFold(entity))
710
+ );
711
+ return rejected.length > 0 ? {
712
+ kind: "scope",
713
+ payload: buildOutOfScopePayload(sql, rejected, [...allowedEntities])
714
+ } : null;
715
+ }
716
+ };
717
+ function validateMetadataResponse(value) {
718
+ if (!isRecord2(value)) {
719
+ throw new Error("PostHog HogQLMetadata response must be a JSON object.");
720
+ }
721
+ if (typeof value.isValid !== "boolean") {
722
+ throw new Error("PostHog HogQLMetadata response has no isValid boolean.");
723
+ }
724
+ const errors = validateNotices(value.errors, "errors");
725
+ validateNotices(value.warnings, "warnings");
726
+ validateNotices(value.notices, "notices");
727
+ const tableNames = validateTableNames(value.table_names);
728
+ if (!value.isValid || errors.length > 0) {
729
+ const reason = errors.map((notice) => notice.message).join("; ");
730
+ throw new Error(
731
+ reason ? `PostHog rejected the HogQL query: ${reason}` : "PostHog rejected the HogQL query as invalid."
732
+ );
733
+ }
734
+ return {
735
+ isValid: true,
736
+ errors,
737
+ warnings: value.warnings,
738
+ notices: value.notices,
739
+ table_names: tableNames
740
+ };
741
+ }
742
+ function validateNotices(value, name) {
743
+ if (!Array.isArray(value)) {
744
+ throw new Error(`PostHog HogQLMetadata response has no ${name} array.`);
745
+ }
746
+ for (const notice of value) {
747
+ if (!isRecord2(notice) || typeof notice.message !== "string") {
748
+ throw new Error(
749
+ `PostHog HogQLMetadata response has an invalid ${name} notice.`
750
+ );
751
+ }
752
+ }
753
+ return value;
754
+ }
755
+ function validateTableNames(value) {
756
+ if (!Array.isArray(value) || value.some((name) => typeof name !== "string" || name.length === 0)) {
757
+ throw new Error("PostHog HogQLMetadata response has invalid table_names.");
758
+ }
759
+ return [...new Set(value)];
760
+ }
761
+ function buildOutOfScopePayload(sql, referencedEntities, allowedEntities) {
762
+ return {
763
+ error: `Query references entities outside grounded scope: ${referencedEntities.join(", ")}`,
764
+ error_type: "OUT_OF_SCOPE",
765
+ suggestion: "Restrict the query to grounded tables/views or expand grounding to include the referenced entities.",
766
+ sql_attempted: sql,
767
+ referenced_entities: referencedEntities,
768
+ allowed_entities: allowedEntities
769
+ };
770
+ }
771
+ function caseFold(value) {
772
+ return value.toLowerCase();
773
+ }
774
+ function isRecord2(value) {
775
+ return typeof value === "object" && value !== null && !Array.isArray(value);
776
+ }
777
+
778
+ // packages/text2sql/src/lib/adapters/posthog/posthog.ts
779
+ var PostHog = class extends Adapter {
780
+ transport;
781
+ grounding;
782
+ defaultSchema = void 0;
783
+ systemSchemas = ["system"];
784
+ formatterLanguage = "clickhouse";
785
+ constructor(options) {
786
+ if (!options?.transport || typeof options.transport.query !== "function") {
787
+ throw new Error("PostHog adapter requires a transport.");
788
+ }
789
+ if (typeof options.transport.listEventDefinitions !== "function" || typeof options.transport.listPropertyDefinitions !== "function") {
790
+ throw new Error(
791
+ "PostHog transport must provide event and property definition methods."
792
+ );
793
+ }
794
+ super(new PostHogSqlPolicyAnalyzer(options.transport));
795
+ this.transport = options.transport;
796
+ this.grounding = options.grounding ?? [];
797
+ }
798
+ async execute(sql, values) {
799
+ return this.#execute(
800
+ await this.enforceExecutionPolicy(sql),
801
+ "deepagents_text2sql_execute",
802
+ values
803
+ );
804
+ }
805
+ async executeImpl(sql) {
806
+ return this.#execute(sql, "deepagents_text2sql_execute");
807
+ }
808
+ validateImpl() {
809
+ return void 0;
810
+ }
811
+ async runQuery(sql) {
812
+ return await this.#execute(sql, "deepagents_text2sql_grounding");
813
+ }
814
+ quoteIdentifier(name) {
815
+ return `\`${name.replace(/`/g, "``")}\``;
816
+ }
817
+ escape(value) {
818
+ return value.replace(/`/g, "``");
819
+ }
820
+ buildSampleRowsQuery(tableName, columns, limit) {
821
+ const { schema: schema2, table: table2 } = this.parseTableName(tableName);
822
+ const relation = schema2 ? `${this.quoteIdentifier(schema2)}.${this.quoteIdentifier(table2)}` : this.quoteIdentifier(table2);
823
+ const projection = columns?.length ? columns.map((column2) => this.quoteIdentifier(column2)).join(", ") : "*";
824
+ return `SELECT ${projection} FROM ${relation} LIMIT ${limit}`;
825
+ }
826
+ async query(request) {
827
+ return this.transport.query(request);
828
+ }
829
+ async #execute(sql, name, values) {
830
+ const response = await this.transport.query({
831
+ query: {
832
+ kind: "HogQLQuery",
833
+ query: sql,
834
+ ...values === void 0 ? {} : { values }
835
+ },
836
+ name
837
+ });
838
+ return rowsFromResponse(response);
839
+ }
840
+ };
841
+ function rowsFromResponse(value) {
842
+ if (!isRecord3(value) || !Array.isArray(value.results)) {
843
+ throw new Error("PostHog HogQLQuery response has no results array.");
844
+ }
845
+ if (value.columns !== void 0 && (!Array.isArray(value.columns) || value.columns.some(
846
+ (column2) => typeof column2 !== "string" || column2.length === 0
847
+ ))) {
848
+ throw new Error("PostHog HogQLQuery response has invalid columns.");
849
+ }
850
+ if (Array.isArray(value.columns) && new Set(value.columns).size !== value.columns.length) {
851
+ throw new Error(
852
+ "PostHog HogQLQuery response contains duplicate column names; alias every selected expression uniquely."
853
+ );
854
+ }
855
+ if (value.results.length === 0) return [];
856
+ if (!Array.isArray(value.columns)) {
857
+ throw new Error(
858
+ "PostHog HogQLQuery response requires columns for non-empty results."
859
+ );
860
+ }
861
+ const columns = value.columns;
862
+ return value.results.map((row, index2) => {
863
+ if (!Array.isArray(row) || row.length !== columns.length) {
864
+ throw new Error(
865
+ `PostHog HogQLQuery result row ${index2} does not match the column count.`
866
+ );
867
+ }
868
+ return Object.fromEntries(
869
+ columns.map((column2, columnIndex) => [column2, row[columnIndex]])
870
+ );
871
+ });
872
+ }
873
+ function isRecord3(value) {
874
+ return typeof value === "object" && value !== null && !Array.isArray(value);
875
+ }
876
+
877
+ // packages/text2sql/src/lib/adapters/posthog/schema.posthog.grounding.ts
878
+ var SCALAR_FIELD_TYPES = /* @__PURE__ */ new Set([
879
+ "integer",
880
+ "float",
881
+ "decimal",
882
+ "string",
883
+ "datetime",
884
+ "date",
885
+ "boolean",
886
+ "array",
887
+ "json"
888
+ ]);
889
+ var FIELD_TYPES = /* @__PURE__ */ new Set([
890
+ ...SCALAR_FIELD_TYPES,
891
+ "lazy_table",
892
+ "virtual_table",
893
+ "field_traverser",
894
+ "expression",
895
+ "view",
896
+ "materialized_view",
897
+ "unknown"
898
+ ]);
899
+ var TABLE_TYPES = /* @__PURE__ */ new Set([
900
+ "posthog",
901
+ "system",
902
+ "data_warehouse",
903
+ "view",
904
+ "batch_export",
905
+ "materialized_view",
906
+ "managed_view",
907
+ "endpoint"
908
+ ]);
909
+ var VIEW_TYPES = /* @__PURE__ */ new Set([
910
+ "view",
911
+ "materialized_view",
912
+ "managed_view",
913
+ "endpoint"
914
+ ]);
915
+ var PostHogSchemaGrounding = class extends AbstractGrounding {
916
+ #adapter;
917
+ #config;
918
+ constructor(adapter, config = {}) {
919
+ super("schema", "tables");
920
+ this.#adapter = adapter;
921
+ this.#config = config;
922
+ }
923
+ async execute(ctx) {
924
+ const response = validateSchemaResponse(
925
+ await this.#adapter.query({
926
+ query: { kind: "DatabaseSchemaQuery" },
927
+ name: "deepagents_text2sql_schema"
928
+ })
929
+ );
930
+ const entities = Object.entries(response.tables).filter(
931
+ ([name, table2]) => (this.#config.includeSystem || table2.type !== "system") && matchesFilter2(name, this.#config.filter)
932
+ );
933
+ const tables = [];
934
+ const views = [];
935
+ for (const [index2, [name, table2]] of entities.entries()) {
936
+ ctx.onProgress({
937
+ type: "phase:progress",
938
+ phase: "tables",
939
+ table: name,
940
+ message: `Loading PostHog schema entity ${name}...`,
941
+ current: index2 + 1,
942
+ total: entities.length
943
+ });
944
+ const columns = readColumns(name, table2);
945
+ if (VIEW_TYPES.has(table2.type)) {
946
+ views.push(
947
+ applyColumnFilter(
948
+ {
949
+ name,
950
+ columns,
951
+ ...table2.query?.query ? { definition: table2.query.query } : {}
952
+ },
953
+ this.#config.columns
954
+ )
955
+ );
956
+ } else {
957
+ tables.push(
958
+ applyColumnFilter(
959
+ {
960
+ name,
961
+ columns,
962
+ ...typeof table2.row_count === "number" ? { rowCount: table2.row_count } : {}
963
+ },
964
+ this.#config.columns
965
+ )
966
+ );
967
+ }
968
+ }
969
+ ctx.tables.push(...tables);
970
+ ctx.views.push(...views);
971
+ ctx.relationships.push(...readRelationships(response.joins, tables, views));
972
+ }
973
+ async contributeEntities(ctx) {
974
+ await this.execute(ctx);
975
+ }
976
+ };
977
+ function validateSchemaResponse(value) {
978
+ if (!isRecord4(value) || !isRecord4(value.tables) || !Array.isArray(value.joins)) {
979
+ throw new Error(
980
+ "PostHog DatabaseSchemaQuery response must contain tables and joins."
981
+ );
982
+ }
983
+ for (const [name, valueTable] of Object.entries(value.tables)) {
984
+ if (!isRecord4(valueTable)) {
985
+ throw new Error(`PostHog schema table ${name} must be an object.`);
986
+ }
987
+ if (!TABLE_TYPES.has(valueTable.type)) {
988
+ throw new Error(`PostHog schema table ${name} has an unknown type.`);
989
+ }
990
+ if (typeof valueTable.id !== "string" || typeof valueTable.name !== "string" || !isRecord4(valueTable.fields)) {
991
+ throw new Error(`PostHog schema table ${name} is malformed.`);
992
+ }
993
+ if (valueTable.row_count !== void 0 && valueTable.row_count !== null && (typeof valueTable.row_count !== "number" || !Number.isFinite(valueTable.row_count) || valueTable.row_count < 0)) {
994
+ throw new Error(`PostHog schema table ${name} has an invalid row_count.`);
995
+ }
996
+ if (VIEW_TYPES.has(valueTable.type) && (!isRecord4(valueTable.query) || typeof valueTable.query.query !== "string")) {
997
+ throw new Error(`PostHog schema view ${name} has an invalid query.`);
998
+ }
999
+ validateFields(name, valueTable.fields);
1000
+ }
1001
+ for (const join of value.joins) {
1002
+ if (!isRecord4(join)) {
1003
+ throw new Error("PostHog schema contains a malformed join.");
1004
+ }
1005
+ }
1006
+ return value;
1007
+ }
1008
+ function validateFields(tableName, fields) {
1009
+ for (const [name, value] of Object.entries(fields)) {
1010
+ if (!isRecord4(value) || typeof value.name !== "string" || typeof value.hogql_value !== "string" || typeof value.schema_valid !== "boolean" || !FIELD_TYPES.has(value.type)) {
1011
+ throw new Error(
1012
+ `PostHog schema field ${tableName}.${name} is malformed or has an unknown type.`
1013
+ );
1014
+ }
1015
+ }
1016
+ }
1017
+ function readColumns(tableName, table2) {
1018
+ const columns = [];
1019
+ const names = /* @__PURE__ */ new Set();
1020
+ for (const field of Object.values(table2.fields)) {
1021
+ if (!field.schema_valid || !SCALAR_FIELD_TYPES.has(field.type)) continue;
1022
+ const name = field.hogql_value.trim();
1023
+ if (!name) {
1024
+ throw new Error(`PostHog schema table ${tableName} has an empty field.`);
1025
+ }
1026
+ if (names.has(name)) {
1027
+ throw new Error(
1028
+ `PostHog schema table ${tableName} has duplicate HogQL field ${name}.`
1029
+ );
1030
+ }
1031
+ names.add(name);
1032
+ columns.push({ name, type: field.type });
1033
+ }
1034
+ return columns;
1035
+ }
1036
+ function readRelationships(joins, tables, views) {
1037
+ const entityColumns = new Map(
1038
+ [...tables, ...views].map((entity) => [
1039
+ entity.name,
1040
+ new Set(entity.columns.map((column2) => column2.name))
1041
+ ])
1042
+ );
1043
+ return joins.flatMap((join) => {
1044
+ const sourceTable = readNonEmptyString2(join.source_table_name);
1045
+ const sourceKey = readNonEmptyString2(join.source_table_key);
1046
+ const joiningTable = readNonEmptyString2(join.joining_table_name);
1047
+ const joiningKey = readNonEmptyString2(join.joining_table_key);
1048
+ if (!sourceTable || !sourceKey || !joiningTable || !joiningKey || !entityColumns.get(sourceTable)?.has(sourceKey) || !entityColumns.get(joiningTable)?.has(joiningKey)) {
1049
+ return [];
1050
+ }
1051
+ return [
1052
+ {
1053
+ table: sourceTable,
1054
+ from: [sourceKey],
1055
+ referenced_table: joiningTable,
1056
+ to: [joiningKey]
1057
+ }
1058
+ ];
1059
+ });
1060
+ }
1061
+ function matchesFilter2(name, filter) {
1062
+ if (!filter) return true;
1063
+ if (Array.isArray(filter)) return filter.includes(name);
1064
+ if (filter instanceof RegExp) {
1065
+ filter.lastIndex = 0;
1066
+ return filter.test(name);
1067
+ }
1068
+ return filter(name);
1069
+ }
1070
+ function readNonEmptyString2(value) {
1071
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1072
+ }
1073
+ function isRecord4(value) {
1074
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1075
+ }
1076
+
1077
+ // packages/text2sql/src/lib/adapters/posthog/transport.ts
1078
+ var DEFAULT_TIMEOUT_MS = 45e3;
1079
+ var PAGE_SIZE = 100;
1080
+ var PostHogApiError = class extends Error {
1081
+ status;
1082
+ code;
1083
+ detail;
1084
+ retryAfterMs;
1085
+ constructor(options) {
1086
+ super(options.message);
1087
+ this.name = "PostHogApiError";
1088
+ this.status = options.status;
1089
+ this.code = options.code;
1090
+ this.detail = options.detail;
1091
+ this.retryAfterMs = options.retryAfterMs;
1092
+ }
1093
+ };
1094
+ function createPostHogTransport(options) {
1095
+ const origin = parseOrigin(options.host);
1096
+ const projectId = String(options.projectId ?? "").trim();
1097
+ if (!projectId) throw new Error("PostHog transport requires a projectId.");
1098
+ if (typeof options.getAccessToken !== "function") {
1099
+ throw new Error("PostHog transport requires getAccessToken().");
1100
+ }
1101
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1102
+ if (typeof fetchImpl !== "function") {
1103
+ throw new Error("PostHog transport requires a fetch implementation.");
1104
+ }
1105
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1106
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
1107
+ throw new Error("PostHog transport timeoutMs must be a positive integer.");
1108
+ }
1109
+ const projectPath = `/api/projects/${encodeURIComponent(projectId)}`;
1110
+ const requestJson = async (path, init = {}) => {
1111
+ const token = String(await options.getAccessToken()).trim();
1112
+ if (!token) throw new Error("PostHog getAccessToken() returned no token.");
1113
+ const signal = AbortSignal.timeout(timeoutMs);
1114
+ let response;
1115
+ try {
1116
+ response = await fetchImpl(new URL(path, origin), {
1117
+ ...init,
1118
+ headers: {
1119
+ Accept: "application/json",
1120
+ Authorization: `Bearer ${token}`,
1121
+ ...init.body === void 0 ? {} : { "Content-Type": "application/json" }
1122
+ },
1123
+ redirect: "error",
1124
+ signal
1125
+ });
1126
+ } catch {
1127
+ if (signal.aborted) {
1128
+ throw new Error(`PostHog request timed out after ${timeoutMs}ms.`);
1129
+ }
1130
+ throw new Error("PostHog request failed before receiving a response.");
1131
+ }
1132
+ let body;
1133
+ try {
1134
+ body = await response.text();
1135
+ } catch {
1136
+ if (signal.aborted) {
1137
+ throw new Error(`PostHog request timed out after ${timeoutMs}ms.`);
1138
+ }
1139
+ throw new Error("PostHog response body could not be read.");
1140
+ }
1141
+ const parsed = parseJson(body);
1142
+ if (!response.ok) {
1143
+ throw apiError(response, parsed, token);
1144
+ }
1145
+ if (parsed === void 0) {
1146
+ throw new Error("PostHog returned an empty or non-JSON response.");
1147
+ }
1148
+ return parsed;
1149
+ };
1150
+ const listDefinitions = async (resource, parameters) => {
1151
+ const definitions2 = [];
1152
+ let offset = 0;
1153
+ for (; ; ) {
1154
+ const pageParameters = new URLSearchParams(parameters);
1155
+ pageParameters.set("limit", String(PAGE_SIZE));
1156
+ pageParameters.set("offset", String(offset));
1157
+ const page = asRecord(
1158
+ await requestJson(
1159
+ `${projectPath}/${resource}/?${pageParameters.toString()}`
1160
+ ),
1161
+ `PostHog ${resource} response`
1162
+ );
1163
+ if (!Array.isArray(page.results)) {
1164
+ throw new Error(`PostHog ${resource} response has no results array.`);
1165
+ }
1166
+ if (page.next !== null && typeof page.next !== "string") {
1167
+ throw new Error(
1168
+ `PostHog ${resource} response has an invalid next page.`
1169
+ );
1170
+ }
1171
+ definitions2.push(...page.results);
1172
+ if (page.next === null) return definitions2;
1173
+ if (page.results.length === 0) {
1174
+ throw new Error(
1175
+ `PostHog ${resource} returned an empty non-final page.`
1176
+ );
1177
+ }
1178
+ offset += page.results.length;
1179
+ }
1180
+ };
1181
+ return {
1182
+ async query(request) {
1183
+ return await requestJson(`${projectPath}/query/`, {
1184
+ method: "POST",
1185
+ body: JSON.stringify(request)
1186
+ });
1187
+ },
1188
+ listEventDefinitions() {
1189
+ return listDefinitions(
1190
+ "event_definitions",
1191
+ new URLSearchParams({
1192
+ exclude_hidden: "true",
1193
+ exclude_stale: "true"
1194
+ })
1195
+ );
1196
+ },
1197
+ listPropertyDefinitions({
1198
+ type,
1199
+ groupTypeIndex
1200
+ }) {
1201
+ if (type === "group" && (groupTypeIndex === void 0 || !Number.isInteger(groupTypeIndex) || groupTypeIndex < 0)) {
1202
+ throw new Error(
1203
+ "PostHog group property definitions require a non-negative integer groupTypeIndex."
1204
+ );
1205
+ }
1206
+ const parameters = new URLSearchParams({
1207
+ type,
1208
+ exclude_hidden: "true",
1209
+ exclude_restricted: "true"
1210
+ });
1211
+ if (type === "group") {
1212
+ parameters.set("group_type_index", String(groupTypeIndex));
1213
+ }
1214
+ return listDefinitions("property_definitions", parameters);
1215
+ }
1216
+ };
1217
+ }
1218
+ function parseOrigin(host) {
1219
+ let url;
1220
+ try {
1221
+ url = new URL(host);
1222
+ } catch {
1223
+ throw new Error("PostHog host must be an absolute URL.");
1224
+ }
1225
+ const isLoopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
1226
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback)) {
1227
+ throw new Error(
1228
+ "PostHog host must use HTTPS except for loopback development hosts."
1229
+ );
1230
+ }
1231
+ if (url.username || url.password || url.search || url.hash) {
1232
+ throw new Error(
1233
+ "PostHog host must be an origin without credentials or query data."
1234
+ );
1235
+ }
1236
+ if (url.pathname !== "/" && url.pathname !== "") {
1237
+ throw new Error("PostHog host must not include a path.");
1238
+ }
1239
+ url.pathname = "/";
1240
+ return url;
1241
+ }
1242
+ function parseJson(body) {
1243
+ if (!body.trim()) return void 0;
1244
+ try {
1245
+ return JSON.parse(body);
1246
+ } catch {
1247
+ return void 0;
1248
+ }
1249
+ }
1250
+ function apiError(response, body, accessToken) {
1251
+ const record = isRecord5(body) ? body : void 0;
1252
+ const detail = redact(
1253
+ readString(record?.detail) ?? readString(record?.error),
1254
+ accessToken
1255
+ );
1256
+ const code = redact(readString(record?.code), accessToken);
1257
+ const retryAfterMs = parseRetryAfter(response.headers.get("retry-after"));
1258
+ return new PostHogApiError({
1259
+ status: response.status,
1260
+ message: detail ? `PostHog API returned ${response.status}: ${detail}` : `PostHog API returned HTTP ${response.status}.`,
1261
+ code,
1262
+ detail,
1263
+ retryAfterMs
1264
+ });
1265
+ }
1266
+ function parseRetryAfter(value) {
1267
+ if (value === null) return void 0;
1268
+ const seconds = Number(value);
1269
+ if (Number.isFinite(seconds)) {
1270
+ return seconds >= 0 ? seconds * 1e3 : void 0;
1271
+ }
1272
+ const date = Date.parse(value);
1273
+ return Number.isFinite(date) ? Math.max(0, date - Date.now()) : void 0;
1274
+ }
1275
+ function asRecord(value, label) {
1276
+ if (!isRecord5(value)) throw new Error(`${label} must be a JSON object.`);
1277
+ return value;
1278
+ }
1279
+ function isRecord5(value) {
1280
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1281
+ }
1282
+ function readString(value) {
1283
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1284
+ }
1285
+ function redact(value, secret) {
1286
+ return value?.replaceAll(secret, "[REDACTED]");
1287
+ }
1288
+
1289
+ // packages/text2sql/src/lib/adapters/posthog/index.ts
1290
+ function info() {
1291
+ return () => new PostHogInfoGrounding();
1292
+ }
1293
+ function schema(config = {}) {
1294
+ return (adapter) => new PostHogSchemaGrounding(adapter, config);
1295
+ }
1296
+ function definitions(config = {}) {
1297
+ return (adapter) => new PostHogDefinitionsGrounding(adapter, config);
1298
+ }
1299
+ var posthog_default = {
1300
+ PostHog,
1301
+ definitions,
1302
+ info,
1303
+ schema
1304
+ };
1305
+ export {
1306
+ PostHog,
1307
+ PostHogApiError,
1308
+ PostHogDefinitionsGrounding,
1309
+ PostHogSchemaGrounding,
1310
+ PostHogSqlPolicyAnalyzer,
1311
+ createPostHogTransport,
1312
+ posthog_default as default,
1313
+ definitions,
1314
+ info,
1315
+ schema
1316
+ };
1317
+ //# sourceMappingURL=index.js.map