@zhin.js/plugin-code-runner 0.0.1

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/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/lib/index.js ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @zhin.js/plugin-code-runner
3
+ *
4
+ * 沙箱代码执行插件 —— 通过 glot.io API 安全运行代码片段
5
+ *
6
+ * 功能:
7
+ * - AI 工具 run_code:AI 可调用执行代码
8
+ * - 命令「运行 <language> <code>」:用户直接运行代码
9
+ *
10
+ * 安全:
11
+ * - 代码长度上限 10000 字符
12
+ * - 语言白名单校验
13
+ * - 不执行本地代码,全部通过 glot.io 远程沙箱
14
+ */
15
+ import { usePlugin, MessageCommand, ZhinTool } from 'zhin.js';
16
+ const { addCommand, addTool, logger } = usePlugin();
17
+ // ─── 常量 ────────────────────────────────────────────────────────────────────
18
+ const MAX_CODE_LENGTH = 10000;
19
+ const SUPPORTED_LANGUAGES = {
20
+ python: 'main.py',
21
+ javascript: 'main.js',
22
+ typescript: 'main.ts',
23
+ go: 'main.go',
24
+ rust: 'main.rs',
25
+ java: 'Main.java',
26
+ c: 'main.c',
27
+ cpp: 'main.cpp',
28
+ ruby: 'main.rb',
29
+ php: 'main.php',
30
+ };
31
+ const GLOT_API_BASE = 'https://glot.io/api/run';
32
+ async function runCode(language, code) {
33
+ const lang = language.toLowerCase().trim();
34
+ if (!SUPPORTED_LANGUAGES[lang]) {
35
+ return {
36
+ stdout: '',
37
+ stderr: '',
38
+ error: `不支持的语言: ${lang}。支持: ${Object.keys(SUPPORTED_LANGUAGES).join(', ')}`,
39
+ };
40
+ }
41
+ if (code.length > MAX_CODE_LENGTH) {
42
+ return {
43
+ stdout: '',
44
+ stderr: '',
45
+ error: `代码长度超过上限 ${MAX_CODE_LENGTH} 字符(当前 ${code.length})`,
46
+ };
47
+ }
48
+ const fileName = SUPPORTED_LANGUAGES[lang];
49
+ const url = `${GLOT_API_BASE}/${lang}/latest`;
50
+ try {
51
+ const response = await fetch(url, {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify({
55
+ files: [{ name: fileName, content: code }],
56
+ }),
57
+ signal: AbortSignal.timeout(30_000),
58
+ });
59
+ if (!response.ok) {
60
+ return {
61
+ stdout: '',
62
+ stderr: '',
63
+ error: `API 请求失败: ${response.status} ${response.statusText}`,
64
+ };
65
+ }
66
+ const data = (await response.json());
67
+ return {
68
+ stdout: data.stdout || '',
69
+ stderr: data.stderr || '',
70
+ error: data.error || '',
71
+ };
72
+ }
73
+ catch (e) {
74
+ const msg = e?.name === 'TimeoutError' ? '请求超时(30s)' : e?.message || '未知错误';
75
+ return { stdout: '', stderr: '', error: msg };
76
+ }
77
+ }
78
+ function formatResult(result) {
79
+ const parts = [];
80
+ if (result.stdout)
81
+ parts.push(`[stdout]\n${result.stdout}`);
82
+ if (result.stderr)
83
+ parts.push(`[stderr]\n${result.stderr}`);
84
+ if (result.error)
85
+ parts.push(`[error]\n${result.error}`);
86
+ return parts.length ? parts.join('\n') : '(无输出)';
87
+ }
88
+ // ─── AI Tool: run_code ───────────────────────────────────────────────────────
89
+ addTool(new ZhinTool('run_code')
90
+ .desc('在沙箱中运行代码片段,返回 stdout/stderr/error')
91
+ .keyword('运行代码', '执行代码', 'run code', 'execute', '代码')
92
+ .tag('code', 'run', 'execute', 'sandbox')
93
+ .param('language', { type: 'string', description: '编程语言(python/javascript/typescript/go/rust/java/c/cpp/ruby/php)' }, true)
94
+ .param('code', { type: 'string', description: '要执行的代码' }, true)
95
+ .execute(async (args) => {
96
+ const result = await runCode(args.language, args.code);
97
+ return formatResult(result);
98
+ })
99
+ .toTool());
100
+ // ─── 命令: 运行 ─────────────────────────────────────────────────────────────
101
+ addCommand(new MessageCommand('运行 <language:text> <code:text>')
102
+ .desc('在沙箱中运行代码片段')
103
+ .action(async (_message, result) => {
104
+ const { language, code } = result.params;
105
+ logger.info(`运行代码: language=${language}, length=${code.length}`);
106
+ const res = await runCode(language, code);
107
+ return formatResult(res);
108
+ }));
109
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@zhin.js/plugin-code-runner",
3
+ "version": "0.0.1",
4
+ "description": "Sandboxed code execution plugin for Zhin.js — run code snippets via AI or command",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "types": "./lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "development": "./src/index.ts",
12
+ "import": "./lib/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": ["src", "lib", "README.md"],
17
+ "scripts": {
18
+ "build": "tsc --build",
19
+ "clean": "rimraf lib"
20
+ },
21
+ "keywords": ["zhin", "zhin.js", "bot", "plugin", "code-runner", "sandbox", "execute"],
22
+ "author": { "name": "lc-cn", "email": "admin@liucl.cn", "url": "https://github.com/lc-cn" },
23
+ "license": "MIT",
24
+ "dependencies": {},
25
+ "devDependencies": { "typescript": "^5.9.3", "zhin.js": "workspace:*" },
26
+ "peerDependencies": { "zhin.js": ">=1.0.60" },
27
+ "repository": { "type": "git", "url": "git+https://github.com/zhinjs/zhin.git", "directory": "plugins/utils/code-runner" },
28
+ "publishConfig": {
29
+ "access": "public",
30
+ "registry": "https://registry.npmjs.org"
31
+ }
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @zhin.js/plugin-code-runner
3
+ *
4
+ * 沙箱代码执行插件 —— 通过 glot.io API 安全运行代码片段
5
+ *
6
+ * 功能:
7
+ * - AI 工具 run_code:AI 可调用执行代码
8
+ * - 命令「运行 <language> <code>」:用户直接运行代码
9
+ *
10
+ * 安全:
11
+ * - 代码长度上限 10000 字符
12
+ * - 语言白名单校验
13
+ * - 不执行本地代码,全部通过 glot.io 远程沙箱
14
+ */
15
+ import { usePlugin, MessageCommand, ZhinTool } from 'zhin.js'
16
+
17
+ const { addCommand, addTool, logger } = usePlugin()
18
+
19
+ // ─── 常量 ────────────────────────────────────────────────────────────────────
20
+
21
+ const MAX_CODE_LENGTH = 10000
22
+
23
+ const SUPPORTED_LANGUAGES: Record<string, string> = {
24
+ python: 'main.py',
25
+ javascript: 'main.js',
26
+ typescript: 'main.ts',
27
+ go: 'main.go',
28
+ rust: 'main.rs',
29
+ java: 'Main.java',
30
+ c: 'main.c',
31
+ cpp: 'main.cpp',
32
+ ruby: 'main.rb',
33
+ php: 'main.php',
34
+ }
35
+
36
+ const GLOT_API_BASE = 'https://glot.io/api/run'
37
+
38
+ // ─── 核心执行函数 ────────────────────────────────────────────────────────────
39
+
40
+ interface RunResult {
41
+ stdout: string
42
+ stderr: string
43
+ error: string
44
+ }
45
+
46
+ async function runCode(language: string, code: string): Promise<RunResult> {
47
+ const lang = language.toLowerCase().trim()
48
+
49
+ if (!SUPPORTED_LANGUAGES[lang]) {
50
+ return {
51
+ stdout: '',
52
+ stderr: '',
53
+ error: `不支持的语言: ${lang}。支持: ${Object.keys(SUPPORTED_LANGUAGES).join(', ')}`,
54
+ }
55
+ }
56
+
57
+ if (code.length > MAX_CODE_LENGTH) {
58
+ return {
59
+ stdout: '',
60
+ stderr: '',
61
+ error: `代码长度超过上限 ${MAX_CODE_LENGTH} 字符(当前 ${code.length})`,
62
+ }
63
+ }
64
+
65
+ const fileName = SUPPORTED_LANGUAGES[lang]
66
+ const url = `${GLOT_API_BASE}/${lang}/latest`
67
+
68
+ try {
69
+ const response = await fetch(url, {
70
+ method: 'POST',
71
+ headers: { 'Content-Type': 'application/json' },
72
+ body: JSON.stringify({
73
+ files: [{ name: fileName, content: code }],
74
+ }),
75
+ signal: AbortSignal.timeout(30_000),
76
+ })
77
+
78
+ if (!response.ok) {
79
+ return {
80
+ stdout: '',
81
+ stderr: '',
82
+ error: `API 请求失败: ${response.status} ${response.statusText}`,
83
+ }
84
+ }
85
+
86
+ const data = (await response.json()) as RunResult
87
+ return {
88
+ stdout: data.stdout || '',
89
+ stderr: data.stderr || '',
90
+ error: data.error || '',
91
+ }
92
+ } catch (e: any) {
93
+ const msg = e?.name === 'TimeoutError' ? '请求超时(30s)' : e?.message || '未知错误'
94
+ return { stdout: '', stderr: '', error: msg }
95
+ }
96
+ }
97
+
98
+ function formatResult(result: RunResult): string {
99
+ const parts: string[] = []
100
+ if (result.stdout) parts.push(`[stdout]\n${result.stdout}`)
101
+ if (result.stderr) parts.push(`[stderr]\n${result.stderr}`)
102
+ if (result.error) parts.push(`[error]\n${result.error}`)
103
+ return parts.length ? parts.join('\n') : '(无输出)'
104
+ }
105
+
106
+ // ─── AI Tool: run_code ───────────────────────────────────────────────────────
107
+
108
+ addTool(
109
+ new ZhinTool('run_code')
110
+ .desc('在沙箱中运行代码片段,返回 stdout/stderr/error')
111
+ .keyword('运行代码', '执行代码', 'run code', 'execute', '代码')
112
+ .tag('code', 'run', 'execute', 'sandbox')
113
+ .param('language', { type: 'string', description: '编程语言(python/javascript/typescript/go/rust/java/c/cpp/ruby/php)' }, true)
114
+ .param('code', { type: 'string', description: '要执行的代码' }, true)
115
+ .execute(async (args) => {
116
+ const result = await runCode(args.language, args.code)
117
+ return formatResult(result)
118
+ })
119
+ .toTool(),
120
+ )
121
+
122
+ // ─── 命令: 运行 ─────────────────────────────────────────────────────────────
123
+
124
+ addCommand(
125
+ new MessageCommand('运行 <language:text> <code:text>')
126
+ .desc('在沙箱中运行代码片段')
127
+ .action(async (_message, result) => {
128
+ const { language, code } = result.params as { language: string; code: string }
129
+ logger.info(`运行代码: language=${language}, length=${code.length}`)
130
+ const res = await runCode(language, code)
131
+ return formatResult(res)
132
+ }),
133
+ )