@xmanrui/dsh-im 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,8 +5,13 @@ import { withSessionBindingLock } from './session-binding-lock.mjs';
5
5
 
6
6
  const MODEL_COMMAND = /^\/model(?=$|\s)/i;
7
7
  const MODELS_COMMAND = /^\/models(?=$|\s)/i;
8
- const MODEL_USAGE = '用法:/model <序号> 或 /model <provider>/<model>';
8
+ const REASONING_COMMAND = /^\/reasoning(?=$|\s)/i;
9
+ const REASONINGS_COMMAND = /^\/reasonings(?=$|\s)/i;
10
+ const REASONING_LIST_COMMAND = /^\/reasoninglist(?=$|\s)/i;
11
+ const MODEL_USAGE = '用法:/model <序号或 provider/model> [推理等级ID]';
9
12
  const MODELS_USAGE = '用法:/models(不带参数)';
13
+ const REASONING_USAGE = '用法:/reasoning [序号、等级ID或 --default]';
14
+ const REASONING_LIST_USAGE = '用法:/reasoninglist 或 /reasonings(不带参数)';
10
15
  const SESSION_BINDING_CHANGED = 'session-binding-changed';
11
16
  const MODEL_SELECTION_MISMATCH = 'model-selection-mismatch';
12
17
  const UNSAFE_DISPLAY_TEXT_GLOBAL = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]+/gu;
@@ -28,6 +33,35 @@ function rpcOptions(signal) {
28
33
  return signal ? { signal } : {};
29
34
  }
30
35
 
36
+ function normalizeReasoning(value) {
37
+ if (value === undefined) return undefined;
38
+ if (!value || typeof value !== 'object'
39
+ || !Array.isArray(value.efforts) || value.efforts.length === 0) {
40
+ throw new TypeError('Harness returned invalid model reasoning metadata');
41
+ }
42
+ const efforts = value.efforts.map((effort) => {
43
+ if (!effort || typeof effort !== 'object'
44
+ || typeof effort.id !== 'string' || !effort.id
45
+ || typeof effort.name !== 'string' || !effort.name
46
+ || (effort.description !== undefined && typeof effort.description !== 'string')) {
47
+ throw new TypeError('Harness returned an invalid reasoning effort');
48
+ }
49
+ return {
50
+ id: effort.id,
51
+ name: effort.name,
52
+ ...(effort.description === undefined ? {} : { description: effort.description }),
53
+ };
54
+ });
55
+ if (value.defaultEffort !== undefined
56
+ && (typeof value.defaultEffort !== 'string' || !value.defaultEffort)) {
57
+ throw new TypeError('Harness returned an invalid default reasoning effort');
58
+ }
59
+ return {
60
+ efforts,
61
+ ...(value.defaultEffort === undefined ? {} : { defaultEffort: value.defaultEffort }),
62
+ };
63
+ }
64
+
31
65
  function normalizeCatalog(value, { requireCurrent = false } = {}) {
32
66
  if (!value || typeof value !== 'object'
33
67
  || !Array.isArray(value.groups) || !Array.isArray(value.failures)) {
@@ -46,10 +80,18 @@ function normalizeCatalog(value, { requireCurrent = false } = {}) {
46
80
  models: group.models.map((model) => {
47
81
  if (!model || typeof model !== 'object'
48
82
  || typeof model.id !== 'string' || !model.id
49
- || typeof model.name !== 'string' || !model.name) {
83
+ || typeof model.name !== 'string' || !model.name
84
+ || (model.description !== undefined && typeof model.description !== 'string')) {
50
85
  throw new TypeError('Harness returned an invalid model');
51
86
  }
52
- return { id: model.id, name: model.name };
87
+ return {
88
+ id: model.id,
89
+ name: model.name,
90
+ ...(model.description === undefined ? {} : { description: model.description }),
91
+ ...(model.reasoning === undefined
92
+ ? {}
93
+ : { reasoning: normalizeReasoning(model.reasoning) }),
94
+ };
53
95
  }),
54
96
  };
55
97
  });
@@ -65,10 +107,19 @@ function normalizeCatalog(value, { requireCurrent = false } = {}) {
65
107
  if (value.current !== undefined) {
66
108
  if (!value.current || typeof value.current !== 'object'
67
109
  || typeof value.current.provider !== 'string' || !value.current.provider
68
- || typeof value.current.model !== 'string' || !value.current.model) {
110
+ || typeof value.current.model !== 'string' || !value.current.model
111
+ || (value.current.reasoningEffort !== undefined
112
+ && (typeof value.current.reasoningEffort !== 'string'
113
+ || !value.current.reasoningEffort))) {
69
114
  throw new TypeError('Harness returned an invalid current model');
70
115
  }
71
- current = { provider: value.current.provider, model: value.current.model };
116
+ current = {
117
+ provider: value.current.provider,
118
+ model: value.current.model,
119
+ ...(value.current.reasoningEffort === undefined
120
+ ? {}
121
+ : { reasoningEffort: value.current.reasoningEffort }),
122
+ };
72
123
  } else if (requireCurrent) {
73
124
  throw new TypeError('Harness returned no current model');
74
125
  }
@@ -83,6 +134,24 @@ function sameModel(left, right) {
83
134
  return left?.provider === right?.provider && left?.model === right?.model;
84
135
  }
85
136
 
137
+ function sameSelection(left, right) {
138
+ return sameModel(left, right) && left?.reasoningEffort === right?.reasoningEffort;
139
+ }
140
+
141
+ function confirmsSelection(actual, requested) {
142
+ return sameModel(actual, requested)
143
+ && (requested.reasoningEffort === undefined
144
+ || actual?.reasoningEffort === requested.reasoningEffort);
145
+ }
146
+
147
+ function selectionText(selection) {
148
+ if (!selection?.provider || !selection?.model) return '';
149
+ const id = modelId(selection.provider, selection.model);
150
+ return selection.reasoningEffort === undefined
151
+ ? id
152
+ : `${id} · reasoningEffort=${safeDisplayText(selection.reasoningEffort)}`;
153
+ }
154
+
86
155
  function selectionMismatch(expected, actual, source) {
87
156
  const error = new Error(`Harness ${source} did not confirm the selected model`);
88
157
  error.code = MODEL_SELECTION_MISMATCH;
@@ -129,12 +198,54 @@ function modelAt(catalog, requestedIndex) {
129
198
  return null;
130
199
  }
131
200
 
132
- function modelNumberRequest(requested) {
201
+ function positiveNumberRequest(requested) {
133
202
  if (!/^\d+$/u.test(requested)) return null;
134
203
  const index = Number(requested);
135
204
  return { index: Number.isSafeInteger(index) && index > 0 ? index : null };
136
205
  }
137
206
 
207
+ function modelForSelection(catalog, selection) {
208
+ if (!selection) return null;
209
+ const group = catalog.groups.find(({ id }) => id === selection.provider);
210
+ return group?.models.find(({ id }) => id === selection.model) ?? null;
211
+ }
212
+
213
+ function reasoningEffortAt(model, requestedIndex) {
214
+ return model?.reasoning?.efforts?.[requestedIndex - 1] ?? null;
215
+ }
216
+
217
+ function reasoningEffortById(model, requestedId) {
218
+ return model?.reasoning?.efforts?.find(({ id }) => id === requestedId) ?? null;
219
+ }
220
+
221
+ function effectiveReasoningEffort(current, model) {
222
+ return current?.reasoningEffort ?? model?.reasoning?.defaultEffort;
223
+ }
224
+
225
+ function reasoningEffortText(model, effortId) {
226
+ if (effortId === undefined) return t('Default(由模型或 Provider 决定)');
227
+ const effort = reasoningEffortById(model, effortId);
228
+ if (!effort) return safeDisplayText(effortId);
229
+ const name = safeDisplayText(effort.name);
230
+ const id = safeDisplayText(effort.id);
231
+ return name === id ? id : `${name} (${id})`;
232
+ }
233
+
234
+ function currentReasoningEffortText(catalog) {
235
+ const model = modelForSelection(catalog, catalog.current);
236
+ return reasoningEffortText(
237
+ model,
238
+ effectiveReasoningEffort(catalog.current, model),
239
+ );
240
+ }
241
+
242
+ function reasoningMarker(effortId, currentId, defaultId) {
243
+ if (effortId === currentId && effortId === defaultId) return t('(当前、默认)');
244
+ if (effortId === currentId) return t('(当前)');
245
+ if (effortId === defaultId) return t('(默认)');
246
+ return '';
247
+ }
248
+
138
249
  function invalidModelNumberMessage(requested) {
139
250
  return [
140
251
  t('模型序号无效:{input}', { input: safeDisplayText(requested) }),
@@ -143,6 +254,29 @@ function invalidModelNumberMessage(requested) {
143
254
  ].join('\n');
144
255
  }
145
256
 
257
+ function invalidReasoningNumberMessage(requested) {
258
+ return [
259
+ t('推理等级序号无效:{input}', { input: safeDisplayText(requested) }),
260
+ '',
261
+ t('请发送 /reasoninglist 查看并输入有效的正整数序号。'),
262
+ ].join('\n');
263
+ }
264
+
265
+ function unsupportedReasoningMessage(selection, requested, model) {
266
+ const lines = [
267
+ t('模型不支持推理等级:{effort}', { effort: safeDisplayText(requested) }),
268
+ '',
269
+ safeDisplayText(selectionText(selection)),
270
+ ];
271
+ const ids = model?.reasoning?.efforts?.map(({ id }) => safeDisplayText(id)) ?? [];
272
+ if (ids.length > 0) {
273
+ lines.push(t('可用推理等级:{efforts}', { efforts: ids.join(', ') }));
274
+ } else {
275
+ lines.push(t('该模型不提供可切换的推理等级。'));
276
+ }
277
+ return lines.join('\n');
278
+ }
279
+
146
280
  function formatCatalog(catalog) {
147
281
  const currentId = catalog.current
148
282
  ? modelId(catalog.current.provider, catalog.current.model)
@@ -164,20 +298,72 @@ function formatCatalog(catalog) {
164
298
  lines.push(`- ${safeDisplayText(failure.name) || safeDisplayText(failure.id)}`);
165
299
  }
166
300
  }
167
- if (index > 0) lines.push('', t('切换模型:/model <序号>'));
301
+ if (index > 0) lines.push('', t('切换模型:/model <序号> [推理等级ID]'));
168
302
  return lines.join('\n');
169
303
  }
170
304
 
171
- function currentModelMessage(current) {
305
+ function currentModelMessage(catalog) {
172
306
  return [
173
307
  t('当前模型:'),
174
- modelId(current.provider, current.model),
308
+ modelId(catalog.current.provider, catalog.current.model),
309
+ t('当前推理等级:{effort}', { effort: currentReasoningEffortText(catalog) }),
175
310
  '',
176
311
  t('查看全部模型:/models'),
177
- t('切换模型:/model <序号>'),
312
+ t('查看可用推理等级:/reasoninglist'),
313
+ t('切换模型:/model <序号> [推理等级ID]'),
178
314
  ].join('\n');
179
315
  }
180
316
 
317
+ function currentReasoningMessage(catalog) {
318
+ return [
319
+ t('当前模型:'),
320
+ modelId(catalog.current.provider, catalog.current.model),
321
+ t('当前推理等级:{effort}', { effort: currentReasoningEffortText(catalog) }),
322
+ '',
323
+ t('查看可用推理等级:/reasoninglist'),
324
+ t('切换推理等级:/reasoning <序号或等级ID>'),
325
+ t('恢复默认等级:/reasoning --default'),
326
+ ].join('\n');
327
+ }
328
+
329
+ function formatReasoningCatalog(catalog) {
330
+ const current = catalog.current;
331
+ const model = modelForSelection(catalog, current);
332
+ const currentEffort = effectiveReasoningEffort(current, model);
333
+ const lines = [
334
+ t('当前模型:'),
335
+ modelId(current.provider, current.model),
336
+ t('当前推理等级:{effort}', { effort: reasoningEffortText(model, currentEffort) }),
337
+ '',
338
+ t('可用推理等级:'),
339
+ ];
340
+ if (!model?.reasoning) {
341
+ lines.push(
342
+ t('该模型不提供可切换的推理等级。'),
343
+ '',
344
+ t('恢复默认等级:/reasoning --default'),
345
+ );
346
+ return lines.join('\n');
347
+ }
348
+ for (const [index, effort] of model.reasoning.efforts.entries()) {
349
+ const label = reasoningEffortText(model, effort.id);
350
+ const marker = reasoningMarker(
351
+ effort.id,
352
+ currentEffort,
353
+ model.reasoning.defaultEffort,
354
+ );
355
+ lines.push(`${index + 1}. ${label}${marker}`);
356
+ const description = safeDisplayText(effort.description);
357
+ if (description) lines.push(` ${description}`);
358
+ }
359
+ lines.push(
360
+ '',
361
+ t('切换推理等级:/reasoning <序号或等级ID>'),
362
+ t('恢复默认等级:/reasoning --default'),
363
+ );
364
+ return lines.join('\n');
365
+ }
366
+
181
367
  function noSessionMessage() {
182
368
  return [
183
369
  t('当前聊天还没有会话。'),
@@ -187,6 +373,14 @@ function noSessionMessage() {
187
373
  ].join('\n');
188
374
  }
189
375
 
376
+ function noReasoningSessionMessage() {
377
+ return [
378
+ t('当前聊天还没有会话。'),
379
+ '',
380
+ t('请先发送一条普通消息创建会话。'),
381
+ ].join('\n');
382
+ }
383
+
190
384
  function errorCode(error) {
191
385
  return error?.code ?? error?.failure?.code;
192
386
  }
@@ -200,6 +394,9 @@ function modelErrorMessage(error, action) {
200
394
  return t('当前聊天绑定的会话已不存在,请重试。');
201
395
  }
202
396
  if (code === 'model-unavailable') {
397
+ if (action === 'reasoning-select') {
398
+ return t('无法切换推理等级。当前模型或推理等级不可用。');
399
+ }
203
400
  return t('无法切换到该模型。模型当前不可用,或不支持当前会话中的图片。');
204
401
  }
205
402
  if (code === WORKSPACE_SESSION_STALE || code === 'workspace-bot-not-found') {
@@ -211,26 +408,32 @@ function modelErrorMessage(error, action) {
211
408
  if (code === MODEL_SELECTION_MISMATCH) {
212
409
  const expected = error?.expected;
213
410
  const actual = error?.actual;
214
- const lines = [t('模型切换失败,请稍后重试。')];
411
+ const lines = [action === 'reasoning-select'
412
+ ? t('推理等级切换失败,请稍后重试。')
413
+ : t('模型切换失败,请稍后重试。')];
215
414
  if (expected?.provider && expected?.model) {
216
- lines.push('', `requested: ${safeDisplayText(modelId(expected.provider, expected.model))}`);
415
+ lines.push('', `requested: ${safeDisplayText(selectionText(expected))}`);
217
416
  }
218
417
  if (actual?.provider && actual?.model) {
219
418
  const label = error?.source === 'models.current'
220
419
  ? t('当前模型:')
221
420
  : 'selectModel.selected:';
222
- lines.push(`${label} ${safeDisplayText(modelId(actual.provider, actual.model))}`);
421
+ lines.push(`${label} ${safeDisplayText(selectionText(actual))}`);
223
422
  } else {
224
423
  lines.push(`${error?.source ?? 'Harness'}: unconfirmed`);
225
424
  }
226
425
  return lines.join('\n');
227
426
  }
228
427
  if (code === 'cancelled' || error?.name === 'AbortError') {
229
- return action === 'list' ? t('获取模型列表已取消。') : t('模型切换已取消。');
428
+ if (action === 'list') return t('获取模型列表已取消。');
429
+ if (action === 'reasoning-list') return t('获取推理等级列表已取消。');
430
+ if (action === 'reasoning-select') return t('推理等级切换已取消。');
431
+ return t('模型切换已取消。');
230
432
  }
231
- return action === 'list'
232
- ? t('暂时无法获取模型列表,请稍后重试。')
233
- : t('模型切换失败,请稍后重试。');
433
+ if (action === 'list') return t('暂时无法获取模型列表,请稍后重试。');
434
+ if (action === 'reasoning-list') return t('暂时无法获取推理等级,请稍后重试。');
435
+ if (action === 'reasoning-select') return t('推理等级切换失败,请稍后重试。');
436
+ return t('模型切换失败,请稍后重试。');
234
437
  }
235
438
 
236
439
  async function boundSession(harness, state, key, options) {
@@ -279,12 +482,12 @@ async function selectAndVerifyModel(session, selection, options) {
279
482
  throw new TypeError('Harness session does not support model selection');
280
483
  }
281
484
  const selected = (await session.selectModel(selection, options))?.selected;
282
- if (!sameModel(selected, selection)) {
485
+ if (!confirmsSelection(selected, selection)) {
283
486
  throw selectionMismatch(selection, selected, 'selectModel.selected');
284
487
  }
285
488
  const current = (await sessionCatalog(session, options)).current;
286
- if (!sameModel(current, selection)) {
287
- throw selectionMismatch(selection, current, 'models.current');
489
+ if (!sameSelection(current, selected)) {
490
+ throw selectionMismatch(selected, current, 'models.current');
288
491
  }
289
492
  return current;
290
493
  }
@@ -293,17 +496,29 @@ function isModelsCommand(command) {
293
496
  return MODELS_COMMAND.test(command);
294
497
  }
295
498
 
499
+ function isReasoningListCommand(command) {
500
+ return REASONING_LIST_COMMAND.test(command) || REASONINGS_COMMAND.test(command);
501
+ }
502
+
503
+ function isReasoningCommand(command) {
504
+ return REASONING_COMMAND.test(command);
505
+ }
506
+
296
507
  export function isModelCommand(text) {
297
508
  if (typeof text !== 'string') return false;
298
509
  const command = text.trim();
299
- return MODELS_COMMAND.test(command) || MODEL_COMMAND.test(command);
510
+ return MODELS_COMMAND.test(command)
511
+ || MODEL_COMMAND.test(command)
512
+ || REASONING_LIST_COMMAND.test(command)
513
+ || REASONINGS_COMMAND.test(command)
514
+ || REASONING_COMMAND.test(command);
300
515
  }
301
516
 
302
517
  export async function runModelCommand(text, harness, state, key, options = {}) {
303
518
  if (!isModelCommand(text)) return null;
304
519
  const command = text.trim();
305
520
  if (options.hasImages) {
306
- return commandResult(t('模型命令仅支持纯文字,请移除图片后重试。'));
521
+ return commandResult(t('模型和推理等级命令仅支持纯文字,请移除图片后重试。'));
307
522
  }
308
523
  const requestOptions = rpcOptions(options.signal);
309
524
 
@@ -320,20 +535,115 @@ export async function runModelCommand(text, harness, state, key, options = {}) {
320
535
  }
321
536
  }
322
537
 
323
- const match = /^\/model(?:[ \t]+([^\s]+))?[ \t]*$/iu.exec(command);
538
+ if (isReasoningListCommand(command)) {
539
+ if (!/^\/(?:reasoninglist|reasonings)[ \t]*$/iu.test(command)) {
540
+ return commandResult(t(REASONING_LIST_USAGE));
541
+ }
542
+ try {
543
+ const bound = await boundSession(harness, state, key, requestOptions);
544
+ if (!bound) return commandResult(noReasoningSessionMessage());
545
+ return commandResult(formatReasoningCatalog(
546
+ await sessionCatalog(bound.session, requestOptions),
547
+ ));
548
+ } catch (error) {
549
+ return commandResult(modelErrorMessage(error, 'reasoning-list'));
550
+ }
551
+ }
552
+
553
+ if (isReasoningCommand(command)) {
554
+ const match = /^\/reasoning(?:[ \t]+([^\s]+))?[ \t]*$/iu.exec(command);
555
+ if (!match) return commandResult(t(REASONING_USAGE));
556
+ const requested = match[1];
557
+ if (!requested) {
558
+ try {
559
+ const bound = await boundSession(harness, state, key, requestOptions);
560
+ if (!bound) return commandResult(noReasoningSessionMessage());
561
+ return commandResult(currentReasoningMessage(
562
+ await sessionCatalog(bound.session, requestOptions),
563
+ ));
564
+ } catch (error) {
565
+ return commandResult(modelErrorMessage(error, 'reasoning-list'));
566
+ }
567
+ }
568
+ if (options.pendingInteraction) {
569
+ return commandResult([
570
+ t('当前任务正在等待你的回答或审批。'),
571
+ '',
572
+ t('请先处理当前请求,或者发送 /stop 停止任务。'),
573
+ ].join('\n'));
574
+ }
575
+ try {
576
+ return await withSessionBindingLock(state, key, async () => {
577
+ const bound = await boundSession(harness, state, key, requestOptions);
578
+ if (!bound) return commandResult(noReasoningSessionMessage());
579
+ if (await sessionIsBusy(bound.session, options.control, requestOptions)) {
580
+ return commandResult(t('当前任务正在运行,请等待完成或先发送 /stop。'));
581
+ }
582
+ const catalog = await sessionCatalog(bound.session, requestOptions);
583
+ const current = catalog.current;
584
+ const model = modelForSelection(catalog, current);
585
+
586
+ let effort;
587
+ if (requested.toLowerCase() === '--default') {
588
+ effort = undefined;
589
+ } else {
590
+ if (!model?.reasoning) {
591
+ return commandResult(unsupportedReasoningMessage(current, requested, model));
592
+ }
593
+ effort = reasoningEffortById(model, requested);
594
+ if (!effort) {
595
+ const numberRequest = positiveNumberRequest(requested);
596
+ if (numberRequest?.index === null) {
597
+ return commandResult(invalidReasoningNumberMessage(requested));
598
+ }
599
+ if (!numberRequest) {
600
+ return commandResult(unsupportedReasoningMessage(current, requested, model));
601
+ }
602
+ effort = reasoningEffortAt(model, numberRequest.index);
603
+ if (!effort) return commandResult(invalidReasoningNumberMessage(requested));
604
+ }
605
+ effort = effort.id;
606
+ }
607
+
608
+ const selection = {
609
+ provider: current.provider,
610
+ model: current.model,
611
+ ...(effort === undefined ? {} : { reasoningEffort: effort }),
612
+ };
613
+ const applied = await selectAndVerifyModel(bound.session, selection, requestOptions);
614
+ assertSessionBinding(state, key, bound.sessionId);
615
+ return commandResult(t(`推理等级已切换为:
616
+ {effort}
617
+
618
+ 当前模型:{model}
619
+ 后续消息将使用该推理等级。`, {
620
+ model: modelId(applied.provider, applied.model),
621
+ effort: reasoningEffortText(
622
+ model,
623
+ effectiveReasoningEffort(applied, model),
624
+ ),
625
+ }));
626
+ });
627
+ } catch (error) {
628
+ return commandResult(modelErrorMessage(error, 'reasoning-select'));
629
+ }
630
+ }
631
+
632
+ const match = /^\/model(?:[ \t]+([^\s]+)(?:[ \t]+([^\s]+))?)?[ \t]*$/iu.exec(command);
324
633
  if (!match) return commandResult(t(MODEL_USAGE));
325
634
  const requested = match[1];
635
+ const requestedEffort = match[2];
326
636
  if (!requested) {
327
637
  try {
328
638
  const bound = await boundSession(harness, state, key, requestOptions);
329
639
  if (!bound) return commandResult(noSessionMessage());
330
640
  const catalog = await sessionCatalog(bound.session, requestOptions);
331
- return commandResult(currentModelMessage(catalog.current));
641
+ return commandResult(currentModelMessage(catalog));
332
642
  } catch (error) {
333
643
  return commandResult(modelErrorMessage(error, 'select'));
334
644
  }
335
645
  }
336
- const numberRequest = modelNumberRequest(requested);
646
+ const numberRequest = positiveNumberRequest(requested);
337
647
  if (numberRequest?.index === null) {
338
648
  return commandResult(invalidModelNumberMessage(requested));
339
649
  }
@@ -370,6 +680,18 @@ export async function runModelCommand(text, harness, state, key, options = {}) {
370
680
  t('请发送 /models 查看可用模型。'),
371
681
  ].join('\n'));
372
682
  }
683
+ const targetModel = modelForSelection(catalog, selection);
684
+ if (requestedEffort !== undefined) {
685
+ const effort = reasoningEffortById(targetModel, requestedEffort);
686
+ if (!effort) {
687
+ return commandResult(unsupportedReasoningMessage(
688
+ selection,
689
+ requestedEffort,
690
+ targetModel,
691
+ ));
692
+ }
693
+ selection.reasoningEffort = effort.id;
694
+ }
373
695
 
374
696
  let applied;
375
697
  if (bound) {
@@ -400,8 +722,15 @@ export async function runModelCommand(text, harness, state, key, options = {}) {
400
722
  }
401
723
  return commandResult(t(`模型已切换为:
402
724
  {model}
403
-
404
- 后续消息将使用该模型。`, { model: modelId(applied.provider, applied.model) }));
725
+ 推理等级:{effort}
726
+
727
+ 后续消息将使用该模型和推理等级。`, {
728
+ model: modelId(applied.provider, applied.model),
729
+ effort: reasoningEffortText(
730
+ targetModel,
731
+ effectiveReasoningEffort(applied, targetModel),
732
+ ),
733
+ }));
405
734
  });
406
735
  } catch (error) {
407
736
  return commandResult(modelErrorMessage(error, 'select'));
@@ -401,8 +401,10 @@ export class TextHarnessBridge {
401
401
  t('/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题'),
402
402
  t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
403
403
  t('/models 按序号列出所有可用模型'),
404
- t('/model [序号或完整模型ID] 查看或切换当前会话模型'),
405
- t('示例:先发 /models,再发 /model 2'),
404
+ t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
405
+ t('/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级'),
406
+ t('/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型'),
407
+ t('示例:先发 /models,再发 /model 2 [推理等级ID]'),
406
408
  t('/presetlist 按序号列出可用 Agent Preset'),
407
409
  t('/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset'),
408
410
  t('纯数字 ID:/preset id:<ID>'),
@@ -53,8 +53,10 @@ function helpText() {
53
53
  t('/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题'),
54
54
  t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
55
55
  t('/models 按序号列出所有可用模型'),
56
- t('/model [序号或完整模型ID] 查看或切换当前会话模型'),
57
- t('示例:先发 /models,再发 /model 2'),
56
+ t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
57
+ t('/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级'),
58
+ t('/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型'),
59
+ t('示例:先发 /models,再发 /model 2 [推理等级ID]'),
58
60
  t('/presetlist 按序号列出可用 Agent Preset'),
59
61
  t('/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset'),
60
62
  t('纯数字 ID:/preset id:<ID>'),
@@ -12,6 +12,7 @@ export const WEIXIN_QR_BASE_URL = 'https://ilinkai.weixin.qq.com/';
12
12
  export const WEIXIN_PROTOCOL_VERSION = '2.4.6';
13
13
  export const DEFAULT_BOT_TYPE = '3';
14
14
  export const WEIXIN_CDN_BASE_URL = 'https://novac2c.cdn.weixin.qq.com/c2c';
15
+ export const DEFAULT_WEIXIN_MAX_MESSAGE_CHARS = 1_800;
15
16
 
16
17
  const WEIXIN_CDN_HOST = 'novac2c.cdn.weixin.qq.com';
17
18
 
@@ -744,7 +745,7 @@ export function weixinMessageId(message) {
744
745
  return nonEmptyString(message?.client_id);
745
746
  }
746
747
 
747
- export function splitWeixinText(text, maxChars = 4_000) {
748
+ export function splitWeixinText(text, maxChars = DEFAULT_WEIXIN_MAX_MESSAGE_CHARS) {
748
749
  if (text.length <= maxChars) return [text];
749
750
  const chunks = [];
750
751
  let remaining = text;
@@ -1,4 +1,5 @@
1
1
  import {
2
+ DEFAULT_WEIXIN_MAX_MESSAGE_CHARS,
2
3
  extractWeixinFiles,
3
4
  extractWeixinImages,
4
5
  extractWeixinText,
@@ -59,8 +60,10 @@ const HELP_TEXT = () => [
59
60
  t('/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题'),
60
61
  t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
61
62
  t('/models 按序号列出所有可用模型'),
62
- t('/model [序号或完整模型ID] 查看或切换当前会话模型'),
63
- t('示例:先发 /models,再发 /model 2'),
63
+ t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
64
+ t('/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级'),
65
+ t('/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型'),
66
+ t('示例:先发 /models,再发 /model 2 [推理等级ID]'),
64
67
  t('/presetlist 按序号列出可用 Agent Preset'),
65
68
  t('/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset'),
66
69
  t('纯数字 ID:/preset id:<ID>'),
@@ -184,7 +187,7 @@ export class WeixinHarnessBridge {
184
187
  status = createWeixinBridgeStatus(),
185
188
  logger = console,
186
189
  replyTimeoutMs = 600_000,
187
- maxMessageChars = 4_000,
190
+ maxMessageChars = DEFAULT_WEIXIN_MAX_MESSAGE_CHARS,
188
191
  signal,
189
192
  }) {
190
193
  if (!api || typeof api.sendText !== 'function') throw new TypeError('Weixin API is required');
@@ -1,4 +1,4 @@
1
- import { WeixinApiError } from './weixin-api.mjs';
1
+ import { DEFAULT_WEIXIN_MAX_MESSAGE_CHARS, WeixinApiError } from './weixin-api.mjs';
2
2
  import { createWeixinBridgeStatus, WeixinHarnessBridge } from './weixin-bridge.mjs';
3
3
  import {
4
4
  connectionTestTarget,
@@ -109,7 +109,7 @@ export class WeixinRuntime {
109
109
  state,
110
110
  logger = console,
111
111
  replyTimeoutMs = 600_000,
112
- maxMessageChars = 4_000,
112
+ maxMessageChars = DEFAULT_WEIXIN_MAX_MESSAGE_CHARS,
113
113
  startRetryDelaysMs,
114
114
  }) {
115
115
  if (!api || !config || !token || !harness || !state) {