@cicctencent/agent-server 0.2.83 → 0.2.84
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/cjs/automation/index.d.ts +3 -0
- package/dist/cjs/automation/index.d.ts.map +1 -1
- package/dist/cjs/automation/index.js +6 -1
- package/dist/cjs/automation/index.js.map +1 -1
- package/dist/cjs/automation/node-task-runtime.d.ts +2 -0
- package/dist/cjs/automation/node-task-runtime.d.ts.map +1 -1
- package/dist/cjs/automation/node-task-runtime.js +42 -1
- package/dist/cjs/automation/node-task-runtime.js.map +1 -1
- package/dist/cjs/automation/node-task-validator.d.ts.map +1 -1
- package/dist/cjs/automation/node-task-validator.js +17 -2
- package/dist/cjs/automation/node-task-validator.js.map +1 -1
- package/dist/cjs/automation/python-prelude.d.ts +6 -0
- package/dist/cjs/automation/python-prelude.d.ts.map +1 -0
- package/dist/cjs/automation/python-prelude.js +290 -0
- package/dist/cjs/automation/python-prelude.js.map +1 -0
- package/dist/cjs/automation/python-task-runtime.d.ts +17 -0
- package/dist/cjs/automation/python-task-runtime.d.ts.map +1 -0
- package/dist/cjs/automation/python-task-runtime.js +244 -0
- package/dist/cjs/automation/python-task-runtime.js.map +1 -0
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +5 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/sandbox/local-sandbox.d.ts.map +1 -1
- package/dist/cjs/sandbox/local-sandbox.js +5 -8
- package/dist/cjs/sandbox/local-sandbox.js.map +1 -1
- package/dist/esm/automation/index.d.ts +3 -0
- package/dist/esm/automation/index.d.ts.map +1 -1
- package/dist/esm/automation/index.js +2 -0
- package/dist/esm/automation/index.js.map +1 -1
- package/dist/esm/automation/node-task-runtime.d.ts +2 -0
- package/dist/esm/automation/node-task-runtime.d.ts.map +1 -1
- package/dist/esm/automation/node-task-runtime.js +42 -1
- package/dist/esm/automation/node-task-runtime.js.map +1 -1
- package/dist/esm/automation/node-task-validator.d.ts.map +1 -1
- package/dist/esm/automation/node-task-validator.js +17 -2
- package/dist/esm/automation/node-task-validator.js.map +1 -1
- package/dist/esm/automation/python-prelude.d.ts +6 -0
- package/dist/esm/automation/python-prelude.d.ts.map +1 -0
- package/dist/esm/automation/python-prelude.js +287 -0
- package/dist/esm/automation/python-prelude.js.map +1 -0
- package/dist/esm/automation/python-task-runtime.d.ts +17 -0
- package/dist/esm/automation/python-task-runtime.d.ts.map +1 -0
- package/dist/esm/automation/python-task-runtime.js +206 -0
- package/dist/esm/automation/python-task-runtime.js.map +1 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/sandbox/local-sandbox.d.ts.map +1 -1
- package/dist/esm/sandbox/local-sandbox.js +5 -8
- package/dist/esm/sandbox/local-sandbox.js.map +1 -1
- package/package.json +2 -2
|
@@ -2,4 +2,7 @@ export { NodeAutomationRuntimeRunner, getNodeAutomationRuntimeRunner } from './n
|
|
|
2
2
|
export type { NodeAutomationRuntimeOptions } from './node-task-runtime.js';
|
|
3
3
|
export { NodeAutomationLibraryRegistry, getNodeAutomationLibraryRegistry } from './node-task-library.js';
|
|
4
4
|
export { validateNodeAutomationTaskPackage } from './node-task-validator.js';
|
|
5
|
+
export { PythonAutomationRuntimeRunner, getPythonAutomationRuntimeRunner } from './python-task-runtime.js';
|
|
6
|
+
export type { PythonAutomationRuntimeOptions } from './python-task-runtime.js';
|
|
7
|
+
export { PYTHON_PRELUDE_SOURCE } from './python-prelude.js';
|
|
5
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/automation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AACrG,YAAY,EAAE,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,6BAA6B,EAAE,gCAAgC,EAAE,MAAM,wBAAwB,CAAC;AACzG,OAAO,EAAE,iCAAiC,EAAE,MAAM,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/automation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AACrG,YAAY,EAAE,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,6BAA6B,EAAE,gCAAgC,EAAE,MAAM,wBAAwB,CAAC;AACzG,OAAO,EAAE,iCAAiC,EAAE,MAAM,0BAA0B,CAAC;AAC7E,OAAO,EAAE,6BAA6B,EAAE,gCAAgC,EAAE,MAAM,0BAA0B,CAAC;AAC3G,YAAY,EAAE,8BAA8B,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateNodeAutomationTaskPackage = exports.getNodeAutomationLibraryRegistry = exports.NodeAutomationLibraryRegistry = exports.getNodeAutomationRuntimeRunner = exports.NodeAutomationRuntimeRunner = void 0;
|
|
3
|
+
exports.PYTHON_PRELUDE_SOURCE = exports.getPythonAutomationRuntimeRunner = exports.PythonAutomationRuntimeRunner = exports.validateNodeAutomationTaskPackage = exports.getNodeAutomationLibraryRegistry = exports.NodeAutomationLibraryRegistry = exports.getNodeAutomationRuntimeRunner = exports.NodeAutomationRuntimeRunner = void 0;
|
|
4
4
|
var node_task_runtime_ts_1 = require("./node-task-runtime.js");
|
|
5
5
|
Object.defineProperty(exports, "NodeAutomationRuntimeRunner", { enumerable: true, get: function () { return node_task_runtime_ts_1.NodeAutomationRuntimeRunner; } });
|
|
6
6
|
Object.defineProperty(exports, "getNodeAutomationRuntimeRunner", { enumerable: true, get: function () { return node_task_runtime_ts_1.getNodeAutomationRuntimeRunner; } });
|
|
@@ -9,4 +9,9 @@ Object.defineProperty(exports, "NodeAutomationLibraryRegistry", { enumerable: tr
|
|
|
9
9
|
Object.defineProperty(exports, "getNodeAutomationLibraryRegistry", { enumerable: true, get: function () { return node_task_library_ts_1.getNodeAutomationLibraryRegistry; } });
|
|
10
10
|
var node_task_validator_ts_1 = require("./node-task-validator.js");
|
|
11
11
|
Object.defineProperty(exports, "validateNodeAutomationTaskPackage", { enumerable: true, get: function () { return node_task_validator_ts_1.validateNodeAutomationTaskPackage; } });
|
|
12
|
+
var python_task_runtime_ts_1 = require("./python-task-runtime.js");
|
|
13
|
+
Object.defineProperty(exports, "PythonAutomationRuntimeRunner", { enumerable: true, get: function () { return python_task_runtime_ts_1.PythonAutomationRuntimeRunner; } });
|
|
14
|
+
Object.defineProperty(exports, "getPythonAutomationRuntimeRunner", { enumerable: true, get: function () { return python_task_runtime_ts_1.getPythonAutomationRuntimeRunner; } });
|
|
15
|
+
var python_prelude_ts_1 = require("./python-prelude.js");
|
|
16
|
+
Object.defineProperty(exports, "PYTHON_PRELUDE_SOURCE", { enumerable: true, get: function () { return python_prelude_ts_1.PYTHON_PRELUDE_SOURCE; } });
|
|
12
17
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/automation/index.ts"],"names":[],"mappings":";;;AAAA,+DAAqG;AAA5F,mIAAA,2BAA2B,OAAA;AAAE,sIAAA,8BAA8B,OAAA;AAEpE,+DAAyG;AAAhG,qIAAA,6BAA6B,OAAA;AAAE,wIAAA,gCAAgC,OAAA;AACxE,mEAA6E;AAApE,2IAAA,iCAAiC,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/automation/index.ts"],"names":[],"mappings":";;;AAAA,+DAAqG;AAA5F,mIAAA,2BAA2B,OAAA;AAAE,sIAAA,8BAA8B,OAAA;AAEpE,+DAAyG;AAAhG,qIAAA,6BAA6B,OAAA;AAAE,wIAAA,gCAAgC,OAAA;AACxE,mEAA6E;AAApE,2IAAA,iCAAiC,OAAA;AAC1C,mEAA2G;AAAlG,uIAAA,6BAA6B,OAAA;AAAE,0IAAA,gCAAgC,OAAA;AAExE,yDAA4D;AAAnD,0HAAA,qBAAqB,OAAA"}
|
|
@@ -9,6 +9,8 @@ export declare class NodeAutomationRuntimeRunner {
|
|
|
9
9
|
private readonly libraryRegistry;
|
|
10
10
|
constructor(options?: NodeAutomationRuntimeOptions);
|
|
11
11
|
execute(taskPackage: NodeAutomationTaskPackage, options: NodeAutomationRunOptions): Promise<NodeAutomationExecutionResult>;
|
|
12
|
+
/** 剥离 stdout 中的内部结果标记行,保留用户 console.log 输出 */
|
|
13
|
+
private stripMarkerLines;
|
|
12
14
|
private buildEnv;
|
|
13
15
|
private createLogger;
|
|
14
16
|
private createArtifactWriter;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-task-runtime.d.ts","sourceRoot":"","sources":["../../../src/automation/node-task-runtime.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAE7B,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAoC,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AAGzG,MAAM,WAAW,4BAA4B;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,6BAA6B,CAAC;CACjD;
|
|
1
|
+
{"version":3,"file":"node-task-runtime.d.ts","sourceRoot":"","sources":["../../../src/automation/node-task-runtime.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,6BAA6B,EAE7B,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAoC,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AAGzG,MAAM,WAAW,4BAA4B;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,6BAA6B,CAAC;CACjD;AAaD,qBAAa,2BAA2B;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAgC;gBAEpD,OAAO,GAAE,4BAAiC;IAKhD,OAAO,CACX,WAAW,EAAE,yBAAyB,EACtC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,6BAA6B,CAAC;IA+EzC,8CAA8C;IAC9C,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,QAAQ;IAahB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,oBAAoB;IAsG5B,OAAO,CAAC,kBAAkB;CAU3B;AAID,wBAAgB,8BAA8B,IAAI,2BAA2B,CAG5E"}
|
|
@@ -101,12 +101,15 @@ class NodeAutomationRuntimeRunner {
|
|
|
101
101
|
taskId: options.taskId,
|
|
102
102
|
runId,
|
|
103
103
|
workspace: options.workspace,
|
|
104
|
+
agentEndpoint: options.agentEndpoint || process.env.A2A_BASE_URL || '',
|
|
104
105
|
};
|
|
105
106
|
const script = this.buildBootstrapScript(payload);
|
|
106
107
|
const result = await sandbox.execute(script, 'nodejs', options.timeoutMs || taskPackage.runtimeConfig?.timeoutMs || 120000);
|
|
107
108
|
const parsed = this.parseRuntimeResult(result.stdout);
|
|
109
|
+
// 剥离内部结果标记行,避免展示给用户
|
|
110
|
+
const cleanStdout = this.stripMarkerLines(result.stdout);
|
|
108
111
|
return {
|
|
109
|
-
stdout:
|
|
112
|
+
stdout: cleanStdout,
|
|
110
113
|
stderr: result.stderr,
|
|
111
114
|
exitCode: result.exitCode,
|
|
112
115
|
duration: result.duration,
|
|
@@ -116,6 +119,15 @@ class NodeAutomationRuntimeRunner {
|
|
|
116
119
|
artifacts: result.files,
|
|
117
120
|
};
|
|
118
121
|
}
|
|
122
|
+
/** 剥离 stdout 中的内部结果标记行,保留用户 console.log 输出 */
|
|
123
|
+
stripMarkerLines(stdout) {
|
|
124
|
+
return stdout
|
|
125
|
+
.split('\n')
|
|
126
|
+
.filter(line => !line.startsWith('__NODE_AUTOMATION_RESULT__') && !line.startsWith('__NODE_AUTOMATION_ERROR__'))
|
|
127
|
+
.join('\n')
|
|
128
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
129
|
+
.trim();
|
|
130
|
+
}
|
|
119
131
|
buildEnv(taskPackage, env) {
|
|
120
132
|
const allowed = new Set(taskPackage.runtimeConfig?.envKeys || []);
|
|
121
133
|
const safeEnv = {};
|
|
@@ -177,6 +189,35 @@ const context = {
|
|
|
177
189
|
env: __payload.env || {},
|
|
178
190
|
workspace: __payload.workspace,
|
|
179
191
|
libs: __payload.libs || {},
|
|
192
|
+
agentEndpoint: __payload.agentEndpoint,
|
|
193
|
+
agent: {
|
|
194
|
+
ask: async (prompt, opts) => {
|
|
195
|
+
opts = opts || {};
|
|
196
|
+
const endpoint = __payload.agentEndpoint;
|
|
197
|
+
if (!endpoint) throw new Error('Agent endpoint not configured');
|
|
198
|
+
const resp = await (global.sb && global.sb.fetch ? global.sb.fetch(endpoint, {
|
|
199
|
+
method: 'POST',
|
|
200
|
+
json: {
|
|
201
|
+
jsonrpc: '2.0',
|
|
202
|
+
id: opts.id || 1,
|
|
203
|
+
method: 'tasks/send',
|
|
204
|
+
params: {
|
|
205
|
+
message: { role: 'user', parts: [{ type: 'text', text: String(prompt) }] },
|
|
206
|
+
contextId: opts.contextId,
|
|
207
|
+
profile: opts.profile,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
headers: { Accept: 'application/json' },
|
|
211
|
+
}, opts.timeout || 120000) : Promise.resolve({ status: 500, text: 'sb.fetch unavailable' }));
|
|
212
|
+
if (resp.status >= 400) throw new Error('Agent call failed (HTTP ' + resp.status + '): ' + String(resp.text).slice(0, 200));
|
|
213
|
+
const body = typeof resp.data === 'object' && resp.data ? resp.data : {};
|
|
214
|
+
if (body.error) throw new Error('Agent error: ' + JSON.stringify(body.error));
|
|
215
|
+
const task = (body.result && body.result.task) || {};
|
|
216
|
+
if (task.state === 'failed') throw new Error('Agent task failed: ' + (task.error || 'unknown'));
|
|
217
|
+
const parts = (task.result && task.result.parts) || [];
|
|
218
|
+
return parts.filter(p => p.kind === 'text' || p.type === 'text').map(p => p.text || '').join('');
|
|
219
|
+
},
|
|
220
|
+
},
|
|
180
221
|
logger: {
|
|
181
222
|
debug: (message, meta) => __log('debug', message, meta),
|
|
182
223
|
info: (message, meta) => __log('info', message, meta),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-task-runtime.js","sourceRoot":"","sources":["../../../src/automation/node-task-runtime.ts"],"names":[],"mappings":";AAAA,+DAA+D;AAC/D,6CAA6C;AAC7C,+DAA+D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"node-task-runtime.js","sourceRoot":"","sources":["../../../src/automation/node-task-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-task-validator.d.ts","sourceRoot":"","sources":["../../../src/automation/node-task-validator.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,yBAAyB,EAEzB,8BAA8B,EAC/B,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"node-task-validator.d.ts","sourceRoot":"","sources":["../../../src/automation/node-task-validator.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,yBAAyB,EAEzB,8BAA8B,EAC/B,MAAM,yBAAyB,CAAC;AAcjC,wBAAgB,iCAAiC,CAC/C,WAAW,EAAE,yBAAyB,GACrC,8BAA8B,CAqDhC"}
|
|
@@ -10,6 +10,10 @@ const FORBIDDEN_PATTERNS = [
|
|
|
10
10
|
{ code: 'forbidden-fs-absolute-write', pattern: /writeFile(?:Sync)?\s*\(\s*['"]\//, message: '任务代码不能写入绝对路径,请使用 context.artifacts 输出产物。' },
|
|
11
11
|
{ code: 'forbidden-process-env-dump', pattern: /JSON\.stringify\s*\(\s*process\.env\s*\)/, message: '任务代码不能完整输出环境变量。' },
|
|
12
12
|
{ code: 'forbidden-npm-install', pattern: /npm\s+install|pnpm\s+add|yarn\s+add/, message: '任务代码不能在运行时安装依赖,请通过容器基础库注册依赖。' },
|
|
13
|
+
// Python 安全规则
|
|
14
|
+
{ code: 'forbidden-python-subprocess', pattern: /\b(?:subprocess|os\.system|os\.popen|os\.exec\w*)\s*\(/, message: 'Python 任务代码不能使用 subprocess/os.system 等执行外部命令,请通过受控基础库暴露能力。' },
|
|
15
|
+
{ code: 'forbidden-python-eval', pattern: /\b(?:eval|exec)\s*\(/, message: 'Python 任务代码不能使用 eval/exec 执行动态代码。' },
|
|
16
|
+
{ code: 'forbidden-python-absolute-write', pattern: /open\s*\(\s*['"]\/(?!(?:tmp|sandbox))/, message: 'Python 任务代码不能写入沙箱外的绝对路径,请使用相对路径输出产物。' },
|
|
13
17
|
];
|
|
14
18
|
function validateNodeAutomationTaskPackage(taskPackage) {
|
|
15
19
|
const issues = [];
|
|
@@ -17,13 +21,24 @@ function validateNodeAutomationTaskPackage(taskPackage) {
|
|
|
17
21
|
addIssue(issues, 'missing-id', '任务包缺少 id。');
|
|
18
22
|
if (!taskPackage.version)
|
|
19
23
|
addIssue(issues, 'missing-version', '任务包缺少 version。');
|
|
20
|
-
if (taskPackage.runtime !== 'nodejs')
|
|
21
|
-
addIssue(issues, 'invalid-runtime', '代码任务 runtime 必须为 nodejs。');
|
|
24
|
+
if (taskPackage.runtime !== 'nodejs' && taskPackage.runtime !== 'python') {
|
|
25
|
+
addIssue(issues, 'invalid-runtime', '代码任务 runtime 必须为 nodejs 或 python。');
|
|
26
|
+
}
|
|
22
27
|
if (!taskPackage.entry)
|
|
23
28
|
addIssue(issues, 'missing-entry', '任务包缺少入口文件。');
|
|
24
29
|
if (!taskPackage.sourceFiles || Object.keys(taskPackage.sourceFiles).length === 0) {
|
|
25
30
|
addIssue(issues, 'missing-source', '任务包缺少源码文件。');
|
|
26
31
|
}
|
|
32
|
+
// 入口文件扩展名校验(与 runtime 匹配)
|
|
33
|
+
if (taskPackage.entry && taskPackage.runtime) {
|
|
34
|
+
const ext = taskPackage.entry.split('.').pop() || '';
|
|
35
|
+
if (taskPackage.runtime === 'nodejs' && ext !== 'js' && ext !== 'cjs' && ext !== 'mjs') {
|
|
36
|
+
addIssue(issues, 'invalid-entry-ext', `nodejs 任务入口应为 .js/.cjs/.mjs,当前为 ${taskPackage.entry}`);
|
|
37
|
+
}
|
|
38
|
+
if (taskPackage.runtime === 'python' && ext !== 'py') {
|
|
39
|
+
addIssue(issues, 'invalid-entry-ext', `python 任务入口应为 .py,当前为 ${taskPackage.entry}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
27
42
|
if (taskPackage.entry && taskPackage.sourceFiles && !taskPackage.sourceFiles[taskPackage.entry]) {
|
|
28
43
|
addIssue(issues, 'entry-not-found', `入口文件不存在: ${taskPackage.entry}`);
|
|
29
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-task-validator.js","sourceRoot":"","sources":["../../../src/automation/node-task-validator.ts"],"names":[],"mappings":";AAAA,+DAA+D;AAC/D,sCAAsC;AACtC,+DAA+D;;
|
|
1
|
+
{"version":3,"file":"node-task-validator.js","sourceRoot":"","sources":["../../../src/automation/node-task-validator.ts"],"names":[],"mappings":";AAAA,+DAA+D;AAC/D,sCAAsC;AACtC,+DAA+D;;AAoB/D,8EAuDC;AApED,iEAA0E;AAE1E,MAAM,kBAAkB,GAA8D;IACpF,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,6DAA6D,EAAE,OAAO,EAAE,wCAAwC,EAAE;IAC9J,EAAE,IAAI,EAAE,6BAA6B,EAAE,OAAO,EAAE,kCAAkC,EAAE,OAAO,EAAE,0CAA0C,EAAE;IACzI,EAAE,IAAI,EAAE,4BAA4B,EAAE,OAAO,EAAE,0CAA0C,EAAE,OAAO,EAAE,iBAAiB,EAAE;IACvH,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,qCAAqC,EAAE,OAAO,EAAE,8BAA8B,EAAE;IAC1H,cAAc;IACd,EAAE,IAAI,EAAE,6BAA6B,EAAE,OAAO,EAAE,wDAAwD,EAAE,OAAO,EAAE,4DAA4D,EAAE;IACjL,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,sBAAsB,EAAE,OAAO,EAAE,mCAAmC,EAAE;IAChH,EAAE,IAAI,EAAE,iCAAiC,EAAE,OAAO,EAAE,uCAAuC,EAAE,OAAO,EAAE,sCAAsC,EAAE;CAC/I,CAAC;AAEF,SAAgB,iCAAiC,CAC/C,WAAsC;IAEtC,MAAM,MAAM,GAAoC,EAAE,CAAC;IAEnD,IAAI,CAAC,WAAW,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IACjE,IAAI,CAAC,WAAW,CAAC,OAAO;QAAE,QAAQ,CAAC,MAAM,EAAE,iBAAiB,EAAE,gBAAgB,CAAC,CAAC;IAChF,IAAI,WAAW,CAAC,OAAO,KAAK,QAAQ,IAAI,WAAW,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzE,QAAQ,CAAC,MAAM,EAAE,iBAAiB,EAAE,mCAAmC,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,KAAK;QAAE,QAAQ,CAAC,MAAM,EAAE,eAAe,EAAE,YAAY,CAAC,CAAC;IACxE,IAAI,CAAC,WAAW,CAAC,WAAW,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClF,QAAQ,CAAC,MAAM,EAAE,gBAAgB,EAAE,YAAY,CAAC,CAAC;IACnD,CAAC;IAED,0BAA0B;IAC1B,IAAI,WAAW,CAAC,KAAK,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;QAC7C,MAAM,GAAG,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;QACrD,IAAI,WAAW,CAAC,OAAO,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;YACvF,QAAQ,CAAC,MAAM,EAAE,mBAAmB,EAAE,mCAAmC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;QAChG,CAAC;QACD,IAAI,WAAW,CAAC,OAAO,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACrD,QAAQ,CAAC,MAAM,EAAE,mBAAmB,EAAE,yBAAyB,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;IAED,IAAI,WAAW,CAAC,KAAK,IAAI,WAAW,CAAC,WAAW,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;QAChG,QAAQ,CAAC,MAAM,EAAE,iBAAiB,EAAE,YAAY,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,WAAW,IAAI,EAAE,CAAC,EAAE,CAAC;QAChF,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACxD,QAAQ,CAAC,MAAM,EAAE,kBAAkB,EAAE,cAAc,QAAQ,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,kBAAkB,EAAE,CAAC;YACtC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;gBAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,IAAA,uDAAgC,GAAE,CAAC;IACpD,KAAK,MAAM,OAAO,IAAI,WAAW,CAAC,iBAAiB,IAAI,EAAE,EAAE,CAAC;QAC1D,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,QAAQ,CAAC,MAAM,EAAE,wBAAwB,EAAE,WAAW,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,WAAW,CAAC,aAAa,EAAE,SAAS,CAAC;IACvD,IAAI,SAAS,IAAI,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,SAAS,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QAC1E,QAAQ,CAAC,MAAM,EAAE,iBAAiB,EAAE,oCAAoC,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO;QACL,KAAK,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,OAAO,CAAC;QACxD,MAAM;KACP,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CACf,MAAuC,EACvC,IAAY,EACZ,OAAe,EACf,IAAa,EACb,WAAgC,OAAO;IAEvC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python 基础能力库源码。
|
|
3
|
+
* 运行时会将 __AGENT_ENDPOINT__ 替换为真实的 A2A JSON-RPC 端点 URL。
|
|
4
|
+
*/
|
|
5
|
+
export declare const PYTHON_PRELUDE_SOURCE = "# -*- coding: utf-8 -*-\n\"\"\"\nSandbox Prelude for Python automation tasks.\nAuto-imported as `sb` in every task script.\nMirrors the Node.js global.sb capabilities.\n\"\"\"\nimport os\nimport sys\nimport json\nimport csv\nimport io\nimport time\nimport datetime as _dt\nimport urllib.request\nimport urllib.error\nimport urllib.parse\nfrom typing import Any, Dict, List, Optional\n\n# Agent \u8C03\u7528\u7AEF\u70B9\uFF08\u7531\u8FD0\u884C\u65F6\u6CE8\u5165\uFF09\n_AGENT_ENDPOINT = \"__AGENT_ENDPOINT__\"\n_AGENT_CONFIGURED = _AGENT_ENDPOINT != \"__AGENT_ENDPOINT__\"\n\n\nclass _Logger:\n def __init__(self, sink):\n self._sink = sink\n\n def debug(self, message, meta=None):\n self._sink(\"debug\", message, meta)\n\n def info(self, message, meta=None):\n self._sink(\"info\", message, meta)\n\n def warn(self, message, meta=None):\n self._sink(\"warn\", message, meta)\n\n def error(self, message, meta=None):\n self._sink(\"error\", message, meta)\n\n\n# ===== HTTP =====\n\ndef fetch(url, options=None, timeout=30):\n \"\"\"HTTP \u8BF7\u6C42\uFF0C\u8FD4\u56DE {status, headers, data, text}\u3002\"\"\"\n options = options or {}\n method = options.get(\"method\", \"GET\").upper()\n headers = dict(options.get(\"headers\") or {})\n headers.setdefault(\"User-Agent\", \"workai-sandbox/1.0\")\n params = options.get(\"params\")\n if params:\n query = urllib.parse.urlencode(params)\n url = url + (\"&\" if \"?\" in url else \"?\") + query\n body = options.get(\"body\")\n json_body = options.get(\"json\")\n data = None\n if json_body is not None:\n data = json.dumps(json_body).encode(\"utf-8\")\n headers.setdefault(\"Content-Type\", \"application/json\")\n elif body is not None:\n if isinstance(body, str):\n data = body.encode(\"utf-8\")\n elif isinstance(body, (bytes, bytearray)):\n data = bytes(body)\n else:\n data = json.dumps(body).encode(\"utf-8\")\n headers.setdefault(\"Content-Type\", \"application/json\")\n req = urllib.request.Request(url, data=data, headers=headers, method=method)\n try:\n with urllib.request.urlopen(req, timeout=timeout) as resp:\n status = resp.status\n resp_headers = dict(resp.headers)\n text = resp.read().decode(\"utf-8\", errors=\"replace\")\n except urllib.error.HTTPError as e:\n status = e.code\n resp_headers = dict(e.headers) if e.headers else {}\n text = e.read().decode(\"utf-8\", errors=\"replace\") if e.fp else \"\"\n parsed = text\n ct = (resp_headers.get(\"Content-Type\") or \"\").lower()\n if \"application/json\" in ct or \"+json\" in ct:\n try:\n parsed = json.loads(text)\n except Exception:\n pass\n return {\"status\": status, \"headers\": resp_headers, \"data\": parsed, \"text\": text}\n\n\ndef get(url, options=None, timeout=30):\n return fetch(url, dict(options or {}, method=\"GET\"), timeout)\n\n\ndef post(url, body=None, options=None, timeout=30):\n opts = dict(options or {}, method=\"POST\")\n if isinstance(body, (dict, list)):\n opts[\"json\"] = body\n else:\n opts[\"body\"] = body\n return fetch(url, opts, timeout)\n\n\n# ===== File I/O =====\n\ndef read_text(file_path, encoding=\"utf-8\"):\n with open(file_path, \"r\", encoding=encoding) as f:\n return f.read()\n\n\ndef write_text(file_path, content, encoding=\"utf-8\"):\n _ensure_parent(file_path)\n with open(file_path, \"w\", encoding=encoding) as f:\n f.write(content)\n\n\ndef append_text(file_path, content, encoding=\"utf-8\"):\n _ensure_parent(file_path)\n with open(file_path, \"a\", encoding=encoding) as f:\n f.write(content)\n\n\ndef read_json(file_path):\n with open(file_path, \"r\", encoding=\"utf-8\") as f:\n return json.load(f)\n\n\ndef write_json(file_path, data, indent=2):\n _ensure_parent(file_path)\n with open(file_path, \"w\", encoding=\"utf-8\") as f:\n json.dump(data, f, ensure_ascii=False, indent=indent)\n\n\ndef read_csv(file_path):\n with open(file_path, \"r\", encoding=\"utf-8\", newline=\"\") as f:\n return list(csv.DictReader(f))\n\n\ndef write_csv(file_path, rows):\n _ensure_parent(file_path)\n if not rows:\n with open(file_path, \"w\", encoding=\"utf-8\", newline=\"\") as f:\n f.write(\"\")\n return\n with open(file_path, \"w\", encoding=\"utf-8\", newline=\"\") as f:\n writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))\n writer.writeheader()\n writer.writerows(rows)\n\n\ndef exists(file_path):\n return os.path.exists(file_path)\n\n\ndef list_dir(dir_path=\".\"):\n return os.listdir(dir_path)\n\n\ndef _ensure_parent(file_path):\n parent = os.path.dirname(os.path.abspath(file_path))\n os.makedirs(parent, exist_ok=True)\n\n\n# ===== Data helpers =====\n\ndef stats(values):\n values = [v for v in values if v is not None]\n n = len(values)\n if n == 0:\n return {\"count\": 0, \"sum\": 0, \"avg\": 0, \"min\": None, \"max\": None}\n s = sum(values)\n return {\"count\": n, \"sum\": s, \"avg\": s / n, \"min\": min(values), \"max\": max(values)}\n\n\ndef sum_of(values):\n return sum(v for v in values if v is not None)\n\n\ndef avg(values):\n vals = [v for v in values if v is not None]\n return sum(vals) / len(vals) if vals else 0\n\n\ndef group_by(rows, key):\n result = {}\n for row in rows:\n k = row.get(key) if isinstance(row, dict) else getattr(row, key, None)\n result.setdefault(k, []).append(row)\n return result\n\n\ndef sort_by(rows, key, reverse=False):\n return sorted(rows, key=lambda r: (r.get(key) if isinstance(r, dict) else getattr(r, key, None)), reverse=reverse)\n\n\ndef unique(values):\n seen = []\n for v in values:\n if v not in seen:\n seen.append(v)\n return seen\n\n\ndef chunk(values, size):\n return [values[i:i + size] for i in range(0, len(values), size)]\n\n\n# ===== Date helpers =====\n\ndef now():\n return _dt.datetime.now()\n\n\ndef format_date(dt, fmt=\"%Y-%m-%d %H:%M:%S\"):\n if isinstance(dt, (int, float)):\n dt = _dt.datetime.fromtimestamp(dt)\n if isinstance(dt, str):\n dt = _dt.datetime.fromisoformat(dt)\n return dt.strftime(fmt)\n\n\ndef parse_date(text, fmt=None):\n if fmt:\n return _dt.datetime.strptime(text, fmt)\n return _dt.datetime.fromisoformat(text)\n\n\ndef days_go(n):\n return _dt.datetime.now() - _dt.timedelta(days=n)\n\n\ndef days_later(n):\n return _dt.datetime.now() + _dt.timedelta(days=n)\n\n\n# ===== Agent =====\n\nclass _Agent:\n \"\"\"\u8C03\u7528\u5E73\u53F0 Agent \u80FD\u529B\uFF08\u57FA\u4E8E A2A JSON-RPC\uFF09\u3002\"\"\"\n\n def ask(self, prompt, options=None):\n \"\"\"\u5411 Agent \u53D1\u9001\u63D0\u793A\u8BCD\u5E76\u540C\u6B65\u7B49\u5F85\u6587\u672C\u56DE\u590D\u3002\"\"\"\n if not _AGENT_CONFIGURED:\n raise RuntimeError(\"Agent endpoint not configured\")\n options = options or {}\n payload = {\n \"jsonrpc\": \"2.0\",\n \"id\": options.get(\"id\", 1),\n \"method\": \"tasks/send\",\n \"params\": {\n \"message\": {\n \"role\": \"user\",\n \"parts\": [{\"type\": \"text\", \"text\": str(prompt)}],\n },\n \"contextId\": options.get(\"contextId\"),\n \"profile\": options.get(\"profile\"),\n },\n }\n resp = fetch(\n _AGENT_ENDPOINT,\n {\"method\": \"POST\", \"json\": payload, \"headers\": {\"Accept\": \"application/json\"}},\n timeout=options.get(\"timeout\", 120),\n )\n if resp[\"status\"] >= 400:\n raise RuntimeError(\"Agent call failed (HTTP %s): %s\" % (resp[\"status\"], resp[\"text\"][:200]))\n body = resp[\"data\"] if isinstance(resp[\"data\"], dict) else {}\n if \"error\" in body:\n raise RuntimeError(\"Agent error: %s\" % body[\"error\"])\n task = (body.get(\"result\") or {}).get(\"task\") or {}\n state = task.get(\"state\")\n if state == \"failed\":\n raise RuntimeError(\"Agent task failed: %s\" % task.get(\"error\", \"unknown\"))\n parts = (task.get(\"result\") or {}).get(\"parts\") or []\n texts = [p.get(\"text\", \"\") for p in parts if p.get(\"kind\") == \"text\" or p.get(\"type\") == \"text\"]\n return \"\".join(texts)\n\n\nagent = _Agent()\n";
|
|
6
|
+
//# sourceMappingURL=python-prelude.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python-prelude.d.ts","sourceRoot":"","sources":["../../../src/automation/python-prelude.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,eAAO,MAAM,qBAAqB,mgRAkRjC,CAAC"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================
|
|
3
|
+
// Python Automation Prelude - Python 自动化任务基础能力库
|
|
4
|
+
// ============================================================
|
|
5
|
+
// 在沙箱初始化时写入 _scripts/_sb.py,由 Python 引导脚本 import。
|
|
6
|
+
// 提供与 Node 端 global.sb 对等的能力:HTTP / 文件 / 数据 / 日期 / 日志 / Agent 调用。
|
|
7
|
+
// Agent 调用端点由运行时通过 __AGENT_ENDPOINT__ 占位符注入。
|
|
8
|
+
// ============================================================
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.PYTHON_PRELUDE_SOURCE = void 0;
|
|
11
|
+
/**
|
|
12
|
+
* Python 基础能力库源码。
|
|
13
|
+
* 运行时会将 __AGENT_ENDPOINT__ 替换为真实的 A2A JSON-RPC 端点 URL。
|
|
14
|
+
*/
|
|
15
|
+
exports.PYTHON_PRELUDE_SOURCE = `# -*- coding: utf-8 -*-
|
|
16
|
+
"""
|
|
17
|
+
Sandbox Prelude for Python automation tasks.
|
|
18
|
+
Auto-imported as \`sb\` in every task script.
|
|
19
|
+
Mirrors the Node.js global.sb capabilities.
|
|
20
|
+
"""
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
import json
|
|
24
|
+
import csv
|
|
25
|
+
import io
|
|
26
|
+
import time
|
|
27
|
+
import datetime as _dt
|
|
28
|
+
import urllib.request
|
|
29
|
+
import urllib.error
|
|
30
|
+
import urllib.parse
|
|
31
|
+
from typing import Any, Dict, List, Optional
|
|
32
|
+
|
|
33
|
+
# Agent 调用端点(由运行时注入)
|
|
34
|
+
_AGENT_ENDPOINT = "__AGENT_ENDPOINT__"
|
|
35
|
+
_AGENT_CONFIGURED = _AGENT_ENDPOINT != "__AGENT_ENDPOINT__"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _Logger:
|
|
39
|
+
def __init__(self, sink):
|
|
40
|
+
self._sink = sink
|
|
41
|
+
|
|
42
|
+
def debug(self, message, meta=None):
|
|
43
|
+
self._sink("debug", message, meta)
|
|
44
|
+
|
|
45
|
+
def info(self, message, meta=None):
|
|
46
|
+
self._sink("info", message, meta)
|
|
47
|
+
|
|
48
|
+
def warn(self, message, meta=None):
|
|
49
|
+
self._sink("warn", message, meta)
|
|
50
|
+
|
|
51
|
+
def error(self, message, meta=None):
|
|
52
|
+
self._sink("error", message, meta)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ===== HTTP =====
|
|
56
|
+
|
|
57
|
+
def fetch(url, options=None, timeout=30):
|
|
58
|
+
"""HTTP 请求,返回 {status, headers, data, text}。"""
|
|
59
|
+
options = options or {}
|
|
60
|
+
method = options.get("method", "GET").upper()
|
|
61
|
+
headers = dict(options.get("headers") or {})
|
|
62
|
+
headers.setdefault("User-Agent", "workai-sandbox/1.0")
|
|
63
|
+
params = options.get("params")
|
|
64
|
+
if params:
|
|
65
|
+
query = urllib.parse.urlencode(params)
|
|
66
|
+
url = url + ("&" if "?" in url else "?") + query
|
|
67
|
+
body = options.get("body")
|
|
68
|
+
json_body = options.get("json")
|
|
69
|
+
data = None
|
|
70
|
+
if json_body is not None:
|
|
71
|
+
data = json.dumps(json_body).encode("utf-8")
|
|
72
|
+
headers.setdefault("Content-Type", "application/json")
|
|
73
|
+
elif body is not None:
|
|
74
|
+
if isinstance(body, str):
|
|
75
|
+
data = body.encode("utf-8")
|
|
76
|
+
elif isinstance(body, (bytes, bytearray)):
|
|
77
|
+
data = bytes(body)
|
|
78
|
+
else:
|
|
79
|
+
data = json.dumps(body).encode("utf-8")
|
|
80
|
+
headers.setdefault("Content-Type", "application/json")
|
|
81
|
+
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
82
|
+
try:
|
|
83
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
84
|
+
status = resp.status
|
|
85
|
+
resp_headers = dict(resp.headers)
|
|
86
|
+
text = resp.read().decode("utf-8", errors="replace")
|
|
87
|
+
except urllib.error.HTTPError as e:
|
|
88
|
+
status = e.code
|
|
89
|
+
resp_headers = dict(e.headers) if e.headers else {}
|
|
90
|
+
text = e.read().decode("utf-8", errors="replace") if e.fp else ""
|
|
91
|
+
parsed = text
|
|
92
|
+
ct = (resp_headers.get("Content-Type") or "").lower()
|
|
93
|
+
if "application/json" in ct or "+json" in ct:
|
|
94
|
+
try:
|
|
95
|
+
parsed = json.loads(text)
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
return {"status": status, "headers": resp_headers, "data": parsed, "text": text}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get(url, options=None, timeout=30):
|
|
102
|
+
return fetch(url, dict(options or {}, method="GET"), timeout)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def post(url, body=None, options=None, timeout=30):
|
|
106
|
+
opts = dict(options or {}, method="POST")
|
|
107
|
+
if isinstance(body, (dict, list)):
|
|
108
|
+
opts["json"] = body
|
|
109
|
+
else:
|
|
110
|
+
opts["body"] = body
|
|
111
|
+
return fetch(url, opts, timeout)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ===== File I/O =====
|
|
115
|
+
|
|
116
|
+
def read_text(file_path, encoding="utf-8"):
|
|
117
|
+
with open(file_path, "r", encoding=encoding) as f:
|
|
118
|
+
return f.read()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def write_text(file_path, content, encoding="utf-8"):
|
|
122
|
+
_ensure_parent(file_path)
|
|
123
|
+
with open(file_path, "w", encoding=encoding) as f:
|
|
124
|
+
f.write(content)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def append_text(file_path, content, encoding="utf-8"):
|
|
128
|
+
_ensure_parent(file_path)
|
|
129
|
+
with open(file_path, "a", encoding=encoding) as f:
|
|
130
|
+
f.write(content)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def read_json(file_path):
|
|
134
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
135
|
+
return json.load(f)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def write_json(file_path, data, indent=2):
|
|
139
|
+
_ensure_parent(file_path)
|
|
140
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
141
|
+
json.dump(data, f, ensure_ascii=False, indent=indent)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def read_csv(file_path):
|
|
145
|
+
with open(file_path, "r", encoding="utf-8", newline="") as f:
|
|
146
|
+
return list(csv.DictReader(f))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def write_csv(file_path, rows):
|
|
150
|
+
_ensure_parent(file_path)
|
|
151
|
+
if not rows:
|
|
152
|
+
with open(file_path, "w", encoding="utf-8", newline="") as f:
|
|
153
|
+
f.write("")
|
|
154
|
+
return
|
|
155
|
+
with open(file_path, "w", encoding="utf-8", newline="") as f:
|
|
156
|
+
writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
|
|
157
|
+
writer.writeheader()
|
|
158
|
+
writer.writerows(rows)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def exists(file_path):
|
|
162
|
+
return os.path.exists(file_path)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def list_dir(dir_path="."):
|
|
166
|
+
return os.listdir(dir_path)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _ensure_parent(file_path):
|
|
170
|
+
parent = os.path.dirname(os.path.abspath(file_path))
|
|
171
|
+
os.makedirs(parent, exist_ok=True)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ===== Data helpers =====
|
|
175
|
+
|
|
176
|
+
def stats(values):
|
|
177
|
+
values = [v for v in values if v is not None]
|
|
178
|
+
n = len(values)
|
|
179
|
+
if n == 0:
|
|
180
|
+
return {"count": 0, "sum": 0, "avg": 0, "min": None, "max": None}
|
|
181
|
+
s = sum(values)
|
|
182
|
+
return {"count": n, "sum": s, "avg": s / n, "min": min(values), "max": max(values)}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def sum_of(values):
|
|
186
|
+
return sum(v for v in values if v is not None)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def avg(values):
|
|
190
|
+
vals = [v for v in values if v is not None]
|
|
191
|
+
return sum(vals) / len(vals) if vals else 0
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def group_by(rows, key):
|
|
195
|
+
result = {}
|
|
196
|
+
for row in rows:
|
|
197
|
+
k = row.get(key) if isinstance(row, dict) else getattr(row, key, None)
|
|
198
|
+
result.setdefault(k, []).append(row)
|
|
199
|
+
return result
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def sort_by(rows, key, reverse=False):
|
|
203
|
+
return sorted(rows, key=lambda r: (r.get(key) if isinstance(r, dict) else getattr(r, key, None)), reverse=reverse)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def unique(values):
|
|
207
|
+
seen = []
|
|
208
|
+
for v in values:
|
|
209
|
+
if v not in seen:
|
|
210
|
+
seen.append(v)
|
|
211
|
+
return seen
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def chunk(values, size):
|
|
215
|
+
return [values[i:i + size] for i in range(0, len(values), size)]
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ===== Date helpers =====
|
|
219
|
+
|
|
220
|
+
def now():
|
|
221
|
+
return _dt.datetime.now()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def format_date(dt, fmt="%Y-%m-%d %H:%M:%S"):
|
|
225
|
+
if isinstance(dt, (int, float)):
|
|
226
|
+
dt = _dt.datetime.fromtimestamp(dt)
|
|
227
|
+
if isinstance(dt, str):
|
|
228
|
+
dt = _dt.datetime.fromisoformat(dt)
|
|
229
|
+
return dt.strftime(fmt)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def parse_date(text, fmt=None):
|
|
233
|
+
if fmt:
|
|
234
|
+
return _dt.datetime.strptime(text, fmt)
|
|
235
|
+
return _dt.datetime.fromisoformat(text)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def days_go(n):
|
|
239
|
+
return _dt.datetime.now() - _dt.timedelta(days=n)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def days_later(n):
|
|
243
|
+
return _dt.datetime.now() + _dt.timedelta(days=n)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ===== Agent =====
|
|
247
|
+
|
|
248
|
+
class _Agent:
|
|
249
|
+
"""调用平台 Agent 能力(基于 A2A JSON-RPC)。"""
|
|
250
|
+
|
|
251
|
+
def ask(self, prompt, options=None):
|
|
252
|
+
"""向 Agent 发送提示词并同步等待文本回复。"""
|
|
253
|
+
if not _AGENT_CONFIGURED:
|
|
254
|
+
raise RuntimeError("Agent endpoint not configured")
|
|
255
|
+
options = options or {}
|
|
256
|
+
payload = {
|
|
257
|
+
"jsonrpc": "2.0",
|
|
258
|
+
"id": options.get("id", 1),
|
|
259
|
+
"method": "tasks/send",
|
|
260
|
+
"params": {
|
|
261
|
+
"message": {
|
|
262
|
+
"role": "user",
|
|
263
|
+
"parts": [{"type": "text", "text": str(prompt)}],
|
|
264
|
+
},
|
|
265
|
+
"contextId": options.get("contextId"),
|
|
266
|
+
"profile": options.get("profile"),
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
resp = fetch(
|
|
270
|
+
_AGENT_ENDPOINT,
|
|
271
|
+
{"method": "POST", "json": payload, "headers": {"Accept": "application/json"}},
|
|
272
|
+
timeout=options.get("timeout", 120),
|
|
273
|
+
)
|
|
274
|
+
if resp["status"] >= 400:
|
|
275
|
+
raise RuntimeError("Agent call failed (HTTP %s): %s" % (resp["status"], resp["text"][:200]))
|
|
276
|
+
body = resp["data"] if isinstance(resp["data"], dict) else {}
|
|
277
|
+
if "error" in body:
|
|
278
|
+
raise RuntimeError("Agent error: %s" % body["error"])
|
|
279
|
+
task = (body.get("result") or {}).get("task") or {}
|
|
280
|
+
state = task.get("state")
|
|
281
|
+
if state == "failed":
|
|
282
|
+
raise RuntimeError("Agent task failed: %s" % task.get("error", "unknown"))
|
|
283
|
+
parts = (task.get("result") or {}).get("parts") or []
|
|
284
|
+
texts = [p.get("text", "") for p in parts if p.get("kind") == "text" or p.get("type") == "text"]
|
|
285
|
+
return "".join(texts)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
agent = _Agent()
|
|
289
|
+
`;
|
|
290
|
+
//# sourceMappingURL=python-prelude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python-prelude.js","sourceRoot":"","sources":["../../../src/automation/python-prelude.ts"],"names":[],"mappings":";AAAA,+DAA+D;AAC/D,gDAAgD;AAChD,+DAA+D;AAC/D,kDAAkD;AAClD,kEAAkE;AAClE,6CAA6C;AAC7C,+DAA+D;;;AAE/D;;;GAGG;AACU,QAAA,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkRpC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { NodeAutomationExecutionResult, NodeAutomationRunOptions, NodeAutomationTaskPackage } from '@cicctencent/agent-core';
|
|
2
|
+
export interface PythonAutomationRuntimeOptions {
|
|
3
|
+
baseDir?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class PythonAutomationRuntimeRunner {
|
|
6
|
+
private readonly baseDir;
|
|
7
|
+
constructor(options?: PythonAutomationRuntimeOptions);
|
|
8
|
+
execute(taskPackage: NodeAutomationTaskPackage, options: NodeAutomationRunOptions): Promise<NodeAutomationExecutionResult>;
|
|
9
|
+
/** 剥离 stdout 中的内部结果/错误标记行,保留用户 print 输出 */
|
|
10
|
+
private stripMarkerLines;
|
|
11
|
+
private buildEnv;
|
|
12
|
+
private buildBootstrapScript;
|
|
13
|
+
private parseRuntimeResult;
|
|
14
|
+
private parseRuntimeError;
|
|
15
|
+
}
|
|
16
|
+
export declare function getPythonAutomationRuntimeRunner(): PythonAutomationRuntimeRunner;
|
|
17
|
+
//# sourceMappingURL=python-task-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python-task-runtime.d.ts","sourceRoot":"","sources":["../../../src/automation/python-task-runtime.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,6BAA6B,EAE7B,wBAAwB,EACxB,yBAAyB,EAC1B,MAAM,yBAAyB,CAAC;AAMjC,MAAM,WAAW,8BAA8B;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAYD,qBAAa,6BAA6B;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,GAAE,8BAAmC;IAIlD,OAAO,CACX,WAAW,EAAE,yBAAyB,EACtC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,6BAA6B,CAAC;IAmEzC,2CAA2C;IAC3C,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,QAAQ;IAahB,OAAO,CAAC,oBAAoB;IAoF5B,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,iBAAiB;CAU1B;AAID,wBAAgB,gCAAgC,IAAI,6BAA6B,CAGhF"}
|