@xpert-ai/plugin-analytics-data-xpert 0.1.0 → 0.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.
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # data-xpert analytics provider
2
2
 
3
+ The Live Dashboard Agent generates this provider’s Definition through `analytics_definition_connections` → `analytics_definition_inspect` → `analytics_definition_register`. Existing toolset/Integration credentials stay in their original configuration entries. Settings → Analytics is for viewing, correction and version management; users do not fill internal IDs. See [the authoring guide](../../../../docs/analytics-providers.md#automatic-definition-generation).
4
+ Configure API URL and audience in Settings → Integrations → data-xpert Analytics. The Agent selects this integration from the authorized catalog.
5
+
3
6
  System plugin `@xpert-ai/plugin-analytics-data-xpert`; provider `data-xpert.ontology`, operation `action`, version 1.
4
7
 
5
- Contributes the data-xpert DataSource type through DataSourceType sync. Server configuration: baseUrl (including /api), audience, assistantCode, assistantXpertId, workspaceId. No database runner is required. Platform code has no ontology dependency.
6
8
 
7
9
  ```json
8
10
  {"resourceId":"datax-semantic-model-<published-model-id>","actionTypeCode":"sql.query_cube_slice","target":{"entityTypeCode":"semantic_cube","entityRef":"<published-model-id>::Sales"},"params":{"dimensions":["Region"],"measures":["Amount"],"filters":[{"field":"Region","op":"contains","value":"west"}],"limit":100},"parameterPaths":{"region":["filters","0","value"]}}
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ const node_path_1 = require("node:path");
5
5
  const plugin_1 = require("./lib/plugin");
6
6
  const packageJson = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, '../package.json'), 'utf8'));
7
7
  const plugin = {
8
- meta: { name: packageJson.name, version: packageJson.version, category: 'database', displayName: 'data-xpert Analytics', description: 'data-xpert Analytics', author: 'XpertAI', level: packageJson.xpert.plugin.level, artifactNamespace: packageJson.xpert.plugin.artifactNamespace },
8
+ meta: { name: packageJson.name, version: packageJson.version, category: 'tools', displayName: 'data-xpert Analytics', description: 'data-xpert Analytics', author: 'XpertAI', level: packageJson.xpert.plugin.level, artifactNamespace: packageJson.xpert.plugin.artifactNamespace },
9
9
  register: () => ({ module: plugin_1.AnalyticsDataXpertPlugin })
10
10
  };
11
11
  exports.default = plugin;
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod/v3';
2
+ import type { AnalyticsBinding, AnalyticsValue, JSONValue, AnalyticsDiscoveryResult } from '@xpert-ai/contracts';
3
+ import { type AnalyticsProviderContext, type AnalyticsProviderResult, type RuntimeCapabilityRegistry } from '@xpert-ai/plugin-sdk';
4
+ export type DataXpertClientContext = Pick<AnalyticsProviderContext, 'actor' | 'options' | 'signal' | 'deadlineAt' | 'maxRows'>;
5
+ /** Native Action arguments stay plugin-owned; the host only interprets Dataset/Binding. */
6
+ export declare const dataXpertPayloadSchema: z.ZodObject<{
7
+ resourceId: z.ZodString;
8
+ actionTypeCode: z.ZodString;
9
+ target: z.ZodEffects<z.ZodObject<{
10
+ entityId: z.ZodOptional<z.ZodString>;
11
+ entityTypeCode: z.ZodOptional<z.ZodString>;
12
+ entityRef: z.ZodOptional<z.ZodString>;
13
+ }, "strict", z.ZodTypeAny, {
14
+ entityTypeCode?: string | undefined;
15
+ entityRef?: string | undefined;
16
+ entityId?: string | undefined;
17
+ }, {
18
+ entityTypeCode?: string | undefined;
19
+ entityRef?: string | undefined;
20
+ entityId?: string | undefined;
21
+ }>, {
22
+ entityTypeCode?: string | undefined;
23
+ entityRef?: string | undefined;
24
+ entityId?: string | undefined;
25
+ }, {
26
+ entityTypeCode?: string | undefined;
27
+ entityRef?: string | undefined;
28
+ entityId?: string | undefined;
29
+ }>;
30
+ params: z.ZodRecord<z.ZodString, z.ZodType<JSONValue, z.ZodTypeDef, JSONValue>>;
31
+ parameterPaths: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
32
+ }, "strict", z.ZodTypeAny, {
33
+ params: Record<string, JSONValue>;
34
+ resourceId: string;
35
+ target: {
36
+ entityTypeCode?: string | undefined;
37
+ entityRef?: string | undefined;
38
+ entityId?: string | undefined;
39
+ };
40
+ actionTypeCode: string;
41
+ parameterPaths: Record<string, string[]>;
42
+ }, {
43
+ params: Record<string, JSONValue>;
44
+ resourceId: string;
45
+ target: {
46
+ entityTypeCode?: string | undefined;
47
+ entityRef?: string | undefined;
48
+ entityId?: string | undefined;
49
+ };
50
+ actionTypeCode: string;
51
+ parameterPaths?: Record<string, string[]> | undefined;
52
+ }>;
53
+ export declare class DataXpertAnalyticsClient {
54
+ private readonly runtime;
55
+ constructor(runtime: RuntimeCapabilityRegistry);
56
+ discover(request: JSONValue, context: DataXpertClientContext): Promise<AnalyticsDiscoveryResult>;
57
+ private request;
58
+ execute(binding: AnalyticsBinding, parameters: Record<string, AnalyticsValue>, context: DataXpertClientContext): Promise<AnalyticsProviderResult>;
59
+ }
60
+ /** Typed paths replace whole parameter values, never splice text into SQL/MDX/OData. */
61
+ export declare function bindParameters(params: Record<string, JSONValue>, paths: Record<string, string[]>, parameters: Record<string, AnalyticsValue>): Record<string, JSONValue>;
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.DataXpertAnalyticsClient = exports.dataXpertPayloadSchema = void 0;
16
+ exports.bindParameters = bindParameters;
17
+ const common_1 = require("@nestjs/common");
18
+ const v3_1 = require("zod/v3");
19
+ const discovery_1 = require("./discovery");
20
+ const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
21
+ const scalar = v3_1.z.union([v3_1.z.string(), v3_1.z.number().finite(), v3_1.z.boolean(), v3_1.z.null()]);
22
+ const json = v3_1.z.lazy(() => v3_1.z.union([scalar, v3_1.z.array(json), v3_1.z.record(json)]));
23
+ /** Native Action arguments stay plugin-owned; the host only interprets Dataset/Binding. */
24
+ exports.dataXpertPayloadSchema = v3_1.z.object({
25
+ resourceId: v3_1.z.string().min(1).max(160), actionTypeCode: v3_1.z.string().min(1).max(160),
26
+ target: v3_1.z.object({
27
+ entityId: v3_1.z.string().trim().min(1).max(256).optional(),
28
+ entityTypeCode: v3_1.z.string().trim().min(1).max(256).optional(),
29
+ entityRef: v3_1.z.string().trim().min(1).max(512).optional()
30
+ }).strict().refine(target => Boolean(target.entityId || target.entityRef), 'Target reference is required'),
31
+ params: v3_1.z.record(json),
32
+ parameterPaths: v3_1.z.record(v3_1.z.array(v3_1.z.string().min(1)).min(1).max(8)).default({})
33
+ }).strict();
34
+ /** Trusted DataSource configuration; never merge it with payloads, controls or discovery requests. */
35
+ const optionsSchema = v3_1.z.object({
36
+ baseUrl: v3_1.z.string().url(), audience: v3_1.z.string().min(1),
37
+ assistantCode: v3_1.z.string().min(1), assistantXpertId: v3_1.z.string().uuid(), workspaceId: v3_1.z.string().uuid()
38
+ }).strict();
39
+ const responseSchema = v3_1.z.object({
40
+ effect: v3_1.z.object({ version: v3_1.z.literal(2), kind: v3_1.z.enum(['rows', 'series', 'entity']),
41
+ columns: v3_1.z.array(v3_1.z.object({ key: v3_1.z.string(), semanticRef: v3_1.z.string().optional() })),
42
+ rows: v3_1.z.array(v3_1.z.record(scalar)),
43
+ summary: v3_1.z.object({ rowCount: v3_1.z.number().int().nonnegative(), returnedRowCount: v3_1.z.number().int().nonnegative(), truncated: v3_1.z.boolean() }) }),
44
+ fetchedAt: v3_1.z.string().datetime(), executionId: v3_1.z.string(), auditRef: v3_1.z.string(), fromCache: v3_1.z.literal(false)
45
+ });
46
+ let DataXpertAnalyticsClient = class DataXpertAnalyticsClient {
47
+ constructor(runtime) {
48
+ this.runtime = runtime;
49
+ }
50
+ async discover(request, context) {
51
+ const empty = request && typeof request === 'object' && !Array.isArray(request) && Object.keys(request).length === 0;
52
+ const parsed = discovery_1.dataXpertDiscoveryRequestSchema.parse(empty ? { kind: 'resources' } : request);
53
+ const response = await this.request('discover', context, { request: parsed });
54
+ const result = v3_1.z.object({ items: v3_1.z.array(v3_1.z.unknown()),
55
+ nextRequest: json.optional() }).strict().parse(await boundedJson(response));
56
+ return plugin_sdk_1.analyticsDiscoveryResultSchema.parse({ ...result, queryContract: discovery_1.dataXpertQueryContract });
57
+ }
58
+ async request(endpoint, context, body) {
59
+ context.signal.throwIfAborted();
60
+ const options = optionsSchema.parse(context.options);
61
+ const base = new URL(options.baseUrl);
62
+ if (!['http:', 'https:'].includes(base.protocol) || base.username || base.password || base.search || base.hash)
63
+ throw new Error('DATAX_URL_INVALID');
64
+ const token = await this.runtime.require(plugin_sdk_1.ActorTokenRuntimeFactoryCapability).createScopedApi(context.actor).getToken({ audience: options.audience });
65
+ context.signal.throwIfAborted();
66
+ const response = await fetch(`${base.href.replace(/\/$/, '')}/agent-workbench/analytics/${endpoint}`, {
67
+ method: 'POST', redirect: 'error', signal: context.signal,
68
+ headers: { Authorization: `Bearer ${token.token}`, 'Content-Type': 'application/json', 'organization-id': context.actor.organizationId },
69
+ body: JSON.stringify({ assistant: { code: options.assistantCode, xpertId: options.assistantXpertId, workspaceId: options.workspaceId }, ...body })
70
+ });
71
+ if (!response.ok) {
72
+ // Do not retain an unconsumed upstream error stream or expose its potentially sensitive body.
73
+ await response.body?.cancel().catch(() => undefined);
74
+ throw new Error(`DATAX_QUERY_HTTP_${response.status}`);
75
+ }
76
+ return response;
77
+ }
78
+ async execute(binding, parameters, context) {
79
+ const payload = exports.dataXpertPayloadSchema.parse(binding.payload);
80
+ const response = await this.request('query', context, {
81
+ binding: { id: binding.id, resourceId: payload.resourceId, actionTypeCode: payload.actionTypeCode, target: payload.target,
82
+ params: bindParameters(payload.params, payload.parameterPaths, parameters) }, maxRows: context.maxRows
83
+ });
84
+ const result = responseSchema.parse(await boundedJson(response));
85
+ return { columns: result.effect.columns, rows: result.effect.rows,
86
+ completeness: { returnedRows: result.effect.summary.returnedRowCount, totalRows: result.effect.summary.rowCount,
87
+ totalAccuracy: result.effect.summary.truncated ? 'unknown' : 'exact', scope: result.effect.summary.truncated ? 'page' : 'full_query_result',
88
+ truncated: result.effect.summary.truncated }, sourceFetchedAt: result.fetchedAt,
89
+ upstreamExecutionId: result.executionId, auditRef: result.auditRef };
90
+ }
91
+ };
92
+ exports.DataXpertAnalyticsClient = DataXpertAnalyticsClient;
93
+ exports.DataXpertAnalyticsClient = DataXpertAnalyticsClient = __decorate([
94
+ (0, common_1.Injectable)(),
95
+ __param(0, (0, common_1.Inject)(plugin_sdk_1.XPERT_RUNTIME_CAPABILITIES_TOKEN)),
96
+ __metadata("design:paramtypes", [Object])
97
+ ], DataXpertAnalyticsClient);
98
+ /** Typed paths replace whole parameter values, never splice text into SQL/MDX/OData. */
99
+ function bindParameters(params, paths, parameters) {
100
+ const result = structuredClone(params);
101
+ if (Object.keys(parameters).some((name) => !Object.hasOwn(paths, name)) || Object.keys(paths).some((name) => !Object.hasOwn(parameters, name)))
102
+ throw new Error('DATAX_PARAMETER_MAPPING_INVALID');
103
+ if (new Set(Object.values(paths).map((path) => JSON.stringify(path))).size !== Object.keys(paths).length)
104
+ throw new Error('DATAX_PARAMETER_MAPPING_INVALID');
105
+ for (const [name, path] of Object.entries(paths)) {
106
+ let current = result;
107
+ for (const [index, key] of path.entries()) {
108
+ if (['__proto__', 'prototype', 'constructor'].includes(key) || !current || typeof current !== 'object' || !Object.hasOwn(current, key))
109
+ throw new common_1.BadRequestException('DATAX_PARAMETER_PATH_INVALID: parameterPaths are relative to params. Omit the leading params segment and target an existing scalar, for example ["slicers","1","value"].');
110
+ if (Array.isArray(current)) {
111
+ if (!/^(0|[1-9]\d*)$/.test(key) || Number(key) >= current.length)
112
+ throw new Error('DATAX_PARAMETER_PATH_INVALID');
113
+ const position = Number(key);
114
+ if (index === path.length - 1) {
115
+ if (current[position] !== null && typeof current[position] === 'object')
116
+ throw new Error('DATAX_PARAMETER_PATH_NOT_SCALAR');
117
+ current[position] = parameters[name];
118
+ }
119
+ else
120
+ current = current[position];
121
+ }
122
+ else if (index === path.length - 1) {
123
+ if (current[key] !== null && typeof current[key] === 'object')
124
+ throw new Error('DATAX_PARAMETER_PATH_NOT_SCALAR');
125
+ current[key] = parameters[name];
126
+ }
127
+ else
128
+ current = current[key];
129
+ }
130
+ }
131
+ return result;
132
+ }
133
+ async function boundedJson(response) {
134
+ if (!response.body)
135
+ throw new Error('DATAX_EMPTY_RESPONSE');
136
+ const reader = response.body.getReader();
137
+ const chunks = [];
138
+ let size = 0;
139
+ try {
140
+ for (;;) {
141
+ const chunk = await reader.read();
142
+ if (chunk.done)
143
+ break;
144
+ size += chunk.value.byteLength;
145
+ if (size > 2_500_000)
146
+ throw new Error('DATAX_RESPONSE_TOO_LARGE');
147
+ chunks.push(chunk.value);
148
+ }
149
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
150
+ }
151
+ finally {
152
+ await reader.cancel();
153
+ }
154
+ }
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod/v3';
2
+ import type { IIntegration } from '@xpert-ai/contracts';
3
+ import { type IntegrationStrategy, type TIntegrationStrategyParams } from '@xpert-ai/plugin-sdk';
4
+ export declare const DATA_XPERT_INTEGRATION = "analytics-data-xpert";
5
+ export declare const connectionSchema: z.ZodObject<{
6
+ baseUrl: z.ZodEffects<z.ZodString, string, string>;
7
+ audience: z.ZodString;
8
+ }, "strict", z.ZodTypeAny, {
9
+ baseUrl: string;
10
+ audience: string;
11
+ }, {
12
+ baseUrl: string;
13
+ audience: string;
14
+ }>;
15
+ export declare class DataXpertAnalyticsIntegration implements IntegrationStrategy<z.output<typeof connectionSchema>> {
16
+ readonly meta: {
17
+ name: string;
18
+ label: {
19
+ en_US: string;
20
+ zh_Hans: string;
21
+ };
22
+ icon: {
23
+ type: "font";
24
+ value: string;
25
+ };
26
+ schema: {
27
+ type: "object";
28
+ properties: {
29
+ baseUrl: {
30
+ type: "string";
31
+ title: string;
32
+ };
33
+ audience: {
34
+ type: "string";
35
+ title: string;
36
+ };
37
+ };
38
+ required: string[];
39
+ };
40
+ };
41
+ prepareConfig(config: z.output<typeof connectionSchema>): {
42
+ baseUrl: string;
43
+ audience: string;
44
+ };
45
+ validateConfig(config: z.output<typeof connectionSchema>): Promise<void>;
46
+ execute(_integration: IIntegration<z.output<typeof connectionSchema>>, _payload: TIntegrationStrategyParams): Promise<never>;
47
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.DataXpertAnalyticsIntegration = exports.connectionSchema = exports.DATA_XPERT_INTEGRATION = void 0;
10
+ const v3_1 = require("zod/v3");
11
+ const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
12
+ exports.DATA_XPERT_INTEGRATION = 'analytics-data-xpert';
13
+ exports.connectionSchema = v3_1.z.object({
14
+ baseUrl: v3_1.z.string().url().refine(value => { const url = new URL(value); return ['http:', 'https:'].includes(url.protocol) && !url.username && !url.password && !url.search && !url.hash; }),
15
+ audience: v3_1.z.string().min(1)
16
+ }).strict();
17
+ let DataXpertAnalyticsIntegration = class DataXpertAnalyticsIntegration {
18
+ constructor() {
19
+ this.meta = { name: exports.DATA_XPERT_INTEGRATION, label: { en_US: 'data-xpert Analytics', zh_Hans: 'data-xpert 分析服务' }, icon: { type: 'font', value: 'ri-bar-chart-box-line' },
20
+ schema: { type: 'object', properties: { baseUrl: { type: 'string', title: 'API URL' }, audience: { type: 'string', title: 'OIDC audience' } }, required: ['baseUrl', 'audience'] } };
21
+ }
22
+ prepareConfig(config) { return exports.connectionSchema.parse(config); }
23
+ async validateConfig(config) { exports.connectionSchema.parse(config); }
24
+ async execute(_integration, _payload) { throw new Error('DATAX_USE_ANALYTICS_RUNTIME'); }
25
+ };
26
+ exports.DataXpertAnalyticsIntegration = DataXpertAnalyticsIntegration;
27
+ exports.DataXpertAnalyticsIntegration = DataXpertAnalyticsIntegration = __decorate([
28
+ (0, plugin_sdk_1.IntegrationStrategyKey)(exports.DATA_XPERT_INTEGRATION)
29
+ ], DataXpertAnalyticsIntegration);
@@ -7,11 +7,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  };
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.AnalyticsDataXpertPlugin = void 0;
10
+ const client_1 = require("./client");
10
11
  const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
11
12
  const provider_1 = require("./provider");
13
+ const cqrs_1 = require("@nestjs/cqrs");
14
+ const integration_1 = require("./integration");
12
15
  let AnalyticsDataXpertPlugin = class AnalyticsDataXpertPlugin {
13
16
  };
14
17
  exports.AnalyticsDataXpertPlugin = AnalyticsDataXpertPlugin;
15
18
  exports.AnalyticsDataXpertPlugin = AnalyticsDataXpertPlugin = __decorate([
16
- (0, plugin_sdk_1.XpertServerPlugin)({ providers: [provider_1.DataXpertAnalyticsProvider] })
19
+ (0, plugin_sdk_1.XpertServerPlugin)({ imports: [cqrs_1.CqrsModule], providers: [provider_1.DataXpertAnalyticsProvider, client_1.DataXpertAnalyticsClient, integration_1.DataXpertAnalyticsIntegration] })
17
20
  ], AnalyticsDataXpertPlugin);
@@ -1,61 +1,61 @@
1
- import { z } from 'zod/v3';
2
- import type { AnalyticsBinding, AnalyticsProviderDescriptor, AnalyticsValue, JSONValue, AnalyticsDiscoveryResult } from '@xpert-ai/contracts';
3
- import { type AnalyticsQueryProvider, type AnalyticsProviderContext, type AnalyticsProviderResult, type RuntimeCapabilityRegistry } from '@xpert-ai/plugin-sdk';
4
- /** Native Action arguments stay plugin-owned; the host only interprets Dataset/Binding. */
5
- export declare const dataXpertPayloadSchema: z.ZodObject<{
6
- resourceId: z.ZodString;
7
- actionTypeCode: z.ZodString;
8
- target: z.ZodEffects<z.ZodObject<{
9
- entityId: z.ZodOptional<z.ZodString>;
10
- entityTypeCode: z.ZodOptional<z.ZodString>;
11
- entityRef: z.ZodOptional<z.ZodString>;
12
- }, "strict", z.ZodTypeAny, {
13
- entityTypeCode?: string | undefined;
14
- entityRef?: string | undefined;
15
- entityId?: string | undefined;
16
- }, {
17
- entityTypeCode?: string | undefined;
18
- entityRef?: string | undefined;
19
- entityId?: string | undefined;
20
- }>, {
21
- entityTypeCode?: string | undefined;
22
- entityRef?: string | undefined;
23
- entityId?: string | undefined;
24
- }, {
25
- entityTypeCode?: string | undefined;
26
- entityRef?: string | undefined;
27
- entityId?: string | undefined;
28
- }>;
29
- params: z.ZodRecord<z.ZodString, z.ZodType<JSONValue, z.ZodTypeDef, JSONValue>>;
30
- parameterPaths: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
31
- }, "strict", z.ZodTypeAny, {
32
- params: Record<string, JSONValue>;
33
- resourceId: string;
34
- target: {
35
- entityTypeCode?: string | undefined;
36
- entityRef?: string | undefined;
37
- entityId?: string | undefined;
1
+ import { QueryBus } from '@nestjs/cqrs';
2
+ import type { AnalyticsBinding, AnalyticsValue, JSONValue } from '@xpert-ai/contracts';
3
+ import { type AnalyticsQueryProvider, type AnalyticsProviderContext, type AnalyticsDefinitionAuthoring, type DataSourceActor } from '@xpert-ai/plugin-sdk';
4
+ import { DataXpertAnalyticsClient } from './client';
5
+ export declare class DataXpertAnalyticsProvider implements AnalyticsQueryProvider {
6
+ private readonly engine;
7
+ private readonly queries;
8
+ constructor(engine: DataXpertAnalyticsClient, queries: QueryBus);
9
+ readonly descriptor: {
10
+ id: string;
11
+ name: string;
12
+ sourceKinds: ["definition"];
13
+ operations: {
14
+ name: string;
15
+ contractVersion: number;
16
+ readOnly: true;
17
+ }[];
38
18
  };
39
- actionTypeCode: string;
40
- parameterPaths: Record<string, string[]>;
41
- }, {
42
- params: Record<string, JSONValue>;
43
- resourceId: string;
44
- target: {
45
- entityTypeCode?: string | undefined;
46
- entityRef?: string | undefined;
47
- entityId?: string | undefined;
19
+ readonly authoring: AnalyticsDefinitionAuthoring;
20
+ private authoringContext;
21
+ readonly configuration: {
22
+ kind: "definition";
23
+ connection: "integration";
24
+ schema: {
25
+ type: string;
26
+ properties: {
27
+ integrationId: {
28
+ type: string;
29
+ format: string;
30
+ title: string;
31
+ };
32
+ assistantCode: {
33
+ type: string;
34
+ title: string;
35
+ };
36
+ assistantXpertId: {
37
+ type: string;
38
+ format: string;
39
+ title: string;
40
+ };
41
+ workspaceId: {
42
+ type: string;
43
+ format: string;
44
+ title: string;
45
+ };
46
+ };
47
+ required: string[];
48
+ additionalProperties: boolean;
49
+ };
48
50
  };
49
- actionTypeCode: string;
50
- parameterPaths?: Record<string, string[]> | undefined;
51
- }>;
52
- export declare class DataXpertAnalyticsProvider implements AnalyticsQueryProvider {
53
- private readonly runtime;
54
- readonly descriptor: AnalyticsProviderDescriptor;
55
- constructor(runtime: RuntimeCapabilityRegistry);
56
- discover(request: JSONValue, context: AnalyticsProviderContext): Promise<AnalyticsDiscoveryResult>;
57
- private request;
58
- execute(binding: AnalyticsBinding, parameters: Record<string, AnalyticsValue>, context: AnalyticsProviderContext): Promise<AnalyticsProviderResult>;
51
+ validateConfiguration(options: Record<string, JSONValue>, actor: DataSourceActor): Promise<{
52
+ assistantCode: string;
53
+ assistantXpertId: string;
54
+ workspaceId: string;
55
+ integrationId: string;
56
+ }>;
57
+ private connection;
58
+ private run;
59
+ discover(request: JSONValue, context: AnalyticsProviderContext): Promise<import("@xpert-ai/contracts").AnalyticsDiscoveryResult>;
60
+ execute(binding: AnalyticsBinding, parameters: Record<string, AnalyticsValue>, context: AnalyticsProviderContext): Promise<import("@xpert-ai/plugin-sdk").AnalyticsProviderResult>;
59
61
  }
60
- /** Typed paths replace whole parameter values, never splice text into SQL/MDX/OData. */
61
- export declare function bindParameters(params: Record<string, JSONValue>, paths: Record<string, string[]>, parameters: Record<string, AnalyticsValue>): Record<string, JSONValue>;
@@ -8,157 +8,70 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
- var __param = (this && this.__param) || function (paramIndex, decorator) {
12
- return function (target, key) { decorator(target, key, paramIndex); }
13
- };
14
11
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.DataXpertAnalyticsProvider = exports.dataXpertPayloadSchema = void 0;
16
- exports.bindParameters = bindParameters;
17
- const common_1 = require("@nestjs/common");
12
+ exports.DataXpertAnalyticsProvider = void 0;
13
+ const cqrs_1 = require("@nestjs/cqrs");
18
14
  const v3_1 = require("zod/v3");
19
- const discovery_1 = require("./discovery");
20
15
  const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
21
- const scalar = v3_1.z.union([v3_1.z.string(), v3_1.z.number().finite(), v3_1.z.boolean(), v3_1.z.null()]);
22
- const json = v3_1.z.lazy(() => v3_1.z.union([scalar, v3_1.z.array(json), v3_1.z.record(json)]));
23
- /** Native Action arguments stay plugin-owned; the host only interprets Dataset/Binding. */
24
- exports.dataXpertPayloadSchema = v3_1.z.object({
25
- resourceId: v3_1.z.string().min(1).max(160), actionTypeCode: v3_1.z.string().min(1).max(160),
26
- target: v3_1.z.object({
27
- entityId: v3_1.z.string().trim().min(1).max(256).optional(),
28
- entityTypeCode: v3_1.z.string().trim().min(1).max(256).optional(),
29
- entityRef: v3_1.z.string().trim().min(1).max(512).optional()
30
- }).strict().refine(target => Boolean(target.entityId || target.entityRef), 'Target reference is required'),
31
- params: v3_1.z.record(json),
32
- parameterPaths: v3_1.z.record(v3_1.z.array(v3_1.z.string().min(1)).min(1).max(8)).default({})
33
- }).strict();
34
- /** Trusted DataSource configuration; never merge it with payloads, controls or discovery requests. */
35
- const optionsSchema = v3_1.z.object({
36
- baseUrl: v3_1.z.string().url(), audience: v3_1.z.string().min(1),
37
- assistantCode: v3_1.z.string().min(1), assistantXpertId: v3_1.z.string().uuid(), workspaceId: v3_1.z.string().uuid()
38
- }).strict();
39
- const responseSchema = v3_1.z.object({
40
- effect: v3_1.z.object({ version: v3_1.z.literal(2), kind: v3_1.z.enum(['rows', 'series', 'entity']),
41
- columns: v3_1.z.array(v3_1.z.object({ key: v3_1.z.string(), semanticRef: v3_1.z.string().optional() })),
42
- rows: v3_1.z.array(v3_1.z.record(scalar)),
43
- summary: v3_1.z.object({ rowCount: v3_1.z.number().int().nonnegative(), returnedRowCount: v3_1.z.number().int().nonnegative(), truncated: v3_1.z.boolean() }) }),
44
- fetchedAt: v3_1.z.string().datetime(), executionId: v3_1.z.string(), auditRef: v3_1.z.string(), fromCache: v3_1.z.literal(false)
45
- });
16
+ const client_1 = require("./client");
17
+ const integration_1 = require("./integration");
18
+ const optionsSchema = v3_1.z.object({ integrationId: v3_1.z.string().uuid(), assistantCode: v3_1.z.string().min(1), assistantXpertId: v3_1.z.string().uuid(), workspaceId: v3_1.z.string().uuid() }).strict();
46
19
  let DataXpertAnalyticsProvider = class DataXpertAnalyticsProvider {
47
- constructor(runtime) {
48
- this.runtime = runtime;
49
- this.descriptor = {
50
- id: 'data-xpert.ontology', name: 'data-xpert Ontology', dataSourceTypes: ['data-xpert'],
51
- operations: [{ name: 'action', contractVersion: 1, readOnly: true }],
52
- connectionType: { type: 'data-xpert', name: 'data-xpert', configuration: {
53
- type: 'object', properties: { baseUrl: { type: 'string', title: 'API URL' }, audience: { type: 'string', title: 'OIDC audience' },
54
- assistantCode: { type: 'string', title: 'Assistant code' }, assistantXpertId: { type: 'string', title: 'Assistant Xpert ID' },
55
- workspaceId: { type: 'string', title: 'Workspace ID' } },
56
- required: ['baseUrl', 'audience', 'assistantCode', 'assistantXpertId', 'workspaceId'], additionalProperties: false
57
- } }
20
+ constructor(engine, queries) {
21
+ this.engine = engine;
22
+ this.queries = queries;
23
+ this.descriptor = { id: 'data-xpert.ontology', name: 'data-xpert Ontology', sourceKinds: ['definition'], operations: [{ name: 'action', contractVersion: 1, readOnly: true }] };
24
+ this.authoring = {
25
+ integrationProvider: integration_1.DATA_XPERT_INTEGRATION,
26
+ inspectSchema: { type: 'object', additionalProperties: true },
27
+ definitionSchema: { type: 'object', properties: {}, additionalProperties: false },
28
+ instructions: 'The system supplies integration and current Assistant identity. Inspect the authorized ontology, following nextRequest/inspectRequest. Register with definition {} then discover and preview the returned source. Do not ask the user for integration, workspace or Assistant IDs.',
29
+ inspect: (input, context) => this.run(this.authoringContext(context), resolved => this.engine.discover(input, resolved)).then(result => JSON.parse(JSON.stringify(result))),
30
+ prepare: async (input, context) => {
31
+ v3_1.z.object({}).strict().parse(input);
32
+ const resolved = this.authoringContext(context);
33
+ await this.run(resolved, current => this.engine.discover({}, current));
34
+ return resolved.options;
35
+ }
58
36
  };
37
+ this.configuration = { kind: 'definition', connection: 'integration', schema: {
38
+ type: 'object', properties: { integrationId: { type: 'string', format: 'uuid', title: 'Integration ID' }, assistantCode: { type: 'string', title: 'Assistant code' }, assistantXpertId: { type: 'string', format: 'uuid', title: 'Assistant ID' }, workspaceId: { type: 'string', format: 'uuid', title: 'Workspace ID' } },
39
+ required: ['integrationId', 'assistantCode', 'assistantXpertId', 'workspaceId'], additionalProperties: false
40
+ } };
59
41
  }
60
- async discover(request, context) {
61
- const empty = request && typeof request === 'object' && !Array.isArray(request) && Object.keys(request).length === 0;
62
- const parsed = discovery_1.dataXpertDiscoveryRequestSchema.parse(empty ? { kind: 'resources' } : request);
63
- const response = await this.request('discover', context, { request: parsed });
64
- const result = v3_1.z.object({ items: v3_1.z.array(v3_1.z.unknown()),
65
- nextRequest: json.optional() }).strict().parse(await boundedJson(response));
66
- return plugin_sdk_1.analyticsDiscoveryResultSchema.parse({ ...result, schemaVersion: 1, queryContract: discovery_1.dataXpertQueryContract });
42
+ authoringContext(context) {
43
+ if (context.connection.kind !== 'integration')
44
+ throw new Error('ANALYTICS_CONNECTION_INVALID');
45
+ // The host uses the Assistant UUID as x-assistant-code for data-xpert calls.
46
+ return { actor: context.actor, signal: context.signal, deadlineAt: context.deadlineAt, maxRows: 5,
47
+ options: { integrationId: context.connection.integrationId, assistantCode: context.xpertId,
48
+ assistantXpertId: context.xpertId, workspaceId: context.workspaceId } };
67
49
  }
68
- async request(endpoint, context, body) {
69
- context.signal.throwIfAborted();
70
- const options = optionsSchema.parse(context.options);
71
- const base = new URL(options.baseUrl);
72
- if (!['http:', 'https:'].includes(base.protocol) || base.username || base.password || base.search || base.hash)
73
- throw new Error('DATAX_URL_INVALID');
74
- const token = await this.runtime.require(plugin_sdk_1.ActorTokenRuntimeFactoryCapability).createScopedApi(context.actor).getToken({ audience: options.audience });
75
- context.signal.throwIfAborted();
76
- const response = await fetch(`${base.href.replace(/\/$/, '')}/agent-workbench/analytics/${endpoint}`, {
77
- method: 'POST', redirect: 'error', signal: context.signal,
78
- headers: { Authorization: `Bearer ${token.token}`, 'Content-Type': 'application/json', 'organization-id': context.actor.organizationId },
79
- body: JSON.stringify({ assistant: { code: options.assistantCode, xpertId: options.assistantXpertId, workspaceId: options.workspaceId }, ...body })
80
- });
81
- if (!response.ok) {
82
- // Do not retain an unconsumed upstream error stream or expose its potentially sensitive body.
83
- await response.body?.cancel().catch(() => undefined);
84
- throw new Error(`DATAX_QUERY_HTTP_${response.status}`);
85
- }
86
- return response;
50
+ async validateConfiguration(options, actor) {
51
+ const parsed = optionsSchema.parse(options);
52
+ await this.connection(parsed.integrationId, actor);
53
+ return parsed;
87
54
  }
88
- async execute(binding, parameters, context) {
89
- const payload = exports.dataXpertPayloadSchema.parse(binding.payload);
90
- const response = await this.request('query', context, {
91
- binding: { id: binding.id, resourceId: payload.resourceId, actionTypeCode: payload.actionTypeCode, target: payload.target,
92
- params: bindParameters(payload.params, payload.parameterPaths, parameters) }, maxRows: context.maxRows
93
- });
94
- const result = responseSchema.parse(await boundedJson(response));
95
- return { columns: result.effect.columns, rows: result.effect.rows,
96
- completeness: { returnedRows: result.effect.summary.returnedRowCount, totalRows: result.effect.summary.rowCount,
97
- totalAccuracy: result.effect.summary.truncated ? 'unknown' : 'exact', scope: result.effect.summary.truncated ? 'page' : 'full_query_result',
98
- truncated: result.effect.summary.truncated }, sourceFetchedAt: result.fetchedAt,
99
- upstreamExecutionId: result.executionId, auditRef: result.auditRef };
55
+ async connection(id, actor) {
56
+ const integration = await this.queries.execute(new plugin_sdk_1.ResolveConnectorIntegrationQuery(id, integration_1.DATA_XPERT_INTEGRATION, { tenantId: actor.tenantId, organizationId: actor.organizationId }));
57
+ if (!integration || integration.tenantId !== actor.tenantId || (integration.organizationId && integration.organizationId !== actor.organizationId))
58
+ throw new Error('DATAX_INTEGRATION_UNAVAILABLE');
59
+ return integration_1.connectionSchema.parse(integration.options);
100
60
  }
61
+ async run(context, execute) {
62
+ const { integrationId, ...target } = optionsSchema.parse(context.options);
63
+ const connection = await this.connection(integrationId, context.actor);
64
+ const result = await execute({ ...context, options: { ...target, ...connection } });
65
+ const current = await this.connection(integrationId, context.actor);
66
+ if (JSON.stringify(current) !== JSON.stringify(connection))
67
+ throw new Error('DATAX_INTEGRATION_CHANGED');
68
+ return result;
69
+ }
70
+ discover(request, context) { return this.run(context, resolved => this.engine.discover(request, resolved)); }
71
+ execute(binding, parameters, context) { return this.run(context, resolved => this.engine.execute(binding, parameters, resolved)); }
101
72
  };
102
73
  exports.DataXpertAnalyticsProvider = DataXpertAnalyticsProvider;
103
74
  exports.DataXpertAnalyticsProvider = DataXpertAnalyticsProvider = __decorate([
104
75
  (0, plugin_sdk_1.AnalyticsProvider)('data-xpert.ontology'),
105
- __param(0, (0, common_1.Inject)(plugin_sdk_1.XPERT_RUNTIME_CAPABILITIES_TOKEN)),
106
- __metadata("design:paramtypes", [Object])
76
+ __metadata("design:paramtypes", [client_1.DataXpertAnalyticsClient, cqrs_1.QueryBus])
107
77
  ], DataXpertAnalyticsProvider);
108
- /** Typed paths replace whole parameter values, never splice text into SQL/MDX/OData. */
109
- function bindParameters(params, paths, parameters) {
110
- const result = structuredClone(params);
111
- if (Object.keys(parameters).some((name) => !Object.hasOwn(paths, name)) || Object.keys(paths).some((name) => !Object.hasOwn(parameters, name)))
112
- throw new Error('DATAX_PARAMETER_MAPPING_INVALID');
113
- if (new Set(Object.values(paths).map((path) => JSON.stringify(path))).size !== Object.keys(paths).length)
114
- throw new Error('DATAX_PARAMETER_MAPPING_INVALID');
115
- for (const [name, path] of Object.entries(paths)) {
116
- let current = result;
117
- for (const [index, key] of path.entries()) {
118
- if (['__proto__', 'prototype', 'constructor'].includes(key) || !current || typeof current !== 'object' || !Object.hasOwn(current, key))
119
- throw new common_1.BadRequestException('DATAX_PARAMETER_PATH_INVALID: parameterPaths are relative to params. Omit the leading params segment and target an existing scalar, for example ["slicers","1","value"].');
120
- if (Array.isArray(current)) {
121
- if (!/^(0|[1-9]\d*)$/.test(key) || Number(key) >= current.length)
122
- throw new Error('DATAX_PARAMETER_PATH_INVALID');
123
- const position = Number(key);
124
- if (index === path.length - 1) {
125
- if (current[position] !== null && typeof current[position] === 'object')
126
- throw new Error('DATAX_PARAMETER_PATH_NOT_SCALAR');
127
- current[position] = parameters[name];
128
- }
129
- else
130
- current = current[position];
131
- }
132
- else if (index === path.length - 1) {
133
- if (current[key] !== null && typeof current[key] === 'object')
134
- throw new Error('DATAX_PARAMETER_PATH_NOT_SCALAR');
135
- current[key] = parameters[name];
136
- }
137
- else
138
- current = current[key];
139
- }
140
- }
141
- return result;
142
- }
143
- async function boundedJson(response) {
144
- if (!response.body)
145
- throw new Error('DATAX_EMPTY_RESPONSE');
146
- const reader = response.body.getReader();
147
- const chunks = [];
148
- let size = 0;
149
- try {
150
- for (;;) {
151
- const chunk = await reader.read();
152
- if (chunk.done)
153
- break;
154
- size += chunk.value.byteLength;
155
- if (size > 2_500_000)
156
- throw new Error('DATAX_RESPONSE_TOO_LARGE');
157
- chunks.push(chunk.value);
158
- }
159
- return JSON.parse(Buffer.concat(chunks).toString('utf8'));
160
- }
161
- finally {
162
- await reader.cancel();
163
- }
164
- }
package/docs/index.mdx CHANGED
@@ -7,8 +7,7 @@ description: "Adapt governed ontology Actions to Xpert Dataset/Binding without c
7
7
 
8
8
  Xpert owns the shared Dataset/Binding protocol, current actor, source configuration,
9
9
  parameter validation, execution budget and Artifact persistence. This system plugin registers
10
- `data-xpert.ontology`, operation `action`, contract version `1`, and the `data-xpert`
11
- DataSource type. It converts declared scalar parameters into native Action arguments and
10
+ `data-xpert.ontology`, operation `action`, contract version `1`, a Definition configuration entry and the `analytics-data-xpert` Integration strategy. It converts declared scalar parameters into native Action arguments and
12
11
  maps canonical rows, completeness and audit receipts back to the shared Dataset path.
13
12
 
14
13
  Data-xpert owns published resource discovery, ontology adapter metadata, Action input
@@ -20,8 +19,7 @@ The provider ID is a stable Binding reference; package ownership uses the
20
19
 
21
20
  ## Server configuration and identity
22
21
 
23
- Configure `baseUrl` (including `/api`), `audience`, `assistantCode`, `assistantXpertId`
24
- and `workspaceId` on the platform DataSource. The Assistant and workspace must already
22
+ Create a **data-xpert Analytics** Integration with `baseUrl` (including `/api`) and `audience`. In **Settings → Analytics**, publish a Definition with `integrationId`, `assistantCode`, `assistantXpertId` and `workspaceId`. The Integration is resolved in the current tenant/organization before and after each request. The Assistant and workspace must already
25
23
  have the intended published resources and viewer policy in data-xpert.
26
24
 
27
25
  The provider gets a short-lived token through
@@ -5,7 +5,7 @@ description: "Separate transport-contract tests from real SSO and end-to-end das
5
5
 
6
6
  ## Compatibility
7
7
 
8
- Development dependencies pin published SDK/contracts 3.25.0, which export AnalyticsProvider,
8
+ Development dependencies pin published SDK/contracts 3.26.1, which export AnalyticsProvider,
9
9
  AnalyticsQueryProvider, ActorTokenRuntimeFactoryCapability and the shared Dataset/Binding
10
10
  contracts. The host must provide the corresponding runtime capabilities.
11
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xpert-ai/plugin-analytics-data-xpert",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Governed data-xpert ontology analytics provider",
5
5
  "main": "./index.cjs",
6
6
  "types": "./dist/index.d.ts",
@@ -27,21 +27,23 @@
27
27
  }
28
28
  },
29
29
  "peerDependencies": {
30
- "@xpert-ai/plugin-sdk": "^3.25.0",
31
- "@xpert-ai/contracts": "^3.25.0",
30
+ "@xpert-ai/plugin-sdk": "^3.26.1",
31
+ "@xpert-ai/contracts": "^3.26.1",
32
32
  "@nestjs/common": "^11.1.6",
33
33
  "@nestjs/core": "^11.1.6",
34
- "zod": "3.25.67"
34
+ "zod": "3.25.67",
35
+ "@nestjs/cqrs": "^11.0.3"
35
36
  },
36
37
  "devDependencies": {
37
- "@xpert-ai/plugin-sdk": "3.25.0",
38
- "@xpert-ai/contracts": "3.25.0",
38
+ "@xpert-ai/plugin-sdk": "3.26.1",
39
+ "@xpert-ai/contracts": "3.26.1",
39
40
  "@nestjs/common": "^11.1.6",
40
41
  "@nestjs/core": "^11.1.6",
41
42
  "zod": "3.25.67",
42
43
  "typescript": "^5.9.2",
43
44
  "@types/node": "^20.19.9",
44
- "reflect-metadata": "^0.2.2"
45
+ "reflect-metadata": "^0.2.2",
46
+ "@nestjs/cqrs": "^11.0.3"
45
47
  },
46
48
  "repository": {
47
49
  "type": "git",