灵渠 LingQu
中间件管道与扩展点 SDK

用 TypeScript 编写插件

以 TypeScript 的 AssemblyScript 兼容子集编写、经 asc 编译为 WASM 沙箱可加载单元——沙箱 ABI 与线性内存约定、@json 契约类型、出站脱敏实战与逐 chunk 处理、构建注册、限制与失败处置,面向 TS 团队的字符串治理场景

用 TypeScript 为灵渠平台编写插件,走的是 沙箱可加载单元 形态——以 TypeScript 的 AssemblyScript 兼容子集编写业务逻辑,经 asc 编译为 .wasm,跨边界以 JSON 交换、隔离执行、内存受限,单一产物跨环境加载。TypeScript 无法编译为原生 .so,因此本页所有路径都落在沙箱形态。若你的治理逻辑对延迟极敏感、或需要原生外部 IO,应改用原生形态,参见 用 Go 编写原生插件

本页定位。 本页是 扩展点 SDK 参考自定义插件开发 在 TypeScript 上的语言落地指南——只讲「照着 TS 写出可加载 .wasm 插件」的具体路径,概念与契约(前置 / 后置对称 Hook、order、短路、逐 chunk、fail-open / fail-closed、能力边界)不再重述,需要时一句话回指。沙箱 ABI 与通用 host-guest 约定以 用 WebAssembly 编写插件 为权威,本页与其共用同一套约定。

1. 概述:为什么用 TypeScript 写插件

TypeScript 插件的目标读者,是已经用 TS 维护业务代码、希望复用同一套类型思维来写治理逻辑的团队。它的取舍很清晰:

维度TypeScript(沙箱 / WASM)原生(Go / .so
编写语言TS 的 AssemblyScript 兼容子集Go
产物单一 .wasm平台专属 .so
跨环境移植单产物处处可加载须按目标环境分别构建
构建对齐无要求须与平台运行环境构建参数对齐
隔离性沙箱隔离、内存受限与平台同进程
执行开销含 JSON 跨边界序列化开销最低(同进程内调用)
外部 IO无(沙箱内不可直接外联)可(同进程能力)

一句话取舍:需要跨多套部署环境分发、希望沙箱隔离、逻辑以字符串 / JSON 处置为主(校验、脱敏、改写提示)——选 TypeScript;需要原生外部连接、对延迟极敏感、且部署环境可控——选 Go 原生形态。两种形态对外导出的符号语义一致,业务逻辑可在二者间平移。

2. 加载形态:沙箱可加载单元

TypeScript 插件落在 自定义插件开发 §4.1 所述的 沙箱可加载单元

  • 跨边界以结构化数据(JSON 字符串)交换,宿主与插件之间不共享内存对象图。
  • 隔离执行、内存受限——插件在独立的线性内存里运行,无法触达宿主进程的其余部分。
  • 单一产物跨环境加载——.wasm 不区分运行环境家族 / 处理器架构 / 构建工具版本,没有原生形态那种「构建参数对齐」要求

这与原生形态正相反:原生单元随平台运行环境、同进程内直接调用,性能最优但要求与平台运行环境构建参数对齐(运行环境家族、处理器架构、构建工具版本一致)。如果这些对齐成本可接受且你需要原生能力,转到 用 Go 编写原生插件

3. 工程脚手架

3.1 安装工具链与初始化

AssemblyScript 工具链与一个 AssemblyScript 兼容的 JSON 序列化库(如 json-as)即可起步:

# 安装编译器与 JSON 序列化库(二选一)
bun add -D assemblyscript json-as
npm install --save-dev assemblyscript json-as

# 初始化 AssemblyScript 脚手架(生成 assembly/ 目录与 asconfig.json)
bunx asinit .
npx asinit .

3.2 目录与配置

asinit 生成的工程结构,调整为如下形态:

plugin-redact/
├── assembly/
│   └── index.ts        # 插件入口:导出 ABI 函数与 Hook
├── build/
│   └── plugin.wasm     # 构建产物(注册时引用)
├── asconfig.json       # 编译目标与选项
└── package.json        # 构建脚本

asconfig.json 启用增量运行时(让 heap.alloc / heap.free 可用)并挂上 json-as 的编译期转换:

{
  "targets": {
    "release": {
      "outFile": "build/plugin.wasm",
      "optimizeLevel": 3,
      "shrinkLevel": 1,
      "noAssert": false
    }
  },
  "options": {
    "runtime": "incremental",
    "exportRuntime": true,
    "transform": ["json-as/transform"]
  }
}

package.json 的构建脚本:

{
  "scripts": {
    "asbuild": "asc assembly/index.ts --target release --transform json-as/transform -o build/plugin.wasm"
  }
}

不设置 bindings 选项,asc 仅产出裸 .wasm、不生成宿主侧 JS / .d.ts 胶水——平台按本页 ABI 直接加载该 .wasmtransform: json-as/transformjson-as 的硬性要求,缺它则 @json 装饰的类不会获得序列化能力。

4. 沙箱 ABI 与内存约定

平台以一套固定的 host-guest ABI 加载 .wasm。插件作者只需照表导出函数、遵守内存约定即可——这套约定与 用 WebAssembly 编写插件(沙箱可加载单元的通用 ABI 权威页)完全一致。

4.1 必须导出的函数

导出函数签名职责
mallocmalloc(size:u32)->u32供宿主写入入参时申请线性内存
freefree(ptr:u32)free(ptr:u32,size:u32)释放申请的内存
get_nameget_name()->u64返回打包指针,指向插件名字符串
initinit(config_ptr:u32,config_len:u32)->i32解析配置、一次性初始化,成功返回 0
pre_hookpre_hook(input_ptr:u32,input_len:u32)->u64前置 Hook,返回打包指针指向出参 JSON
post_hookpost_hook(input_ptr:u32,input_len:u32)->u64后置 Hook,同上
cleanupcleanup()->i32释放资源,成功返回 0

这组小写函数名即抽象契约符号 get_name / init / pre_hook / post_hook / cleanup 在沙箱 ABI 上的落地(见 自定义插件开发 §2),外加沙箱形态特有的 malloc / free 内存管理对。

4.2 线性内存与打包指针

复杂数据一律以 JSON 字符串跨边界。一次 Hook 调用的数据流:

宿主 malloc(len) ──▶ 写入入参 JSON ──▶ 传 ptr/len ──▶ 插件 readString 解析

宿主读取出参后 free(ptr) ◀── 返回打包指针 ◀── malloc+writeString 出参 JSON ◀── 处置

返回值用打包 u64:高 32 位存指针、低 32 位存长度。宿主收到后拆包、按长度读取那段 JSON、再 free 掉这块缓冲:

打包返回值 u64
┌─────────────────┬─────────────────┐
│    高 32 位       │    低 32 位       │
│    指针 ptr       │    长度 len       │
└─────────────────┴─────────────────┘

流式不另设导出函数。扩展点 SDK §2.3,流式响应下 post_hook 会被 多次调用(每个 chunk 一次,stream.is_stream / chunk_index / is_last 标识进度),而不是另设一个单独的 chunk 处理导出。前置 pre_hook 在一次请求中仍只调用一次。

下图给出 host-guest 一次后置调用的完整往返:

malloc 申请入参缓冲; 返回缓冲指针; 写入入参 JSON 后调用 post_hook; readString 解析 治理处置 脱敏; malloc 写出参 JSON; 返回打包指针 高位指针 低位长度; 读取出参 JSON 后 free; 宿主平台; 沙箱插件 wasm;

4.3 readString / writeString 工具函数

字符串跨边界的两个核心工具,用 String.UTF8.encode / decodeload<u8> / store<u8> 实现:

// 高 32 位存指针、低 32 位存长度
function pack(ptr: u32, len: u32): u64 {
  return ((ptr as u64) << 32) | (len as u64);
}

// 从线性内存的 ptr 处读取 len 字节,解码为字符串
function readString(ptr: u32, len: u32): string {
  const buf = new ArrayBuffer(len);
  const dest = changetype<usize>(buf);
  for (let i: u32 = 0; i < len; i++) {
    store<u8>(dest + i, load<u8>(ptr + i));
  }
  return String.UTF8.decode(buf, false);
}

// 把字符串编码为 UTF-8、写入新申请的缓冲、返回打包指针
function writeString(s: string): u64 {
  const buf = String.UTF8.encode(s, false);
  const len = buf.byteLength as u32;
  const ptr = malloc(len);
  const src = changetype<usize>(buf);
  for (let i: u32 = 0; i < len; i++) {
    store<u8>(ptr + i, load<u8>(src + i));
  }
  return pack(ptr, len);
}

malloc / free 直接转交 AssemblyScript 增量运行时的堆:

export function malloc(size: u32): u32 {
  return heap.alloc(size) as u32;
}

export function free(ptr: u32): void {
  heap.free(ptr);
}

5. 契约类型:用 @json 映射 Hook 入参出参

Hook 的入参出参以 @json 装饰的 class 映射,字段名 严格等于 扩展点 SDK §2 的契约 JSON——action / request / short_circuit_response / reject_reason / context,以及 response / usage / stream / block_reason 等。不需要解析的子树(如 messagescontextshort_circuit_response)可用 JSON.Raw 原样透传,省去逐字段建模:

import { JSON } from "json-as";

// ── 前置 Hook 入参(节选关键字段) ──
@json
class RequestBody {
  model: string = "";
  messages: JSON.Raw = JSON.Raw.from("[]"); // 透传,不逐条建模
  stream: bool = false;
}

@json
class Routing {
  complexity: string = "";
  candidate_model: string = "";
}

@json
class PreInput {
  phase: string = "";
  request_id: string = "";
  api_key_id: string = "";
  request: RequestBody = new RequestBody();
  routing: Routing = new Routing();
  context: JSON.Raw = JSON.Raw.from("{}");
}

// ── 前置 Hook 出参 ──
@json
class RejectReason {
  code: string = "";
  message: string = "";
}

@json
class PreOutput {
  action: string = "continue"; // continue | rewrite | short_circuit | reject
  request: JSON.Raw | null = null;
  short_circuit_response: JSON.Raw | null = null;
  reject_reason: RejectReason | null = null;
  context: JSON.Raw = JSON.Raw.from("{}");
}

// ── 后置 Hook 入参(节选关键字段) ──
@json
class ResponseBody {
  model: string = "";
  content: string = "";
  finish_reason: string = "";
}

@json
class StreamInfo {
  is_stream: bool = false;
  chunk_index: i32 = 0;
  is_last: bool = false;
}

@json
class PostInput {
  phase: string = "";
  request_id: string = "";
  api_key_id: string = "";
  response: ResponseBody = new ResponseBody();
  stream: StreamInfo = new StreamInfo();
  context: JSON.Raw = JSON.Raw.from("{}");
}

// ── 后置 Hook 出参 ──
@json
class RedactedResponse {
  content: string = "";
}

@json
class PostOutput {
  action: string = "pass"; // pass | redact | block
  response: RedactedResponse | null = null;
}

action 取值的完整语义见 扩展点 SDK §2:前置 continue / rewrite / short_circuit / reject,后置 pass / redact / block。本页代码原样复用这些字符串,不另造取值。

6. 完整最小示例:直通插件

下面是一个完整、可编译的 assembly/index.ts——「直通」插件什么也不改,只跑通「加载 → 挂载 → 调用 → 卸载」全链路:pre_hook 放行并向后置写一个 context 标记,post_hook 逆序回收后放行。

import { JSON } from "json-as";

// ── 内存与字符串工具 ──
export function malloc(size: u32): u32 { return heap.alloc(size) as u32; }
export function free(ptr: u32): void { heap.free(ptr); }

function pack(ptr: u32, len: u32): u64 {
  return ((ptr as u64) << 32) | (len as u64);
}
function readString(ptr: u32, len: u32): string {
  const buf = new ArrayBuffer(len);
  const dest = changetype<usize>(buf);
  for (let i: u32 = 0; i < len; i++) store<u8>(dest + i, load<u8>(ptr + i));
  return String.UTF8.decode(buf, false);
}
function writeString(s: string): u64 {
  const buf = String.UTF8.encode(s, false);
  const len = buf.byteLength as u32;
  const ptr = malloc(len);
  const src = changetype<usize>(buf);
  for (let i: u32 = 0; i < len; i++) store<u8>(ptr + i, load<u8>(src + i));
  return pack(ptr, len);
}

// ── 契约类型(字段名严格对齐 Hook 契约) ──
@json
class PreInput {
  phase: string = "";
  request_id: string = "";
  api_key_id: string = "";
  context: JSON.Raw = JSON.Raw.from("{}");
}
@json
class Marker {
  seen_by: string = "直通插件";
  request_id: string = "";
}
@json
class PreOutput {
  action: string = "continue";
  context: JSON.Raw = JSON.Raw.from("{}");
}
@json
class PostOutput {
  action: string = "pass";
}

// ── 模块级状态(init 写入、cleanup 清空) ──
let pluginConfig: string = "";

// ── 生命周期与 Hook 导出 ──
export function get_name(): u64 {
  return writeString("直通插件");
}

export function init(config_ptr: u32, config_len: u32): i32 {
  pluginConfig = readString(config_ptr, config_len);
  return 0; // 成功
}

export function pre_hook(input_ptr: u32, input_len: u32): u64 {
  const evt = JSON.parse<PreInput>(readString(input_ptr, input_len));
  const marker = new Marker();
  marker.request_id = evt.request_id;
  const out = new PreOutput();
  out.action = "continue";
  out.context = JSON.Raw.from(JSON.stringify<Marker>(marker)); // 传给配对后置
  return writeString(JSON.stringify<PreOutput>(out));
}

export function post_hook(input_ptr: u32, input_len: u32): u64 {
  // 直通:放行出站响应(逆序收尾,前置写入的 context 由宿主回传)
  const out = new PostOutput();
  out.action = "pass";
  return writeString(JSON.stringify<PostOutput>(out));
}

export function cleanup(): i32 {
  pluginConfig = "";
  return 0; // 成功
}

直通骨架的价值在于先跑通全链路,再逐步把 actioncontinue / pass 改为 rewrite / short_circuit / redact / block,填入真实治理逻辑——下一节就是这样的实战。

7. 实战示例:出站敏感信息脱敏插件

一个挂在 后置段 的治理插件:复核上游返回内容,对其中的邮箱、手机号、证件号等敏感片段就地脱敏,返回 action=redact 与脱敏后的 response.content。它只在治理面改写出站文本——不重新生成、不加工合成内容(能力边界见 §13)。

7.1 脱敏逻辑

正则能力由一个 AssemblyScript 兼容的正则库提供(AssemblyScript 标准库不内建完整 RegExp,见 §10)。脱敏用 redact 三遍替换实现:

import { JSON } from "json-as";
import { RegExp } from "assemblyscript-regex"; // AS 兼容正则库

// 三类敏感片段的脱敏模式
const EMAIL = new RegExp("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}", "g");
const PHONE = new RegExp("1[3-9][0-9]{9}", "g");
const IDNO  = new RegExp("[0-9]{17}[0-9Xx]", "g");

// 用全局正则就地替换所有匹配片段为掩码(exec 循环,不依赖 replace)
function maskAll(text: string, re: RegExp, mask: string): string {
  let result = "";
  let cursor = 0;
  let m = re.exec(text);
  while (m != null) {
    const start = m.index;
    const matched = m.matches[0];
    result += text.substring(cursor, start);
    result += mask;
    cursor = start + matched.length;
    m = re.exec(text); // 全局标志推进 lastIndex
  }
  result += text.substring(cursor);
  return result;
}

function redact(text: string): string {
  let out = maskAll(text, EMAIL, "[已脱敏·邮箱]");
  out = maskAll(out, PHONE, "[已脱敏·手机号]");
  out = maskAll(out, IDNO,  "[已脱敏·证件号]");
  return out;
}

后置 Hook 主体——只有真正改动了内容才返回 redact,否则放行:

@json
class ResponseBody { model: string = ""; content: string = ""; finish_reason: string = ""; }
@json
class StreamInfo { is_stream: bool = false; chunk_index: i32 = 0; is_last: bool = false; }
@json
class PostInput {
  phase: string = "";
  request_id: string = "";
  response: ResponseBody = new ResponseBody();
  stream: StreamInfo = new StreamInfo();
  context: JSON.Raw = JSON.Raw.from("{}");
}
@json
class RedactedResponse { content: string = ""; }
@json
class PostOutput { action: string = "pass"; response: RedactedResponse | null = null; }

export function post_hook(input_ptr: u32, input_len: u32): u64 {
  const evt = JSON.parse<PostInput>(readString(input_ptr, input_len));

  // 仅对出站内容做治理面脱敏,不重新生成内容。
  // 流式下本函数被多次调用,每次只拿到当前 chunk,就地脱敏即可。
  const cleaned = redact(evt.response.content);

  const out = new PostOutput();
  if (cleaned != evt.response.content) {
    out.action = "redact";
    const r = new RedactedResponse();
    r.content = cleaned;
    out.response = r;
  } else {
    out.action = "pass";
  }
  return writeString(JSON.stringify<PostOutput>(out));
}

7.2 配对前置 Hook 与上下文

后置脱敏可配一个轻量前置 Hook:放行(continue)并向 context 写一个标记,标明本请求已纳入脱敏治理;该标记在配对后置 Hook 中由宿主原样回传(对称传递、逆序收尾,见 扩展点 SDK §1)。

@json
class PreInput { phase: string = ""; request_id: string = ""; context: JSON.Raw = JSON.Raw.from("{}"); }
@json
class GuardMarker { scanned: bool = true; request_id: string = ""; }
@json
class PreOutput { action: string = "continue"; context: JSON.Raw = JSON.Raw.from("{}"); }

export function pre_hook(input_ptr: u32, input_len: u32): u64 {
  const evt = JSON.parse<PreInput>(readString(input_ptr, input_len));
  const marker = new GuardMarker();
  marker.request_id = evt.request_id;
  const out = new PreOutput();
  out.action = "continue";
  out.context = JSON.Raw.from(JSON.stringify<GuardMarker>(marker));
  return writeString(JSON.stringify<PreOutput>(out));
}

7.3 流式逐 chunk 处理

扩展点 SDK §2.3,当 stream.is_streamtrue 时,本后置 Hook 会被 逐 chunk 多次调用:每次入参 response.content 只是当前一块、chunk_index 递增、末块 is_lasttrue。脱敏天然适配这种逐块模型——每块就地脱敏后随即放行,违规片段不会先于护栏抵达客户端。

stream 字段逐 chunk 时的用法
is_streamtrue 时进入逐块处置分支
chunk_index当前块序号,用于跨块计数 / 落账归并
is_last末块标识;计量类汇总通常在此块完成

跨块边界提醒。 单块就地脱敏对落在 chunk 边界处、被切成两半的敏感串可能漏判。若治理要求覆盖跨块片段,可在 context 里缓存上一块的尾部若干字符与本块拼接后再匹配,并仅输出确认安全的前缀;对边界要求极严、或正则负担较重的场景,更适合改用 原生形态 配合完整正则引擎。request_id 在整条流(含全部 chunk)中保持一致,可用作跨块状态的归并键。

8. 构建与产物

编译为发布版 .wasm

# 经 package.json 脚本
bun run asbuild

# 或直接调用编译器
bunx asc assembly/index.ts --target release --transform json-as/transform -o build/plugin.wasm

产物即 build/plugin.wasm,单文件、自包含。沙箱形态 无平台对齐要求:同一个 .wasm 可在不同运行环境家族 / 处理器架构 / 构建工具版本的平台实例上加载,无需为每种环境分别构建。如需进一步压缩体积,可对产物追加一次 wasm-opt(可选)。

9. 注册与加载

注册流程与 自定义插件开发 §4 完全一致,沙箱形态只是把 artifact_ref 指向 .wasm

先确认实例支持动态加载:

curl https://<平台入口地>/api/v1/deployment \
  -H "Cookie: <控制台会话凭证>"
{
  "deploy_mode": "private",
  "deploy_status": "ready",
  "capabilities": {
    "dynamic_plugins": true
  }
}

capabilities.dynamic_pluginstrue 后,注册脱敏插件(后置段):

curl https://<平台入口地>/api/v1/pipeline/extensions \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Cookie: <控制台会话凭证>" \
  -d '{
    "name": "出站脱敏插件",
    "artifact_ref": "wasm://plugins/redact@1.2.0",
    "node_id": "<安全护栏节点标识>",
    "hook_type": "post",
    "placement": "post",
    "order": 10,
    "enabled": true,
    "config": {
      "mask_email": true,
      "mask_phone": true,
      "mask_id": true
    },
    "fail_policy": "fail_closed"
  }'
字段取值说明
name须与 get_name 返回一致
artifact_ref指向 .wasm 的加载位置 + 版本标识
hook_type / placement脱敏挂后置:均为 post(须匹配)
order段位内位次,值小者先
config传给插件 init 的初始配置
fail_policy脱敏属安全护栏类,取 fail_closed

读取已注册清单用 GET /api/v1/pipeline/extensions(见 扩展点 SDK §3)。热替换:更新 artifact_ref 的版本标识(如 redact@1.2.0redact@1.3.0)即触发平台重载该插件——先以新 .wasm 完成 init,再切换执行,旧实例 cleanup 收尾。

10. 限制

沙箱形态的能力边界主要来自 WASM 隔离与 AssemblyScript 子集:

  • 内存受限、隔离执行:插件在独立线性内存运行,不能触达宿主进程其余部分。
  • 无原生外部 IO:沙箱内不能直接访问文件、网络套接字、环境变量。需要外联能力(如调用外部服务、读本地配置文件)的治理逻辑,应改用 原生形态
  • AssemblyScript 是 TS 的子集:并非任意 TS / npm 运行时库都能编译。

AssemblyScript 可用 / 不可用速查:

类别可用不可用
语法class、函数、泛型、整数 / 浮点类型、boolany、union 类型(受限)、动态原型扩展
标准库StringArrayBufferTypedArrayMap、数学 / 字符串 / 数组操作完整 JS RegExp 内建(需引入 AS 兼容正则库)
依赖AS 兼容库(如 json-as、AS 正则库)依赖 Node / 浏览器 API 的普通 npm 运行时包
运行期线性内存读写、heap.alloc / heap.freeevalProxy、动态反射、运行期装饰器元编程
IO仅经 Hook 入参出参的 JSON 交换文件 / 网络 / 环境变量等原生外部 IO

经验法则:以字符串 / JSON 处置为主、纯计算、可自包含的治理逻辑,沙箱形态足够;一旦需要外联或重型库,转 原生形态

11. 失败处置与启停

失败处置、启停、灰度、热替换均沿用 自定义插件开发 §5 的统一规则,沙箱形态无特殊性:

情形处置
加载 / init 返回非 0该插件不纳入编排;其余插件与内建管道照常运行
前置 Hook 返回 reject请求被结构化拒答,不再转发上游
前置 Hook 执行报错(含 wasm trap)fail_policyfail_open 放行、fail_closed 拒绝;护栏类默认 fail_closed
后置 Hook 返回 block出站响应被拦截;已发生的计量按实落账
后置 Hook 执行报错不影响已完成的上游调用与计量;按 fail_policy 决定是否放行出站内容,护栏类默认拦截
短路路径上的后置异常逆序收尾继续执行其余后置 Hook,单插件异常不阻断整条收尾链
  • 启停enabled 控制单个插件的执行状态,停用即运行时跳过——便于灰度与回退,无需注销。
  • 版本热替换:更新 artifact_ref 版本标识触发重载(见 §9)。
  • 沙箱隔离的额外好处:插件内的 wasm trap 被限制在其线性内存范围内,按 fail_policy 处置,不会连带拖垮宿主进程;安全相关的脱敏 / 拦截一律 fail_closed——「宁可拒绝,不放行未经复核的内容」。

12. 常见问题

为什么 TS 要走 AssemblyScript? 因为沙箱形态的产物是 .wasm,而普通 TypeScript 依赖 Node / 浏览器运行时、无法直接编译为这种自包含的线性内存模块。AssemblyScript 是 TS 的静态类型子集,专门面向 WASM 目标编译,让你以熟悉的 TS 语法写出可加载的 .wasm

能用普通 npm 包吗? 只能用 AssemblyScript 兼容的库(如 json-as、AS 正则库)。依赖 Node / 浏览器 API、或大量使用动态特性的普通 npm 运行时包不能编译进 .wasm(见 §10 速查表)。

.wasm 能跨环境吗? 能。沙箱形态单一产物处处可加载,没有原生形态的「构建参数对齐」要求——同一个 .wasm 在不同运行环境家族 / 处理器架构 / 构建工具版本的实例上都能加载。

流式下跨 chunk 的敏感串怎么处理? 单块就地脱敏会漏掉落在 chunk 边界处被切开的片段。可借 context 缓存上一块尾部字符与本块拼接后再匹配(见 §7.3);边界要求极严或正则负担重时,建议改用 原生形态

何时该改用原生形态? 需要原生外部 IO、对延迟极敏感、或要用完整正则 / 重型库时——转 用 Go 编写原生插件。两种形态导出的符号语义一致,逻辑可平移。

13. 能力边界

TypeScript 插件与所有扩展点共享同一条硬性边界(见 扩展点 SDK §6):

插件只在控制面 / 治理面对请求与响应施加处置——校验、路由提示、缓存判定、脱敏、拦截、计量采集——不对结果内容做实质性的生成、加工或合成。内容生成始终由路由选定的上游模型完成。前置 Hook 的 rewrite 与后置 Hook 的 redact 都限于治理面改写(如去除敏感片段、补充结构化路由提示),不构成对生成内容的实质性创作。

本页的脱敏插件正是这条边界的典型落地:它只把已有出站文本里的敏感片段替换为掩码,既不补写也不重写语义内容。注册一个试图替代上游做内容生成的 TS 插件,不符合扩展点 SDK 的能力定义。

相关链接

  • 概念与编排:三类治理中间件、执行位次、条件分支、短路语义与流式逐 chunk 处理。
  • 扩展点 SDK 参考:前置 / 后置对称 Hook 模型、Hook 签名与生命周期、注册启停、编排约束与失败处置、能力边界声明。
  • 自定义插件开发:插件模型、生命周期符号、两种加载形态、构建与动态加载、注册启停。
  • 用 Go 编写原生插件:原生可加载单元——同进程、性能最优、可原生外部 IO,须与平台运行环境构建参数对齐。
  • 用 WebAssembly 编写插件:沙箱可加载单元的通用 host-guest ABI 与内存约定,以 Rust 为主示例(TS 与 Rust 共用同一套约定)。
  • API 参考:管道编排与扩展点接口的完整端点契约。

On this page