# 单文件预设分享格式 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,
      "prompts": [
        { "identifier": "chatHistory", "marker": true, "role": "user" }
      ],
      "prompt_order": [
        { "character_id": 100001, "order": [
          { "identifier": "chatHistory", "enabled": true }
        ] }
      ],
      "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` 将其移除。专用“预设模式”始终移除这些内容。

## 预填充配置

`prefill` 非空时，原有字段保持必填；`extractOutput` 是同一主版本新增的可选字段，省略时按 `false` 处理：

- `enabled`：是否启用预填充自动兼容。
- `toolCalls`：是否启用 DSML 工具调用转换。
- `extractOutput`：是否启用实验性正文/工具调用提取；按照最终稳定性策略把切换前文本映射到思维链，并移除输出区控制标签。
- `removeNonOfficialTools`：关闭 DSML 处理时，非官方接口是否移除原生工具字段。官方 Beta 的原有规则不变。
- `postToolPrefix.mode`：`inherit` 继承原预设，`custom` 使用工具执行后的独立提示词。
- `postToolPrefix.text`：自定义提示词文本；留空或宏展开为空时继承原预设。

接口地址、API key、用户身份、会话 ID、会话历史、运行中的变量值和本机路径不属于预填充配置。新建分享文件只从全局状态中提取上述接口设置，不打包整个本地数据库。

## 工具预设与分组

`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 暂不支持应用”。

## 工作台导入、编辑与导出

1. “导入 JSON”自动识别 ST JSON 和本格式。导入分享文件后，将原预设保存为当前默认，并保存附带配置（包括包内工具预设和分组）。附带接口配置和工具配置都不会自动应用。
2. “应用包内接口设置（全局）”显式应用文件中的 `prefill`，下一次请求生效。
3. “导入包内工具配置”显式应用文件中的 `tools`：先显示新增、复用、重映射数量以及匹配、失配数量，确认后才写入，且不改变任何模式或会话的工具选择。未知 `tools.version` 时禁止应用并给出提示。
4. “保存接口设置”保存全局接口设置，同时将其附加到当前选中的已保存预设。包内未来扩展字段保留。
5. “导出分享文件”导出一个 `.dsh-preset.json`：提示词和名称使用当前编辑器草稿；接口配置优先使用该预设已附带的配置，没有分享数据时使用全局已保存配置；工具容器使用当前关联的工具预设（仅该预设及其 `groupIds` 引用的分组），没有关联时沿用该预设已保存的包内工具容器，新预设则导出空容器（空的 `presets` 和 `groups`）。尚未保存的接口表单内容不会被导出。
6. “导出 ST JSON”只导出 `preset.data`，用于其他 ST 兼容软件。

导入的未知字段会在相同主版本的读写中保留，包括顶层、`preset` 包装层、`prefill`、工具容器（含未知工具子版本）和扩展对象。编辑名称或提示词只替换对应字段；保存接口设置只替换已知接口配置字段。保留的外来扩展数据也会随分享文件再次导出。

## 版本演进

同一主版本只添加可选字段，旧版本客户端保留其不识别的数据。改变必填字段、字段含义或已有行为时提升外层 `version`；工具子格式独立演进。未知外层主版本必须报错，不按 ST JSON 降级解析，以免丢失配置；未知工具子版本仍可导入、编辑、导出和往返，只禁止应用。
