@enactprotocol/execution 2.2.4 → 2.3.4
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/dist/docker-provider.d.ts +87 -0
- package/dist/docker-provider.d.ts.map +1 -0
- package/dist/docker-provider.js +406 -0
- package/dist/docker-provider.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/local-provider.d.ts +95 -0
- package/dist/local-provider.d.ts.map +1 -0
- package/dist/local-provider.js +369 -0
- package/dist/local-provider.js.map +1 -0
- package/dist/provider.d.ts +24 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +305 -20
- package/dist/provider.js.map +1 -1
- package/dist/remote-provider.d.ts +43 -0
- package/dist/remote-provider.d.ts.map +1 -0
- package/dist/remote-provider.js +154 -0
- package/dist/remote-provider.js.map +1 -0
- package/dist/router.d.ts +62 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +109 -0
- package/dist/router.js.map +1 -0
- package/package.json +2 -2
- package/src/docker-provider.ts +575 -0
- package/src/index.ts +32 -1
- package/src/local-provider.ts +513 -0
- package/src/provider.ts +409 -28
- package/src/remote-provider.ts +231 -0
- package/src/router.ts +143 -0
- package/tests/docker-provider.test.ts +207 -0
- package/tests/local-provider.test.ts +256 -0
- package/tests/remote-provider.test.ts +206 -0
- package/tests/router.test.ts +272 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Execution Provider
|
|
3
|
+
*
|
|
4
|
+
* Executes commands directly on the host system without containerization.
|
|
5
|
+
* This is faster but provides no isolation or sandboxing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import type { Action, ActionsManifest, ToolManifest } from "@enactprotocol/shared";
|
|
12
|
+
import {
|
|
13
|
+
applyDefaults,
|
|
14
|
+
getEffectiveInputSchema,
|
|
15
|
+
prepareActionCommand,
|
|
16
|
+
prepareCommand,
|
|
17
|
+
validateInputs,
|
|
18
|
+
} from "@enactprotocol/shared";
|
|
19
|
+
import type {
|
|
20
|
+
EngineHealth,
|
|
21
|
+
ExecutionErrorCode,
|
|
22
|
+
ExecutionInput,
|
|
23
|
+
ExecutionMetadata,
|
|
24
|
+
ExecutionOptions,
|
|
25
|
+
ExecutionOutput,
|
|
26
|
+
ExecutionProvider,
|
|
27
|
+
ExecutionResult,
|
|
28
|
+
} from "@enactprotocol/shared";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Configuration for the local execution provider
|
|
32
|
+
*/
|
|
33
|
+
export interface LocalProviderConfig {
|
|
34
|
+
/** Default timeout in milliseconds */
|
|
35
|
+
defaultTimeout?: number;
|
|
36
|
+
/** Enable verbose logging */
|
|
37
|
+
verbose?: boolean;
|
|
38
|
+
/** Working directory for execution (defaults to skill directory) */
|
|
39
|
+
workdir?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Provider for executing commands directly on the host system
|
|
44
|
+
*
|
|
45
|
+
* WARNING: This provider offers no sandboxing or isolation.
|
|
46
|
+
* Commands have full access to the filesystem and network.
|
|
47
|
+
*/
|
|
48
|
+
export class LocalExecutionProvider implements ExecutionProvider {
|
|
49
|
+
readonly name = "local";
|
|
50
|
+
private defaultTimeout: number;
|
|
51
|
+
private workdir: string | undefined;
|
|
52
|
+
|
|
53
|
+
constructor(config: LocalProviderConfig = {}) {
|
|
54
|
+
this.defaultTimeout = config.defaultTimeout ?? 300000; // 5 minutes
|
|
55
|
+
this.workdir = config.workdir;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Initialize the provider
|
|
60
|
+
*/
|
|
61
|
+
async initialize(): Promise<void> {
|
|
62
|
+
// No initialization needed for local execution
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if the provider is available (always true for local)
|
|
67
|
+
*/
|
|
68
|
+
async isAvailable(): Promise<boolean> {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get provider health status
|
|
74
|
+
*/
|
|
75
|
+
async getHealth(): Promise<EngineHealth> {
|
|
76
|
+
return {
|
|
77
|
+
healthy: true,
|
|
78
|
+
runtime: "docker", // N/A but required by interface
|
|
79
|
+
consecutiveFailures: 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generate a unique execution ID
|
|
85
|
+
*/
|
|
86
|
+
private generateExecutionId(): string {
|
|
87
|
+
return `local-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parse a timeout string to milliseconds
|
|
92
|
+
*/
|
|
93
|
+
private parseTimeout(timeout?: string): number {
|
|
94
|
+
if (!timeout) {
|
|
95
|
+
return this.defaultTimeout ?? 300000;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const match = timeout.match(/^(\d+)(ms|s|m|h)?$/);
|
|
99
|
+
if (!match) {
|
|
100
|
+
return this.defaultTimeout ?? 300000;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const value = Number.parseInt(match[1] ?? "300000", 10);
|
|
104
|
+
const unit = match[2] ?? "ms";
|
|
105
|
+
|
|
106
|
+
switch (unit) {
|
|
107
|
+
case "ms":
|
|
108
|
+
return value;
|
|
109
|
+
case "s":
|
|
110
|
+
return value * 1000;
|
|
111
|
+
case "m":
|
|
112
|
+
return value * 60000;
|
|
113
|
+
case "h":
|
|
114
|
+
return value * 3600000;
|
|
115
|
+
default:
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create an error result
|
|
122
|
+
*/
|
|
123
|
+
private createErrorResult(
|
|
124
|
+
toolName: string,
|
|
125
|
+
executionId: string,
|
|
126
|
+
startTime: Date,
|
|
127
|
+
code: ExecutionErrorCode,
|
|
128
|
+
message: string
|
|
129
|
+
): ExecutionResult {
|
|
130
|
+
const endTime = new Date();
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
output: {
|
|
134
|
+
stdout: "",
|
|
135
|
+
stderr: message,
|
|
136
|
+
exitCode: 1,
|
|
137
|
+
},
|
|
138
|
+
metadata: {
|
|
139
|
+
toolName,
|
|
140
|
+
containerImage: "local",
|
|
141
|
+
startTime,
|
|
142
|
+
endTime,
|
|
143
|
+
durationMs: endTime.getTime() - startTime.getTime(),
|
|
144
|
+
cached: false,
|
|
145
|
+
executionId,
|
|
146
|
+
},
|
|
147
|
+
error: {
|
|
148
|
+
code,
|
|
149
|
+
message,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Run build commands if specified
|
|
156
|
+
*/
|
|
157
|
+
private async runBuild(
|
|
158
|
+
build: string | string[],
|
|
159
|
+
cwd: string,
|
|
160
|
+
env: Record<string, string>
|
|
161
|
+
): Promise<void> {
|
|
162
|
+
const commands = Array.isArray(build) ? build : [build];
|
|
163
|
+
|
|
164
|
+
for (const command of commands) {
|
|
165
|
+
await this.runCommand(command.split(/\s+/), cwd, env, 600000); // 10 min timeout for build
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Run a command and return the output
|
|
171
|
+
*/
|
|
172
|
+
private runCommand(
|
|
173
|
+
commandArray: string[],
|
|
174
|
+
cwd: string,
|
|
175
|
+
env: Record<string, string>,
|
|
176
|
+
timeoutMs: number
|
|
177
|
+
): Promise<ExecutionOutput> {
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
const [cmd, ...args] = commandArray;
|
|
180
|
+
if (!cmd) {
|
|
181
|
+
reject(new Error("Empty command"));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const child = spawn(cmd, args, {
|
|
186
|
+
cwd,
|
|
187
|
+
env: { ...process.env, ...env },
|
|
188
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
let stdout = "";
|
|
192
|
+
let stderr = "";
|
|
193
|
+
let timedOut = false;
|
|
194
|
+
|
|
195
|
+
const timer = setTimeout(() => {
|
|
196
|
+
timedOut = true;
|
|
197
|
+
child.kill("SIGTERM");
|
|
198
|
+
setTimeout(() => child.kill("SIGKILL"), 5000);
|
|
199
|
+
}, timeoutMs);
|
|
200
|
+
|
|
201
|
+
child.stdout?.on("data", (data: Buffer) => {
|
|
202
|
+
stdout += data.toString();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
child.stderr?.on("data", (data: Buffer) => {
|
|
206
|
+
stderr += data.toString();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
child.on("error", (error) => {
|
|
210
|
+
clearTimeout(timer);
|
|
211
|
+
reject(error);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
child.on("close", (exitCode) => {
|
|
215
|
+
clearTimeout(timer);
|
|
216
|
+
|
|
217
|
+
if (timedOut) {
|
|
218
|
+
reject(new Error("TIMEOUT"));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
resolve({
|
|
223
|
+
stdout,
|
|
224
|
+
stderr,
|
|
225
|
+
exitCode: exitCode ?? 1,
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Execute a tool (for compatibility - uses manifest.command)
|
|
233
|
+
*/
|
|
234
|
+
async execute(
|
|
235
|
+
manifest: ToolManifest,
|
|
236
|
+
input: ExecutionInput,
|
|
237
|
+
options: ExecutionOptions = {}
|
|
238
|
+
): Promise<ExecutionResult> {
|
|
239
|
+
const startTime = new Date();
|
|
240
|
+
const executionId = this.generateExecutionId();
|
|
241
|
+
|
|
242
|
+
if (!manifest.command) {
|
|
243
|
+
return this.createErrorResult(
|
|
244
|
+
manifest.name,
|
|
245
|
+
executionId,
|
|
246
|
+
startTime,
|
|
247
|
+
"COMMAND_ERROR",
|
|
248
|
+
"No command specified in manifest"
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const workdir = options.workdir ?? this.workdir ?? process.cwd();
|
|
253
|
+
const timeoutMs = this.parseTimeout(options.timeout ?? manifest.timeout);
|
|
254
|
+
|
|
255
|
+
// Build environment
|
|
256
|
+
const env: Record<string, string> = {
|
|
257
|
+
...input.envOverrides,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
// Interpolate parameters and prepare command array
|
|
262
|
+
const commandArray = prepareCommand(manifest.command, input.params);
|
|
263
|
+
const output = await this.runCommand(commandArray, workdir, env, timeoutMs);
|
|
264
|
+
|
|
265
|
+
const endTime = new Date();
|
|
266
|
+
const metadata: ExecutionMetadata = {
|
|
267
|
+
toolName: manifest.name,
|
|
268
|
+
containerImage: "local",
|
|
269
|
+
startTime,
|
|
270
|
+
endTime,
|
|
271
|
+
durationMs: endTime.getTime() - startTime.getTime(),
|
|
272
|
+
cached: false,
|
|
273
|
+
executionId,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
if (output.exitCode !== 0) {
|
|
277
|
+
return {
|
|
278
|
+
success: false,
|
|
279
|
+
output,
|
|
280
|
+
metadata,
|
|
281
|
+
error: {
|
|
282
|
+
code: "COMMAND_ERROR",
|
|
283
|
+
message: `Command exited with code ${output.exitCode}`,
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
success: true,
|
|
290
|
+
output,
|
|
291
|
+
metadata,
|
|
292
|
+
};
|
|
293
|
+
} catch (error) {
|
|
294
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
295
|
+
let code: ExecutionErrorCode = "COMMAND_ERROR";
|
|
296
|
+
|
|
297
|
+
if (message === "TIMEOUT") {
|
|
298
|
+
code = "TIMEOUT";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return this.createErrorResult(manifest.name, executionId, startTime, code, message);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Execute a raw command
|
|
307
|
+
*/
|
|
308
|
+
async exec(
|
|
309
|
+
manifest: ToolManifest,
|
|
310
|
+
command: string,
|
|
311
|
+
options: ExecutionOptions = {}
|
|
312
|
+
): Promise<ExecutionResult> {
|
|
313
|
+
const execManifest: ToolManifest = {
|
|
314
|
+
...manifest,
|
|
315
|
+
command,
|
|
316
|
+
};
|
|
317
|
+
return this.execute(execManifest, { params: {} }, options);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Execute an action from ACTIONS.yaml
|
|
322
|
+
*/
|
|
323
|
+
async executeAction(
|
|
324
|
+
manifest: ToolManifest,
|
|
325
|
+
actionsManifest: ActionsManifest,
|
|
326
|
+
actionName: string,
|
|
327
|
+
action: Action,
|
|
328
|
+
input: ExecutionInput,
|
|
329
|
+
options: ExecutionOptions = {}
|
|
330
|
+
): Promise<ExecutionResult> {
|
|
331
|
+
const startTime = new Date();
|
|
332
|
+
const executionId = this.generateExecutionId();
|
|
333
|
+
|
|
334
|
+
// Get effective inputSchema (defaults to empty if not provided)
|
|
335
|
+
const effectiveSchema = getEffectiveInputSchema(action);
|
|
336
|
+
|
|
337
|
+
// Validate inputs against action's inputSchema
|
|
338
|
+
const validation = validateInputs(input.params, effectiveSchema);
|
|
339
|
+
if (!validation.valid) {
|
|
340
|
+
const errorMessages = validation.errors.map((e) => `${e.path}: ${e.message}`);
|
|
341
|
+
return this.createErrorResult(
|
|
342
|
+
`${manifest.name}:${actionName}`,
|
|
343
|
+
executionId,
|
|
344
|
+
startTime,
|
|
345
|
+
"VALIDATION_ERROR",
|
|
346
|
+
`Input validation failed: ${errorMessages.join(", ")}`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Apply defaults to inputs
|
|
351
|
+
const params = applyDefaults(input.params, effectiveSchema);
|
|
352
|
+
|
|
353
|
+
// Prepare the command using {{param}} template system
|
|
354
|
+
let commandArray: string[];
|
|
355
|
+
try {
|
|
356
|
+
const actionCommand = action.command;
|
|
357
|
+
if (typeof actionCommand === "string") {
|
|
358
|
+
// String-form command (no templates allowed - validation ensures this)
|
|
359
|
+
commandArray = actionCommand.split(/\s+/).filter((s) => s.length > 0);
|
|
360
|
+
} else {
|
|
361
|
+
// Array-form command with {{param}} templates
|
|
362
|
+
commandArray = prepareActionCommand(actionCommand, params, effectiveSchema);
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {
|
|
365
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
366
|
+
return this.createErrorResult(
|
|
367
|
+
`${manifest.name}:${actionName}`,
|
|
368
|
+
executionId,
|
|
369
|
+
startTime,
|
|
370
|
+
"COMMAND_ERROR",
|
|
371
|
+
`Failed to prepare action command: ${message}`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (commandArray.length === 0) {
|
|
376
|
+
return this.createErrorResult(
|
|
377
|
+
`${manifest.name}:${actionName}`,
|
|
378
|
+
executionId,
|
|
379
|
+
startTime,
|
|
380
|
+
"COMMAND_ERROR",
|
|
381
|
+
"Action command is empty"
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const workdir = options.workdir ?? this.workdir ?? process.cwd();
|
|
386
|
+
const timeoutMs = this.parseTimeout(options.timeout ?? manifest.timeout);
|
|
387
|
+
|
|
388
|
+
// Build environment
|
|
389
|
+
const env: Record<string, string> = {
|
|
390
|
+
...input.envOverrides,
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// Add env from actions manifest
|
|
394
|
+
if (actionsManifest.env) {
|
|
395
|
+
for (const [key, envVar] of Object.entries(actionsManifest.env)) {
|
|
396
|
+
if (envVar.default && !env[key]) {
|
|
397
|
+
env[key] = envVar.default;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
// Run build commands if present
|
|
404
|
+
if (actionsManifest.build) {
|
|
405
|
+
await this.runBuild(actionsManifest.build, workdir, env);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Execute the action command
|
|
409
|
+
const output = await this.runCommand(commandArray, workdir, env, timeoutMs);
|
|
410
|
+
|
|
411
|
+
const endTime = new Date();
|
|
412
|
+
const metadata: ExecutionMetadata = {
|
|
413
|
+
toolName: `${manifest.name}:${actionName}`,
|
|
414
|
+
containerImage: "local",
|
|
415
|
+
startTime,
|
|
416
|
+
endTime,
|
|
417
|
+
durationMs: endTime.getTime() - startTime.getTime(),
|
|
418
|
+
cached: false,
|
|
419
|
+
executionId,
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
if (manifest.version) {
|
|
423
|
+
metadata.toolVersion = manifest.version;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (output.exitCode !== 0) {
|
|
427
|
+
return {
|
|
428
|
+
success: false,
|
|
429
|
+
output,
|
|
430
|
+
metadata,
|
|
431
|
+
error: {
|
|
432
|
+
code: "COMMAND_ERROR",
|
|
433
|
+
message: `Action "${actionName}" exited with code ${output.exitCode}`,
|
|
434
|
+
...(output.stderr && { details: { stderr: output.stderr } }),
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Validate output against outputSchema if defined
|
|
440
|
+
if (action.outputSchema && output.stdout) {
|
|
441
|
+
try {
|
|
442
|
+
const parsed = JSON.parse(output.stdout);
|
|
443
|
+
output.parsed = parsed;
|
|
444
|
+
} catch {
|
|
445
|
+
// Output is not JSON - that's OK, leave as string
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
success: true,
|
|
451
|
+
output,
|
|
452
|
+
metadata,
|
|
453
|
+
};
|
|
454
|
+
} catch (error) {
|
|
455
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
456
|
+
let code: ExecutionErrorCode = "COMMAND_ERROR";
|
|
457
|
+
|
|
458
|
+
if (message.startsWith("BUILD_ERROR:") || message.includes("build")) {
|
|
459
|
+
code = "BUILD_ERROR";
|
|
460
|
+
} else if (message === "TIMEOUT") {
|
|
461
|
+
code = "TIMEOUT";
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return this.createErrorResult(
|
|
465
|
+
`${manifest.name}:${actionName}`,
|
|
466
|
+
executionId,
|
|
467
|
+
startTime,
|
|
468
|
+
code,
|
|
469
|
+
message
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Shutdown the provider (no-op for local)
|
|
476
|
+
*/
|
|
477
|
+
async shutdown(): Promise<void> {
|
|
478
|
+
// Nothing to clean up
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Check if a directory has a Containerfile or Dockerfile
|
|
484
|
+
*/
|
|
485
|
+
export function hasContainerfile(dir: string): boolean {
|
|
486
|
+
return existsSync(join(dir, "Containerfile")) || existsSync(join(dir, "Dockerfile"));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Determine the execution mode based on skill directory and options
|
|
491
|
+
*/
|
|
492
|
+
export function selectExecutionMode(
|
|
493
|
+
skillDir: string,
|
|
494
|
+
options: { local?: boolean; container?: boolean }
|
|
495
|
+
): "local" | "container" {
|
|
496
|
+
if (options.local) {
|
|
497
|
+
return "local";
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (options.container) {
|
|
501
|
+
return "container";
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Default: container if Containerfile exists, else local
|
|
505
|
+
return hasContainerfile(skillDir) ? "container" : "local";
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Create a local execution provider
|
|
510
|
+
*/
|
|
511
|
+
export function createLocalProvider(config: LocalProviderConfig = {}): LocalExecutionProvider {
|
|
512
|
+
return new LocalExecutionProvider(config);
|
|
513
|
+
}
|