# 单文件预设分享格式 v1

文件名建议为 `名称.dsh-preset.json`，编码为 UTF-8。它是一份普通 JSON，包含一个预设及其附带配置，无需 ZIP、目录或其他配套文件。插件仍可导入、导出普通 SillyTavern JSON。

## 文件结构

以下为空白结构示意，`preset.data` 内存放完整的 SillyTavern 预设：

```json
{
  "format": "dsh-preset-enhance",
  "version": 1,
  "metadata": {
    "name": "",
    "description": "",
    "author": ""
  },
  "preset": {
    "format": "sillytavern",
    "data": {
      "dsh_system_prompt_enabled": true,
      "dsh_system_prompt_templates": {},
      "dsh_request": { "max_tokens": 0, "stream": true },
      "prompts": [
        { "identifier": "chatHistory", "marker": true, "role": "user" }
      ],
      "prompt_order": [
        { "character_id": 100001, "order": [
          { "identifier": "chatHistory", "enabled": true }
        ] }
      ],
      "dsh_prefill_target": "reasoning",
      "assistant_prefill": ""
    }
  },
  "prefill": {
    "enabled": true,
    "toolCalls": true,
    "extractOutput": false,
    "removeNonOfficialTools": true,
    "postToolPrefix": {
      "mode": "inherit",
      "text": ""
    }
  },
  "tools": {
    "version": 1,
    "activePresetId": null,
    "presets": [],
    "groups": []
  },
  "extensions": {}
}
```

| 字段 | 含义 |
| --- | --- |
| `format` | 固定为 `dsh-preset-enhance`，标识外层分享格式。 |
| `version` | 格式主版本，当前为整数 `1`，独立于 npm 插件版本。未知版本拒绝导入。 |
| `metadata` | 可选的名称、说明、作者等分享信息；名称可在工作台编辑。 |
| `preset.format` | 当前固定为 `sillytavern`。 |
| `preset.data` | 完整 ST JSON，保留顺序表、宏、预填充及原有 `extensions` 等字段。 |
| `prefill` | 可选的接口配置对象，省略或 `null` 表示不附带接口设置。 |
| `tools` | 可选的工具预设与分组容器；导入不自动应用，需在工作台显式导入，详见下文。 |
| `extensions` | 可选的扩展对象，建议使用插件名作为键，避免重名。 |

`format`、`version`、`preset` 必填。文件大小限制与原 JSON 导入相同，为 8 MB。自定义提示词的换行按 JSON 规则写为 `\n`；宏保留源文本，在实际请求时展开。

`preset.data.dsh_system_prompt_enabled` 是可选布尔值，省略时按 `true` 处理。它对应工作台置顶的“DSH 系统提示词”模板：在其他 DSH 模式中启用该预设时，`true` 保留模式原有系统提示与运行时注入，`false` 将其移除。专用“预设模式”始终移除这些内容。

`preset.data.dsh_system_prompt_templates` 是可选的「模式 ID → 正文」对象。同一预设中各模式的编辑互不覆盖；没有对应键时动态使用该模式的默认正文，有键时使用保存的修改（包括空字符串）。恢复默认只删除当前模式的键。运行时变量与段落保持宏源文本，未解析的值不写入分享文件。

`preset.data.dsh_request` 是可选请求配置：`max_tokens` 为 `0–1000000` 整数，缺省或 `0` 沿用接口默认；`stream` 为布尔值，缺省为 `true`。这些参数随预设保存与导出，仅在预设注入启用时生效。

宏与模式规则见 [DSH 系统模板与请求参数](docs/api/DSH_SYSTEM_TEMPLATE.md)。

## 预填充配置

`prefill` 非空时，原有字段保持必填；`extractOutput` 是同一主版本新增的可选字段，省略时按 `false` 处理：

- `enabled`：是否启用预填充自动兼容。
- `toolCalls`：是否启用 DSML 工具调用转换。
- `extractOutput`：是否启用实验性正文/工具调用提取；按照最终稳定性策略把切换前文本映射到思维链，并移除输出区控制标签。
- `removeNonOfficialTools`：关闭 DSML 处理时，非官方接口是否移除原生工具字段。官方 Beta 的原有规则不变。
- `postToolPrefix.mode`：`inherit` 继承原预设，`custom` 使用工具执行后的独立提示词。
- `postToolPrefix.text`：自定义提示词文本；留空或宏展开为空时继承原预设。

接口地址、API key、用户身份、会话 ID、会话历史、运行中的变量值和本机路径不属于预填充配置。新建分享文件只从全局状态中提取上述接口设置，不打包整个本地数据库。

`preset.data.dsh_prefill_target` 为 `reasoning`（默认）或 `content`，控制末条预填充写入思考还是正文。它属于预设内容，普通 ST JSON 和单文件分享都保留；不属于全局 `prefill` 接口配置。省略时默认思考，未知值拒绝导入。该设置同样用于工具调用后的自定义前缀；关闭模型思考时按正文发送。

## 工具预设与分组

`tools` 是可选的工具容器，子格式独立于外层 `version` 演进，当前子版本为 `1`。校验会检查数量、唯一性和引用完整性，同时保留同版本未知字段：

```json
{
  "version": 1,
  "activePresetId": "tool-profile-1",
  "presets": [
    {
      "id": "tool-profile-1",
      "name": "只读",
      "description": "",
      "defaultEnabled": true,
      "groupIds": ["tool-group-1"],
      "rules": [
        { "modeId": "plugin-mode-id", "toolName": "plugin-tool-name", "enabled": false }
      ],
      "updatedAt": "2026-01-01T00:00:00.000Z"
    }
  ],
  "groups": [
    {
      "id": "tool-group-1",
      "name": "读取类",
      "description": "",
      "order": 100,
      "members": [
        { "modeId": "plugin-mode-id", "toolName": "plugin-tool-name" }
      ]
    }
  ]
}
```

| 字段 | 含义 |
| --- | --- |
| `tools.version` | 工具子格式版本，整数，当前为 `1`，省略时按 `1` 处理。未知整数版本只保留和再次导出，禁止应用（见下）；非整数或小于 `1` 是格式错误，拒绝整个包。 |
| `tools.activePresetId` | 可选；引用 `presets[].id`，只是作者的推荐项，导入时不会自动启用任何工具。 |
| `tools.presets[].id` | 包内稳定的预设 ID，不能以 `@` 开头。 |
| `tools.presets[].name` | 预设名称，必填。 |
| `tools.presets[].description` | 可选说明。 |
| `tools.presets[].defaultEnabled` | 目录中未被 `rules` 明确覆盖的工具的缺省开关；省略时为 `true`。 |
| `tools.presets[].groupIds` | 引用 `groups[].id`，用于组织工具和批量编辑；必须指向本包内存在的分组。 |
| `tools.presets[].rules` | 逐工具显式规则 `{modeId, toolName, enabled}`；同一 `{modeId,toolName}` 只能出现一次。 |
| `tools.presets[].updatedAt` | 可选 ISO-8601 时间戳，仅用于展示。 |
| `tools.groups[].id` | 包内稳定的分组 ID，不能以 `@` 开头；`@all` 和 `@ungrouped` 是界面计算的虚拟分组，不写入文件。 |
| `tools.groups[].name` | 分组名称，必填。 |
| `tools.groups[].order` | 排序值，省略时为 `100`，用于标签顺序。 |
| `tools.groups[].description` | 可选说明。 |
| `tools.groups[].members` | `{modeId, toolName}` 数组；组内不得重复，同一引用在包内最多属于一个用户分组。 |

语义约定：

- 工具引用统一使用 `{modeId, toolName}`，接收端按实时模式和工具目录解析，不写死内置模式、插件工具或本机安装路径；不同模式的同名工具互相独立。
- 分组只负责组织和批量编辑，运行时仍展开为逐工具规则：先取 `defaultEnabled`，再应用同 `{modeId,toolName}` 的显式规则。
- 目录中不存在的模式或工具不会导致拒绝：引用照常导入、导出并在工作台列为“未匹配”，安装对应插件后自动重新匹配。
- 上限：分组 100 个、预设 100 个、单组 2,000 个成员、单预设 5,000 条规则；ID 最长 100 字符，`modeId` 和 `toolName` 最长 200 字符。
- 以下情况作为格式错误拒绝整个包：重复的分组或预设 ID、组内重复成员、同一工具同时属于两个用户分组、`groupIds` 指向不存在的分组、`activePresetId` 未指向本包内的预设、`presets`/`groups` 存在但不是数组。
- 同一子版本内的未知字段（`tools` 容器、预设、分组、规则、成员各级）原样保留；编辑名称、提示词或接口设置后再次导出时不会丢失。

### 导出范围

- 默认只导出当前关联的工具预设，以及该预设 `groupIds` 引用的用户分组；不会导出其他预设或分组。
- 未关联任何工具预设时：新预设导出空容器 `{"version":1,"activePresetId":null,"presets":[],"groups":[]}`，已导入的分享文件沿用其原有工具容器（原本没有 `tools` 字段时也不额外添加）。
- 不导出 selection（模式默认和会话选择）、会话 ID、目录缓存、引用计数，以及浏览器本地界面状态（标签/内容折叠、最后配置的 DSH 模式、自动保存工具开关）；未匹配引用照常导出。
- `activePresetId` 只记录作者推荐，导入不会自动启用或切换任何工具开关。

### 显式导入与冲突处理

- 导入分享文件从不自动应用工具配置：包内的组和预设随预设一起保存，模式默认和会话的工具开关保持不变。
- 用户在工作台显式选择“导入包内工具配置”后才应用：先显示新增/复用/重映射数量和匹配/失配数量（预览不写入），确认后原子写入。
- ID 相同且内容一致时复用它在本机已有的分组/预设；ID 相同但内容不同时生成新的 UUID，同时重写包内引用（预设的 `groupIds` 与 `activePresetId` 跟随重映射）。
- 接收端按实时目录解析引用：能匹配的计入匹配数，未匹配的保留并提示，等插件恢复后重新匹配。

### 未知工具子版本

`tools.version` 为未知整数（例如未来插件写出的 `2`）时，包仍可正常导入、编辑和再次导出，内容原样保留；只有应用会被拒绝，并显示“分享文件的工具子版本 N 暂不支持应用”。

## 提示词正则

预设可以携带酒馆格式的正则脚本，由工作台编辑，并随预设一起保存、导出和分享。

```json
{
  "extensions": {
    "regex_scripts": [
      {
        "id": "r1",
        "scriptName": "清理括号",
        "findRegex": "/【(.+?)】/g",
        "replaceString": "$1",
        "placement": 1,
        "promptOnly": true,
        "markdownOnly": false,
        "disabled": false,
        "trimStrings": [],
        "substituteRegex": 0
      }
    ],
    "dsh-preset-enhance": {
      "promptRegex": { "enabled": false, "includePrefill": false },
      "displayRegex": { "enabled": false }
    }
  }
}
```

- 规则按 `regex_scripts` 的数组顺序执行，`id` 是稳定标识；本插件不使用的酒馆字段原样保留。
- `dsh-preset-enhance.promptRegex` 是本插件的执行开关：两个字段缺省都是关闭，字段缺失同样按关闭处理，因此升级不会让已有预设突然改写请求。
- 提示词通道执行 `promptOnly: true` 且 `placement` 含 `1`（用户输入）或 `2`（助手输出）的规则；正文显示通道执行 `markdownOnly: true` 且 `placement` 含 `2` 的规则。两者均要求规则未禁用、查找式非空。斜杠命令、世界书与推理位置保留但不执行。
- `displayRegex.enabled` 默认关闭；正文包启用并就绪后才生效。两通道可同时开启，分别从请求副本和助手正文显示副本计算，不互用替换结果，不写回会话记录。正文替换不要求启用预设注入。详细规则见[正文正则](docs/api/DISPLAY_REGEX.md)。
- 导入导出要求语义无损：JSON 的空白、缩进与键顺序不保证逐字节相同，未知字段与未执行的规则一并保留。
## 工作台导入、编辑与导出

1. “导入 JSON”自动识别 ST JSON 和本格式。导入分享文件后，将原预设保存为当前默认，并保存附带配置（包括包内工具预设和分组）。有效的 `prefill` 与预设在同一事务内恢复到全局，对其他会话也生效；缺失或 null 保持本机设置，工具配置不自动应用。
2. “应用包内接口设置（全局）”可再次恢复文件中的 `prefill`，下一次请求生效。带接口配置的分享文件导入、再次应用与保存接口设置时，正文提取开启且格式条目缺失会补入模板。普通 ST JSON 或没有接口配置的包，仅在已有正文提取开关开启且预设含预填充时补入。已有条目保留内容和启停，关闭不删除条目；Messages 下仍保存配置，切回 Chat 后使用。
3. “导入包内工具配置”显式应用文件中的 `tools`：先显示新增、复用、重映射数量以及匹配、失配数量，确认后才写入，且不改变任何模式或会话的工具选择。未知 `tools.version` 时禁止应用并给出提示。
4. “保存接口设置”保存全局接口设置，同时将其附加到当前选中的已保存预设。包内未来扩展字段保留。
5. “导出分享文件”导出一个 `.dsh-preset.json`：提示词和名称使用当前编辑器草稿；接口配置优先使用该预设已附带的配置，没有分享数据时使用全局已保存配置；工具容器使用当前关联的工具预设（仅该预设及其 `groupIds` 引用的分组），没有关联时沿用该预设已保存的包内工具容器，新预设则导出空容器（空的 `presets` 和 `groups`）。尚未保存的接口表单内容不会被导出。
6. “导出 ST JSON”只导出 `preset.data`，用于其他 ST 兼容软件。

导入的未知字段会在相同主版本的读写中保留，包括顶层、`preset` 包装层、`prefill`、工具容器（含未知工具子版本）和扩展对象。编辑名称或提示词只替换对应字段；保存接口设置只替换已知接口配置字段。保留的外来扩展数据也会随分享文件再次导出。

## 版本演进

同一主版本只添加可选字段，旧版本客户端保留其不识别的数据。改变必填字段、字段含义或已有行为时提升外层 `version`；工具子格式独立演进。未知外层主版本必须报错，不按 ST JSON 降级解析，以免丢失配置；未知工具子版本仍可导入、编辑、导出和往返，只禁止应用。

## 外部插件模板引用

ST 预设的 extensions["dsh-preset-enhance"].templateBindings 保存选用模板的提供者、模板 ID、固定版本与内容指纹；prompts 保存文本快照，prompt_order 控制顺序与开关。普通 JSON 与本分享格式都保留这些字段。

静态关联 mode 为 linked；动态关联 mode 为 linked-dynamic，另保留 config（解析器配置 JSON）与 failurePolicy（abort 默认、skip 可选）。快照是模板定义，不是某次动态结果；本轮正文、历史与解析器代码不导出。旧版只认可 linked，会跳过动态关联并提示。动态关联转为本地副本时清除动态槽位，其余定义保留。

依赖缺失时不会用快照自动注入；用户可安装对应提供者，或在编辑器中转为本地副本。字段和接入示例见 [模板 API](docs/api/TEMPLATE_API.md#引用与单文件分享)。

酒馆 marker 关联额外保存 target: "marker" 与 contentSnapshot；原 prompts 条目保留，插件只在请求副本中替换正文，沿用原角色和位置。解除关联恢复原标记内容来源；chatHistory 仅允许 dynamic.output 为 history-patches 的结构化修改，原历史不写回。单文件分享同时保留原标记与外部引用。

默认关联：唯一 targetMarker 候选自动生效，不写入预设；手动关联优先。自动关联的配置保存为带 automatic: true 的 templateBindings；autoTemplateDisabled 位于同一插件扩展下，保存用户禁止自动关联的 marker ID。热重载及插件启停会更新自动关联，不覆盖手动选择和禁止状态。

### 正文正则

`preset.extensions['dsh-preset-enhance'].displayRegex.enabled` 保存正文显示开关，缺失默认关闭。规则与提示词正则共用 `preset.extensions.regex_scripts`，两个通道分别筛选，不复制规则数组。普通 ST JSON 与单文件分享均保留这些扩展及未知字段。执行需要可选显示包，见[正文正则](docs/api/DISPLAY_REGEX.md)。
