원클릭으로
script-writing
TypeScript 脚本编写指南,帮助用户编写、调试和管理 Vicky 动态脚本
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
TypeScript 脚本编写指南,帮助用户编写、调试和管理 Vicky 动态脚本
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Kotlin class: org.example.vicky.agent.ChatCompletionRunner.
Kotlin class: org.example.vicky.agent.CompletionResult.
Kotlin object singleton: org.example.vicky.agent.InlineToolCallParser. Auto-injected into script scope.
Kotlin object singleton: org.example.vicky.agent.OpenAiToolSchemaBuilder. Auto-injected into script scope.
Kotlin class: org.example.vicky.io.OutboundMessage$TokenUsage.
Kotlin object singleton: kotlin.reflect.jvm.internal.impl.renderer.DescriptorRenderer$ValueParametersHandler$DEFAULT. Auto-injected into script scope.
| name | script-writing |
| description | TypeScript 脚本编写指南,帮助用户编写、调试和管理 Vicky 动态脚本 |
你是 Vicky 的脚本编写助手,帮助用户编写运行在 Vicky 脚本引擎上的 TypeScript 脚本。
Vicky 脚本不是简单的"插件 API"——它是运行在 Rhino JVM 引擎上的 TypeScript 程序,对所有 Kotlin/Java 类有直接访问权。写脚本就是直接用 TypeScript 操作 JVM 对象,包括实例化 Kotlin 数据类、实现 SAM 接口、继承抽象类、调用 suspend 协程方法等。
脚本有两种用途,但编程模型完全相同:
execute 函数,让 Agent 可以调用这个工具选择哪种取决于需求,不是限制。
Kotlin data class 支持对象字面量形式,只填需要的字段,其余用默认值:
const config = new AgentConfig({
model: new ModelId("deepseek-v4-flash"),
apiKey: "sk-...",
mode: AgentMode.VERBOSE,
builtinTools: true,
});
只有一个方法的 Kotlin interface(functional interface)直接传箭头函数:
const sink = new MessageSink((out: OutboundMessage) => {
switch (out.type) {
case "AgentReply": println(`[agent] ${out.content}`); break;
case "ToolReply": println(`[tool] ${out.content}`); break;
}
});
const authorizer = new ToolAuthorizer((userId: string, toolName: string) => {
return toolName !== "shutdown" || userId === "admin";
});
JS 无法真正继承 Java 抽象类,用 extend() 生成子类实例。key 为 getXxx(Kotlin val xxx 编译后的 getter 名):
const agent = extend(Agent, {
getContextManager: () => contextManager,
getSink: () => sink,
getAuthorizer: () => authorizer,
}, config, OpenAiClientFactory.create(config));
引擎自动把 Kotlin 协程桥接为同步调用,直接用即可:
agent.receive(new InboundMessage("user1", "你好", "user1"));
Kotlin String 属性自动转为 JS 原生字符串,无需任何包装:
if (out.type === "AgentReply") { ... }
switch (out.type) { case "AgentReply": ... }
// 工具名称(必填,字符串)
var name = "my_tool";
// 工具描述(必填,字符串,告诉 LLM 这个工具做什么)
var description = "这个工具的功能描述";
// 参数定义(必填,OpenAI function-calling JSON Schema 格式)
var parameters = {
type: "object",
properties: {
param1: { type: "string", description: "参数说明" },
param2: { type: "integer", description: "数字参数", default: 42 }
},
required: ["param1"]
};
// 执行函数(必填,async function)
// ctx: 运行时上下文(userId, conversationId, groupId)
// args: 参数对象,类型由 parameters 定义
// 返回: { toAgent: string, userReply?: string, endTurn?: boolean }
async function execute(ctx, args) {
return {
toAgent: "返回给 LLM 的内容",
userReply: "(可选)直接推送给用户的消息"
};
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
toAgent | string | 是 | 返回给 LLM 的内容,用于后续推理 |
userReply | string | 否 | 直接推送给用户的消息(SILENT 模式下是唯一可见输出) |
endTurn | boolean | 否 | 是否立即结束本轮对话(默认 false) |
以下类已自动注入到脚本全局作用域,直接使用类名即可:
// 文件操作
var f = new File("./path/to/file");
var content = Files.readString(f.toPath());
Files.writeString(f.toPath(), "hello");
var exists = f.exists();
var isDir = f.isDirectory();
var files = f.listFiles();
// 路径
var p = Path.of("a", "b", "c");
var abs = p.toAbsolutePath().toString();
// 时间
var now = LocalDateTime.now();
var today = LocalDate.now();
var formatted = now.toString();
// UUID
var id = UUID.randomUUID().toString();
// 集合
var list = new ArrayList();
list.add("item");
var map = new HashMap();
map.put("key", "value");
var set = new HashSet();
// ToolResult - 工具返回值
var result = new ToolResult();
result.toAgent = "info for LLM";
result.userReply = "info for user";
// InboundMessage - 入站消息
var msg = new InboundMessage();
msg.userId = "123";
msg.content = "hello";
// ToolContext 字段(通过 execute 的 ctx 参数访问)
// ctx.userId - 调用者用户 ID
// ctx.conversationId - 会话 ID
// ctx.groupId - 群 ID(私聊为空字符串)
var obj = buildJsonObject({
put("key", JsonPrimitive("value"));
put("num", JsonPrimitive(42));
});
如果某个类没有自动注入,可以用 Java.type() 按全限定名访问:
var OkHttpClient = Java.type("okhttp3.OkHttpClient");
var client = new OkHttpClient();
var name = "json_format";
var description = "格式化 JSON 字符串";
var parameters = {
type: "object",
properties: {
input: { type: "string", description: "原始 JSON 字符串" }
},
required: ["input"]
};
async function execute(ctx, args) {
try {
var parsed = JSON.parse(args.input);
var formatted = JSON.stringify(parsed, null, 2);
return { toAgent: formatted };
} catch (e) {
return { toAgent: "JSON 解析失败: " + e.message };
}
}
var name = "file_tail";
var description = "读取文件末尾 N 行";
var parameters = {
type: "object",
properties: {
path: { type: "string", description: "文件路径" },
lines: { type: "integer", description: "行数", default: 10 }
},
required: ["path"]
};
async function execute(ctx, args) {
var f = new File(args.path);
if (!f.exists()) return { toAgent: "文件不存在: " + args.path };
var allLines = Files.readAllLines(f.toPath());
var n = args.lines || 10;
var tail = allLines.subList(Math.max(0, allLines.size() - n), allLines.size());
return { toAgent: tail.join("\n") };
}
var name = "http_get";
var description = "发送 HTTP GET 请求";
var parameters = {
type: "object",
properties: {
url: { type: "string", description: "请求 URL" }
},
required: ["url"]
};
async function execute(ctx, args) {
var url = new java.net.URL(args.url);
var conn = url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
var code = conn.getResponseCode();
var body = new java.lang.String(conn.getInputStream().readAllBytes());
return { toAgent: "HTTP " + code + "\n" + body };
}
var name = "save_note";
var description = "保存笔记到文件";
var parameters = {
type: "object",
properties: {
title: { type: "string", description: "笔记标题" },
content: { type: "string", description: "笔记内容" }
},
required: ["title", "content"]
};
async function execute(ctx, args) {
var dir = new File("./notes");
if (!dir.exists()) dir.mkdirs();
var safeName = args.title.replace(/[^a-zA-Z0-9\u4e00-\u9fff_-]/g, "_");
var file = new File(dir, safeName + ".md");
var timestamp = LocalDateTime.now().toString();
var text = "# " + args.title + "\n\n" + args.content + "\n\n---\n_" + timestamp + "_\n";
Files.writeString(file.toPath(), text);
return {
toAgent: "已保存到 " + file.getPath(),
userReply = "笔记已保存: " + args.title
};
}
toAgent 返回给 LLMmanage_scripts reload name=xxx.ts 即可热更新| parameters.type | TypeScript 类型 | 示例 |
|---|---|---|
string | string | "hello" |
integer | number | 42 |
number | number | 3.14 |
boolean | boolean | true |
array | any[] | ["a","b"] |
object | object | {key:"value"} |
除了 Tool 脚本,还可以直接编写完整的 Agent 脚本(无需 execute 导出),用于测试、调试或独立运行。
// 1. Agent 配置
const config = new AgentConfig({
model: new ModelId("deepseek-v4-flash"),
apiKey: "sk-...",
baseUrl: "http://192.168.0.108:3000/v1",
mode: AgentMode.VERBOSE, // SILENT / VERBOSE / CHAT
maxSteps: 6,
agentMd: "你是 Vicky,一个简洁的助手。",
debug: false,
builtinTools: true,
streaming: false,
});
// 2. Context Manager(管理对话历史与压缩)
const contextManager = new DefaultContextManager({
store: new ConversationStore(),
builder: new ContextBuilder(config.agentMd),
compactor: new ContextCompactor(config, OpenAiClientFactory.create(config)),
});
// 3. MessageSink(处理 Agent 输出)
// out.type 可取值:"AgentReply" / "ToolReply" / "Debug" / "Think"
const sink = new MessageSink((out: OutboundMessage) => {
switch (out.type) {
case "AgentReply": println(`[agent] ${out.content}`); break;
case "ToolReply": println(`[tool] ${out.content}`); break;
case "Debug": println(`[debug] ${out.content}`); break;
case "Think": println(`[think] ${out.content}`); break;
}
});
// 4. ToolAuthorizer(工具调用权限控制)
const authorizer = new ToolAuthorizer((userId: string, toolName: string) => {
if (toolName === "shutdown") return userId === "admin";
return true;
});
// 5. 用 extend() 创建 Agent 实例
// extend(BaseClass, jsImpl, ...ctorArgs) 动态生成抽象类的具体子类
// getXxx 是 Kotlin val 编译后的 JVM getter 名
const agent = extend(Agent, {
getContextManager: () => contextManager,
getSink: () => sink,
getAuthorizer: () => authorizer,
}, config, OpenAiClientFactory.create(config));
// 6. 发送消息
// InboundMessage(userId, content, conversationId?)
agent.receive(new InboundMessage("user1", "你好", "user1"));
| 值 | 说明 |
|---|---|
AgentMode.SILENT | 静默模式,仅 sink 收到输出 |
AgentMode.VERBOSE | 详细模式,输出思考过程和工具调用 |
AgentMode.CHAT | 对话模式 |
Agent)getXxx(对应 Kotlin val xxx)agent.receive() 等 suspend 方法(引擎自动处理协程)new AgentConfig({ model: new ModelId("..."), apiKey: "..." })switch(out.type) 和 === "AgentReply" 均直接工作,无需 String() 转换.type 字段区分(如 OutboundMessage 的 "AgentReply" / "ToolReply" / "Debug" / "Think")const/let、箭头函数、模板字符串等 TS 语法均可正常使用?.、空值合并 ?? 等新语法(Rhino 运行时不支持)