@xpert-ai/plugin-analytics-data-xpert 0.1.1 → 0.2.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.
- package/README.md +27 -1
- package/dist/index.js +1 -1
- package/dist/lib/client.d.ts +14 -0
- package/dist/lib/client.js +169 -0
- package/dist/lib/diagnostic.d.ts +10 -0
- package/dist/lib/diagnostic.js +58 -0
- package/dist/lib/discovery.js +3 -1
- package/dist/lib/import-targets.d.ts +164 -0
- package/dist/lib/import-targets.js +30 -0
- package/dist/lib/integration.d.ts +47 -0
- package/dist/lib/integration.js +29 -0
- package/dist/lib/payload.d.ts +50 -0
- package/dist/lib/payload.js +17 -0
- package/dist/lib/plugin.js +4 -1
- package/dist/lib/provider.d.ts +58 -58
- package/dist/lib/provider.js +53 -140
- package/dist/lib/result-schema.d.ts +101 -0
- package/dist/lib/result-schema.js +44 -0
- package/docs/index.mdx +2 -4
- package/docs/verification.mdx +4 -3
- package/package.json +9 -7
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"]}}
|
|
@@ -16,4 +18,28 @@ Run `corepack pnpm exec nx run @xpert-ai/plugin-analytics-data-xpert:test` with
|
|
|
16
18
|
|
|
17
19
|
Discovery preserves each adapter's `definition` (identity, version, native metadata and instructions) and each Action's input schema. Semantic Model, OData and future registered read-only adapters share this transport without sharing query syntax.
|
|
18
20
|
|
|
21
|
+
Before constructing a dashboard binding, call `dashboard_discover` with the selected source/provider and `requestJson` encoding:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{"kind":"query-schema","payload":{"resourceId":"<discovered-resource>","actionTypeCode":"<discovered-read-action>","target":{"entityRef":"<canonical-target>"},"params":{},"parameterPaths":{}},"parameters":{}}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Omit `datasetId` to let the provider generate a valid local output key from the native payload, including its projection. An optional `datasetId` accepts a nonempty string of up to 512 characters: valid output keys (a letter followed by up to 79 letters, digits, `_`, `.` or `-`) are preserved; native references or other aliases are replaced with a generated key. Native resource IDs and targets are unchanged. Copy the returned `output.datasetId` as well as its fields.
|
|
28
|
+
|
|
29
|
+
Use the Action's actual bounded query arguments in `params`. This plugin-owned discovery request executes the same authorized `/agent-workbench/analytics/query` path as preview, with the current actor, host row limit and cancellation signal. It does not require a new DataXpert discovery endpoint. Copy `items[0].metadata.output` into the preview binding and use the same native payload and parameter values. The response includes no business rows and is not a preview receipt; a successful `dashboard_preview` is still required before saving.
|
|
30
|
+
|
|
31
|
+
Output fields retain exact native keys and declared types, roles and nullability, including empty results. Decimal/int64 cells remain exact strings. Stable field IDs are derived from native keys; duplicate semantic selectors use exact keys instead. Grain is left empty until query semantics establish uniqueness. Missing declarations fail with `DATAX_RESULT_SCHEMA_UNAVAILABLE`; duplicate native keys fail with `DATAX_RESULT_SCHEMA_AMBIGUOUS`. Legacy authored bindings can still execute without these optional declarations. Rediscover after changing the projection; existing incorrect saved bindings must be corrected and previewed before saving.
|
|
32
|
+
|
|
33
|
+
Release compatibility: this path uses existing exports from the published SDK/contracts 3.26.1 and does not require a new host capability. The companion SDK change only adds optional column metadata to its TypeScript contract. Merge and publish this plugin's Changeset, then update the installed plugin; merging source alone does not update an existing deployment. Schema authoring requires DataXpert to supply declared column metadata (including the decimal fix in data-xpert PR #108). SAP query errors, authentication, and model-generated malformed tool calls remain separate acceptance checks.
|
|
34
|
+
|
|
35
|
+
After building this plugin and Live Dashboard, run `node plugin-dev-harness/verify-datax-dashboard-schema.mjs` from the repository root. It invokes the real discovery and preview tools, provider and HTTP parser with published SDKs. Its local HTTP source and host-service fixtures do not establish customer SSO, SAP or browser acceptance.
|
|
36
|
+
|
|
19
37
|
See [architecture and Binding usage](docs/index.mdx) and [compatibility and verification](docs/verification.mdx).
|
|
38
|
+
# Public query diagnostics
|
|
39
|
+
|
|
40
|
+
Non-success HTTP responses may contain the shared `xpert.analytics.diagnostic` v1 envelope.
|
|
41
|
+
Only validated public causes and opaque audit references cross into `AnalyticsProviderError`.
|
|
42
|
+
Malformed, sensitive, oversized or unfinished bodies use the existing status-only fallback.
|
|
43
|
+
Parameter mapping failures use provider-authored repair guidance without echoing values.
|
|
44
|
+
|
|
45
|
+
Structured diagnostics require `@xpert-ai/contracts` and `@xpert-ai/plugin-sdk` 3.32.0 or a compatible later 3.x release.
|
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: '
|
|
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,14 @@
|
|
|
1
|
+
import type { AnalyticsBinding, AnalyticsValue, JSONValue, AnalyticsDiscoveryResult } from '@xpert-ai/contracts';
|
|
2
|
+
export { dataXpertPayloadSchema } from './payload';
|
|
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
|
+
export declare class DataXpertAnalyticsClient {
|
|
6
|
+
private readonly runtime;
|
|
7
|
+
constructor(runtime: RuntimeCapabilityRegistry);
|
|
8
|
+
discover(request: JSONValue, context: DataXpertClientContext): Promise<AnalyticsDiscoveryResult>;
|
|
9
|
+
private request;
|
|
10
|
+
execute(binding: AnalyticsBinding, parameters: Record<string, AnalyticsValue>, context: DataXpertClientContext): Promise<AnalyticsProviderResult>;
|
|
11
|
+
private query;
|
|
12
|
+
}
|
|
13
|
+
/** Typed paths replace whole parameter values, never splice text into SQL/MDX/OData. */
|
|
14
|
+
export declare function bindParameters(params: Record<string, JSONValue>, paths: Record<string, string[]>, parameters: Record<string, AnalyticsValue>): Record<string, JSONValue>;
|
|
@@ -0,0 +1,169 @@
|
|
|
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 diagnostic_1 = require("./diagnostic");
|
|
21
|
+
const payload_1 = require("./payload");
|
|
22
|
+
var payload_2 = require("./payload");
|
|
23
|
+
Object.defineProperty(exports, "dataXpertPayloadSchema", { enumerable: true, get: function () { return payload_2.dataXpertPayloadSchema; } });
|
|
24
|
+
const import_targets_1 = require("./import-targets");
|
|
25
|
+
const result_schema_1 = require("./result-schema");
|
|
26
|
+
const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
|
|
27
|
+
const scalar = v3_1.z.union([v3_1.z.string(), v3_1.z.number().finite(), v3_1.z.boolean(), v3_1.z.null()]);
|
|
28
|
+
const json = v3_1.z.lazy(() => v3_1.z.union([scalar, v3_1.z.array(json), v3_1.z.record(json)]));
|
|
29
|
+
const querySchemaRequestSchema = v3_1.z.object({
|
|
30
|
+
kind: v3_1.z.literal('query-schema'), datasetId: v3_1.z.string().min(1).max(512).optional(),
|
|
31
|
+
payload: payload_1.dataXpertPayloadSchema, parameters: v3_1.z.record(scalar).default({})
|
|
32
|
+
}).strict();
|
|
33
|
+
/** Trusted DataSource configuration; never merge it with payloads, controls or discovery requests. */
|
|
34
|
+
const optionsSchema = v3_1.z.object({
|
|
35
|
+
baseUrl: v3_1.z.string().url(), audience: v3_1.z.string().min(1),
|
|
36
|
+
assistantCode: v3_1.z.string().min(1), assistantXpertId: v3_1.z.string().uuid(), workspaceId: v3_1.z.string().uuid()
|
|
37
|
+
}).strict();
|
|
38
|
+
const responseSchema = v3_1.z.object({
|
|
39
|
+
effect: v3_1.z.object({ version: v3_1.z.literal(2), kind: v3_1.z.enum(['rows', 'series', 'entity']),
|
|
40
|
+
columns: v3_1.z.array(result_schema_1.dataXpertResultColumnSchema),
|
|
41
|
+
rows: v3_1.z.array(v3_1.z.record(scalar)),
|
|
42
|
+
summary: v3_1.z.object({ rowCount: v3_1.z.number().int().nonnegative(), returnedRowCount: v3_1.z.number().int().nonnegative(), truncated: v3_1.z.boolean() }) }),
|
|
43
|
+
fetchedAt: v3_1.z.string().datetime(), executionId: v3_1.z.string(), auditRef: v3_1.z.string(), fromCache: v3_1.z.literal(false)
|
|
44
|
+
});
|
|
45
|
+
let DataXpertAnalyticsClient = class DataXpertAnalyticsClient {
|
|
46
|
+
constructor(runtime) {
|
|
47
|
+
this.runtime = runtime;
|
|
48
|
+
}
|
|
49
|
+
async discover(request, context) {
|
|
50
|
+
const importing = import_targets_1.importTargetRequest.safeParse(request);
|
|
51
|
+
if (importing.success)
|
|
52
|
+
return (0, import_targets_1.discoverImportTargets)(importing.data, browse => this.discover(browse, context));
|
|
53
|
+
if (request && typeof request === 'object' && !Array.isArray(request) && request.kind === 'query-schema') {
|
|
54
|
+
const parsed = querySchemaRequestSchema.parse(request);
|
|
55
|
+
const datasetId = (0, result_schema_1.createDataXpertDatasetId)(parsed.payload, parsed.datasetId);
|
|
56
|
+
const result = await this.query(datasetId, parsed.payload, parsed.parameters, context);
|
|
57
|
+
return plugin_sdk_1.analyticsDiscoveryResultSchema.parse({
|
|
58
|
+
items: [{ id: datasetId, name: datasetId, kind: 'dataset',
|
|
59
|
+
metadata: { output: (0, result_schema_1.createDataXpertOutput)(datasetId, result.effect.columns) } }],
|
|
60
|
+
queryContract: discovery_1.dataXpertQueryContract
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const empty = request && typeof request === 'object' && !Array.isArray(request) && Object.keys(request).length === 0;
|
|
64
|
+
const parsed = discovery_1.dataXpertDiscoveryRequestSchema.parse(empty ? { kind: 'resources' } : request);
|
|
65
|
+
const response = await this.request('discover', context, { request: parsed });
|
|
66
|
+
const result = v3_1.z.object({ items: v3_1.z.array(v3_1.z.unknown()),
|
|
67
|
+
nextRequest: json.optional() }).strict().parse(await boundedJson(response));
|
|
68
|
+
return plugin_sdk_1.analyticsDiscoveryResultSchema.parse({ ...result, queryContract: discovery_1.dataXpertQueryContract });
|
|
69
|
+
}
|
|
70
|
+
async request(endpoint, context, body) {
|
|
71
|
+
context.signal.throwIfAborted();
|
|
72
|
+
const options = optionsSchema.parse(context.options);
|
|
73
|
+
const base = new URL(options.baseUrl);
|
|
74
|
+
if (!['http:', 'https:'].includes(base.protocol) || base.username || base.password || base.search || base.hash)
|
|
75
|
+
throw new Error('DATAX_URL_INVALID');
|
|
76
|
+
const token = await this.runtime.require(plugin_sdk_1.ActorTokenRuntimeFactoryCapability).createScopedApi(context.actor).getToken({ audience: options.audience });
|
|
77
|
+
context.signal.throwIfAborted();
|
|
78
|
+
const response = await fetch(`${base.href.replace(/\/$/, '')}/agent-workbench/analytics/${endpoint}`, {
|
|
79
|
+
method: 'POST', redirect: 'error', signal: context.signal,
|
|
80
|
+
headers: { Authorization: `Bearer ${token.token}`, 'Content-Type': 'application/json', 'organization-id': context.actor.organizationId },
|
|
81
|
+
body: JSON.stringify({ assistant: { code: options.assistantCode, xpertId: options.assistantXpertId, workspaceId: options.workspaceId }, ...body })
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const error = await (0, diagnostic_1.readAnalyticsHttpError)(response, context.signal, context.deadlineAt);
|
|
85
|
+
context.signal.throwIfAborted();
|
|
86
|
+
throw error ?? new Error(`DATAX_QUERY_HTTP_${response.status}`);
|
|
87
|
+
}
|
|
88
|
+
return response;
|
|
89
|
+
}
|
|
90
|
+
async execute(binding, parameters, context) {
|
|
91
|
+
const payload = payload_1.dataXpertPayloadSchema.parse(binding.payload);
|
|
92
|
+
const result = await this.query(binding.id, payload, parameters, context);
|
|
93
|
+
return { columns: result.effect.columns, rows: result.effect.rows,
|
|
94
|
+
completeness: { returnedRows: result.effect.summary.returnedRowCount, totalRows: result.effect.summary.rowCount,
|
|
95
|
+
totalAccuracy: result.effect.summary.truncated ? 'unknown' : 'exact', scope: result.effect.summary.truncated ? 'page' : 'full_query_result',
|
|
96
|
+
truncated: result.effect.summary.truncated }, sourceFetchedAt: result.fetchedAt,
|
|
97
|
+
upstreamExecutionId: result.executionId, auditRef: result.auditRef };
|
|
98
|
+
}
|
|
99
|
+
async query(id, payload, parameters, context) {
|
|
100
|
+
const response = await this.request('query', context, {
|
|
101
|
+
binding: { id, resourceId: payload.resourceId, actionTypeCode: payload.actionTypeCode, target: payload.target,
|
|
102
|
+
params: bindParameters(payload.params, payload.parameterPaths, parameters) }, maxRows: context.maxRows
|
|
103
|
+
});
|
|
104
|
+
return responseSchema.parse(await boundedJson(response));
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
exports.DataXpertAnalyticsClient = DataXpertAnalyticsClient;
|
|
108
|
+
exports.DataXpertAnalyticsClient = DataXpertAnalyticsClient = __decorate([
|
|
109
|
+
(0, common_1.Injectable)(),
|
|
110
|
+
__param(0, (0, common_1.Inject)(plugin_sdk_1.XPERT_RUNTIME_CAPABILITIES_TOKEN)),
|
|
111
|
+
__metadata("design:paramtypes", [Object])
|
|
112
|
+
], DataXpertAnalyticsClient);
|
|
113
|
+
/** Typed paths replace whole parameter values, never splice text into SQL/MDX/OData. */
|
|
114
|
+
function bindParameters(params, paths, parameters) {
|
|
115
|
+
const result = structuredClone(params);
|
|
116
|
+
if (Object.keys(parameters).some((name) => !Object.hasOwn(paths, name)) || Object.keys(paths).some((name) => !Object.hasOwn(parameters, name)))
|
|
117
|
+
throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_MAPPING_INVALID');
|
|
118
|
+
if (new Set(Object.values(paths).map((path) => JSON.stringify(path))).size !== Object.keys(paths).length)
|
|
119
|
+
throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_MAPPING_INVALID');
|
|
120
|
+
for (const [name, path] of Object.entries(paths)) {
|
|
121
|
+
let current = result;
|
|
122
|
+
for (const [index, key] of path.entries()) {
|
|
123
|
+
if (['__proto__', 'prototype', 'constructor'].includes(key) || !current || typeof current !== 'object' || !Object.hasOwn(current, key))
|
|
124
|
+
throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_PATH_INVALID');
|
|
125
|
+
if (Array.isArray(current)) {
|
|
126
|
+
if (!/^(0|[1-9]\d*)$/.test(key) || Number(key) >= current.length)
|
|
127
|
+
throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_PATH_INVALID');
|
|
128
|
+
const position = Number(key);
|
|
129
|
+
if (index === path.length - 1) {
|
|
130
|
+
if (current[position] !== null && typeof current[position] === 'object')
|
|
131
|
+
throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_PATH_NOT_SCALAR');
|
|
132
|
+
current[position] = parameters[name];
|
|
133
|
+
}
|
|
134
|
+
else
|
|
135
|
+
current = current[position];
|
|
136
|
+
}
|
|
137
|
+
else if (index === path.length - 1) {
|
|
138
|
+
if (current[key] !== null && typeof current[key] === 'object')
|
|
139
|
+
throw (0, diagnostic_1.dataXpertParameterError)('DATAX_PARAMETER_PATH_NOT_SCALAR');
|
|
140
|
+
current[key] = parameters[name];
|
|
141
|
+
}
|
|
142
|
+
else
|
|
143
|
+
current = current[key];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
async function boundedJson(response) {
|
|
149
|
+
if (!response.body)
|
|
150
|
+
throw new Error('DATAX_EMPTY_RESPONSE');
|
|
151
|
+
const reader = response.body.getReader();
|
|
152
|
+
const chunks = [];
|
|
153
|
+
let size = 0;
|
|
154
|
+
try {
|
|
155
|
+
for (;;) {
|
|
156
|
+
const chunk = await reader.read();
|
|
157
|
+
if (chunk.done)
|
|
158
|
+
break;
|
|
159
|
+
size += chunk.value.byteLength;
|
|
160
|
+
if (size > 2_500_000)
|
|
161
|
+
throw new Error('DATAX_RESPONSE_TOO_LARGE');
|
|
162
|
+
chunks.push(chunk.value);
|
|
163
|
+
}
|
|
164
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
await reader.cancel();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AnalyticsProviderError } from '@xpert-ai/plugin-sdk';
|
|
2
|
+
declare const parameterMessages: {
|
|
3
|
+
DATAX_PARAMETER_MAPPING_INVALID: string;
|
|
4
|
+
DATAX_PARAMETER_PATH_INVALID: string;
|
|
5
|
+
DATAX_PARAMETER_PATH_NOT_SCALAR: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function dataXpertParameterError(code: keyof typeof parameterMessages): AnalyticsProviderError;
|
|
8
|
+
/** Error responses get a smaller byte/time budget than results. Never retain or echo an unknown body. */
|
|
9
|
+
export declare function readAnalyticsHttpError(response: Response, signal: AbortSignal, deadlineAt?: number): Promise<AnalyticsProviderError | undefined>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dataXpertParameterError = dataXpertParameterError;
|
|
4
|
+
exports.readAnalyticsHttpError = readAnalyticsHttpError;
|
|
5
|
+
const contracts_1 = require("@xpert-ai/contracts");
|
|
6
|
+
const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
|
|
7
|
+
const parameterMessages = {
|
|
8
|
+
DATAX_PARAMETER_MAPPING_INVALID: 'Parameter names must match parameterPaths exactly, with a distinct target path for each parameter.',
|
|
9
|
+
DATAX_PARAMETER_PATH_INVALID: 'parameterPaths are relative to params. Omit the leading params segment and target an existing scalar, for example ["slicers","1","value"].',
|
|
10
|
+
DATAX_PARAMETER_PATH_NOT_SCALAR: 'A parameter path must target a scalar value, not an entire object or array. Select the existing filter value field.'
|
|
11
|
+
};
|
|
12
|
+
function dataXpertParameterError(code) {
|
|
13
|
+
return new plugin_sdk_1.AnalyticsProviderError({ schema: 'xpert.analytics.diagnostic', version: 1,
|
|
14
|
+
code, stage: 'validation', retryable: false, message: parameterMessages[code] });
|
|
15
|
+
}
|
|
16
|
+
/** Error responses get a smaller byte/time budget than results. Never retain or echo an unknown body. */
|
|
17
|
+
async function readAnalyticsHttpError(response, signal, deadlineAt) {
|
|
18
|
+
if (!/^application\/json(?:\s*;|$)/i.test(response.headers.get('content-type') ?? '') || !response.body) {
|
|
19
|
+
await response.body?.cancel().catch(() => undefined);
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const reader = response.body.getReader();
|
|
23
|
+
const budget = Math.min(1000, Math.max(0, (deadlineAt ?? Infinity) - Date.now()));
|
|
24
|
+
let timer;
|
|
25
|
+
let abort = () => undefined;
|
|
26
|
+
const cancelled = new Promise(resolve => {
|
|
27
|
+
abort = () => resolve(undefined);
|
|
28
|
+
timer = setTimeout(abort, budget);
|
|
29
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
30
|
+
});
|
|
31
|
+
const read = async () => {
|
|
32
|
+
const chunks = [];
|
|
33
|
+
let size = 0;
|
|
34
|
+
for (;;) {
|
|
35
|
+
const chunk = await reader.read();
|
|
36
|
+
if (chunk.done)
|
|
37
|
+
break;
|
|
38
|
+
size += chunk.value.byteLength;
|
|
39
|
+
if (size > 16_384)
|
|
40
|
+
return undefined;
|
|
41
|
+
chunks.push(chunk.value);
|
|
42
|
+
}
|
|
43
|
+
const diagnostic = (0, contracts_1.parseAnalyticsDiagnostic)(JSON.parse(Buffer.concat(chunks).toString('utf8')));
|
|
44
|
+
return diagnostic && new plugin_sdk_1.AnalyticsProviderError(diagnostic);
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
signal.throwIfAborted();
|
|
48
|
+
return await Promise.race([read(), cancelled]);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
signal.removeEventListener('abort', abort);
|
|
56
|
+
await reader.cancel().catch(() => undefined);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/lib/discovery.js
CHANGED
|
@@ -22,6 +22,7 @@ exports.dataXpertDiscoveryRequestSchema = v3_1.z.discriminatedUnion('kind', [
|
|
|
22
22
|
exports.dataXpertQueryContract = {
|
|
23
23
|
operation: 'action', contractVersion: 1,
|
|
24
24
|
payloadSchema: {
|
|
25
|
+
'x-xpert-import': { version: 1, request: { kind: 'import-targets' } },
|
|
25
26
|
type: 'object', additionalProperties: false,
|
|
26
27
|
required: ['resourceId', 'actionTypeCode', 'target', 'params', 'parameterPaths'],
|
|
27
28
|
properties: {
|
|
@@ -47,6 +48,7 @@ exports.dataXpertQueryContract = {
|
|
|
47
48
|
'Entity metadata.definition contains adapterId, contractVersion, metadata and instructions. Follow that adapter-specific definition and the discovered Action inputSchema/inputHint. Preserve canonical targets, resource IDs and exact field names. Do not infer adapter types from names or apply one adapter\'s query, filter or time rules to another.',
|
|
48
49
|
'Use only published metadata and authorized previews to establish business metrics, time axes, supported filters and complete aggregation. Missing metadata does not authorize guessing; obtain sufficient evidence before generating a time filter.',
|
|
49
50
|
'Use parameterPaths relative to params to replace an existing scalar value, for example {region:["filters","0","value"]}. Paths must be unique. Never interpolate values into query text. Controls supply scalar values; arrays remain static unless the native Action explicitly supports another declared scalar mapping. Null handling and filter operators are defined by the Action schema and adapter instructions.',
|
|
50
|
-
'
|
|
51
|
+
'Before authoring a binding, use dashboard_discover with the same source/providerId and requestJson encoding {kind:"query-schema",payload:<native Action envelope>,parameters:<mapped scalar values, or {}>}. This runs that bounded read-only Action under the current viewer policy; respect the native Action limits and filters. It returns items[0].metadata.output without business rows. Omit datasetId to let the provider generate a valid output key. Copy this output into the dashboard_preview binding, preserving datasetId, field IDs, sourceField, logicalType, role and nullable. Decimal/int64 remain exact strings; do not infer types from sample cells or names. If declarations are missing, obtain source metadata instead of guessing.',
|
|
52
|
+
'The discovered output has empty grain because column roles alone do not prove uniqueness. Declare grain only when supported by query semantics. Always run dashboard_preview with the same payload and parameter values to validate the binding and obtain a preview receipt before saving; schema discovery is not a preview receipt. Re-discover after changing the query projection. A partial page is not a complete business aggregate.'
|
|
51
53
|
].join(' ')
|
|
52
54
|
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { z } from 'zod/v3';
|
|
2
|
+
import type { AnalyticsDiscoveryResult, JSONValue } from '@xpert-ai/contracts';
|
|
3
|
+
/** Provider-owned reference migration. Native Action arguments and output contracts remain unchanged. */
|
|
4
|
+
export declare const importTargetRequest: z.ZodObject<{
|
|
5
|
+
kind: z.ZodLiteral<"import-targets">;
|
|
6
|
+
payload: 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
|
+
browse: z.ZodDefault<z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
|
|
54
|
+
kind: z.ZodLiteral<"resources">;
|
|
55
|
+
after: z.ZodOptional<z.ZodString>;
|
|
56
|
+
}, "strict", z.ZodTypeAny, {
|
|
57
|
+
kind: "resources";
|
|
58
|
+
after?: string | undefined;
|
|
59
|
+
}, {
|
|
60
|
+
kind: "resources";
|
|
61
|
+
after?: string | undefined;
|
|
62
|
+
}>, z.ZodObject<{
|
|
63
|
+
kind: z.ZodLiteral<"entities">;
|
|
64
|
+
resourceId: z.ZodString;
|
|
65
|
+
entityTypeCode: z.ZodOptional<z.ZodString>;
|
|
66
|
+
query: z.ZodOptional<z.ZodString>;
|
|
67
|
+
}, "strict", z.ZodTypeAny, {
|
|
68
|
+
kind: "entities";
|
|
69
|
+
resourceId: string;
|
|
70
|
+
entityTypeCode?: string | undefined;
|
|
71
|
+
query?: string | undefined;
|
|
72
|
+
}, {
|
|
73
|
+
kind: "entities";
|
|
74
|
+
resourceId: string;
|
|
75
|
+
entityTypeCode?: string | undefined;
|
|
76
|
+
query?: string | undefined;
|
|
77
|
+
}>, z.ZodObject<{
|
|
78
|
+
kind: z.ZodLiteral<"actions">;
|
|
79
|
+
resourceId: z.ZodString;
|
|
80
|
+
target: z.ZodObject<{
|
|
81
|
+
entityTypeCode: z.ZodString;
|
|
82
|
+
entityRef: z.ZodString;
|
|
83
|
+
}, "strict", z.ZodTypeAny, {
|
|
84
|
+
entityTypeCode: string;
|
|
85
|
+
entityRef: string;
|
|
86
|
+
}, {
|
|
87
|
+
entityTypeCode: string;
|
|
88
|
+
entityRef: string;
|
|
89
|
+
}>;
|
|
90
|
+
}, "strict", z.ZodTypeAny, {
|
|
91
|
+
kind: "actions";
|
|
92
|
+
resourceId: string;
|
|
93
|
+
target: {
|
|
94
|
+
entityTypeCode: string;
|
|
95
|
+
entityRef: string;
|
|
96
|
+
};
|
|
97
|
+
}, {
|
|
98
|
+
kind: "actions";
|
|
99
|
+
resourceId: string;
|
|
100
|
+
target: {
|
|
101
|
+
entityTypeCode: string;
|
|
102
|
+
entityRef: string;
|
|
103
|
+
};
|
|
104
|
+
}>]>>;
|
|
105
|
+
}, "strict", z.ZodTypeAny, {
|
|
106
|
+
kind: "import-targets";
|
|
107
|
+
payload: {
|
|
108
|
+
params: Record<string, JSONValue>;
|
|
109
|
+
resourceId: string;
|
|
110
|
+
target: {
|
|
111
|
+
entityTypeCode?: string | undefined;
|
|
112
|
+
entityRef?: string | undefined;
|
|
113
|
+
entityId?: string | undefined;
|
|
114
|
+
};
|
|
115
|
+
actionTypeCode: string;
|
|
116
|
+
parameterPaths: Record<string, string[]>;
|
|
117
|
+
};
|
|
118
|
+
browse: {
|
|
119
|
+
kind: "resources";
|
|
120
|
+
after?: string | undefined;
|
|
121
|
+
} | {
|
|
122
|
+
kind: "entities";
|
|
123
|
+
resourceId: string;
|
|
124
|
+
entityTypeCode?: string | undefined;
|
|
125
|
+
query?: string | undefined;
|
|
126
|
+
} | {
|
|
127
|
+
kind: "actions";
|
|
128
|
+
resourceId: string;
|
|
129
|
+
target: {
|
|
130
|
+
entityTypeCode: string;
|
|
131
|
+
entityRef: string;
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
}, {
|
|
135
|
+
kind: "import-targets";
|
|
136
|
+
payload: {
|
|
137
|
+
params: Record<string, JSONValue>;
|
|
138
|
+
resourceId: string;
|
|
139
|
+
target: {
|
|
140
|
+
entityTypeCode?: string | undefined;
|
|
141
|
+
entityRef?: string | undefined;
|
|
142
|
+
entityId?: string | undefined;
|
|
143
|
+
};
|
|
144
|
+
actionTypeCode: string;
|
|
145
|
+
parameterPaths?: Record<string, string[]> | undefined;
|
|
146
|
+
};
|
|
147
|
+
browse?: {
|
|
148
|
+
kind: "resources";
|
|
149
|
+
after?: string | undefined;
|
|
150
|
+
} | {
|
|
151
|
+
kind: "entities";
|
|
152
|
+
resourceId: string;
|
|
153
|
+
entityTypeCode?: string | undefined;
|
|
154
|
+
query?: string | undefined;
|
|
155
|
+
} | {
|
|
156
|
+
kind: "actions";
|
|
157
|
+
resourceId: string;
|
|
158
|
+
target: {
|
|
159
|
+
entityTypeCode: string;
|
|
160
|
+
entityRef: string;
|
|
161
|
+
};
|
|
162
|
+
} | undefined;
|
|
163
|
+
}>;
|
|
164
|
+
export declare function discoverImportTargets(input: z.infer<typeof importTargetRequest>, discover: (request: JSONValue) => Promise<AnalyticsDiscoveryResult>): Promise<AnalyticsDiscoveryResult>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.importTargetRequest = void 0;
|
|
4
|
+
exports.discoverImportTargets = discoverImportTargets;
|
|
5
|
+
const v3_1 = require("zod/v3");
|
|
6
|
+
const payload_1 = require("./payload");
|
|
7
|
+
const discovery_1 = require("./discovery");
|
|
8
|
+
/** Provider-owned reference migration. Native Action arguments and output contracts remain unchanged. */
|
|
9
|
+
exports.importTargetRequest = v3_1.z.object({
|
|
10
|
+
kind: v3_1.z.literal('import-targets'), payload: payload_1.dataXpertPayloadSchema,
|
|
11
|
+
browse: discovery_1.dataXpertDiscoveryRequestSchema.default({ kind: 'resources' })
|
|
12
|
+
}).strict();
|
|
13
|
+
async function discoverImportTargets(input, discover) {
|
|
14
|
+
const result = await discover(input.browse);
|
|
15
|
+
const wrap = (browse) => ({ kind: 'import-targets', payload: { ...input.payload, params: {}, parameterPaths: {} }, browse });
|
|
16
|
+
return { ...result,
|
|
17
|
+
items: result.items.flatMap(item => {
|
|
18
|
+
const next = discovery_1.dataXpertDiscoveryRequestSchema.safeParse(item.inspectRequest);
|
|
19
|
+
if (next.success && next.data.kind === 'actions') {
|
|
20
|
+
const target = next.data.target;
|
|
21
|
+
if (input.payload.target.entityTypeCode && target.entityTypeCode !== input.payload.target.entityTypeCode)
|
|
22
|
+
return [];
|
|
23
|
+
return [{ id: item.id, name: item.name, kind: 'dataset', description: item.description,
|
|
24
|
+
metadata: { schema: 'xpert.analytics.import-target', version: 1,
|
|
25
|
+
payloadPatch: { resourceId: next.data.resourceId, target } } }];
|
|
26
|
+
}
|
|
27
|
+
return item.inspectRequest ? [{ ...item, inspectRequest: wrap(item.inspectRequest) }] : [];
|
|
28
|
+
}),
|
|
29
|
+
...(result.nextRequest ? { nextRequest: wrap(result.nextRequest) } : {}) };
|
|
30
|
+
}
|
|
@@ -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);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from 'zod/v3';
|
|
2
|
+
import type { JSONValue } from '@xpert-ai/contracts';
|
|
3
|
+
/** Native Action arguments stay plugin-owned; the host only interprets Dataset/Binding. */
|
|
4
|
+
export declare const dataXpertPayloadSchema: z.ZodObject<{
|
|
5
|
+
resourceId: z.ZodString;
|
|
6
|
+
actionTypeCode: z.ZodString;
|
|
7
|
+
target: z.ZodEffects<z.ZodObject<{
|
|
8
|
+
entityId: z.ZodOptional<z.ZodString>;
|
|
9
|
+
entityTypeCode: z.ZodOptional<z.ZodString>;
|
|
10
|
+
entityRef: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, "strict", z.ZodTypeAny, {
|
|
12
|
+
entityTypeCode?: string | undefined;
|
|
13
|
+
entityRef?: string | undefined;
|
|
14
|
+
entityId?: string | undefined;
|
|
15
|
+
}, {
|
|
16
|
+
entityTypeCode?: string | undefined;
|
|
17
|
+
entityRef?: string | undefined;
|
|
18
|
+
entityId?: string | undefined;
|
|
19
|
+
}>, {
|
|
20
|
+
entityTypeCode?: string | undefined;
|
|
21
|
+
entityRef?: string | undefined;
|
|
22
|
+
entityId?: string | undefined;
|
|
23
|
+
}, {
|
|
24
|
+
entityTypeCode?: string | undefined;
|
|
25
|
+
entityRef?: string | undefined;
|
|
26
|
+
entityId?: string | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
params: z.ZodRecord<z.ZodString, z.ZodType<JSONValue, z.ZodTypeDef, JSONValue>>;
|
|
29
|
+
parameterPaths: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
|
|
30
|
+
}, "strict", z.ZodTypeAny, {
|
|
31
|
+
params: Record<string, JSONValue>;
|
|
32
|
+
resourceId: string;
|
|
33
|
+
target: {
|
|
34
|
+
entityTypeCode?: string | undefined;
|
|
35
|
+
entityRef?: string | undefined;
|
|
36
|
+
entityId?: string | undefined;
|
|
37
|
+
};
|
|
38
|
+
actionTypeCode: string;
|
|
39
|
+
parameterPaths: Record<string, string[]>;
|
|
40
|
+
}, {
|
|
41
|
+
params: Record<string, JSONValue>;
|
|
42
|
+
resourceId: string;
|
|
43
|
+
target: {
|
|
44
|
+
entityTypeCode?: string | undefined;
|
|
45
|
+
entityRef?: string | undefined;
|
|
46
|
+
entityId?: string | undefined;
|
|
47
|
+
};
|
|
48
|
+
actionTypeCode: string;
|
|
49
|
+
parameterPaths?: Record<string, string[]> | undefined;
|
|
50
|
+
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dataXpertPayloadSchema = void 0;
|
|
4
|
+
const v3_1 = require("zod/v3");
|
|
5
|
+
const scalar = v3_1.z.union([v3_1.z.string(), v3_1.z.number().finite(), v3_1.z.boolean(), v3_1.z.null()]);
|
|
6
|
+
const json = v3_1.z.lazy(() => v3_1.z.union([scalar, v3_1.z.array(json), v3_1.z.record(json)]));
|
|
7
|
+
/** Native Action arguments stay plugin-owned; the host only interprets Dataset/Binding. */
|
|
8
|
+
exports.dataXpertPayloadSchema = v3_1.z.object({
|
|
9
|
+
resourceId: v3_1.z.string().min(1).max(160), actionTypeCode: v3_1.z.string().min(1).max(160),
|
|
10
|
+
target: v3_1.z.object({
|
|
11
|
+
entityId: v3_1.z.string().trim().min(1).max(256).optional(),
|
|
12
|
+
entityTypeCode: v3_1.z.string().trim().min(1).max(256).optional(),
|
|
13
|
+
entityRef: v3_1.z.string().trim().min(1).max(512).optional()
|
|
14
|
+
}).strict().refine(target => Boolean(target.entityId || target.entityRef), 'Target reference is required'),
|
|
15
|
+
params: v3_1.z.record(json),
|
|
16
|
+
parameterPaths: v3_1.z.record(v3_1.z.array(v3_1.z.string().min(1)).min(1).max(8)).default({})
|
|
17
|
+
}).strict();
|
package/dist/lib/plugin.js
CHANGED
|
@@ -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);
|
package/dist/lib/provider.d.ts
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { AnalyticsBinding,
|
|
3
|
-
import { type AnalyticsQueryProvider, type AnalyticsProviderContext, type
|
|
4
|
-
|
|
5
|
-
export declare
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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>;
|
package/dist/lib/provider.js
CHANGED
|
@@ -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 =
|
|
16
|
-
|
|
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
|
|
22
|
-
const
|
|
23
|
-
|
|
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(
|
|
48
|
-
this.
|
|
49
|
-
this.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { z } from 'zod/v3';
|
|
2
|
+
import type { JSONValue } from '@xpert-ai/contracts';
|
|
3
|
+
export declare const dataXpertResultColumnSchema: z.ZodObject<{
|
|
4
|
+
label: z.ZodOptional<z.ZodString>;
|
|
5
|
+
logicalType: z.ZodOptional<z.ZodEnum<["string", "boolean", "int64", "float64", "decimal", "date", "timestamp"]>>;
|
|
6
|
+
nullable: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
role: z.ZodOptional<z.ZodEnum<["dimension", "measure", "time", "identifier"]>>;
|
|
8
|
+
semanticRef: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
9
|
+
unit: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
10
|
+
} & {
|
|
11
|
+
key: z.ZodString;
|
|
12
|
+
}, "strip", z.ZodTypeAny, {
|
|
13
|
+
key: string;
|
|
14
|
+
label?: string | undefined;
|
|
15
|
+
logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp" | undefined;
|
|
16
|
+
nullable?: boolean | undefined;
|
|
17
|
+
role?: "dimension" | "measure" | "time" | "identifier" | undefined;
|
|
18
|
+
semanticRef?: string | undefined;
|
|
19
|
+
unit?: string | undefined;
|
|
20
|
+
}, {
|
|
21
|
+
key: string;
|
|
22
|
+
label?: string | undefined;
|
|
23
|
+
logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp" | undefined;
|
|
24
|
+
nullable?: boolean | undefined;
|
|
25
|
+
role?: "dimension" | "measure" | "time" | "identifier" | undefined;
|
|
26
|
+
semanticRef?: string | undefined;
|
|
27
|
+
unit?: string | undefined;
|
|
28
|
+
}>;
|
|
29
|
+
export declare const dataXpertOutputSchema: z.ZodObject<{
|
|
30
|
+
datasetId: z.ZodString;
|
|
31
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
32
|
+
id: z.ZodString;
|
|
33
|
+
sourceField: z.ZodString;
|
|
34
|
+
label: z.ZodString;
|
|
35
|
+
logicalType: z.ZodEnum<["string", "boolean", "int64", "float64", "decimal", "date", "timestamp"]>;
|
|
36
|
+
nullable: z.ZodBoolean;
|
|
37
|
+
role: z.ZodEnum<["dimension", "measure", "time", "identifier"]>;
|
|
38
|
+
semanticRef: z.ZodOptional<z.ZodString>;
|
|
39
|
+
unit: z.ZodOptional<z.ZodString>;
|
|
40
|
+
}, "strict", z.ZodTypeAny, {
|
|
41
|
+
id?: string;
|
|
42
|
+
sourceField?: string;
|
|
43
|
+
label?: string;
|
|
44
|
+
logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp";
|
|
45
|
+
nullable?: boolean;
|
|
46
|
+
role?: "dimension" | "measure" | "time" | "identifier";
|
|
47
|
+
semanticRef?: string;
|
|
48
|
+
unit?: string;
|
|
49
|
+
}, {
|
|
50
|
+
id?: string;
|
|
51
|
+
sourceField?: string;
|
|
52
|
+
label?: string;
|
|
53
|
+
logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp";
|
|
54
|
+
nullable?: boolean;
|
|
55
|
+
role?: "dimension" | "measure" | "time" | "identifier";
|
|
56
|
+
semanticRef?: string;
|
|
57
|
+
unit?: string;
|
|
58
|
+
}>, "many">;
|
|
59
|
+
grain: z.ZodArray<z.ZodString, "many">;
|
|
60
|
+
}, "strict", z.ZodTypeAny, {
|
|
61
|
+
datasetId?: string;
|
|
62
|
+
fields?: {
|
|
63
|
+
id?: string;
|
|
64
|
+
sourceField?: string;
|
|
65
|
+
label?: string;
|
|
66
|
+
logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp";
|
|
67
|
+
nullable?: boolean;
|
|
68
|
+
role?: "dimension" | "measure" | "time" | "identifier";
|
|
69
|
+
semanticRef?: string;
|
|
70
|
+
unit?: string;
|
|
71
|
+
}[];
|
|
72
|
+
grain?: string[];
|
|
73
|
+
}, {
|
|
74
|
+
datasetId?: string;
|
|
75
|
+
fields?: {
|
|
76
|
+
id?: string;
|
|
77
|
+
sourceField?: string;
|
|
78
|
+
label?: string;
|
|
79
|
+
logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp";
|
|
80
|
+
nullable?: boolean;
|
|
81
|
+
role?: "dimension" | "measure" | "time" | "identifier";
|
|
82
|
+
semanticRef?: string;
|
|
83
|
+
unit?: string;
|
|
84
|
+
}[];
|
|
85
|
+
grain?: string[];
|
|
86
|
+
}>;
|
|
87
|
+
export declare function createDataXpertDatasetId(payload: JSONValue, requestedId?: string): string;
|
|
88
|
+
export declare function createDataXpertOutput(datasetId: string, columns: z.output<typeof dataXpertResultColumnSchema>[]): {
|
|
89
|
+
datasetId?: string;
|
|
90
|
+
fields?: {
|
|
91
|
+
id?: string;
|
|
92
|
+
sourceField?: string;
|
|
93
|
+
label?: string;
|
|
94
|
+
logicalType?: "string" | "boolean" | "date" | "int64" | "float64" | "decimal" | "timestamp";
|
|
95
|
+
nullable?: boolean;
|
|
96
|
+
role?: "dimension" | "measure" | "time" | "identifier";
|
|
97
|
+
semanticRef?: string;
|
|
98
|
+
unit?: string;
|
|
99
|
+
}[];
|
|
100
|
+
grain?: string[];
|
|
101
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dataXpertOutputSchema = exports.dataXpertResultColumnSchema = void 0;
|
|
4
|
+
exports.createDataXpertDatasetId = createDataXpertDatasetId;
|
|
5
|
+
exports.createDataXpertOutput = createDataXpertOutput;
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const v3_1 = require("zod/v3");
|
|
8
|
+
const analytics_1 = require("@xpert-ai/plugin-sdk/analytics");
|
|
9
|
+
// Optional on legacy responses; schema authoring requires explicit declarations.
|
|
10
|
+
exports.dataXpertResultColumnSchema = analytics_1.analyticsFieldSchema
|
|
11
|
+
.omit({ id: true, sourceField: true })
|
|
12
|
+
.partial()
|
|
13
|
+
.extend({ key: v3_1.z.string() })
|
|
14
|
+
.strip();
|
|
15
|
+
exports.dataXpertOutputSchema = analytics_1.analyticsBindingObjectSchema.shape.output;
|
|
16
|
+
function createDataXpertDatasetId(payload, requestedId) {
|
|
17
|
+
const parsed = exports.dataXpertOutputSchema.shape.datasetId.safeParse(requestedId);
|
|
18
|
+
if (parsed.success)
|
|
19
|
+
return parsed.data;
|
|
20
|
+
// Native references are not output keys. Include the query projection to separate bindings.
|
|
21
|
+
return `dataset_${(0, node_crypto_1.createHash)('sha256').update(JSON.stringify(payload)).digest('hex')}`;
|
|
22
|
+
}
|
|
23
|
+
function createDataXpertOutput(datasetId, columns) {
|
|
24
|
+
if (columns.length === 0 || columns.length > 80)
|
|
25
|
+
throw new Error('DATAX_RESULT_SCHEMA_UNAVAILABLE: expected 1 to 80 declared columns');
|
|
26
|
+
if (new Set(columns.map(column => column.key)).size !== columns.length)
|
|
27
|
+
throw new Error('DATAX_RESULT_SCHEMA_AMBIGUOUS');
|
|
28
|
+
const fields = columns.map(column => {
|
|
29
|
+
if (column.logicalType === undefined || column.role === undefined || column.nullable === undefined)
|
|
30
|
+
throw new Error('DATAX_RESULT_SCHEMA_UNAVAILABLE');
|
|
31
|
+
// Repeated semantic selectors cannot identify a column; its unique native key can.
|
|
32
|
+
const semanticRef = columns.filter(item => item.semanticRef === column.semanticRef).length === 1
|
|
33
|
+
? column.semanticRef : undefined;
|
|
34
|
+
return {
|
|
35
|
+
id: `field_${(0, node_crypto_1.createHash)('sha256').update(column.key).digest('hex')}`,
|
|
36
|
+
sourceField: column.key, label: column.label ?? column.key.slice(0, 160),
|
|
37
|
+
logicalType: column.logicalType, role: column.role, nullable: column.nullable,
|
|
38
|
+
...(semanticRef === undefined ? {} : { semanticRef }),
|
|
39
|
+
...(column.unit === undefined ? {} : { unit: column.unit })
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
// Column roles do not establish row uniqueness or business aggregation grain.
|
|
43
|
+
return exports.dataXpertOutputSchema.parse({ datasetId, fields, grain: [] });
|
|
44
|
+
}
|
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
|
-
|
|
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
|
package/docs/verification.mdx
CHANGED
|
@@ -5,9 +5,10 @@ description: "Separate transport-contract tests from real SSO and end-to-end das
|
|
|
5
5
|
|
|
6
6
|
## Compatibility
|
|
7
7
|
|
|
8
|
-
Development dependencies pin
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
Development dependencies pin `@xpert-ai/contracts` and `@xpert-ai/plugin-sdk` to 3.32.0.
|
|
9
|
+
Both peer dependencies require `^3.32.0`. The plugin validates upstream error responses with
|
|
10
|
+
`parseAnalyticsDiagnostic` and returns accepted diagnostics through `AnalyticsProviderError`.
|
|
11
|
+
The host must provide the Analytics and Actor Token runtime capabilities.
|
|
11
12
|
|
|
12
13
|
Data-xpert must expose `POST /agent-workbench/analytics/discover` and
|
|
13
14
|
`POST /agent-workbench/analytics/query` under the configured API base. Discovery returns
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xpert-ai/plugin-analytics-data-xpert",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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.
|
|
31
|
-
"@xpert-ai/contracts": "^3.
|
|
30
|
+
"@xpert-ai/plugin-sdk": "^3.32.0",
|
|
31
|
+
"@xpert-ai/contracts": "^3.32.0",
|
|
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.
|
|
38
|
-
"@xpert-ai/contracts": "3.
|
|
38
|
+
"@xpert-ai/plugin-sdk": "3.32.0",
|
|
39
|
+
"@xpert-ai/contracts": "3.32.0",
|
|
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",
|