@xpert-ai/plugin-sdk 3.8.0 → 3.8.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xpert-ai/plugin-sdk",
3
- "version": "3.8.0",
3
+ "version": "3.8.3",
4
4
  "license": "AGPL-3.0",
5
5
  "repository": {
6
6
  "type": "git",
package/src/index.d.ts CHANGED
@@ -13,5 +13,6 @@ export * from './lib/data/index';
13
13
  export * from './lib/ai-model/index';
14
14
  export * from './lib/agent/index';
15
15
  export * from './lib/channel/index';
16
+ export * from './lib/file/index';
16
17
  export * from './lib/strategy';
17
18
  export * from './lib/sandbox/index';
@@ -1,4 +1,5 @@
1
1
  export * from './file-system';
2
+ export * from './plugin-config';
2
3
  export * from './permissions';
3
4
  export * from './schema';
4
5
  export * from './i18n';
@@ -0,0 +1,9 @@
1
+ export interface PluginConfigResolveOptions<TConfig extends object = Record<string, any>> {
2
+ organizationId?: string;
3
+ fallbackToGlobal?: boolean;
4
+ defaults?: Partial<TConfig>;
5
+ }
6
+ export interface IPluginConfigResolver {
7
+ resolve<TConfig extends object = Record<string, any>>(pluginName: string, options?: PluginConfigResolveOptions<TConfig>): TConfig;
8
+ }
9
+ export declare const PLUGIN_CONFIG_RESOLVER_TOKEN = "XPERT_PLUGIN_CONFIG_RESOLVER";
@@ -0,0 +1,3 @@
1
+ export * from './provider.decorator';
2
+ export * from './provider.interface';
3
+ export * from './provider.registry';
@@ -0,0 +1,2 @@
1
+ export declare const FILE_STORAGE_PROVIDER = "FILE_STORAGE_PROVIDER";
2
+ export declare const FileStorageProvider: (provider: string) => <TFunction extends Function, Y>(target: object | TFunction, propertyKey?: string | symbol, descriptor?: TypedPropertyDescriptor<Y>) => void;
@@ -0,0 +1,14 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { FileStorageOption, FileSystem, UploadedFile } from '@metad/contracts';
4
+ export interface IFileStorageProvider {
5
+ readonly name: string;
6
+ readonly config?: FileSystem & Record<string, any>;
7
+ url(path: string): string;
8
+ path(path: string): string;
9
+ handler(options: FileStorageOption): any;
10
+ getFile(file: string): Promise<Buffer>;
11
+ putFile(fileContent: string | Buffer | URL, path?: string): Promise<UploadedFile>;
12
+ deleteFile(path: string): Promise<void>;
13
+ mapUploadedFile?(file: any): UploadedFile;
14
+ }
@@ -0,0 +1,6 @@
1
+ import { DiscoveryService, Reflector } from '@nestjs/core';
2
+ import { BaseStrategyRegistry } from '../../strategy';
3
+ import { IFileStorageProvider } from './provider.interface';
4
+ export declare class FileStorageProviderRegistry extends BaseStrategyRegistry<IFileStorageProvider> {
5
+ constructor(discoveryService: DiscoveryService, reflector: Reflector);
6
+ }
@@ -0,0 +1,3 @@
1
+ export * from './strategy.decorator';
2
+ export * from './strategy.interface';
3
+ export * from './strategy.registry';
@@ -0,0 +1,2 @@
1
+ export declare const FILE_UPLOAD_TARGET_STRATEGY = "FILE_UPLOAD_TARGET_STRATEGY";
2
+ export declare const FileUploadTargetStrategy: (type: string) => <TFunction extends Function, Y>(target: object | TFunction, propertyKey?: string | symbol, descriptor?: TypedPropertyDescriptor<Y>) => void;
@@ -0,0 +1,25 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { IFileAssetDestination, IFileAssetSource, IStorageFile, IUploadFileTarget } from '@metad/contracts';
4
+ export type TFileUploadRequestContext = {
5
+ tenantId?: string;
6
+ organizationId?: string;
7
+ userId?: string;
8
+ };
9
+ export type TResolvedFileUploadSource = {
10
+ name: string;
11
+ originalName: string;
12
+ mimeType?: string;
13
+ size?: number;
14
+ buffer: Buffer;
15
+ source: IFileAssetSource;
16
+ storageFile?: IStorageFile;
17
+ };
18
+ export type TFileUploadContext = {
19
+ request: TFileUploadRequestContext;
20
+ metadata?: Record<string, any>;
21
+ };
22
+ export type TStorageProviderType = string;
23
+ export interface IFileUploadTargetStrategy<TTarget extends IUploadFileTarget = IUploadFileTarget> {
24
+ upload(source: TResolvedFileUploadSource, target: TTarget, context: TFileUploadContext): Promise<IFileAssetDestination>;
25
+ }
@@ -0,0 +1,6 @@
1
+ import { DiscoveryService, Reflector } from '@nestjs/core';
2
+ import { BaseStrategyRegistry } from '../../strategy';
3
+ import { IFileUploadTargetStrategy } from './strategy.interface';
4
+ export declare class FileUploadTargetRegistry extends BaseStrategyRegistry<IFileUploadTargetStrategy> {
5
+ constructor(discoveryService: DiscoveryService, reflector: Reflector);
6
+ }
@@ -0,0 +1,2 @@
1
+ export * from './file-storage';
2
+ export * from './file-upload';
@@ -0,0 +1,27 @@
1
+ export interface SandboxExecutionOptions {
2
+ timeoutMs?: number;
3
+ maxOutputBytes?: number;
4
+ }
5
+ export interface ResolvedSandboxExecutionOptions {
6
+ timeoutMs: number;
7
+ maxOutputBytes: number;
8
+ }
9
+ export declare const SANDBOX_SHELL_TIMEOUT_LIMITS_SEC: {
10
+ readonly min: 1;
11
+ readonly max: 3600;
12
+ };
13
+ export declare const DEFAULT_SANDBOX_SHELL_TIMEOUT_SEC = 600;
14
+ export declare const DEFAULT_SANDBOX_FILE_SEARCH_TIMEOUT_SEC = 120;
15
+ export declare const DEFAULT_SANDBOX_FILE_OPERATION_TIMEOUT_SEC = 120;
16
+ export declare const DEFAULT_SANDBOX_SHELL_TIMEOUT_MS: number;
17
+ export declare const DEFAULT_SANDBOX_FILE_SEARCH_TIMEOUT_MS: number;
18
+ export declare const DEFAULT_SANDBOX_FILE_OPERATION_TIMEOUT_MS: number;
19
+ export declare const DEFAULT_SANDBOX_EXECUTION_MAX_OUTPUT_BYTES: number;
20
+ export declare const DEFAULT_SANDBOX_SHELL_EXECUTION_OPTIONS: Readonly<SandboxExecutionOptions>;
21
+ export declare const DEFAULT_SANDBOX_FILE_SEARCH_EXECUTION_OPTIONS: Readonly<SandboxExecutionOptions>;
22
+ export declare const DEFAULT_SANDBOX_FILE_OPERATION_EXECUTION_OPTIONS: Readonly<SandboxExecutionOptions>;
23
+ export declare function secondsToMilliseconds(seconds: number): number;
24
+ export declare function formatSandboxTimeout(timeoutMs: number): string;
25
+ export declare function buildSandboxTimeoutMessage(subject: string, timeoutMs: number): string;
26
+ export declare function appendSandboxMessage(output: string, message: string): string;
27
+ export declare function resolveSandboxExecutionOptions(options?: SandboxExecutionOptions, defaults?: SandboxExecutionOptions): ResolvedSandboxExecutionOptions;
@@ -1,3 +1,4 @@
1
+ export * from './execution';
1
2
  export * from './protocol';
2
3
  export * from './sandbox';
3
4
  export * from './sandbox.decorator';
@@ -1,3 +1,4 @@
1
+ import type { SandboxExecutionOptions } from './execution';
1
2
  /**
2
3
  * Protocol definition for pluggable memory backends.
3
4
  *
@@ -33,6 +34,25 @@ export interface GrepMatch {
33
34
  /** The matching line text */
34
35
  text: string;
35
36
  }
37
+ /**
38
+ * Read mode for file reading operations.
39
+ */
40
+ export type ReadMode = 'slice' | 'indentation';
41
+ /**
42
+ * Configuration for indentation-aware file reading.
43
+ */
44
+ export interface IndentationOptions {
45
+ /** Anchor line number (1-indexed), defaults to offset */
46
+ anchor_line?: number;
47
+ /** Maximum indentation depth to collect; 0 means unlimited */
48
+ max_levels?: number;
49
+ /** Whether to include sibling blocks at the same indentation level */
50
+ include_siblings?: boolean;
51
+ /** Whether to include header lines (comments) above the anchor block */
52
+ include_header?: boolean;
53
+ /** Hard cap on returned lines */
54
+ max_lines?: number;
55
+ }
36
56
  /**
37
57
  * File data structure used by backends.
38
58
  *
@@ -88,6 +108,39 @@ export interface EditResult {
88
108
  /** Metadata for the edit operation, attached to the ToolMessage */
89
109
  metadata?: Record<string, unknown>;
90
110
  }
111
+ /**
112
+ * Single edit operation for multi-edit.
113
+ */
114
+ export interface EditOperation {
115
+ /** String to find and replace */
116
+ oldString: string;
117
+ /** Replacement string */
118
+ newString: string;
119
+ /** If true, replace all occurrences (default: false) */
120
+ replaceAll?: boolean;
121
+ }
122
+ /**
123
+ * Result from backend multi-edit operations.
124
+ *
125
+ * Checkpoint backends populate filesUpdate with {file_path: file_data} for LangGraph state.
126
+ * External backends set filesUpdate to null (already persisted to disk/S3/database/etc).
127
+ */
128
+ export interface MultiEditResult {
129
+ /** Error message on failure, undefined on success */
130
+ error?: string;
131
+ /** File path of edited file, undefined on failure */
132
+ path?: string;
133
+ /**
134
+ * State update dict for checkpoint backends, null for external storage.
135
+ * Checkpoint backends populate this with {file_path: file_data} for LangGraph state.
136
+ * External backends set null (already persisted to disk/S3/database/etc).
137
+ */
138
+ filesUpdate?: Record<string, FileData> | null;
139
+ /** Results from each individual edit operation */
140
+ results?: EditResult[];
141
+ /** Metadata for the multi-edit operation, attached to the ToolMessage */
142
+ metadata?: Record<string, unknown>;
143
+ }
91
144
  /**
92
145
  * Result of code execution.
93
146
  * Simplified schema optimized for LLM consumption.
@@ -99,11 +152,13 @@ export interface ExecuteResponse {
99
152
  exitCode: number | null;
100
153
  /** Whether the output was truncated due to backend limitations */
101
154
  truncated: boolean;
155
+ /** Whether the command exceeded its timeout and was terminated */
156
+ timedOut?: boolean;
102
157
  }
103
158
  /**
104
159
  * Standardized error codes for file upload/download operations.
105
160
  */
106
- export type FileOperationError = "file_not_found" | "permission_denied" | "is_directory" | "invalid_path";
161
+ export type FileOperationError = 'file_not_found' | 'permission_denied' | 'is_directory' | 'invalid_path';
107
162
  /**
108
163
  * Result of a single file download operation.
109
164
  */
@@ -146,15 +201,27 @@ export interface BackendProtocol {
146
201
  * @returns List of FileInfo objects for files and directories directly in the directory
147
202
  */
148
203
  lsInfo(path: string): MaybePromise<FileInfo[]>;
204
+ /**
205
+ * List directory contents recursively with depth control and pagination.
206
+ *
207
+ * @param dirPath - Absolute path to directory
208
+ * @param offset - 1-indexed entry number to start from (default: 1)
209
+ * @param limit - Maximum number of entries to return (default: 25)
210
+ * @param depth - Maximum depth to traverse (default: 2)
211
+ * @returns Human-readable directory tree with indentation
212
+ */
213
+ listDir(dirPath: string, offset?: number, limit?: number, depth?: number): MaybePromise<string>;
149
214
  /**
150
215
  * Read file content with line numbers or an error string.
151
216
  *
152
217
  * @param filePath - Absolute file path
153
- * @param offset - Line offset to start reading from (0-indexed), default 0
154
- * @param limit - Maximum number of lines to read, default 500
218
+ * @param offset - Line offset to start reading from (1-indexed), default 1
219
+ * @param limit - Maximum number of lines to read, default 2000
220
+ * @param mode - Read mode: 'slice' (default) or 'indentation'
221
+ * @param indentation - Configuration for indentation mode
155
222
  * @returns Formatted file content with line numbers, or error message
156
223
  */
157
- read(filePath: string, offset?: number, limit?: number): MaybePromise<string>;
224
+ read(filePath: string, offset?: number, limit?: number, mode?: ReadMode, indentation?: IndentationOptions): MaybePromise<string>;
158
225
  /**
159
226
  * Structured search results or error string for invalid input.
160
227
  *
@@ -162,10 +229,19 @@ export interface BackendProtocol {
162
229
  *
163
230
  * @param pattern - Regex pattern to search for
164
231
  * @param path - Base path to search from (default: null)
165
- * @param glob - Optional glob pattern to filter files (e.g., "*.py")
232
+ * @param include - Optional glob pattern to filter files (e.g., "*.py")
166
233
  * @returns List of GrepMatch objects or error string for invalid regex
167
234
  */
168
- grepRaw(pattern: string, path?: string | null, glob?: string | null): MaybePromise<GrepMatch[] | string>;
235
+ grepRaw(pattern: string, path?: string | null, include?: string | null): MaybePromise<GrepMatch[] | string>;
236
+ /**
237
+ * Search file contents for a regex pattern, returning human-readable output.
238
+ *
239
+ * @param pattern - Regex pattern to search for
240
+ * @param path - Base path to search from (default: workspace root)
241
+ * @param include - Optional glob pattern to filter files (e.g., "*.js", "*.{ts,tsx}")
242
+ * @returns Human-readable output with matches grouped by file
243
+ */
244
+ grep(pattern: string, path?: string | null, include?: string | null): MaybePromise<string>;
169
245
  /**
170
246
  * Structured glob matching returning FileInfo objects.
171
247
  *
@@ -174,6 +250,14 @@ export interface BackendProtocol {
174
250
  * @returns List of FileInfo objects matching the pattern
175
251
  */
176
252
  globInfo(pattern: string, path?: string): MaybePromise<FileInfo[]>;
253
+ /**
254
+ * Find files matching a glob pattern, returning human-readable output.
255
+ *
256
+ * @param pattern - Glob pattern (e.g., `*.py`, `**\/*.ts`)
257
+ * @param path - Base path to search from (default: workspace root)
258
+ * @returns Human-readable output with matching file paths
259
+ */
260
+ glob(pattern: string, path?: string): MaybePromise<string>;
177
261
  /**
178
262
  * Create a new file.
179
263
  *
@@ -182,6 +266,14 @@ export interface BackendProtocol {
182
266
  * @returns WriteResult with error populated on failure
183
267
  */
184
268
  write(filePath: string, content: string): MaybePromise<WriteResult>;
269
+ /**
270
+ * Append content to a file. Creates the file if it doesn't exist.
271
+ *
272
+ * @param filePath - Absolute file path
273
+ * @param content - Content to append
274
+ * @returns WriteResult with error populated on failure
275
+ */
276
+ append(filePath: string, content: string): MaybePromise<WriteResult>;
185
277
  /**
186
278
  * Edit a file by replacing string occurrences.
187
279
  *
@@ -192,6 +284,16 @@ export interface BackendProtocol {
192
284
  * @returns EditResult with error, path, filesUpdate, and occurrences
193
285
  */
194
286
  edit(filePath: string, oldString: string, newString: string, replaceAll?: boolean): MaybePromise<EditResult>;
287
+ /**
288
+ * Perform multiple sequential edits on a single file.
289
+ * All edits are applied sequentially, with each edit operating on the result of the previous edit.
290
+ * All edits must succeed for the operation to succeed (atomic).
291
+ *
292
+ * @param filePath - Absolute file path
293
+ * @param edits - Array of edit operations to perform sequentially
294
+ * @returns MultiEditResult with error, path, filesUpdate, and individual results
295
+ */
296
+ multiEdit(filePath: string, edits: EditOperation[]): MaybePromise<MultiEditResult>;
195
297
  /**
196
298
  * Upload multiple files.
197
299
  *
@@ -219,7 +321,26 @@ export interface SandboxBackendProtocol extends BackendProtocol {
219
321
  * @param command - Full shell command string to execute
220
322
  * @returns ExecuteResponse with combined output, exit code, and truncation flag
221
323
  */
222
- execute(command: string): MaybePromise<ExecuteResponse>;
324
+ execute(command: string, options?: SandboxExecutionOptions): MaybePromise<ExecuteResponse>;
325
+ /**
326
+ * Execute a command with line-by-line streaming output.
327
+ *
328
+ * When implemented, the middleware layer can push incremental output
329
+ * to the frontend as each line arrives, rather than waiting for the
330
+ * command to finish. Implementations MUST buffer partial lines and
331
+ * only invoke `onLine` with complete lines (LF-terminated).
332
+ *
333
+ * The returned `ExecuteResponse.output` contains the full collected
334
+ * output, identical to what `execute()` would return.
335
+ *
336
+ * Falls back to `execute()` + single `onLine` call in `BaseSandbox`
337
+ * when a concrete backend does not override this method.
338
+ *
339
+ * @param command - Full shell command string to execute
340
+ * @param onLine - Callback invoked once per complete output line
341
+ * @returns ExecuteResponse with combined output, exit code, and truncation flag
342
+ */
343
+ streamExecute?(command: string, onLine: (line: string) => void, options?: SandboxExecutionOptions): MaybePromise<ExecuteResponse>;
223
344
  /** Unique identifier for the sandbox backend instance */
224
345
  readonly id: string;
225
346
  }
@@ -7,7 +7,8 @@
7
7
  *
8
8
  * Requires Node.js 20+ on the sandbox host.
9
9
  */
10
- import type { EditResult, ExecuteResponse, FileDownloadResponse, FileInfo, FileUploadResponse, GrepMatch, MaybePromise, SandboxBackendProtocol, WriteResult } from "./protocol";
10
+ import type { EditOperation, EditResult, ExecuteResponse, FileDownloadResponse, FileInfo, FileUploadResponse, GrepMatch, IndentationOptions, MaybePromise, MultiEditResult, ReadMode, SandboxBackendProtocol, WriteResult } from './protocol';
11
+ import { SandboxExecutionOptions } from './execution';
11
12
  /**
12
13
  * Base sandbox implementation with execute() as the only abstract method.
13
14
  *
@@ -18,13 +19,15 @@ import type { EditResult, ExecuteResponse, FileDownloadResponse, FileInfo, FileU
18
19
  * Requires Node.js 20+ on the sandbox host.
19
20
  */
20
21
  export declare abstract class BaseSandbox implements SandboxBackendProtocol {
22
+ workingDirectory: string;
21
23
  /** Unique identifier for the sandbox backend */
22
24
  abstract readonly id: string;
23
25
  /**
24
26
  * Execute a command in the sandbox.
25
27
  * This is the only method concrete implementations must provide.
26
28
  */
27
- abstract execute(command: string): MaybePromise<ExecuteResponse>;
29
+ abstract execute(command: string, options?: SandboxExecutionOptions): MaybePromise<ExecuteResponse>;
30
+ streamExecute(command: string, onLine: (line: string) => void, options?: SandboxExecutionOptions): Promise<ExecuteResponse>;
28
31
  /**
29
32
  * Upload multiple files to the sandbox.
30
33
  * Implementations must support partial success.
@@ -42,29 +45,60 @@ export declare abstract class BaseSandbox implements SandboxBackendProtocol {
42
45
  * @returns List of FileInfo objects for files and directories directly in the directory.
43
46
  */
44
47
  lsInfo(path: string): Promise<FileInfo[]>;
48
+ /**
49
+ * List directory contents recursively with depth control and pagination.
50
+ *
51
+ * @param dirPath - Absolute path to directory
52
+ * @param offset - 1-indexed entry number to start from (default: 1)
53
+ * @param limit - Maximum number of entries to return (default: 25)
54
+ * @param depth - Maximum depth to traverse (default: 2)
55
+ * @returns Human-readable directory tree with indentation
56
+ */
57
+ listDir(dirPath: string, offset?: number, limit?: number, depth?: number): Promise<string>;
45
58
  /**
46
59
  * Read file content with line numbers.
47
60
  *
48
61
  * @param filePath - Absolute file path
49
- * @param offset - Line offset to start reading from (0-indexed)
62
+ * @param offset - Line offset to start reading from (1-indexed)
50
63
  * @param limit - Maximum number of lines to read
64
+ * @param mode - Read mode: 'slice' (default) or 'indentation'
65
+ * @param indentation - Configuration for indentation mode
51
66
  * @returns Formatted file content with line numbers, or error message
52
67
  */
53
- read(filePath: string, offset?: number, limit?: number): Promise<string>;
68
+ read(filePath: string, offset?: number, limit?: number, mode?: ReadMode, indentation?: IndentationOptions): Promise<string>;
54
69
  /**
55
70
  * Structured search results or error string for invalid input.
56
71
  */
57
- grepRaw(pattern: string, path?: string, glob?: string | null): Promise<GrepMatch[] | string>;
72
+ grepRaw(pattern: string, path?: string, include?: string | null): Promise<GrepMatch[] | string>;
73
+ /**
74
+ * Search file contents for a regex pattern, returning human-readable output.
75
+ */
76
+ grep(pattern: string, path?: string, include?: string | null): Promise<string>;
58
77
  /**
59
78
  * Structured glob matching returning FileInfo objects.
60
79
  */
61
80
  globInfo(pattern: string, path?: string): Promise<FileInfo[]>;
81
+ /**
82
+ * Find files matching a glob pattern, returning human-readable output.
83
+ */
84
+ glob(pattern: string, path?: string): Promise<string>;
62
85
  /**
63
86
  * Create a new file with content.
64
87
  */
65
88
  write(filePath: string, content: string): Promise<WriteResult>;
89
+ private writeViaUpload;
90
+ /**
91
+ * Append content to a file. Creates the file if it doesn't exist.
92
+ */
93
+ append(filePath: string, content: string): Promise<WriteResult>;
66
94
  /**
67
95
  * Edit a file by replacing string occurrences.
68
96
  */
69
97
  edit(filePath: string, oldString: string, newString: string, replaceAll?: boolean): Promise<EditResult>;
98
+ /**
99
+ * Perform multiple sequential edits on a single file.
100
+ * All edits are applied sequentially, with each edit operating on the result of the previous edit.
101
+ * All edits must succeed for the operation to succeed (atomic).
102
+ */
103
+ multiEdit(filePath: string, edits: EditOperation[]): Promise<MultiEditResult>;
70
104
  }
@@ -1,4 +1,4 @@
1
- import { PluginMeta } from '@metad/contracts';
1
+ import { JsonSchemaObjectType, PluginMeta } from '@metad/contracts';
2
2
  import type { DynamicModule, INestApplicationContext } from '@nestjs/common';
3
3
  import { ModuleRef } from '@nestjs/core';
4
4
  import type { ZodSchema } from 'zod';
@@ -30,6 +30,8 @@ export interface PluginConfigSpec<T extends object = any> {
30
30
  schema?: ZodSchema<T>;
31
31
  /** Default configuration */
32
32
  defaults?: Partial<T>;
33
+ /** Optional JSON schema used by frontend configuration forms. */
34
+ formSchema?: JsonSchemaObjectType;
33
35
  }
34
36
  export interface XpertPlugin<TConfig extends object = any> extends PluginLifecycle, PluginHealth {
35
37
  meta: PluginMeta;