@xpert-ai/plugin-sdk 3.18.6 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/MIDDLEWARE_APPS.md +71 -0
  3. package/README.md +2 -0
  4. package/data-workbench.cjs.js +217 -66
  5. package/data-workbench.esm.js +217 -66
  6. package/index.cjs.js +162 -46
  7. package/index.esm.js +148 -47
  8. package/package.json +2 -2
  9. package/runtime-capabilities.md +35 -0
  10. package/src/index.d.ts +3 -0
  11. package/src/lib/agent/index.d.ts +1 -0
  12. package/src/lib/agent/middleware/capabilities/assistant-task.d.ts +55 -20
  13. package/src/lib/agent/middleware/types.d.ts +13 -3
  14. package/src/lib/agent/runtime/capability.d.ts +29 -0
  15. package/src/lib/agent/runtime/index.d.ts +3 -0
  16. package/src/lib/agent/runtime/strategy.d.ts +50 -0
  17. package/src/lib/agent/runtime/types.d.ts +132 -0
  18. package/src/lib/assistant-capability/index.d.ts +42 -0
  19. package/src/lib/connector/runtime-capability.d.ts +2 -0
  20. package/src/lib/connector/strategy.interface.d.ts +8 -0
  21. package/src/lib/core/context/request-context.d.ts +6 -0
  22. package/src/lib/data/datasource/workbench-pagination.d.ts +6 -0
  23. package/src/lib/data/datasource/workbench-sql-lexer.d.ts +11 -0
  24. package/src/lib/data/datasource/workbench-sql-syntax.d.ts +3 -0
  25. package/src/lib/evolution/runtime-capability.d.ts +2 -1
  26. package/src/lib/integration/strategy.interface.d.ts +22 -0
  27. package/src/lib/mcp/app.d.ts +10 -0
  28. package/src/lib/project-task/index.d.ts +59 -0
  29. package/src/lib/project-type/index.d.ts +64 -0
  30. package/src/lib/rag/analyzer/index.d.ts +3 -0
  31. package/src/lib/rag/analyzer/strategy.decorator.d.ts +2 -0
  32. package/src/lib/rag/analyzer/strategy.interface.d.ts +11 -0
  33. package/src/lib/rag/analyzer/strategy.registry.d.ts +8 -0
  34. package/src/lib/rag/index.d.ts +1 -0
  35. package/src/lib/runtime/capabilities/knowledge-graph.d.ts +59 -0
  36. package/src/lib/runtime/capabilities/knowledgebase-documents.d.ts +21 -0
  37. package/src/lib/runtime/capabilities/knowledgebase.d.ts +8 -3
  38. package/src/lib/runtime/capabilities/project-access.d.ts +2 -1
  39. package/src/lib/runtime/capabilities/project-provisioning.d.ts +3 -0
  40. package/src/lib/runtime/runtime-scope.d.ts +7 -1
  41. package/src/lib/sandbox/execution-environment.d.ts +10 -0
  42. package/src/lib/sandbox/execution.d.ts +3 -0
  43. package/src/lib/sandbox/index.d.ts +1 -0
  44. package/src/lib/sandbox/protocol.d.ts +2 -0
  45. package/src/lib/tool-provider/types.d.ts +6 -0
  46. package/src/lib/types.d.ts +2 -1
  47. package/src/lib/workflow/trigger/strategy.interface.d.ts +2 -1
@@ -6,6 +6,159 @@ const DataSourceRuntimeCapability = Object.freeze({
6
6
  __type: undefined
7
7
  });
8
8
 
9
+ /** Mask comments and quoted content without changing source offsets. Shared by guards and pagination. */ function lexWorkbenchSql(source) {
10
+ if (!source.trim() || source.length > 100000 || source.includes('\0')) throw new Error('invalid_sql');
11
+ const masked = source.split('');
12
+ const quoted = new Map();
13
+ const hide = (start, end, kind)=>{
14
+ for(let i = start; i < end; i++)if (masked[i] !== '\n') masked[i] = ' ';
15
+ if (kind) {
16
+ const marker = start + 1;
17
+ quoted.set(marker, kind);
18
+ masked[marker] = kind === 'literal' ? '?' : 'i';
19
+ }
20
+ };
21
+ let index = 0;
22
+ while(index < source.length){
23
+ const start = index, c = source[index], next = source[index + 1];
24
+ if (c === '-' && next === '-' || c === '#') {
25
+ while(index < source.length && source[index] !== '\n')index++;
26
+ hide(start, index);
27
+ continue;
28
+ }
29
+ if (c === '/' && next === '*') {
30
+ if (source[index + 2] === '!') throw new Error('executable_comment_not_supported');
31
+ const end = source.indexOf('*/', index + 2);
32
+ if (end < 0) throw new Error('unterminated_comment');
33
+ index = end + 2;
34
+ hide(start, index);
35
+ continue;
36
+ }
37
+ if (c === "'" || c === '"' || c === '`') {
38
+ let closed = false;
39
+ index++;
40
+ while(index < source.length){
41
+ if (source[index] === '\\') throw new Error('use_parameters_for_backslash_literals');
42
+ if (source[index] === c) {
43
+ if (source[index + 1] === c) {
44
+ index += 2;
45
+ continue;
46
+ }
47
+ index++;
48
+ closed = true;
49
+ break;
50
+ }
51
+ index++;
52
+ }
53
+ if (!closed) throw new Error('unterminated_literal');
54
+ hide(start, index, c === "'" ? 'literal' : 'identifier');
55
+ continue;
56
+ }
57
+ if (c === '$') {
58
+ var _source_slice_match;
59
+ const tag = (_source_slice_match = source.slice(index).match(/^\$(?:[A-Za-z_][A-Za-z_0-9]*)?\$/)) == null ? void 0 : _source_slice_match[0];
60
+ if (tag) {
61
+ const end = source.indexOf(tag, index + tag.length);
62
+ if (end < 0) throw new Error('unterminated_literal');
63
+ index = end + tag.length;
64
+ hide(start, index, 'literal');
65
+ continue;
66
+ }
67
+ }
68
+ index++;
69
+ }
70
+ const text = masked.join('');
71
+ const tokens = [
72
+ ...text.matchAll(/[A-Z_][A-Z_0-9]*|[0-9]+|[^\s]/gi)
73
+ ].map((match)=>{
74
+ var _quoted_get;
75
+ return {
76
+ text: match[0].toUpperCase(),
77
+ start: match.index,
78
+ end: match.index + match[0].length,
79
+ kind: (_quoted_get = quoted.get(match.index)) != null ? _quoted_get : /^[A-Z_]/i.test(match[0]) ? 'word' : /^[0-9]/.test(match[0]) ? 'number' : 'symbol'
80
+ };
81
+ });
82
+ return {
83
+ masked: text,
84
+ tokens
85
+ };
86
+ }
87
+
88
+ const MODIFIED_TYPES = new Set('DECIMAL NUMERIC CHAR VARCHAR NCHAR NVARCHAR BINARY VARBINARY DATETIME TIME TIMESTAMP'.split(' '));
89
+ /** Positions where name(...) declares columns or a built-in type, rather than invoking a function. */ function sqlDeclarationPositions(tokens) {
90
+ const declarations = new Set();
91
+ const closing = new Map(), parents = new Map();
92
+ const stack = [];
93
+ tokens.forEach((token, index)=>{
94
+ if (stack.length) parents.set(index, stack[stack.length - 1]);
95
+ if (token.text === '(') stack.push(index);
96
+ if (token.text === ')') {
97
+ const open = stack.pop();
98
+ if (open !== undefined) closing.set(open, index);
99
+ }
100
+ });
101
+ const name = (index)=>{
102
+ var _tokens_index;
103
+ return [
104
+ 'word',
105
+ 'identifier'
106
+ ].includes((_tokens_index = tokens[index]) == null ? void 0 : _tokens_index.kind);
107
+ };
108
+ const is = (index, word)=>{
109
+ var _tokens_index;
110
+ return ((_tokens_index = tokens[index]) == null ? void 0 : _tokens_index.text) === word;
111
+ };
112
+ for(let i = 0; i < tokens.length; i++){
113
+ if (tokens[i].kind !== 'word' || !is(i, 'WITH')) continue;
114
+ let cursor = i + 1;
115
+ if (is(cursor, 'RECURSIVE')) cursor++;
116
+ while(name(cursor)){
117
+ const declaration = cursor++;
118
+ let columns = false;
119
+ if (is(cursor, '(')) {
120
+ const end = closing.get(cursor);
121
+ if (end === undefined || end === cursor + 1) break;
122
+ const columnTokens = tokens.slice(cursor + 1, end);
123
+ if (columnTokens.length % 2 === 0 || !columnTokens.every((token, n)=>n % 2 === 0 ? [
124
+ 'word',
125
+ 'identifier'
126
+ ].includes(token.kind) : token.text === ',')) break;
127
+ columns = true;
128
+ cursor = end + 1;
129
+ }
130
+ if (!is(cursor++, 'AS')) break;
131
+ if (is(cursor, 'NOT')) cursor++;
132
+ if (is(cursor, 'MATERIALIZED')) cursor++;
133
+ if (!is(cursor, '(')) break;
134
+ const end = closing.get(cursor);
135
+ if (end === undefined) break;
136
+ if (columns) declarations.add(tokens[declaration].start);
137
+ cursor = end + 1;
138
+ if (!is(cursor, ',')) break;
139
+ cursor++;
140
+ }
141
+ }
142
+ tokens.forEach((token, i)=>{
143
+ var _tokens_;
144
+ if (token.kind !== 'word' || !MODIFIED_TYPES.has(token.text) || !is(i + 1, '(')) return;
145
+ const end = closing.get(i + 1);
146
+ if (end === undefined) return;
147
+ const args = tokens.slice(i + 2, end);
148
+ if (![
149
+ 1,
150
+ 3
151
+ ].includes(args.length) || !args.every((arg, n)=>n % 2 ? arg.text === ',' : arg.kind === 'number')) return;
152
+ const parent = parents.get(i);
153
+ const owner = parent === undefined ? undefined : (_tokens_ = tokens[parent - 1]) == null ? void 0 : _tokens_.text;
154
+ const cast = owner === 'CAST' && is(i - 1, 'AS');
155
+ const convert = owner === 'CONVERT' && is(i - 1, ',');
156
+ const postgresCast = is(i - 1, ':') && is(i - 2, ':');
157
+ if (cast || convert || postgresCast) declarations.add(token.start);
158
+ });
159
+ return declarations;
160
+ }
161
+
9
162
  const WRITE = new Set([
10
163
  'INSERT',
11
164
  'UPDATE',
@@ -40,15 +193,16 @@ const FORBIDDEN = new Set([
40
193
  'DETACH'
41
194
  ]);
42
195
  const SAFE_FUNCTIONS = new Set('ABS ACOS ASIN ATAN ATAN2 AVG CAST CEIL CEILING COALESCE CONCAT CONCAT_WS CONVERT COS COUNT CURDATE CURRENT_DATE CURRENT_TIMESTAMP DATE DATE_ADD DATE_SUB DATE_FORMAT DATEDIFF DAY DAYOFMONTH DAYOFWEEK EXTRACT FLOOR FORMAT GREATEST GROUP_CONCAT IF IFNULL JSON_EXTRACT JSON_LENGTH JSON_UNQUOTE LEAST LEFT LENGTH LN LOG LOWER LPAD LTRIM MAX MD5 MIN MOD MONTH NOW NULLIF POWER RAND REPLACE REVERSE RIGHT ROUND ROW_NUMBER RANK DENSE_RANK LAG LEAD FIRST_VALUE LAST_VALUE LPAD RPAD RTRIM SIGN SIN SQRT STDDEV STDDEV_POP STDDEV_SAMP SUBSTR SUBSTRING SUM TAN TO_CHAR TO_DATE TO_TIMESTAMP TRIM TRUNC UPPER VAR_POP VAR_SAMP VERSION WEEK YEAR DATABASE CURRENT_DATABASE CURRENT_SCHEMA ARRAY_AGG STRING_AGG JSON_AGG JSON_BUILD_OBJECT PG_GET_VIEWDEF'.split(' '));
43
- const SQL_PARENS = new Set('IN AS EXISTS OVER PARTITION VALUES SELECT WITH AND OR NOT ON USING FILTER GROUPING ROLLUP CUBE DISTINCT EXPLAIN'.split(' '));
196
+ // FROM/JOIN can introduce a parenthesized subquery, not a function call.
197
+ const SQL_PARENS = new Set('IN AS EXISTS OVER PARTITION VALUES SELECT WITH FROM JOIN AND OR NOT ON USING FILTER GROUPING ROLLUP CUBE DISTINCT EXPLAIN'.split(' '));
44
198
  const UNSAFE_FUNCTIONS = /\b(pg_sleep|sleep|benchmark|dblink\w*|lo_\w+|pg_(?:read|write|ls|stat|terminate|cancel|reload|rotate|log|create|drop|promote|switch|backup|replication|advisory)\w*|get_lock|release_lock|load_file|nextval|setval|set_config|http\w*|s3|hdfs|jdbc|mysql|postgresql|file|url)\s*\(/i;
45
199
  function splitWorkbenchSql(source) {
46
- if (!source.trim() || source.length > 100000 || source.includes('\0')) throw new Error('invalid_sql');
200
+ const { masked, tokens } = lexWorkbenchSql(source);
47
201
  const statements = [];
48
- let start = 0, index = 0, clean = '';
202
+ let start = 0;
49
203
  const finish = (end)=>{
50
- const text = clean.trim();
51
- if (text) {
204
+ const text = masked.slice(start, end);
205
+ if (text.trim()) {
52
206
  var _text_toUpperCase_match;
53
207
  const words = (_text_toUpperCase_match = text.toUpperCase().match(/[A-Z_][A-Z_0-9]*/g)) != null ? _text_toUpperCase_match : [];
54
208
  const first = words[0];
@@ -59,10 +213,11 @@ function splitWorkbenchSql(source) {
59
213
  if (words.some((word, index)=>WRITE.has(word) && !(first === 'SHOW' && index === 1 && word === 'CREATE') || FORBIDDEN.has(word)) || UNSAFE_FUNCTIONS.test(text) || /\bFOR\s+(UPDATE|SHARE)\b/i.test(text) || /:=|\bEXPLAIN\s+ANALYZE\b|@/i.test(text)) effect = 'unsupported';
60
214
  // eslint-disable-next-line no-control-regex -- intentional: reject any non-ASCII/control input
61
215
  if (/[^\x00-\x7f]/.test(text) || /\.\s*[A-Z_][A-Z_0-9]*\s*\(/i.test(text)) effect = 'unsupported';
216
+ const declarations = sqlDeclarationPositions(tokens.filter((token)=>token.start >= start && token.end <= end));
62
217
  if ([
63
218
  ...text.matchAll(/([A-Z_][A-Z_0-9]*)\s*\(/gi)
64
- ].some((match)=>!SAFE_FUNCTIONS.has(match[1].toUpperCase()) && !SQL_PARENS.has(match[1].toUpperCase()))) effect = 'unsupported';
65
- if (first === 'SHOW' && !/^(SHOW\s+(DATABASES|SCHEMAS|CATALOGS|TABLES|FULL\s+TABLES|COLUMNS|FULL\s+COLUMNS|INDEX|INDEXES|KEYS|CREATE\s+TABLE|CREATE\s+VIEW|PARTITIONS|VARIABLES|STATUS|GRANTS))\b/i.test(text)) effect = 'unsupported';
219
+ ].some((match)=>!declarations.has(start + match.index) && !SAFE_FUNCTIONS.has(match[1].toUpperCase()) && !SQL_PARENS.has(match[1].toUpperCase()))) effect = 'unsupported';
220
+ if (first === 'SHOW' && !/^(SHOW\s+(DATABASES|SCHEMAS|CATALOGS|TABLES|FULL\s+TABLES|COLUMNS|FULL\s+COLUMNS|INDEX|INDEXES|KEYS|CREATE\s+TABLE|CREATE\s+VIEW|PARTITIONS|VARIABLES|STATUS|GRANTS))\b/i.test(text.trim())) effect = 'unsupported';
66
221
  }
67
222
  // CREATE/ALTER routines, external resources and privileged SQL are outside this app.
68
223
  if (effect === 'write' && /\b(FUNCTION|PROCEDURE|TRIGGER|USER|ROLE|CATALOG|RESOURCE|SYSTEM|SERVER|EXTENSION|FILE|OUTFILE|DUMPFILE|EXECUTE)\b/i.test(text)) effect = 'unsupported';
@@ -72,63 +227,10 @@ function splitWorkbenchSql(source) {
72
227
  effect
73
228
  });
74
229
  }
75
- clean = '';
76
230
  start = end + 1;
77
231
  };
78
- while(index < source.length){
79
- const c = source[index], next = source[index + 1];
80
- if (c === '-' && next === '-' || c === '#') {
81
- while(index < source.length && source[index] !== '\n')index++;
82
- clean += ' ';
83
- continue;
84
- }
85
- if (c === '/' && next === '*') {
86
- if (source[index + 2] === '!') throw new Error('executable_comment_not_supported');
87
- const end = source.indexOf('*/', index + 2);
88
- if (end < 0) throw new Error('unterminated_comment');
89
- index = end + 2;
90
- clean += ' ';
91
- continue;
92
- }
93
- if (c === "'" || c === '"' || c === '`') {
94
- const quote = c;
95
- let closed = false;
96
- index++;
97
- while(index < source.length){
98
- if (source[index] === '\\') throw new Error('use_parameters_for_backslash_literals');
99
- if (source[index] === quote) {
100
- if (source[index + 1] === quote) {
101
- index += 2;
102
- continue;
103
- }
104
- index++;
105
- closed = true;
106
- break;
107
- }
108
- index++;
109
- }
110
- if (!closed) throw new Error('unterminated_literal');
111
- clean += quote === "'" ? ' ? ' : ' identifier ';
112
- continue;
113
- }
114
- if (c === '$') {
115
- var _source_slice_match;
116
- const tag = (_source_slice_match = source.slice(index).match(/^\$(?:[A-Za-z_][A-Za-z_0-9]*)?\$/)) == null ? void 0 : _source_slice_match[0];
117
- if (tag) {
118
- const end = source.indexOf(tag, index + tag.length);
119
- if (end < 0) throw new Error('unterminated_literal');
120
- index = end + tag.length;
121
- clean += ' ? ';
122
- continue;
123
- }
124
- }
125
- if (c === ';') {
126
- finish(index);
127
- index++;
128
- continue;
129
- }
130
- clean += c;
131
- index++;
232
+ for (const token of tokens){
233
+ if (token.kind === 'symbol' && token.text === ';') finish(token.start);
132
234
  }
133
235
  finish(source.length);
134
236
  if (!statements.length || statements.length > 50) throw new Error('statement_count_exceeded');
@@ -146,6 +248,53 @@ function quoteDatabaseIdentifier(name, engine) {
146
248
  return quote + name.split(quote).join(quote + quote) + quote;
147
249
  }
148
250
 
251
+ /** Apply a page to a MySQL result without turning its (possibly duplicate) output names into table columns. */ function paginateMysqlQuery(sql, parameters = [], limit, offset) {
252
+ const { tokens } = lexWorkbenchSql(sql);
253
+ const top = [];
254
+ let depth = 0;
255
+ for (const token of tokens){
256
+ if (token.text === '(') depth++;
257
+ if (!depth) top.push(token);
258
+ if (token.text === ')') depth--;
259
+ if (depth < 0) throw new Error('unsupported_pagination');
260
+ }
261
+ if (depth) throw new Error('unsupported_pagination');
262
+ const start = top.findIndex((token)=>token.kind === 'word' && token.text === 'LIMIT');
263
+ if (start < 0) return {
264
+ sql: `${sql}\nLIMIT ${limit + 1} OFFSET ${offset}`,
265
+ parameters
266
+ };
267
+ const clause = top.slice(start + 1);
268
+ const bindings = tokens.filter((token)=>token.text === '?' && token.kind === 'symbol');
269
+ const consumed = new Set();
270
+ const integer = (token)=>{
271
+ if ((token == null ? void 0 : token.kind) === 'number') return BigInt(token.text);
272
+ if ((token == null ? void 0 : token.kind) === 'symbol' && token.text === '?') {
273
+ const index = bindings.indexOf(token), value = parameters[index];
274
+ if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
275
+ consumed.add(index);
276
+ return BigInt(value);
277
+ }
278
+ }
279
+ throw new Error('unsupported_pagination');
280
+ };
281
+ let count, skip = 0n;
282
+ if (clause.length === 1) count = integer(clause[0]);
283
+ else if (clause.length === 3 && clause[1].text === ',') {
284
+ skip = integer(clause[0]);
285
+ count = integer(clause[2]);
286
+ } else if (clause.length === 3 && clause[1].kind === 'word' && clause[1].text === 'OFFSET') {
287
+ count = integer(clause[0]);
288
+ skip = integer(clause[2]);
289
+ } else throw new Error('unsupported_pagination');
290
+ const remaining = count > BigInt(offset) ? count - BigInt(offset) : 0n;
291
+ const size = remaining < BigInt(limit + 1) ? remaining : BigInt(limit + 1);
292
+ return {
293
+ sql: `${sql.slice(0, top[start].start)}LIMIT ${size} OFFSET ${skip + BigInt(offset)}${sql.slice(clause[clause.length - 1].end)}`,
294
+ parameters: parameters.filter((_, index)=>!consumed.has(index))
295
+ };
296
+ }
297
+
149
298
  const number = (value)=>Number(value != null ? value : 0);
150
299
  const string = (value)=>value == null ? '' : String(value);
151
300
  const cell = (result, row, name)=>row[result.columns.findIndex((column)=>column.name.toLowerCase() === name.toLowerCase())];
@@ -387,12 +536,14 @@ const cell = (result, row, name)=>row[result.columns.findIndex((column)=>column.
387
536
  if (input.mode === 'write' && !(await this.capabilities()).writes) throw new Error('engine_version_writes_unavailable');
388
537
  if (input.mode === 'write' && offset) throw new Error('mutation_cannot_be_paginated');
389
538
  let sql = statements[0].sql;
390
- // Wrap a SELECT instead of appending LIMIT after an existing LIMIT or trailing comment.
539
+ let parameters = input.parameters;
391
540
  if (input.mode === 'read' && [
392
541
  'SELECT',
393
542
  'WITH'
394
- ].includes(statements[0].words[0])) sql = `SELECT * FROM (\n${sql}\n) AS ${this.quote('_db_studio_page')} LIMIT ${limit + 1} OFFSET ${offset}`;
395
- else if (offset) throw new Error('statement_cannot_be_paginated');
543
+ ].includes(statements[0].words[0])) {
544
+ if (this.engine === 'mysql') ({ sql, parameters } = paginateMysqlQuery(sql, parameters, limit, offset));
545
+ else sql = `SELECT * FROM (\n${sql}\n) AS ${this.quote('_db_studio_page')} LIMIT ${limit + 1} OFFSET ${offset}`;
546
+ } else if (offset) throw new Error('statement_cannot_be_paginated');
396
547
  this.busy = true;
397
548
  let readTransaction = false;
398
549
  try {
@@ -401,7 +552,7 @@ const cell = (result, row, name)=>row[result.columns.findIndex((column)=>column.
401
552
  readTransaction = true;
402
553
  }
403
554
  var _input_timeoutMs;
404
- const result = await this.raw(sql, input.parameters, limit + 1, signal, Math.max(1, Math.min((_input_timeoutMs = input.timeoutMs) != null ? _input_timeoutMs : 30000, 120000)));
555
+ const result = await this.raw(sql, parameters, limit + 1, signal, Math.max(1, Math.min((_input_timeoutMs = input.timeoutMs) != null ? _input_timeoutMs : 30000, 120000)));
405
556
  const hasMore = result.rows.length > limit || result.truncated;
406
557
  return _extends({}, result, {
407
558
  rows: result.rows.slice(0, limit),