mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-12 06:50:45 +08:00
feat: add reasoning effort commands
This commit is contained in:
parent
36cbbe48e7
commit
9e39333ff6
24 changed files with 1346 additions and 240 deletions
|
|
@ -57,8 +57,10 @@ const HELP_TEXT_LINES = [
|
|||
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
||||
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
||||
'/models 按序号列出所有可用模型',
|
||||
'/model [序号或完整模型ID] 查看或切换当前会话模型',
|
||||
'示例:先发 /models,再发 /model 2',
|
||||
'/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级',
|
||||
'/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级',
|
||||
'/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型',
|
||||
'示例:先发 /models,再发 /model 2 [推理等级ID]',
|
||||
'/presetlist 按序号列出可用 Agent Preset',
|
||||
'/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset',
|
||||
'纯数字 ID:/preset id:<ID>',
|
||||
|
|
|
|||
|
|
@ -509,7 +509,9 @@ export function menuHelpText() {
|
|||
'纯数字 ID:/preset id:<ID>',
|
||||
'/preset --default 跟随 Host 默认',
|
||||
'/models 列出模型',
|
||||
'/model 2 按序号切换模型',
|
||||
'/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级',
|
||||
'/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级',
|
||||
'/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型',
|
||||
'',
|
||||
'🎮 任务控制',
|
||||
'/stop 停止当前任务',
|
||||
|
|
@ -559,7 +561,9 @@ const HELP_TEXT_COMMANDS = [
|
|||
'`/preset [序号/ID]` — 切换预设',
|
||||
'`/preset --default` — 跟随默认',
|
||||
'`/models` — 列出模型',
|
||||
'`/model 2` — 切换模型',
|
||||
'`/reasoninglist` 或 `/reasonings` — 按序号列出当前模型可用推理等级',
|
||||
'`/reasoning [序号、等级ID或 --default]` — 查看或切换当前推理等级',
|
||||
'`/model [序号或完整模型ID] [推理等级ID]` — 查看或切换当前会话模型',
|
||||
'`/repair` — 修复卡片按钮',
|
||||
].join('\n');
|
||||
|
||||
|
|
|
|||
|
|
@ -70,8 +70,10 @@ function helpText() {
|
|||
t('/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
||||
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
||||
t('/models 按序号列出所有可用模型'),
|
||||
t('/model [序号或完整模型ID] 查看或切换当前会话模型'),
|
||||
t('示例:先发 /models,再发 /model 2'),
|
||||
t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
|
||||
t('/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级'),
|
||||
t('/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型'),
|
||||
t('示例:先发 /models,再发 /model 2 [推理等级ID]'),
|
||||
t('/presetlist 按序号列出可用 Agent Preset'),
|
||||
t('/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset'),
|
||||
t('纯数字 ID:/preset id:<ID>'),
|
||||
|
|
|
|||
|
|
@ -104,7 +104,26 @@ function validModelSelection(value) {
|
|||
&& Boolean(value.provider)
|
||||
&& typeof value.model === 'string'
|
||||
&& Boolean(value.model)
|
||||
&& (value.reasoningEffort === undefined || typeof value.reasoningEffort === 'string');
|
||||
&& (value.reasoningEffort === undefined
|
||||
|| (typeof value.reasoningEffort === 'string' && Boolean(value.reasoningEffort)));
|
||||
}
|
||||
|
||||
function validModelReasoning(value) {
|
||||
return value !== null
|
||||
&& typeof value === 'object'
|
||||
&& Array.isArray(value.efforts)
|
||||
&& value.efforts.length > 0
|
||||
&& value.efforts.every((effort) => (
|
||||
effort !== null
|
||||
&& typeof effort === 'object'
|
||||
&& typeof effort.id === 'string'
|
||||
&& Boolean(effort.id)
|
||||
&& typeof effort.name === 'string'
|
||||
&& Boolean(effort.name)
|
||||
&& (effort.description === undefined || typeof effort.description === 'string')
|
||||
))
|
||||
&& (value.defaultEffort === undefined
|
||||
|| (typeof value.defaultEffort === 'string' && Boolean(value.defaultEffort)));
|
||||
}
|
||||
|
||||
function validateModelCatalog(value, method, { session = false } = {}) {
|
||||
|
|
@ -123,7 +142,9 @@ function validateModelCatalog(value, method, { session = false } = {}) {
|
|||
for (const model of group.models) {
|
||||
if (!model || typeof model !== 'object'
|
||||
|| typeof model.id !== 'string' || !model.id
|
||||
|| typeof model.name !== 'string' || !model.name) {
|
||||
|| typeof model.name !== 'string' || !model.name
|
||||
|| (model.description !== undefined && typeof model.description !== 'string')
|
||||
|| (model.reasoning !== undefined && !validModelReasoning(model.reasoning))) {
|
||||
throw new Error(`Harness returned an invalid response for ${method}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -728,6 +749,9 @@ export class HarnessClient {
|
|||
sessionId,
|
||||
provider: selection.provider,
|
||||
model: selection.model,
|
||||
...(selection.reasoningEffort === undefined
|
||||
? {}
|
||||
: { reasoningEffort: selection.reasoningEffort }),
|
||||
}, 30_000, signal ? { ...options, signal } : options);
|
||||
};
|
||||
const value = this.#sessionMaintenanceExecutor
|
||||
|
|
|
|||
|
|
@ -236,13 +236,12 @@ export default {
|
|||
'/unwatch ID 取消关注': '/unwatch ID Stop watching a session',
|
||||
'🤖 预设 / 模型': '🤖 Presets / models',
|
||||
'/models 列出模型': '/models List models',
|
||||
'/model 2 按序号切换模型': '/model 2 Switch model by index',
|
||||
'🎮 任务控制': '🎮 Task controls',
|
||||
'/steer 指令 给 Agent 补充指令': '/steer INSTRUCTION Steer the Agent',
|
||||
'**📋 卡片功能**\n\n1. 会话下拉 — 切换当前绑定会话\n2. 工作区下拉 — 切换工作区\n3. 🤖 预设下拉 — 切换 Agent 预设\n4. 🧠 模型下拉 — 切换模型\n5. 🆕 新会话 — 开启全新会话\n6. 📋 会话/关注 — 查看/绑定会话,管理关注\n7. ⏹ 停止 — 停止当前任务\n8. 📐 压缩 — 压缩当前会话上下文\n9. 补充指令 — 给 Agent 发送指令\n10. 🗄 归档切换 — 显示/隐藏归档会话\n11. 📊 状态 — 查看系统连接状态\n12. 📖 帮助 — 查看本帮助':
|
||||
'**📋 Card features**\n\n1. Session dropdown — switch the bound session\n2. Workspace dropdown — switch workspace\n3. 🤖 Preset dropdown — switch Agent Preset\n4. 🧠 Model dropdown — switch model\n5. 🆕 New session — start fresh\n6. 📋 Sessions/watches — view or bind sessions and manage watches\n7. ⏹ Stop — stop the current task\n8. 📐 Compact — compact the current session context\n9. Steer task — send an instruction to the Agent\n10. 🗄 Archived toggle — show or hide archived sessions\n11. 📊 Status — view connection status\n12. 📖 Help — view this help',
|
||||
'**⌨️ 文本命令**\n\n`/m` — 打开菜单卡片\n`/new` — 开启全新会话\n`/session ID` — 绑定已有会话\n`/sessionlist [工作区]` — 列出会话\n`/workspace 路径` — 切换工作区\n`/workspacelist` — 列出工作区\n`/status` — 查看连接状态\n`/compact` — 压缩上下文\n`/stop` — 停止当前任务\n`/steer 指令` — 补充指令\n`/watch ID` — 关注会话\n`/watchlist` — 关注列表\n`/unwatch ID` — 取消关注\n`/archived on/off` — 归档显隐\n`/presetlist` — 列出预设\n`/preset [序号/ID]` — 切换预设\n`/preset --default` — 跟随默认\n`/models` — 列出模型\n`/model 2` — 切换模型\n`/repair` — 修复卡片按钮':
|
||||
'**⌨️ Text commands**\n\n`/m` — open the menu card\n`/new` — start a new session\n`/session ID` — bind an existing session\n`/sessionlist [workspace]` — list sessions\n`/workspace PATH` — switch workspace\n`/workspacelist` — list workspaces\n`/status` — view connection status\n`/compact` — compact context\n`/stop` — stop the current task\n`/steer INSTRUCTION` — steer the task\n`/watch ID` — watch a session\n`/watchlist` — list watched sessions\n`/unwatch ID` — stop watching\n`/archived on/off` — show or hide archived sessions\n`/presetlist` — list presets\n`/preset [index/ID]` — switch preset\n`/preset --default` — follow default\n`/models` — list models\n`/model 2` — switch model\n`/repair` — repair card buttons',
|
||||
'**⌨️ 文本命令**\n\n`/m` — 打开菜单卡片\n`/new` — 开启全新会话\n`/session ID` — 绑定已有会话\n`/sessionlist [工作区]` — 列出会话\n`/workspace 路径` — 切换工作区\n`/workspacelist` — 列出工作区\n`/status` — 查看连接状态\n`/compact` — 压缩上下文\n`/stop` — 停止当前任务\n`/steer 指令` — 补充指令\n`/watch ID` — 关注会话\n`/watchlist` — 关注列表\n`/unwatch ID` — 取消关注\n`/archived on/off` — 归档显隐\n`/presetlist` — 列出预设\n`/preset [序号/ID]` — 切换预设\n`/preset --default` — 跟随默认\n`/models` — 列出模型\n`/reasoninglist` 或 `/reasonings` — 按序号列出当前模型可用推理等级\n`/reasoning [序号、等级ID或 --default]` — 查看或切换当前推理等级\n`/model [序号或完整模型ID] [推理等级ID]` — 查看或切换当前会话模型\n`/repair` — 修复卡片按钮':
|
||||
'**⌨️ Text commands**\n\n`/m` — open the menu card\n`/new` — start a new session\n`/session ID` — bind an existing session\n`/sessionlist [workspace]` — list sessions\n`/workspace PATH` — switch workspace\n`/workspacelist` — list workspaces\n`/status` — view connection status\n`/compact` — compact context\n`/stop` — stop the current task\n`/steer INSTRUCTION` — steer the task\n`/watch ID` — watch a session\n`/watchlist` — list watched sessions\n`/unwatch ID` — stop watching\n`/archived on/off` — show or hide archived sessions\n`/presetlist` — list presets\n`/preset [index/ID]` — switch preset\n`/preset --default` — follow default\n`/models` — list models\n`/reasoninglist` or `/reasonings` — list reasoning efforts for the current model\n`/reasoning [index, effort ID, or --default]` — show or switch reasoning effort\n`/model [index or full model ID] [reasoning effort ID]` — show or switch the current Session model\n`/repair` — repair card buttons',
|
||||
'**💡 数字兜底**\n回复数字快速操作:\n**1**工作区列表 · **2**新会话 · **3**会话/关注\n**4**状态 · **5**修复 · **6**帮助':
|
||||
'**💡 Number fallback**\nReply with a number for a quick action:\n**1** Workspace list · **2** New session · **3** Sessions/watches\n**4** Status · **5** Repair · **6** Help',
|
||||
'从下方下拉选择补充指令;最后一项可自定义输入。':
|
||||
|
|
|
|||
|
|
@ -50,9 +50,17 @@ export default {
|
|||
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话':
|
||||
'/session <Session ID or workspace index> Bind this chat to the specified session',
|
||||
'/models 按序号列出所有可用模型': '/models List all available models by index',
|
||||
'/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级':
|
||||
'/reasoninglist or /reasonings List reasoning efforts for the current model by index',
|
||||
'/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级':
|
||||
'/reasoning [index, effort ID, or --default] Show or switch the current reasoning effort',
|
||||
'/model [序号或完整模型ID] 查看或切换当前会话模型':
|
||||
'/model [index or full model ID] Show or switch the model of the current session',
|
||||
'/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型':
|
||||
'/model [index or full model ID] [reasoning effort ID] Show or switch the current Session model',
|
||||
'示例:先发 /models,再发 /model 2': 'Example: send /models first, then /model 2',
|
||||
'示例:先发 /models,再发 /model 2 [推理等级ID]':
|
||||
'Example: send /models first, then /model 2 [reasoning effort ID]',
|
||||
'/presetlist 按序号列出可用 Agent Preset': '/presetlist List available Agent Presets by index',
|
||||
'/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset':
|
||||
'/preset [index or full ID] Show or set the Agent Preset of this bot',
|
||||
|
|
|
|||
|
|
@ -134,17 +134,39 @@ export default {
|
|||
|
||||
// model-command.mjs
|
||||
'用法:/model <序号> 或 /model <provider>/<model>': 'Usage: /model <index> or /model <provider>/<model>',
|
||||
'用法:/model <序号或 provider/model> [推理等级ID]':
|
||||
'Usage: /model <index or provider/model> [reasoning effort ID]',
|
||||
'用法:/models(不带参数)': 'Usage: /models (no arguments)',
|
||||
'用法:/reasoning [序号、等级ID或 --default]':
|
||||
'Usage: /reasoning [index, effort ID, or --default]',
|
||||
'用法:/reasoninglist 或 /reasonings(不带参数)':
|
||||
'Usage: /reasoninglist or /reasonings (no arguments)',
|
||||
'模型序号无效:{input}': 'Invalid model index: {input}',
|
||||
'请发送 /models 查看并输入有效的正整数序号。':
|
||||
'Please send /models to view the list and enter a valid positive integer index.',
|
||||
'推理等级序号无效:{input}': 'Invalid reasoning effort index: {input}',
|
||||
'请发送 /reasoninglist 查看并输入有效的正整数序号。':
|
||||
'Please send /reasoninglist and enter a valid positive integer index.',
|
||||
'可用模型:': 'Available models:',
|
||||
'可用推理等级:': 'Available reasoning efforts:',
|
||||
'可用推理等级:{efforts}': 'Available reasoning efforts: {efforts}',
|
||||
'当前没有可用模型。': 'No models are currently available.',
|
||||
'以下模型提供方暂时不可用:': 'The following model providers are temporarily unavailable:',
|
||||
'切换模型:/model <序号>': 'To switch models: /model <index>',
|
||||
'切换模型:/model <序号> [推理等级ID]':
|
||||
'Switch model: /model <index> [reasoning effort ID]',
|
||||
'当前模型:': 'Current model:',
|
||||
'当前推理等级:{effort}': 'Current reasoning effort: {effort}',
|
||||
'Default(由模型或 Provider 决定)': 'Default (determined by the model or Provider)',
|
||||
'(当前、默认)': ' (current, default)',
|
||||
'(默认)': ' (default)',
|
||||
'查看全部模型:/models': 'See all models: /models',
|
||||
'查看可用推理等级:/reasoninglist': 'See reasoning efforts: /reasoninglist',
|
||||
'切换推理等级:/reasoning <序号或等级ID>':
|
||||
'Switch reasoning effort: /reasoning <index or effort ID>',
|
||||
'恢复默认等级:/reasoning --default': 'Restore the default effort: /reasoning --default',
|
||||
'当前聊天还没有会话。': 'This chat has no Session yet.',
|
||||
'请先发送一条普通消息创建会话。': 'Send a regular message first to create a Session.',
|
||||
'查看模型:/models': 'View models: /models',
|
||||
'选择模型:/model <序号>': 'Select a model: /model <index>',
|
||||
'当前任务正在运行,请等待完成或先发送 /stop。':
|
||||
|
|
@ -153,22 +175,39 @@ export default {
|
|||
'The Session bound to this chat no longer exists; please try again.',
|
||||
'无法切换到该模型。模型当前不可用,或不支持当前会话中的图片。':
|
||||
'Cannot switch to that model. It is currently unavailable, or does not support the images in this Session.',
|
||||
'无法切换推理等级。当前模型或推理等级不可用。':
|
||||
'Cannot switch the reasoning effort. The current model or effort is unavailable.',
|
||||
'当前聊天绑定的会话已发生变化,请重试。':
|
||||
'The Session bound to this chat has changed; please try again.',
|
||||
'获取模型列表已取消。': 'Fetching the model list was cancelled.',
|
||||
'获取推理等级列表已取消。': 'Fetching the reasoning effort list was cancelled.',
|
||||
'模型切换已取消。': 'The model switch was cancelled.',
|
||||
'推理等级切换已取消。': 'The reasoning effort switch was cancelled.',
|
||||
'暂时无法获取模型列表,请稍后重试。':
|
||||
'Unable to get the model list right now; please try again later.',
|
||||
'暂时无法获取推理等级,请稍后重试。':
|
||||
'Unable to get reasoning efforts right now; please try again later.',
|
||||
'模型切换失败,请稍后重试。': 'Failed to switch the model; please try again later.',
|
||||
'推理等级切换失败,请稍后重试。':
|
||||
'Failed to switch the reasoning effort; please try again later.',
|
||||
'模型命令仅支持纯文字,请移除图片后重试。':
|
||||
'Model commands support text only; please remove images and try again.',
|
||||
'模型和推理等级命令仅支持纯文字,请移除图片后重试。':
|
||||
'Model and reasoning effort commands support text only; please remove images and try again.',
|
||||
'当前任务正在等待你的回答或审批。': 'The current task is waiting for your answer or approval.',
|
||||
'请先处理当前请求,或者发送 /stop 停止任务。':
|
||||
'Please handle the current request first, or send /stop to stop the task.',
|
||||
'没有找到模型:{model}': 'Model not found: {model}',
|
||||
'请发送 /models 查看可用模型。': 'Please send /models to view available models.',
|
||||
'模型不支持推理等级:{effort}': 'The model does not support reasoning effort: {effort}',
|
||||
'该模型不提供可切换的推理等级。':
|
||||
'This model does not provide switchable reasoning efforts.',
|
||||
'模型已切换为:\n{model}\n\n后续消息将使用该模型。':
|
||||
'Model switched to:\n{model}\n\nSubsequent messages will use this model.',
|
||||
'模型已切换为:\n{model}\n推理等级:{effort}\n\n后续消息将使用该模型和推理等级。':
|
||||
'Model switched to:\n{model}\nReasoning effort: {effort}\n\nSubsequent messages will use this model and reasoning effort.',
|
||||
'推理等级已切换为:\n{effort}\n\n当前模型:{model}\n后续消息将使用该推理等级。':
|
||||
'Reasoning effort switched to:\n{effort}\n\nCurrent model: {model}\nSubsequent messages will use this reasoning effort.',
|
||||
|
||||
// compact-command.mjs
|
||||
'用法:/compact(不带参数)': 'Usage: /compact (no arguments)',
|
||||
|
|
|
|||
|
|
@ -5,8 +5,13 @@ import { withSessionBindingLock } from './session-binding-lock.mjs';
|
|||
|
||||
const MODEL_COMMAND = /^\/model(?=$|\s)/i;
|
||||
const MODELS_COMMAND = /^\/models(?=$|\s)/i;
|
||||
const MODEL_USAGE = '用法:/model <序号> 或 /model <provider>/<model>';
|
||||
const REASONING_COMMAND = /^\/reasoning(?=$|\s)/i;
|
||||
const REASONINGS_COMMAND = /^\/reasonings(?=$|\s)/i;
|
||||
const REASONING_LIST_COMMAND = /^\/reasoninglist(?=$|\s)/i;
|
||||
const MODEL_USAGE = '用法:/model <序号或 provider/model> [推理等级ID]';
|
||||
const MODELS_USAGE = '用法:/models(不带参数)';
|
||||
const REASONING_USAGE = '用法:/reasoning [序号、等级ID或 --default]';
|
||||
const REASONING_LIST_USAGE = '用法:/reasoninglist 或 /reasonings(不带参数)';
|
||||
const SESSION_BINDING_CHANGED = 'session-binding-changed';
|
||||
const MODEL_SELECTION_MISMATCH = 'model-selection-mismatch';
|
||||
const UNSAFE_DISPLAY_TEXT_GLOBAL = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]+/gu;
|
||||
|
|
@ -28,6 +33,35 @@ function rpcOptions(signal) {
|
|||
return signal ? { signal } : {};
|
||||
}
|
||||
|
||||
function normalizeReasoning(value) {
|
||||
if (value === undefined) return undefined;
|
||||
if (!value || typeof value !== 'object'
|
||||
|| !Array.isArray(value.efforts) || value.efforts.length === 0) {
|
||||
throw new TypeError('Harness returned invalid model reasoning metadata');
|
||||
}
|
||||
const efforts = value.efforts.map((effort) => {
|
||||
if (!effort || typeof effort !== 'object'
|
||||
|| typeof effort.id !== 'string' || !effort.id
|
||||
|| typeof effort.name !== 'string' || !effort.name
|
||||
|| (effort.description !== undefined && typeof effort.description !== 'string')) {
|
||||
throw new TypeError('Harness returned an invalid reasoning effort');
|
||||
}
|
||||
return {
|
||||
id: effort.id,
|
||||
name: effort.name,
|
||||
...(effort.description === undefined ? {} : { description: effort.description }),
|
||||
};
|
||||
});
|
||||
if (value.defaultEffort !== undefined
|
||||
&& (typeof value.defaultEffort !== 'string' || !value.defaultEffort)) {
|
||||
throw new TypeError('Harness returned an invalid default reasoning effort');
|
||||
}
|
||||
return {
|
||||
efforts,
|
||||
...(value.defaultEffort === undefined ? {} : { defaultEffort: value.defaultEffort }),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeCatalog(value, { requireCurrent = false } = {}) {
|
||||
if (!value || typeof value !== 'object'
|
||||
|| !Array.isArray(value.groups) || !Array.isArray(value.failures)) {
|
||||
|
|
@ -46,10 +80,18 @@ function normalizeCatalog(value, { requireCurrent = false } = {}) {
|
|||
models: group.models.map((model) => {
|
||||
if (!model || typeof model !== 'object'
|
||||
|| typeof model.id !== 'string' || !model.id
|
||||
|| typeof model.name !== 'string' || !model.name) {
|
||||
|| typeof model.name !== 'string' || !model.name
|
||||
|| (model.description !== undefined && typeof model.description !== 'string')) {
|
||||
throw new TypeError('Harness returned an invalid model');
|
||||
}
|
||||
return { id: model.id, name: model.name };
|
||||
return {
|
||||
id: model.id,
|
||||
name: model.name,
|
||||
...(model.description === undefined ? {} : { description: model.description }),
|
||||
...(model.reasoning === undefined
|
||||
? {}
|
||||
: { reasoning: normalizeReasoning(model.reasoning) }),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
|
@ -65,10 +107,19 @@ function normalizeCatalog(value, { requireCurrent = false } = {}) {
|
|||
if (value.current !== undefined) {
|
||||
if (!value.current || typeof value.current !== 'object'
|
||||
|| typeof value.current.provider !== 'string' || !value.current.provider
|
||||
|| typeof value.current.model !== 'string' || !value.current.model) {
|
||||
|| typeof value.current.model !== 'string' || !value.current.model
|
||||
|| (value.current.reasoningEffort !== undefined
|
||||
&& (typeof value.current.reasoningEffort !== 'string'
|
||||
|| !value.current.reasoningEffort))) {
|
||||
throw new TypeError('Harness returned an invalid current model');
|
||||
}
|
||||
current = { provider: value.current.provider, model: value.current.model };
|
||||
current = {
|
||||
provider: value.current.provider,
|
||||
model: value.current.model,
|
||||
...(value.current.reasoningEffort === undefined
|
||||
? {}
|
||||
: { reasoningEffort: value.current.reasoningEffort }),
|
||||
};
|
||||
} else if (requireCurrent) {
|
||||
throw new TypeError('Harness returned no current model');
|
||||
}
|
||||
|
|
@ -83,6 +134,24 @@ function sameModel(left, right) {
|
|||
return left?.provider === right?.provider && left?.model === right?.model;
|
||||
}
|
||||
|
||||
function sameSelection(left, right) {
|
||||
return sameModel(left, right) && left?.reasoningEffort === right?.reasoningEffort;
|
||||
}
|
||||
|
||||
function confirmsSelection(actual, requested) {
|
||||
return sameModel(actual, requested)
|
||||
&& (requested.reasoningEffort === undefined
|
||||
|| actual?.reasoningEffort === requested.reasoningEffort);
|
||||
}
|
||||
|
||||
function selectionText(selection) {
|
||||
if (!selection?.provider || !selection?.model) return '';
|
||||
const id = modelId(selection.provider, selection.model);
|
||||
return selection.reasoningEffort === undefined
|
||||
? id
|
||||
: `${id} · reasoningEffort=${safeDisplayText(selection.reasoningEffort)}`;
|
||||
}
|
||||
|
||||
function selectionMismatch(expected, actual, source) {
|
||||
const error = new Error(`Harness ${source} did not confirm the selected model`);
|
||||
error.code = MODEL_SELECTION_MISMATCH;
|
||||
|
|
@ -129,12 +198,54 @@ function modelAt(catalog, requestedIndex) {
|
|||
return null;
|
||||
}
|
||||
|
||||
function modelNumberRequest(requested) {
|
||||
function positiveNumberRequest(requested) {
|
||||
if (!/^\d+$/u.test(requested)) return null;
|
||||
const index = Number(requested);
|
||||
return { index: Number.isSafeInteger(index) && index > 0 ? index : null };
|
||||
}
|
||||
|
||||
function modelForSelection(catalog, selection) {
|
||||
if (!selection) return null;
|
||||
const group = catalog.groups.find(({ id }) => id === selection.provider);
|
||||
return group?.models.find(({ id }) => id === selection.model) ?? null;
|
||||
}
|
||||
|
||||
function reasoningEffortAt(model, requestedIndex) {
|
||||
return model?.reasoning?.efforts?.[requestedIndex - 1] ?? null;
|
||||
}
|
||||
|
||||
function reasoningEffortById(model, requestedId) {
|
||||
return model?.reasoning?.efforts?.find(({ id }) => id === requestedId) ?? null;
|
||||
}
|
||||
|
||||
function effectiveReasoningEffort(current, model) {
|
||||
return current?.reasoningEffort ?? model?.reasoning?.defaultEffort;
|
||||
}
|
||||
|
||||
function reasoningEffortText(model, effortId) {
|
||||
if (effortId === undefined) return t('Default(由模型或 Provider 决定)');
|
||||
const effort = reasoningEffortById(model, effortId);
|
||||
if (!effort) return safeDisplayText(effortId);
|
||||
const name = safeDisplayText(effort.name);
|
||||
const id = safeDisplayText(effort.id);
|
||||
return name === id ? id : `${name} (${id})`;
|
||||
}
|
||||
|
||||
function currentReasoningEffortText(catalog) {
|
||||
const model = modelForSelection(catalog, catalog.current);
|
||||
return reasoningEffortText(
|
||||
model,
|
||||
effectiveReasoningEffort(catalog.current, model),
|
||||
);
|
||||
}
|
||||
|
||||
function reasoningMarker(effortId, currentId, defaultId) {
|
||||
if (effortId === currentId && effortId === defaultId) return t('(当前、默认)');
|
||||
if (effortId === currentId) return t('(当前)');
|
||||
if (effortId === defaultId) return t('(默认)');
|
||||
return '';
|
||||
}
|
||||
|
||||
function invalidModelNumberMessage(requested) {
|
||||
return [
|
||||
t('模型序号无效:{input}', { input: safeDisplayText(requested) }),
|
||||
|
|
@ -143,6 +254,29 @@ function invalidModelNumberMessage(requested) {
|
|||
].join('\n');
|
||||
}
|
||||
|
||||
function invalidReasoningNumberMessage(requested) {
|
||||
return [
|
||||
t('推理等级序号无效:{input}', { input: safeDisplayText(requested) }),
|
||||
'',
|
||||
t('请发送 /reasoninglist 查看并输入有效的正整数序号。'),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function unsupportedReasoningMessage(selection, requested, model) {
|
||||
const lines = [
|
||||
t('模型不支持推理等级:{effort}', { effort: safeDisplayText(requested) }),
|
||||
'',
|
||||
safeDisplayText(selectionText(selection)),
|
||||
];
|
||||
const ids = model?.reasoning?.efforts?.map(({ id }) => safeDisplayText(id)) ?? [];
|
||||
if (ids.length > 0) {
|
||||
lines.push(t('可用推理等级:{efforts}', { efforts: ids.join(', ') }));
|
||||
} else {
|
||||
lines.push(t('该模型不提供可切换的推理等级。'));
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function formatCatalog(catalog) {
|
||||
const currentId = catalog.current
|
||||
? modelId(catalog.current.provider, catalog.current.model)
|
||||
|
|
@ -164,20 +298,72 @@ function formatCatalog(catalog) {
|
|||
lines.push(`- ${safeDisplayText(failure.name) || safeDisplayText(failure.id)}`);
|
||||
}
|
||||
}
|
||||
if (index > 0) lines.push('', t('切换模型:/model <序号>'));
|
||||
if (index > 0) lines.push('', t('切换模型:/model <序号> [推理等级ID]'));
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function currentModelMessage(current) {
|
||||
function currentModelMessage(catalog) {
|
||||
return [
|
||||
t('当前模型:'),
|
||||
modelId(current.provider, current.model),
|
||||
modelId(catalog.current.provider, catalog.current.model),
|
||||
t('当前推理等级:{effort}', { effort: currentReasoningEffortText(catalog) }),
|
||||
'',
|
||||
t('查看全部模型:/models'),
|
||||
t('切换模型:/model <序号>'),
|
||||
t('查看可用推理等级:/reasoninglist'),
|
||||
t('切换模型:/model <序号> [推理等级ID]'),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function currentReasoningMessage(catalog) {
|
||||
return [
|
||||
t('当前模型:'),
|
||||
modelId(catalog.current.provider, catalog.current.model),
|
||||
t('当前推理等级:{effort}', { effort: currentReasoningEffortText(catalog) }),
|
||||
'',
|
||||
t('查看可用推理等级:/reasoninglist'),
|
||||
t('切换推理等级:/reasoning <序号或等级ID>'),
|
||||
t('恢复默认等级:/reasoning --default'),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function formatReasoningCatalog(catalog) {
|
||||
const current = catalog.current;
|
||||
const model = modelForSelection(catalog, current);
|
||||
const currentEffort = effectiveReasoningEffort(current, model);
|
||||
const lines = [
|
||||
t('当前模型:'),
|
||||
modelId(current.provider, current.model),
|
||||
t('当前推理等级:{effort}', { effort: reasoningEffortText(model, currentEffort) }),
|
||||
'',
|
||||
t('可用推理等级:'),
|
||||
];
|
||||
if (!model?.reasoning) {
|
||||
lines.push(
|
||||
t('该模型不提供可切换的推理等级。'),
|
||||
'',
|
||||
t('恢复默认等级:/reasoning --default'),
|
||||
);
|
||||
return lines.join('\n');
|
||||
}
|
||||
for (const [index, effort] of model.reasoning.efforts.entries()) {
|
||||
const label = reasoningEffortText(model, effort.id);
|
||||
const marker = reasoningMarker(
|
||||
effort.id,
|
||||
currentEffort,
|
||||
model.reasoning.defaultEffort,
|
||||
);
|
||||
lines.push(`${index + 1}. ${label}${marker}`);
|
||||
const description = safeDisplayText(effort.description);
|
||||
if (description) lines.push(` ${description}`);
|
||||
}
|
||||
lines.push(
|
||||
'',
|
||||
t('切换推理等级:/reasoning <序号或等级ID>'),
|
||||
t('恢复默认等级:/reasoning --default'),
|
||||
);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function noSessionMessage() {
|
||||
return [
|
||||
t('当前聊天还没有会话。'),
|
||||
|
|
@ -187,6 +373,14 @@ function noSessionMessage() {
|
|||
].join('\n');
|
||||
}
|
||||
|
||||
function noReasoningSessionMessage() {
|
||||
return [
|
||||
t('当前聊天还没有会话。'),
|
||||
'',
|
||||
t('请先发送一条普通消息创建会话。'),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function errorCode(error) {
|
||||
return error?.code ?? error?.failure?.code;
|
||||
}
|
||||
|
|
@ -200,6 +394,9 @@ function modelErrorMessage(error, action) {
|
|||
return t('当前聊天绑定的会话已不存在,请重试。');
|
||||
}
|
||||
if (code === 'model-unavailable') {
|
||||
if (action === 'reasoning-select') {
|
||||
return t('无法切换推理等级。当前模型或推理等级不可用。');
|
||||
}
|
||||
return t('无法切换到该模型。模型当前不可用,或不支持当前会话中的图片。');
|
||||
}
|
||||
if (code === WORKSPACE_SESSION_STALE || code === 'workspace-bot-not-found') {
|
||||
|
|
@ -211,26 +408,32 @@ function modelErrorMessage(error, action) {
|
|||
if (code === MODEL_SELECTION_MISMATCH) {
|
||||
const expected = error?.expected;
|
||||
const actual = error?.actual;
|
||||
const lines = [t('模型切换失败,请稍后重试。')];
|
||||
const lines = [action === 'reasoning-select'
|
||||
? t('推理等级切换失败,请稍后重试。')
|
||||
: t('模型切换失败,请稍后重试。')];
|
||||
if (expected?.provider && expected?.model) {
|
||||
lines.push('', `requested: ${safeDisplayText(modelId(expected.provider, expected.model))}`);
|
||||
lines.push('', `requested: ${safeDisplayText(selectionText(expected))}`);
|
||||
}
|
||||
if (actual?.provider && actual?.model) {
|
||||
const label = error?.source === 'models.current'
|
||||
? t('当前模型:')
|
||||
: 'selectModel.selected:';
|
||||
lines.push(`${label} ${safeDisplayText(modelId(actual.provider, actual.model))}`);
|
||||
lines.push(`${label} ${safeDisplayText(selectionText(actual))}`);
|
||||
} else {
|
||||
lines.push(`${error?.source ?? 'Harness'}: unconfirmed`);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
if (code === 'cancelled' || error?.name === 'AbortError') {
|
||||
return action === 'list' ? t('获取模型列表已取消。') : t('模型切换已取消。');
|
||||
if (action === 'list') return t('获取模型列表已取消。');
|
||||
if (action === 'reasoning-list') return t('获取推理等级列表已取消。');
|
||||
if (action === 'reasoning-select') return t('推理等级切换已取消。');
|
||||
return t('模型切换已取消。');
|
||||
}
|
||||
return action === 'list'
|
||||
? t('暂时无法获取模型列表,请稍后重试。')
|
||||
: t('模型切换失败,请稍后重试。');
|
||||
if (action === 'list') return t('暂时无法获取模型列表,请稍后重试。');
|
||||
if (action === 'reasoning-list') return t('暂时无法获取推理等级,请稍后重试。');
|
||||
if (action === 'reasoning-select') return t('推理等级切换失败,请稍后重试。');
|
||||
return t('模型切换失败,请稍后重试。');
|
||||
}
|
||||
|
||||
async function boundSession(harness, state, key, options) {
|
||||
|
|
@ -279,12 +482,12 @@ async function selectAndVerifyModel(session, selection, options) {
|
|||
throw new TypeError('Harness session does not support model selection');
|
||||
}
|
||||
const selected = (await session.selectModel(selection, options))?.selected;
|
||||
if (!sameModel(selected, selection)) {
|
||||
if (!confirmsSelection(selected, selection)) {
|
||||
throw selectionMismatch(selection, selected, 'selectModel.selected');
|
||||
}
|
||||
const current = (await sessionCatalog(session, options)).current;
|
||||
if (!sameModel(current, selection)) {
|
||||
throw selectionMismatch(selection, current, 'models.current');
|
||||
if (!sameSelection(current, selected)) {
|
||||
throw selectionMismatch(selected, current, 'models.current');
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
|
@ -293,17 +496,29 @@ function isModelsCommand(command) {
|
|||
return MODELS_COMMAND.test(command);
|
||||
}
|
||||
|
||||
function isReasoningListCommand(command) {
|
||||
return REASONING_LIST_COMMAND.test(command) || REASONINGS_COMMAND.test(command);
|
||||
}
|
||||
|
||||
function isReasoningCommand(command) {
|
||||
return REASONING_COMMAND.test(command);
|
||||
}
|
||||
|
||||
export function isModelCommand(text) {
|
||||
if (typeof text !== 'string') return false;
|
||||
const command = text.trim();
|
||||
return MODELS_COMMAND.test(command) || MODEL_COMMAND.test(command);
|
||||
return MODELS_COMMAND.test(command)
|
||||
|| MODEL_COMMAND.test(command)
|
||||
|| REASONING_LIST_COMMAND.test(command)
|
||||
|| REASONINGS_COMMAND.test(command)
|
||||
|| REASONING_COMMAND.test(command);
|
||||
}
|
||||
|
||||
export async function runModelCommand(text, harness, state, key, options = {}) {
|
||||
if (!isModelCommand(text)) return null;
|
||||
const command = text.trim();
|
||||
if (options.hasImages) {
|
||||
return commandResult(t('模型命令仅支持纯文字,请移除图片后重试。'));
|
||||
return commandResult(t('模型和推理等级命令仅支持纯文字,请移除图片后重试。'));
|
||||
}
|
||||
const requestOptions = rpcOptions(options.signal);
|
||||
|
||||
|
|
@ -320,20 +535,115 @@ export async function runModelCommand(text, harness, state, key, options = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
const match = /^\/model(?:[ \t]+([^\s]+))?[ \t]*$/iu.exec(command);
|
||||
if (isReasoningListCommand(command)) {
|
||||
if (!/^\/(?:reasoninglist|reasonings)[ \t]*$/iu.test(command)) {
|
||||
return commandResult(t(REASONING_LIST_USAGE));
|
||||
}
|
||||
try {
|
||||
const bound = await boundSession(harness, state, key, requestOptions);
|
||||
if (!bound) return commandResult(noReasoningSessionMessage());
|
||||
return commandResult(formatReasoningCatalog(
|
||||
await sessionCatalog(bound.session, requestOptions),
|
||||
));
|
||||
} catch (error) {
|
||||
return commandResult(modelErrorMessage(error, 'reasoning-list'));
|
||||
}
|
||||
}
|
||||
|
||||
if (isReasoningCommand(command)) {
|
||||
const match = /^\/reasoning(?:[ \t]+([^\s]+))?[ \t]*$/iu.exec(command);
|
||||
if (!match) return commandResult(t(REASONING_USAGE));
|
||||
const requested = match[1];
|
||||
if (!requested) {
|
||||
try {
|
||||
const bound = await boundSession(harness, state, key, requestOptions);
|
||||
if (!bound) return commandResult(noReasoningSessionMessage());
|
||||
return commandResult(currentReasoningMessage(
|
||||
await sessionCatalog(bound.session, requestOptions),
|
||||
));
|
||||
} catch (error) {
|
||||
return commandResult(modelErrorMessage(error, 'reasoning-list'));
|
||||
}
|
||||
}
|
||||
if (options.pendingInteraction) {
|
||||
return commandResult([
|
||||
t('当前任务正在等待你的回答或审批。'),
|
||||
'',
|
||||
t('请先处理当前请求,或者发送 /stop 停止任务。'),
|
||||
].join('\n'));
|
||||
}
|
||||
try {
|
||||
return await withSessionBindingLock(state, key, async () => {
|
||||
const bound = await boundSession(harness, state, key, requestOptions);
|
||||
if (!bound) return commandResult(noReasoningSessionMessage());
|
||||
if (await sessionIsBusy(bound.session, options.control, requestOptions)) {
|
||||
return commandResult(t('当前任务正在运行,请等待完成或先发送 /stop。'));
|
||||
}
|
||||
const catalog = await sessionCatalog(bound.session, requestOptions);
|
||||
const current = catalog.current;
|
||||
const model = modelForSelection(catalog, current);
|
||||
|
||||
let effort;
|
||||
if (requested.toLowerCase() === '--default') {
|
||||
effort = undefined;
|
||||
} else {
|
||||
if (!model?.reasoning) {
|
||||
return commandResult(unsupportedReasoningMessage(current, requested, model));
|
||||
}
|
||||
effort = reasoningEffortById(model, requested);
|
||||
if (!effort) {
|
||||
const numberRequest = positiveNumberRequest(requested);
|
||||
if (numberRequest?.index === null) {
|
||||
return commandResult(invalidReasoningNumberMessage(requested));
|
||||
}
|
||||
if (!numberRequest) {
|
||||
return commandResult(unsupportedReasoningMessage(current, requested, model));
|
||||
}
|
||||
effort = reasoningEffortAt(model, numberRequest.index);
|
||||
if (!effort) return commandResult(invalidReasoningNumberMessage(requested));
|
||||
}
|
||||
effort = effort.id;
|
||||
}
|
||||
|
||||
const selection = {
|
||||
provider: current.provider,
|
||||
model: current.model,
|
||||
...(effort === undefined ? {} : { reasoningEffort: effort }),
|
||||
};
|
||||
const applied = await selectAndVerifyModel(bound.session, selection, requestOptions);
|
||||
assertSessionBinding(state, key, bound.sessionId);
|
||||
return commandResult(t(`推理等级已切换为:
|
||||
{effort}
|
||||
|
||||
当前模型:{model}
|
||||
后续消息将使用该推理等级。`, {
|
||||
model: modelId(applied.provider, applied.model),
|
||||
effort: reasoningEffortText(
|
||||
model,
|
||||
effectiveReasoningEffort(applied, model),
|
||||
),
|
||||
}));
|
||||
});
|
||||
} catch (error) {
|
||||
return commandResult(modelErrorMessage(error, 'reasoning-select'));
|
||||
}
|
||||
}
|
||||
|
||||
const match = /^\/model(?:[ \t]+([^\s]+)(?:[ \t]+([^\s]+))?)?[ \t]*$/iu.exec(command);
|
||||
if (!match) return commandResult(t(MODEL_USAGE));
|
||||
const requested = match[1];
|
||||
const requestedEffort = match[2];
|
||||
if (!requested) {
|
||||
try {
|
||||
const bound = await boundSession(harness, state, key, requestOptions);
|
||||
if (!bound) return commandResult(noSessionMessage());
|
||||
const catalog = await sessionCatalog(bound.session, requestOptions);
|
||||
return commandResult(currentModelMessage(catalog.current));
|
||||
return commandResult(currentModelMessage(catalog));
|
||||
} catch (error) {
|
||||
return commandResult(modelErrorMessage(error, 'select'));
|
||||
}
|
||||
}
|
||||
const numberRequest = modelNumberRequest(requested);
|
||||
const numberRequest = positiveNumberRequest(requested);
|
||||
if (numberRequest?.index === null) {
|
||||
return commandResult(invalidModelNumberMessage(requested));
|
||||
}
|
||||
|
|
@ -370,6 +680,18 @@ export async function runModelCommand(text, harness, state, key, options = {}) {
|
|||
t('请发送 /models 查看可用模型。'),
|
||||
].join('\n'));
|
||||
}
|
||||
const targetModel = modelForSelection(catalog, selection);
|
||||
if (requestedEffort !== undefined) {
|
||||
const effort = reasoningEffortById(targetModel, requestedEffort);
|
||||
if (!effort) {
|
||||
return commandResult(unsupportedReasoningMessage(
|
||||
selection,
|
||||
requestedEffort,
|
||||
targetModel,
|
||||
));
|
||||
}
|
||||
selection.reasoningEffort = effort.id;
|
||||
}
|
||||
|
||||
let applied;
|
||||
if (bound) {
|
||||
|
|
@ -400,8 +722,15 @@ export async function runModelCommand(text, harness, state, key, options = {}) {
|
|||
}
|
||||
return commandResult(t(`模型已切换为:
|
||||
{model}
|
||||
推理等级:{effort}
|
||||
|
||||
后续消息将使用该模型。`, { model: modelId(applied.provider, applied.model) }));
|
||||
后续消息将使用该模型和推理等级。`, {
|
||||
model: modelId(applied.provider, applied.model),
|
||||
effort: reasoningEffortText(
|
||||
targetModel,
|
||||
effectiveReasoningEffort(applied, targetModel),
|
||||
),
|
||||
}));
|
||||
});
|
||||
} catch (error) {
|
||||
return commandResult(modelErrorMessage(error, 'select'));
|
||||
|
|
|
|||
|
|
@ -401,8 +401,10 @@ export class TextHarnessBridge {
|
|||
t('/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
||||
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
||||
t('/models 按序号列出所有可用模型'),
|
||||
t('/model [序号或完整模型ID] 查看或切换当前会话模型'),
|
||||
t('示例:先发 /models,再发 /model 2'),
|
||||
t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
|
||||
t('/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级'),
|
||||
t('/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型'),
|
||||
t('示例:先发 /models,再发 /model 2 [推理等级ID]'),
|
||||
t('/presetlist 按序号列出可用 Agent Preset'),
|
||||
t('/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset'),
|
||||
t('纯数字 ID:/preset id:<ID>'),
|
||||
|
|
|
|||
|
|
@ -53,8 +53,10 @@ function helpText() {
|
|||
t('/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
||||
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
||||
t('/models 按序号列出所有可用模型'),
|
||||
t('/model [序号或完整模型ID] 查看或切换当前会话模型'),
|
||||
t('示例:先发 /models,再发 /model 2'),
|
||||
t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
|
||||
t('/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级'),
|
||||
t('/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型'),
|
||||
t('示例:先发 /models,再发 /model 2 [推理等级ID]'),
|
||||
t('/presetlist 按序号列出可用 Agent Preset'),
|
||||
t('/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset'),
|
||||
t('纯数字 ID:/preset id:<ID>'),
|
||||
|
|
|
|||
|
|
@ -60,8 +60,10 @@ const HELP_TEXT = () => [
|
|||
t('/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
||||
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
||||
t('/models 按序号列出所有可用模型'),
|
||||
t('/model [序号或完整模型ID] 查看或切换当前会话模型'),
|
||||
t('示例:先发 /models,再发 /model 2'),
|
||||
t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
|
||||
t('/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级'),
|
||||
t('/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型'),
|
||||
t('示例:先发 /models,再发 /model 2 [推理等级ID]'),
|
||||
t('/presetlist 按序号列出可用 Agent Preset'),
|
||||
t('/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset'),
|
||||
t('纯数字 ID:/preset id:<ID>'),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue