@yejiming/dsh-data-agent 0.0.12 → 0.1.0

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 (60) hide show
  1. package/README.en.md +97 -16
  2. package/README.md +97 -16
  3. package/conformance/dsh-ecosystem/baseline.json +59 -0
  4. package/conformance/dsh-ecosystem/dependencies.json +41 -0
  5. package/conformance/dsh-ecosystem/fixtures/host-degraded.fixture.json +12 -0
  6. package/conformance/dsh-ecosystem/fixtures/host-eligible.fixture.json +13 -0
  7. package/conformance/dsh-ecosystem/fixtures/host-rejected.fixture.json +12 -0
  8. package/conformance/dsh-ecosystem/fixtures/profiles/native-only/package.json +8 -0
  9. package/conformance/dsh-ecosystem/fixtures/profiles/native-plus-adapter/package.json +9 -0
  10. package/conformance/dsh-ecosystem/inventory.json +100 -0
  11. package/conformance/dsh-ecosystem/restrictions.json +20 -0
  12. package/cordis.patch.yml +6 -3
  13. package/dsh-plugin.json +72 -0
  14. package/lib/catalog-DEJqOXRo.js +1944 -0
  15. package/lib/catalog-identity-CVftmvQL.js +96 -0
  16. package/lib/client.js +3465 -138
  17. package/lib/client.js.map +1 -1
  18. package/lib/command-CzzSPmag.js +1719 -0
  19. package/lib/command.js +2 -2
  20. package/lib/{connections-5sfdEDsG.js → connections-CFXOZTHZ.js} +964 -68
  21. package/lib/ecosystem.js +19 -0
  22. package/lib/index.js +392 -34
  23. package/lib/routes.js +260 -8
  24. package/lib/{tool-DgL0fBfj.js → tool-DNkywSph.js} +367 -168
  25. package/lib/tool.js +1 -1
  26. package/lib/types/catalog-adapters.d.ts +52 -0
  27. package/lib/types/catalog-ai.d.ts +49 -0
  28. package/lib/types/catalog-command.d.ts +28 -0
  29. package/lib/types/catalog-identity.d.ts +23 -0
  30. package/lib/types/catalog-storage.d.ts +265 -0
  31. package/lib/types/catalog-tools.d.ts +5 -0
  32. package/lib/types/catalog-tui.d.ts +18 -0
  33. package/lib/types/catalog-types.d.ts +1376 -0
  34. package/lib/types/catalog.d.ts +59 -0
  35. package/lib/types/client/CatalogPanel.d.ts +15 -0
  36. package/lib/types/client/DataAgentWorkbench.d.ts +1 -2
  37. package/lib/types/client/QueryResultTable.d.ts +13 -0
  38. package/lib/types/client/catalog-client.d.ts +57 -0
  39. package/lib/types/client/locales.d.ts +290 -0
  40. package/lib/types/client/persistence.d.ts +3 -1
  41. package/lib/types/client/query-export.d.ts +11 -0
  42. package/lib/types/client-discovery.d.ts +3 -4
  43. package/lib/types/clients.d.ts +14 -8
  44. package/lib/types/command.d.ts +16 -5
  45. package/lib/types/connections.d.ts +42 -4
  46. package/lib/types/database-types.d.ts +23 -0
  47. package/lib/types/defaults.d.ts +26 -0
  48. package/lib/types/ecosystem.d.ts +13 -0
  49. package/lib/types/index.d.ts +58 -15
  50. package/lib/types/query.d.ts +13 -11
  51. package/lib/types/sql.d.ts +9 -1
  52. package/lib/types/storage.d.ts +11 -1
  53. package/lib/types/structured-read.d.ts +2 -2
  54. package/lib/types/structured.d.ts +1 -1
  55. package/lib/types/tool.d.ts +5 -5
  56. package/lib/types/tui-connection-form.d.ts +21 -12
  57. package/package.json +30 -4
  58. package/preset/data-agent/agent.cordis.yml +13 -2
  59. package/lib/command-DuCpwVbl.js +0 -875
  60. package/lib/defaults-DP4RyRh1.js +0 -21
package/lib/routes.js CHANGED
@@ -1,6 +1,8 @@
1
- import { i as DEFAULT_MAX_RESULT_CHARS, o as DEFAULT_QUERY_TIMEOUT_MS, r as DEFAULT_MAX_QUERY_CHARS, t as DEFAULT_CONNECT_TIMEOUT_MS } from "./defaults-DP4RyRh1.js";
1
+ import { C as isDatabaseType, b as DATABASE_TYPES, d as DEFAULT_CONNECT_TIMEOUT_MS, f as DEFAULT_MAX_QUERY_CHARS, h as DEFAULT_QUERY_TIMEOUT_MS, p as DEFAULT_MAX_RESULT_CHARS } from "./connections-CFXOZTHZ.js";
2
+ import { d as catalogSearchRequestSchema, h as semanticDefinitionSchema, l as catalogScopeSchema, t as CatalogVersionConflictError } from "./catalog-DEJqOXRo.js";
2
3
  import { resolve } from "node:path";
3
4
  import z from "schemastery";
5
+ import { z as z$1 } from "zod";
4
6
  //#region src/routes.ts
5
7
  const name = "data-agent-routes";
6
8
  /** Headless profiles activate this row without waiting forever for webServer. */
@@ -20,12 +22,13 @@ function validateConnectBody(value, cwd = process.cwd()) {
20
22
  const candidate = value;
21
23
  const sessionId = requireString(candidate.sessionId, "sessionId");
22
24
  const type = candidate.type;
23
- if (!isDatabaseType(type)) throw new Error("type 必须是 \"mysql\"、\"postgres\"、\"sqlite\"、\"oracle\"、\"hive\" 或 \"impala\"");
25
+ if (!isDatabaseType(type)) throw new Error(`type 必须是受支持的数据库类型:${DATABASE_TYPES.join("")}`);
24
26
  const database = requireString(candidate.database, "database");
25
27
  const password = optionalString(candidate.password, "password");
26
28
  const passwordRef = optionalString(candidate.passwordRef, "passwordRef");
27
29
  if (password !== void 0 && passwordRef !== void 0) throw new Error("password 与 passwordRef 不能同时提供");
28
30
  const readonly = optionalBoolean(candidate.readonly, "readonly");
31
+ const secure = optionalBoolean(candidate.secure, "secure");
29
32
  const profileId = optionalString(candidate.profileId, "profileId");
30
33
  const profileName = optionalString(candidate.name, "name");
31
34
  const request = {
@@ -34,6 +37,7 @@ function validateConnectBody(value, cwd = process.cwd()) {
34
37
  database: type === "sqlite" ? resolve(cwd, database) : database
35
38
  };
36
39
  if (readonly !== void 0) request.readonly = readonly;
40
+ if (type === "clickhouse" && secure !== void 0) request.secure = secure;
37
41
  if (profileId !== void 0) request.profileId = profileId;
38
42
  if (profileName !== void 0) request.name = profileName;
39
43
  if (type === "sqlite") return request;
@@ -50,7 +54,13 @@ function validateConnectBody(value, cwd = process.cwd()) {
50
54
  }
51
55
  /** Register Web routes only when both the webserver and shared service exist. */
52
56
  function apply(ctx, _config) {
53
- ctx.inject(["webServer", "dataAgentConnections"], (scope) => {
57
+ ctx.inject([
58
+ "webServer",
59
+ "dataAgentConnections",
60
+ "dataAgentCatalog",
61
+ "dataAgentCatalogScanner",
62
+ "dataAgentCatalogReview"
63
+ ], (scope) => {
54
64
  scope.effect(() => {
55
65
  const dispose = scope.webServer.register({
56
66
  kind: "prefix",
@@ -133,13 +143,181 @@ function apply(ctx, _config) {
133
143
  const sql = requireString(body.sql, "sql");
134
144
  writeJson(200, {
135
145
  ok: true,
136
- result: await scope.dataAgentConnections.query(sessionId, sql, signal)
146
+ result: await scope.dataAgentConnections.executeInteractive(sessionId, sql, signal)
147
+ });
148
+ return;
149
+ }
150
+ if (req.method === "GET" && routePathIs(segments, "catalog", "sources")) {
151
+ assertOnlySearchParams(url.searchParams, []);
152
+ writeJson(200, {
153
+ ok: true,
154
+ sources: scope.dataAgentCatalog.listSources()
155
+ });
156
+ return;
157
+ }
158
+ if (req.method === "GET" && routePathIs(segments, "catalog", "status")) {
159
+ assertOnlySearchParams(url.searchParams, ["sourceId"]);
160
+ const sourceId = requireBoundedString(url.searchParams.get("sourceId"), "sourceId");
161
+ writeJson(200, {
162
+ ok: true,
163
+ status: scope.dataAgentCatalog.status(sourceId) ?? null
164
+ });
165
+ return;
166
+ }
167
+ if (req.method === "GET" && routePathIs(segments, "catalog", "runs")) {
168
+ assertOnlySearchParams(url.searchParams, ["sourceId", "limit"]);
169
+ const sourceId = requireBoundedString(url.searchParams.get("sourceId"), "sourceId");
170
+ const limit = optionalPositiveInteger(url.searchParams.get("limit"), "limit", 200);
171
+ writeJson(200, {
172
+ ok: true,
173
+ runs: scope.dataAgentCatalog.listRuns(sourceId, limit)
174
+ });
175
+ return;
176
+ }
177
+ if (req.method === "POST" && routePathIs(segments, "catalog", "scan")) {
178
+ const body = catalogScanBodySchema.parse(await readJson(req));
179
+ if (body.sourceId !== void 0) {
180
+ if (scope.dataAgentConnections.get(body.sessionId)?.profileId !== body.sourceId) throw new Error("sourceId does not match the session connection");
181
+ }
182
+ writeJson(202, {
183
+ ok: true,
184
+ run: await scope.dataAgentCatalogScanner.start({
185
+ sessionId: body.sessionId,
186
+ scope: body.scope
187
+ })
188
+ });
189
+ return;
190
+ }
191
+ if (req.method === "POST" && routePathIs(segments, "catalog", "cancel")) {
192
+ const body = catalogCancelBodySchema.parse(await readJson(req));
193
+ writeJson(200, {
194
+ ok: true,
195
+ run: await scope.dataAgentCatalogScanner.cancel(body.sourceId, body.runId)
196
+ });
197
+ return;
198
+ }
199
+ if (req.method === "GET" && routePathIs(segments, "catalog", "search")) {
200
+ assertOnlySearchParams(url.searchParams, [
201
+ "sourceId",
202
+ "query",
203
+ "schema",
204
+ "assetKinds",
205
+ "semanticKinds",
206
+ "assetStatuses",
207
+ "semanticStatuses",
208
+ "includeInferred",
209
+ "cursor",
210
+ "pageSize"
211
+ ]);
212
+ const sourceId = requireBoundedString(url.searchParams.get("sourceId"), "sourceId");
213
+ const query = requireBoundedString(url.searchParams.get("query"), "query", 512);
214
+ const pageSize = optionalPositiveInteger(url.searchParams.get("pageSize"), "pageSize", 200);
215
+ const includeInferred = optionalBooleanQuery(url.searchParams.get("includeInferred"), "includeInferred");
216
+ const request = catalogSearchRequestSchema.parse({
217
+ query,
218
+ filters: {
219
+ sourceId,
220
+ ...url.searchParams.get("schema") !== null ? { schema: url.searchParams.get("schema") } : {},
221
+ ...csvParam(url.searchParams, "assetKinds") !== void 0 ? { assetKinds: csvParam(url.searchParams, "assetKinds") } : {},
222
+ ...csvParam(url.searchParams, "semanticKinds") !== void 0 ? { semanticKinds: csvParam(url.searchParams, "semanticKinds") } : {},
223
+ ...csvParam(url.searchParams, "assetStatuses") !== void 0 ? { assetStatuses: csvParam(url.searchParams, "assetStatuses") } : {},
224
+ ...csvParam(url.searchParams, "semanticStatuses") !== void 0 ? { semanticStatuses: csvParam(url.searchParams, "semanticStatuses") } : {},
225
+ includeInferred: includeInferred ?? false
226
+ },
227
+ ...url.searchParams.get("cursor") !== null ? { cursor: url.searchParams.get("cursor") } : {},
228
+ ...pageSize !== void 0 ? { pageSize } : {}
229
+ });
230
+ writeJson(200, {
231
+ ok: true,
232
+ page: await scope.dataAgentCatalog.search(request)
233
+ });
234
+ return;
235
+ }
236
+ if (req.method === "GET" && segments.length === 3 && segments[0] === "catalog" && segments[1] === "assets") {
237
+ assertOnlySearchParams(url.searchParams, [
238
+ "sourceId",
239
+ "cursor",
240
+ "pageSize"
241
+ ]);
242
+ const sourceId = requireBoundedString(url.searchParams.get("sourceId"), "sourceId");
243
+ const assetId = requireBoundedString(segments[2], "assetId");
244
+ const pageSize = optionalPositiveInteger(url.searchParams.get("pageSize"), "pageSize", 200);
245
+ const cursor = optionalBoundedString(url.searchParams.get("cursor"), "cursor", 512);
246
+ writeJson(200, {
247
+ ok: true,
248
+ detail: scope.dataAgentCatalog.getAsset(sourceId, assetId, cursor, pageSize)
249
+ });
250
+ return;
251
+ }
252
+ if (req.method === "GET" && routePathIs(segments, "catalog", "diff")) {
253
+ assertOnlySearchParams(url.searchParams, [
254
+ "sourceId",
255
+ "from",
256
+ "to",
257
+ "cursor",
258
+ "pageSize"
259
+ ]);
260
+ const sourceId = requireBoundedString(url.searchParams.get("sourceId"), "sourceId");
261
+ const fromRunId = optionalBoundedString(url.searchParams.get("from"), "from", 256);
262
+ const toRunId = optionalBoundedString(url.searchParams.get("to"), "to", 256);
263
+ if (fromRunId === void 0 !== (toRunId === void 0)) throw new Error("from and to must be supplied together");
264
+ const cursor = optionalBoundedString(url.searchParams.get("cursor"), "cursor", 512);
265
+ const pageSize = optionalPositiveInteger(url.searchParams.get("pageSize"), "pageSize", 200);
266
+ writeJson(200, {
267
+ ok: true,
268
+ diff: scope.dataAgentCatalog.diff(sourceId, fromRunId, toRunId, cursor, pageSize)
269
+ });
270
+ return;
271
+ }
272
+ if (req.method === "GET" && segments.length === 3 && segments[0] === "catalog" && segments[1] === "semantics") {
273
+ assertOnlySearchParams(url.searchParams, ["sourceId", "version"]);
274
+ const sourceId = requireBoundedString(url.searchParams.get("sourceId"), "sourceId");
275
+ const semanticId = requireBoundedString(segments[2], "semanticId");
276
+ const version = optionalPositiveInteger(url.searchParams.get("version"), "version", Number.MAX_SAFE_INTEGER);
277
+ writeJson(200, {
278
+ ok: true,
279
+ semantic: scope.dataAgentCatalog.getSemantic(sourceId, semanticId, version)
280
+ });
281
+ return;
282
+ }
283
+ if (req.method === "POST" && routePathIs(segments, "catalog", "semantics")) {
284
+ const body = catalogSemanticSaveBodySchema.parse(await readJson(req));
285
+ writeJson(200, {
286
+ ok: true,
287
+ semantic: await scope.dataAgentCatalogReview.saveCandidate(body.sourceId, body.definition, body.semanticId, body.expectedVersion)
288
+ });
289
+ return;
290
+ }
291
+ if (req.method === "POST" && segments.length === 4 && segments[0] === "catalog" && segments[1] === "semantics" && segments[3] === "verify") {
292
+ const body = catalogSemanticVerifyBodySchema.parse(await readJson(req));
293
+ writeJson(200, {
294
+ ok: true,
295
+ semantic: await scope.dataAgentCatalogReview.verify(body.sourceId, requireBoundedString(segments[2], "semanticId"), body.expectedVersion, body.definition)
296
+ });
297
+ return;
298
+ }
299
+ if (req.method === "POST" && segments.length === 4 && segments[0] === "catalog" && segments[1] === "semantics" && segments[3] === "retire") {
300
+ const body = catalogSemanticRetireBodySchema.parse(await readJson(req));
301
+ writeJson(200, {
302
+ ok: true,
303
+ semantic: await scope.dataAgentCatalogReview.retire(body.sourceId, requireBoundedString(segments[2], "semanticId"), body.expectedVersion, body.revisionNote)
304
+ });
305
+ return;
306
+ }
307
+ if (req.method === "POST" && segments.length === 4 && segments[0] === "catalog" && segments[1] === "semantics" && segments[3] === "dismiss") {
308
+ const body = catalogSemanticDismissBodySchema.parse(await readJson(req));
309
+ writeJson(200, {
310
+ ok: true,
311
+ semantic: await scope.dataAgentCatalogReview.dismissMeaning(body.sourceId, requireBoundedString(segments[2], "semanticId"), body.expectedVersion)
137
312
  });
138
313
  return;
139
314
  }
140
315
  writeJson(404, { error: "unknown data-agent route" });
141
316
  } catch (error) {
142
- writeJson(400, { error: error instanceof Error ? error.message : String(error) });
317
+ writeJson(error instanceof CatalogVersionConflictError ? 409 : 400, {
318
+ error: sanitizeCatalogRouteError(error instanceof Error ? error.message : String(error)),
319
+ ...error instanceof CatalogVersionConflictError ? { current: error.current } : {}
320
+ });
143
321
  }
144
322
  }
145
323
  });
@@ -152,9 +330,47 @@ function apply(ctx, _config) {
152
330
  function routeIs(segments, expected) {
153
331
  return segments.length === 1 && segments[0] === expected;
154
332
  }
333
+ function routePathIs(segments, ...expected) {
334
+ return segments.length === expected.length && segments.every((value, index) => value === expected[index]);
335
+ }
336
+ const catalogScanBodySchema = z$1.strictObject({
337
+ sessionId: z$1.string().min(1).max(256),
338
+ sourceId: z$1.string().min(1).max(256).optional(),
339
+ scope: catalogScopeSchema
340
+ });
341
+ const catalogCancelBodySchema = z$1.strictObject({
342
+ sourceId: z$1.string().min(1).max(256),
343
+ runId: z$1.string().min(1).max(256).optional()
344
+ });
345
+ const catalogSemanticSaveBodySchema = z$1.strictObject({
346
+ sourceId: z$1.string().min(1).max(256),
347
+ semanticId: z$1.string().min(1).max(256).optional(),
348
+ expectedVersion: z$1.number().int().nonnegative().optional(),
349
+ definition: semanticDefinitionSchema
350
+ });
351
+ const catalogSemanticVerifyBodySchema = z$1.strictObject({
352
+ sourceId: z$1.string().min(1).max(256),
353
+ expectedVersion: z$1.number().int().positive(),
354
+ definition: semanticDefinitionSchema
355
+ });
356
+ const catalogSemanticRetireBodySchema = z$1.strictObject({
357
+ sourceId: z$1.string().min(1).max(256),
358
+ expectedVersion: z$1.number().int().positive(),
359
+ revisionNote: z$1.string().trim().min(1).max(4096)
360
+ });
361
+ const catalogSemanticDismissBodySchema = z$1.strictObject({
362
+ sourceId: z$1.string().min(1).max(256),
363
+ expectedVersion: z$1.number().int().positive()
364
+ });
155
365
  async function readJson(req) {
156
366
  const chunks = [];
157
- for await (const chunk of req) chunks.push(chunk);
367
+ let size = 0;
368
+ for await (const chunk of req) {
369
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
370
+ size += buffer.byteLength;
371
+ if (size > 1048576) throw new Error("JSON request body exceeds 1 MiB");
372
+ chunks.push(buffer);
373
+ }
158
374
  const raw = Buffer.concat(chunks).toString("utf8");
159
375
  return raw.length === 0 ? {} : JSON.parse(raw);
160
376
  }
@@ -177,8 +393,44 @@ function optionalBoolean(value, label) {
177
393
  if (typeof value !== "boolean") throw new Error(`${label} 必须是布尔值`);
178
394
  return value;
179
395
  }
180
- function isDatabaseType(value) {
181
- return value === "mysql" || value === "postgres" || value === "sqlite" || value === "oracle" || value === "hive" || value === "impala";
396
+ function requireBoundedString(value, label, max = 256) {
397
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > max) throw new Error(`${label} must be a non-empty string of at most ${max} characters`);
398
+ return value;
399
+ }
400
+ function optionalBoundedString(value, label, max) {
401
+ if (value === void 0 || value === null || value === "") return void 0;
402
+ return requireBoundedString(value, label, max);
403
+ }
404
+ function optionalPositiveInteger(value, label, max) {
405
+ if (value === null || value === "") return void 0;
406
+ const number = Number(value);
407
+ if (!Number.isInteger(number) || number < 1 || number > max) throw new Error(`${label} must be an integer between 1 and ${max}`);
408
+ return number;
409
+ }
410
+ function optionalBooleanQuery(value, label) {
411
+ if (value === null || value === "") return void 0;
412
+ if (value === "true") return true;
413
+ if (value === "false") return false;
414
+ throw new Error(`${label} must be true or false`);
415
+ }
416
+ function csvParam(search, name) {
417
+ const value = search.get(name);
418
+ if (value === null || value === "") return void 0;
419
+ const items = value.split(",");
420
+ if (items.some((item) => item.length === 0 || item.length > 64) || items.length > 32) throw new Error(`${name} is invalid`);
421
+ return items;
422
+ }
423
+ function assertOnlySearchParams(search, allowed) {
424
+ const accepted = new Set(allowed);
425
+ const seen = /* @__PURE__ */ new Set();
426
+ for (const key of search.keys()) {
427
+ if (!accepted.has(key)) throw new Error(`Unknown query parameter: ${key}`);
428
+ if (seen.has(key)) throw new Error(`Duplicate query parameter: ${key}`);
429
+ seen.add(key);
430
+ }
431
+ }
432
+ function sanitizeCatalogRouteError(message) {
433
+ return message.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, " ").replace(/(?:\/[^\s/:]+){3,}/g, "[PATH]").slice(0, 4096);
182
434
  }
183
435
  //#endregion
184
436
  export { Config, DATA_AGENT_PATH, apply, inject, name, validateConnectBody };