diff --git a/Cargo.lock b/Cargo.lock
index f871a3b8..69b9599a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3143,7 +3143,7 @@ dependencies = [
[[package]]
name = "tw-api"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"serde",
"serde_json",
@@ -3154,7 +3154,7 @@ dependencies = [
[[package]]
name = "tw-bedrock"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -3175,7 +3175,7 @@ dependencies = [
[[package]]
name = "tw-breaker"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"serde",
"serde_json",
@@ -3183,7 +3183,7 @@ dependencies = [
[[package]]
name = "tw-config"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"blake3",
"libc",
@@ -3208,7 +3208,7 @@ dependencies = [
[[package]]
name = "tw-control"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"axum",
"base64",
@@ -3251,7 +3251,7 @@ dependencies = [
[[package]]
name = "tw-dialect"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"memchr",
"serde",
@@ -3260,7 +3260,7 @@ dependencies = [
[[package]]
name = "tw-engine"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"chrono",
"serde",
@@ -3274,7 +3274,7 @@ dependencies = [
[[package]]
name = "tw-gateway"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"arc-swap",
"async-stream",
@@ -3325,7 +3325,7 @@ dependencies = [
[[package]]
name = "tw-guard"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"aho-corasick",
"base64",
@@ -3343,7 +3343,7 @@ dependencies = [
[[package]]
name = "tw-link"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"serde",
"serde_json",
@@ -3357,7 +3357,7 @@ dependencies = [
[[package]]
name = "tw-observe"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"tokio",
"tracing",
@@ -3366,7 +3366,7 @@ dependencies = [
[[package]]
name = "tw-plugin"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"libc",
"rand 0.10.2",
@@ -3381,7 +3381,7 @@ dependencies = [
[[package]]
name = "tw-pricing"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"arc-swap",
"flate2",
@@ -3394,14 +3394,14 @@ dependencies = [
[[package]]
name = "tw-secret"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"thiserror",
]
[[package]]
name = "tw-store"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"blake3",
"bytes",
@@ -3421,7 +3421,7 @@ dependencies = [
[[package]]
name = "tw-types"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"serde",
"serde_json",
@@ -3430,7 +3430,7 @@ dependencies = [
[[package]]
name = "tw-watch"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"libc",
"notify",
@@ -3441,7 +3441,7 @@ dependencies = [
[[package]]
name = "tw-yaml"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"saphyr-parser",
"serde_yaml_ng",
@@ -3451,7 +3451,7 @@ dependencies = [
[[package]]
name = "twcore"
-version = "0.74.0"
+version = "0.75.0"
dependencies = [
"anyhow",
"axum",
diff --git a/Cargo.toml b/Cargo.toml
index b48580fd..436260ae 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -54,7 +54,7 @@ categories = ["network-programming", "web-programming::http-server"]
# 二进制走 CalVer,crate 走 SemVer —— 两者是两回事,卖给不同的人。
# 这里是 crate 的版本。
-version = "0.74.0"
+version = "0.75.0"
[workspace.dependencies]
# ── 内部 crate ───────────────────────────────────────────
diff --git a/crates/tw-gateway/src/plugin/defaults/manifests.json b/crates/tw-gateway/src/plugin/defaults/manifests.json
index 306161cb..7ad66e88 100644
--- a/crates/tw-gateway/src/plugin/defaults/manifests.json
+++ b/crates/tw-gateway/src/plugin/defaults/manifests.json
@@ -32,7 +32,7 @@
}
]
},
- "sha256": "f34e95b9db72c98eadac320f7c3f7af9acb8beae9fa3acd8eda1d0459e18bb6e"
+ "sha256": "30ca8624472ebdbeb6793b19226402ff3934ff832fb690e2ed1b513496e49322"
},
"wsl-paths": {
"manifest": {
@@ -68,6 +68,6 @@
}
]
},
- "sha256": "6aaf3646438f024a6482413eca7fe9f17230d3f5177458476d070485cb333a99"
+ "sha256": "06adbac883676e7587394437c4f483e2020449515a62fb2db69f682495b303df"
}
}
diff --git a/crates/tw-gateway/src/plugin/defaults/reply-language.js b/crates/tw-gateway/src/plugin/defaults/reply-language.js
index 03489878..63d18ee1 100644
--- a/crates/tw-gateway/src/plugin/defaults/reply-language.js
+++ b/crates/tw-gateway/src/plugin/defaults/reply-language.js
@@ -9,6 +9,9 @@
// 权限:system,只读写系统提示词。
// 设置:回答语言,装上时是简体中文。出错时(比如语言名写得不对)拒绝这个请求。
//
+// 附加用的是辅助函数 tw.appendSystem:和原文之间空一行,原文已经有这句就不再附加;改的是
+// 传入的 req,不用返回。
+//
// manifest 是纯数据,写成 core 改写它时的样子:界面改设置时只换这一段,改出来的和原来
// 只差改了的那一行。
//
@@ -36,8 +39,5 @@ export function onRequest(req, ctx) {
"The answer language setting accepts only the name of a language, such as English or Deutsch.",
);
}
- const line = `Always respond in ${language}, unless the user explicitly asks for another language.`;
- if (req.system.includes(line)) return undefined;
- req.system = req.system ? `${req.system}\n\n${line}` : line;
- return req;
+ tw.appendSystem(req, `Always respond in ${language}, unless the user explicitly asks for another language.`);
}
diff --git a/crates/tw-gateway/src/plugin/defaults/wsl-paths.js b/crates/tw-gateway/src/plugin/defaults/wsl-paths.js
index d6c76e37..5e9285bd 100644
--- a/crates/tw-gateway/src/plugin/defaults/wsl-paths.js
+++ b/crates/tw-gateway/src/plugin/defaults/wsl-paths.js
@@ -13,6 +13,10 @@
//
// 设置只有一个开关:客户端在哪一侧。设置改不出任何别的改写。
//
+// 遍历和复制交给辅助函数:tw.toolCalls 逐个交出对话历史里的工具调用,tw.mapStrings 复制
+// 参数、把其中每个字符串换一遍(参数里有 __proto__ 这样的键也照常复制)。改的是传入的
+// 对象,不用返回;什么都没换时请求原样发出,一个字节都不动。
+//
// 权限:messages(对话历史),reply.tool_calls(回答里的工具调用)。reply.tool_calls 是
// 高风险权限:插件能改动模型要执行的操作;改过的工具调用照样经过 Lite 的工具调用审查。
// 设置:客户端运行在 Windows 上(关闭时按客户端在 WSL 里处理)。出错时拒绝这个请求。
@@ -52,39 +56,14 @@ function convert(value, windows) {
return m ? `/mnt/${m[1].toLowerCase()}${(m[2] ?? "/").replaceAll("\\", "/")}` : value;
}
-function rewrite(value, windows) {
- if (typeof value === "string") return convert(value, windows);
- if (Array.isArray(value)) return value.map((item) => rewrite(item, windows));
- if (value !== null && typeof value === "object") {
- // fromEntries 按原样建出每个键,参数里有 __proto__ 这样的键也不会出错
- return Object.fromEntries(
- Object.entries(value).map(([key, item]) => [key, rewrite(item, windows)]),
- );
- }
- return value;
-}
-
-const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
-
export function onRequest(req, ctx) {
const windows = ctx.settings.windows_client === true;
- let changed = false;
- for (const m of req.messages) {
- for (const p of m.parts) {
- if (p.type !== "tool_call") continue;
- const input = rewrite(p.input, windows);
- if (!same(input, p.input)) {
- p.input = input;
- changed = true;
- }
- }
- }
- // 没改就不返回:请求原样发出,一个字节都不动
- return changed ? req : undefined;
+ tw.toolCalls(req, (call) => {
+ call.input = tw.mapStrings(call.input, (s) => convert(s, windows));
+ });
}
export function onToolCall(call, ctx) {
- const input = rewrite(call.input, ctx.settings.windows_client === true);
- if (same(input, call.input)) return undefined;
- return { id: call.id, name: call.name, input };
+ const windows = ctx.settings.windows_client === true;
+ call.input = tw.mapStrings(call.input, (s) => convert(s, windows));
}
diff --git a/crates/tw-gateway/src/plugin/host.rs b/crates/tw-gateway/src/plugin/host.rs
index fd750266..12bbd641 100644
--- a/crates/tw-gateway/src/plugin/host.rs
+++ b/crates/tw-gateway/src/plugin/host.rs
@@ -10,6 +10,7 @@ use std::time::Duration;
use serde_json::Value;
use crate::plugin::engine::Manifest;
+use crate::plugin::reply::Lane;
use crate::plugin::set::LogLine;
pub trait PluginHost: Send + Sync {
@@ -43,11 +44,14 @@ pub trait PluginHost: Send + Sync {
}
/// 一次回答的插件实例。**只给这一次回答用**。
+///
+/// `lane` 是这块文字在这次回答里的编号([`Lane`]):同时进行的几块文字分开缓冲。它只交给
+/// 沙箱里的 `tw.streamReplace`,插件代码看不到
pub trait ReplyHost: Send {
/// `None` 是没改
- fn on_text(&mut self, text: &str) -> Invocation