Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ───────────────────────────────────────────
Expand Down
4 changes: 2 additions & 2 deletions crates/tw-gateway/src/plugin/defaults/manifests.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
}
]
},
"sha256": "f34e95b9db72c98eadac320f7c3f7af9acb8beae9fa3acd8eda1d0459e18bb6e"
"sha256": "30ca8624472ebdbeb6793b19226402ff3934ff832fb690e2ed1b513496e49322"
},
"wsl-paths": {
"manifest": {
Expand Down Expand Up @@ -68,6 +68,6 @@
}
]
},
"sha256": "6aaf3646438f024a6482413eca7fe9f17230d3f5177458476d070485cb333a99"
"sha256": "06adbac883676e7587394437c4f483e2020449515a62fb2db69f682495b303df"
}
}
8 changes: 4 additions & 4 deletions crates/tw-gateway/src/plugin/defaults/reply-language.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
// 权限:system,只读写系统提示词。
// 设置:回答语言,装上时是简体中文。出错时(比如语言名写得不对)拒绝这个请求。
//
// 附加用的是辅助函数 tw.appendSystem:和原文之间空一行,原文已经有这句就不再附加;改的是
// 传入的 req,不用返回。
//
// manifest 是纯数据,写成 core 改写它时的样子:界面改设置时只换这一段,改出来的和原来
// 只差改了的那一行。
//
Expand Down Expand Up @@ -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.`);
}
39 changes: 9 additions & 30 deletions crates/tw-gateway/src/plugin/defaults/wsl-paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
//
// 设置只有一个开关:客户端在哪一侧。设置改不出任何别的改写。
//
// 遍历和复制交给辅助函数:tw.toolCalls 逐个交出对话历史里的工具调用,tw.mapStrings 复制
// 参数、把其中每个字符串换一遍(参数里有 __proto__ 这样的键也照常复制)。改的是传入的
// 对象,不用返回;什么都没换时请求原样发出,一个字节都不动。
//
// 权限:messages(对话历史),reply.tool_calls(回答里的工具调用)。reply.tool_calls 是
// 高风险权限:插件能改动模型要执行的操作;改过的工具调用照样经过 Lite 的工具调用审查。
// 设置:客户端运行在 Windows 上(关闭时按客户端在 WSL 里处理)。出错时拒绝这个请求。
Expand Down Expand Up @@ -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));
}
15 changes: 10 additions & 5 deletions crates/tw-gateway/src/plugin/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Option<String>>;
fn on_text(&mut self, lane: Lane, text: &str) -> Invocation<Option<String>>;
/// 流式一块文字结束。`None` 是什么都不补
fn on_text_end(&mut self) -> Invocation<Option<String>>;
fn on_text_end(&mut self, lane: Lane) -> Invocation<Option<String>>;
fn on_tool_call(&mut self, call: Value) -> Invocation<ToolCallOutcome>;
}

Expand Down Expand Up @@ -302,19 +306,20 @@ pub mod double {
type EndFn = dyn FnMut() -> Invocation<Option<String>> + Send;
type ToolFn = dyn FnMut(Value) -> Invocation<ToolCallOutcome> + Send;

/// 一个回答实例的替身:三个闭包,可以带状态(流式扣住文字的插件就要)。
/// 一个回答实例的替身:三个闭包,可以带状态(流式扣住文字的插件就要)。块编号不交给
/// 闭包:插件代码本来就看不到它
pub struct Closures {
pub text: Box<TextFn>,
pub end: Box<EndFn>,
pub tool: Box<ToolFn>,
}

impl ReplyHost for Closures {
fn on_text(&mut self, text: &str) -> Invocation<Option<String>> {
fn on_text(&mut self, _lane: Lane, text: &str) -> Invocation<Option<String>> {
(self.text)(text)
}

fn on_text_end(&mut self) -> Invocation<Option<String>> {
fn on_text_end(&mut self, _lane: Lane) -> Invocation<Option<String>> {
(self.end)()
}

Expand Down
Loading
Loading