> 原始 Markdown 孪生体（构建期从源 Markdown 生成）。渲染页：https://docs.gatellm.io/zh-CN/reference/scripting-api · 文档索引：https://docs.gatellm.io/zh-CN/llms.txt


# 脚本 API 参考

GateLLM 内置 **JavaScript 脚本引擎**（基于 QuickJS），可对请求和响应的 JSON body 做自定义变换。脚本运行在沙箱中，有严格资源限制，安全且可控。

本章是 API 参考。入门见 [写第一个脚本变换](/zh-CN/howto/write-script-transform.md)，配置参考见 [脚本配置](/zh-CN/reference/scripting-config.md)。

## 基本结构

每个脚本定义一个 `transform` 函数，接收 `body` 和 `context`，**必须返回 body**：

```javascript
function transform(body, context) {
    // 读取和修改 body
    // 读取 context 获取元数据（只读）
    return body;  // 必须！
}
```

| 参数 | 可修改 | 说明 |
|------|--------|------|
| `body` | 是 | JSON 请求体或响应体 |
| `context` | 否 | 请求/响应元数据（模型名、headers 等） |

## Context 对象

`context` 提供当前请求/响应的元数据，**只读**：

### 请求和响应阶段均可用

| 字段 | 类型 | 说明 |
|------|------|------|
| `context.clientModel` | string | 客户端看到的模型名 |
| `context.upstreamModel` | string | 发送给上游的模型 ID |
| `context.upstreamName` | string | 上游分组名 |
| `context.sourceProtocol` | string | 客户端协议（如 `"OpenAIChatCompletions"`） |
| `context.targetProtocol` | string | 上游协议（如 `"Anthropic"`） |
| `context.stream` | boolean | 是否流式请求 |
| `context.headers` | object | 请求 headers（敏感头已过滤） |
| `context.query` | object | URL 查询参数 |
| `context.requestId` | string | 唯一请求 ID |
| `context.clientIp` | string | 客户端 IP |
| `context.accessKeyName` | string | 访问密钥名 |
| `context.accessKeyGroup` | string | 访问密钥分组 |

### 仅响应阶段可用

| 字段 | 类型 | 说明 |
|------|------|------|
| `context.responseStatus` | number | 上游 HTTP 状态码 |
| `context.responseHeaders` | object | 上游响应 headers |
| `context.elapsedMs` | number | 耗时（毫秒） |

> **安全**：`authorization`、`cookie`、`x-api-key` 等敏感 headers 在脚本可见之前已被移除。

### `context.useModel` 切换路由

```javascript
context.useModel("model-name");
```

完整路由切换：base_url、协议、凭证一起换。`body.model = "X"` 仅改请求体的字段，不改路由——用 `context.useModel` 才能跨端点/跨协议切换。

## 请求脚本的两个槽（before / after）

请求侧脚本不再是一个脚本加位置开关，而是**两个独立槽**，各自可空、可同时配置：

| 槽 | 字段 | 运行时机 | 看到的 body | useModel 能力 |
|----|------|---------|-------------|---------------|
| 翻译后 | `request_transform_after` | 协议翻译**之后** | 上游协议格式 | 仅同协议切换 |
| 翻译前 | `request_transform_before` | 协议翻译**之前** | 客户端原始格式 | 支持跨协议切换 |

- **`request_transform_after`**：在协议转换后运行，body 已是上游格式。适合简单的字段修改、上游方言改写。
- **`request_transform_before`**：在协议转换前运行，body 是客户端原始格式。适合需要检测客户端输入（如图片、搜索、plan 模式标记）并切换路由的场景。
- **两个槽可同时配置**：同一路由的 before 与 after 各自独立合并（model 覆盖 upstream），两个脚本都会运行——before 先跑（可触发跨协议 立即 `useModel` 切换），after 后跑。注意：一旦 before 槽触发了 立即 `useModel` 切换路由，**原路由的 after 槽不再执行**（整套 pipeline 换成目标路由的）；after 槽声明的 `useModel` 是同协议 延后切换。`response_transform` 无槽概念，保持单字段。

## 执行顺序

before 槽与 after 槽各自独立运行；同一路由两者都配时，before 先在翻译前阶段跑，after 在翻译后阶段跑。下面分别给出两槽在管线中的位置。

### 请求侧 — after 槽（翻译后）

```text
客户端请求 → 协议转换 → 自动归一化 → request_payload 规则 → 上游脚本 → 模型脚本 → 缓存注入 → 发送到上游
```

### 请求侧 — before 槽（翻译前）

```text
客户端请求 → 上游脚本 → 模型脚本 → (useModel swap) → 协议转换 → 自动归一化 → request_payload 规则 → 缓存注入 → 发送到上游
```

### 响应侧（仅非流式）

```text
上游响应 → 响应脚本 → 反向协议转换 → 返回客户端
```

> 流式请求的响应脚本暂不支持。

## 内置函数

### JSON Path 操作

```javascript
// 读取嵌套值
const temp = json_path(body, "generation_config.temperature");

// 设置值（自动创建中间对象）
set_path(body, "generation_config.max_output_tokens", 4096);

// 删除字段
remove_path(body, "deprecated_field");

// 仅当字段不存在时添加
add_if_absent_path(body, "temperature", 1.0);

// 向数组追加
append_path(body, "messages", { role: "system", content: "Be concise." });
```

### 字符串函数

```javascript
str_len("Hello, 世界!");           // 9（字符数，非字节数）
str_contains("hello world", "world"); // true
str_replace("foo-bar", "-", "_");    // "foo_bar"
str_starts_with("hello", "he");     // true
str_trim("  hello  ");              // "hello"
```

### 正则表达式

```javascript
regex_match("abc123", "[0-9]+");     // true
regex_replace("foo123bar", "[0-9]+", "N");  // "fooNbar"
regex_extract("a=1 b=2", "([a-z]+)=");  // ["a", "b"]
```

### 编码与哈希

```javascript
base64_encode("hello");    // "aGVsbG8="
base64_decode("aGVsbG8="); // "hello"
sha256("hello");           // "2cf24dba5fb0..."
json_encode(body.messages); // 序列化为字符串
json_parse(jsonStr);        // 从字符串解析
```

### 数组函数

```javascript
array_sort([3, 1, 2]);                // [1, 2, 3]
array_unique([1, 2, 2, 3]);           // [1, 2, 3]
array_filter_by(items, "active", true); // 保留 active=true 的元素
array_find(users, "name", "alice");    // 找到第一个 name=alice 的元素
array_pluck(users, "name");           // 提取所有 name 字段
array_sum([1.5, 2.5, 3.0]);           // 7.0
```

### 深拷贝与合并

```javascript
const backup = deep_clone(body);  // 独立副本
const merged = deep_merge(defaults, overrides);  // 递归合并
```

### UUID 与时间

```javascript
uuid();    // "550e8400-e29b-41d4-a716-..."
now();     // 1748620800（秒级时间戳）
now_ms();  // 毫秒级
format_time(now(), "%Y-%m-%d %H:%M:%S");  // "2025-05-31 00:00:00"
```

## 运行限制、错误处理与测试

脚本的沙箱能力边界（可做 / 不能做）、资源限制表（操作数、堆上限、并发执行槽、懒投影语义）、`script_error_mode` 错误处理、常见报错与排查、测试接口（`POST /console/api/scripts/test`）及性能建议，统一见 [脚本运行限制与配置](/zh-CN/reference/scripting-config.md)——那是脚本配置与限制的唯一权威页，本页不再重复。

## 常见问题

**Q：脚本改了 body.model，为什么路由没变？**
`body.model` 只改请求体的字段，不改路由。要切换路由用 `context.useModel("模型名")`。

**Q：流式请求的响应脚本生效吗？**
暂不支持流式响应的脚本。请求脚本对流式请求有效，但响应脚本仅在非流式时生效。

**Q：Before 和 After 脚本位置怎么选？**
- 只改上游格式 body 的字段 → After（默认）
- 需要检测客户端原始格式（如不同协议的图片字段不同）且判定超出 `switch-route` 能表达的范围 → Before
- 「检测图片/联网→切模型」这类纯路由决策（含跨协议）→ 优先用原生 `switch-route` 规则，见 [把含图片的请求路由到视觉模型](/zh-CN/howto/route-image-requests-to-vision-model.md)

**下一步**：[写第一个脚本变换](/zh-CN/howto/write-script-transform.md) 看入门结构；[用脚本做协议翻译修正](/zh-CN/howto/script-protocol-translate.md) 与 [用脚本做响应脱敏](/zh-CN/howto/script-response-redact.md) 看实战示例；[脚本运行限制与配置](/zh-CN/reference/scripting-config.md) 看沙箱、资源限制、错误处理与测试。
