sintbot 1.4.15 → 1.4.17

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.
@@ -6,7 +6,7 @@ const { followupEnabled } = require('../agent/privatecore')
6
6
  // process-local arguments. It neither starts a child nor persists text,
7
7
  // credentials, callback handles, or environment values.
8
8
  function prepareC2RunnerInput({ prompt, appendSystemPrompt, msg, group, provider,
9
- config, sessionId = null, signal, extraEnv, onTurnAccepted, onSpawnFailed, onDurableEvent,
9
+ config, brain = null, sessionId = null, signal, extraEnv, onTurnAccepted, onSpawnFailed, onDurableEvent,
10
10
  onEvent = () => {}, onSteerReady, onSteerLanded, log = () => {} } = {}) {
11
11
  if (typeof prompt !== 'string' || !prompt ||
12
12
  typeof appendSystemPrompt !== 'string' || !appendSystemPrompt ||
@@ -14,6 +14,8 @@ function prepareC2RunnerInput({ prompt, appendSystemPrompt, msg, group, provider
14
14
  !group || typeof group.workdir !== 'string' || !group.workdir ||
15
15
  !provider || !['claude', 'codex'].includes(provider.runner) ||
16
16
  !config || !config.agent || typeof config.agent !== 'object' ||
17
+ (brain !== null && (typeof brain !== 'object' || typeof brain.model !== 'string' ||
18
+ typeof brain.effort !== 'string')) ||
17
19
  (sessionId !== null && (typeof sessionId !== 'string' || !sessionId)) ||
18
20
  !signal || typeof signal.aborted !== 'boolean' ||
19
21
  !extraEnv || typeof extraEnv !== 'object' || Array.isArray(extraEnv) ||
@@ -47,7 +49,10 @@ function prepareC2RunnerInput({ prompt, appendSystemPrompt, msg, group, provider
47
49
  steer: !isCodex && enabled,
48
50
  codexProjectDocFallback: isCodex && group.codexProjectDocFallback === true,
49
51
  onSteerReady, onSteerLanded,
50
- model: group.model,
52
+ // The group's model and depth on this backend (core/brain.js); empty
53
+ // leaves the choice to the executor.
54
+ model: brain ? brain.model || undefined : group.model,
55
+ effort: brain ? brain.effort : '',
51
56
  workdir: group.workdir,
52
57
  sessionId,
53
58
  permissionMode: group.permissionMode || 'dontAsk',
@@ -0,0 +1,132 @@
1
+ 'use strict'
2
+ // 群命令 /model 的词法。纯函数 —— core 不认 runner 名:哪家有什么型号 / 深度,由 bin 注入的
3
+ // modelCatalog 给出形状 { ok, models: [{ id, efforts? }] | null, aliases: [], efforts: [] }(models 为 null = 这家
4
+ // 没有可选清单,型号只能在后台改)。
5
+ //
6
+ // 写法:`/model` 后面的每个词各自**精确**匹配一类(大小写不敏感,顺序随意,每类最多一个):
7
+ // 后端(模型卡名)· 深度(档位名)· 型号(目标后端清单里的 id 或别名)。
8
+ // 没有「默认」可回(模型卡上不再有默认型号 / 深度,⇒「默认 / default」不再是重置词,当认不出的词整条拒绝。
9
+ // ⚠ **不做模糊匹配**(口径):认不出一个词就整条拒绝、一项都不改 —— 换错模型 = 换了花的钱,
10
+ // 宁可让人重发一条也不猜;此前「只认第一个词、后面的悄悄扔掉」正是要修的坑(`/model claude sonnet` 丢了 sonnet)。
11
+ // 型号可以照显示名打:`/model opus-5.5` · `/model Opus 5.5` · `/model claude-opus-5-5` · `/model GPT-6.1 Terra` 是对应的同一个 ——
12
+ // 比对键见 core/modelname.js modelKey(大小写、claude- 前缀、点 / 连字符 / 空格不算),仍是拿清单**逐个精确比对**;
13
+ // 两个清单项归一后撞了键 ⇒ 当认不出(不替人挑)。「Opus 5.5」被空格拆成两个词:一个词后面紧跟纯版本号(5.5 / 4.8)
14
+ // 或变体((1M) / [1m])就并回一个词。
15
+
16
+ const { modelKey } = require('./modelname')
17
+
18
+ // 识别为「深度」这一类的词(能不能用再按目标后端 / 型号查)。Claude Code 的五档 + Codex 服务端认的两档低档。
19
+ const EFFORT_WORDS = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']
20
+ const VERSION_RE = /^\d+(?:[.-]\d+)*$/
21
+ const VARIANT_RE = /^[[(][a-z0-9]+[\])]$/i
22
+
23
+ // 「Opus 5.5 (1M)」「GPT-6.1 Terra」这类被空格拆开的显示名并回一个词:字母结尾 + 纯版本号 · 数字结尾 + 变体 ·
24
+ // 数字结尾 + 纯字母词(Codex 官方名称「GPT-6.1 Terra」照着打被拆成两个词、整条拒绝)。
25
+ // 只在型号类的词之间并:后端名 / 深度词不并、也不被并(`/model gpt-6-luna low` 的 low 仍是深度)。
26
+ function joinVersionWords(words, isOther) {
27
+ const out = []
28
+ for (const raw of words || []) {
29
+ const w = String(raw || '').trim()
30
+ if (!w) continue
31
+ const prev = out[out.length - 1]
32
+ const glue = prev !== undefined && !isOther(prev) && !isOther(w) && (
33
+ VERSION_RE.test(w) ? /[a-z]$/i.test(prev)
34
+ : VARIANT_RE.test(w) ? /\d$/.test(prev)
35
+ : /^[a-z]+$/i.test(w) && /\d$/.test(prev))
36
+ if (glue) out[out.length - 1] = prev + ' ' + w
37
+ else out.push(w)
38
+ }
39
+ return out
40
+ }
41
+
42
+ /**
43
+ * @param words 命令后面的词
44
+ * @param providerNames 群里可选的模型卡名(没隐藏的)
45
+ * @returns {{ provider?, model?, models?, effort? } | { error: { kind: 'dup', what, a, b } }}
46
+ * model 原样保留大小写(查清单时再归一成清单里的写法);型号类写了不止一个 ⇒ models(调用方先逐个认,再判重)
47
+ */
48
+ function parseModelWords(words, providerNames = []) {
49
+ const names = new Map(providerNames.map((n) => [String(n).toLowerCase(), n]))
50
+ const out = {}
51
+ const isOther = (w) => { const lw = w.toLowerCase(); return names.has(lw) || EFFORT_WORDS.includes(lw) }
52
+ for (const w of joinVersionWords(words, isOther)) {
53
+ const lw = w.toLowerCase()
54
+ if (names.has(lw)) {
55
+ const n = names.get(lw)
56
+ if (out.provider && out.provider !== n) return { error: { kind: 'dup', what: '后端', a: out.provider, b: n } }
57
+ out.provider = n
58
+ } else if (EFFORT_WORDS.includes(lw)) {
59
+ if (out.effort && out.effort !== lw) return { error: { kind: 'dup', what: '深度', a: out.effort, b: lw } }
60
+ out.effort = lw
61
+ } else {
62
+ // 型号这一类先都收下,由调用方按清单逐个认:两个词里有认不出的 ⇒ 报「认不出」(多半是打错 / 隐藏的后端名),
63
+ // 都认得出才报「一次只能选一个型号」—— 否则 `/model claude sonnet`(claude 被隐藏)会得到一句莫名其妙的「写了两个型号」
64
+ const ws = out.models || (out.models = [])
65
+ if (!ws.some((x) => x.toLowerCase() === lw)) ws.push(w)
66
+ }
67
+ }
68
+ if (out.models && out.models.length === 1) { out.model = out.models[0]; delete out.models }
69
+ return out
70
+ }
71
+
72
+ // 词 → 清单里的规范写法(id 或别名);认不出 ⇒ null。先原样比(大小写不敏感),再按比对键比 —— 键撞了不止一项 ⇒ null(不替人挑)。
73
+ // 实时清单带 aliasOf(opus → claude-opus-5-5)⇒ 别名认成它此刻指向的**具体型号**存下(钉住,CLI 升级不悄悄换代);
74
+ // 官方显示名(「GPT-6.1 Terra」)也参与比对键 —— 照卡上 / 控制台看到的名字打就认。
75
+ function resolveModel(word, cat) {
76
+ if (!word || !cat) return null
77
+ const lw = String(word).toLowerCase()
78
+ const models = (cat.models || []).map((m) => ({ id: String(m.id), name: m.name ? String(m.name) : '' }))
79
+ const ids = [...models.map((m) => m.id), ...(cat.aliases || []).map(String)]
80
+ const hit = ids.find((x) => x.toLowerCase() === lw)
81
+ if (hit) return hit
82
+ const aliasOf = cat.aliasOf && typeof cat.aliasOf === 'object' ? cat.aliasOf : {} // 别名走下面的比对键(大小写不敏感,与原样比等价)
83
+ const k = modelKey(word)
84
+ if (!k) return null
85
+ const keyed = new Set(ids.filter((x) => modelKey(x) === k))
86
+ for (const m of models) if (m.name && modelKey(m.name) === k) keyed.add(m.id)
87
+ for (const a of Object.keys(aliasOf)) if (modelKey(a) === k && ids.includes(aliasOf[a])) keyed.add(aliasOf[a])
88
+ return keyed.size === 1 ? [...keyed][0] : null
89
+ }
90
+
91
+ // 这家(某型号下)能选的深度:清单里该型号自带档位就用它的,否则用这家的通用档位
92
+ function effortsFor(cat, modelId) {
93
+ const m = cat && modelId ? (cat.models || []).find((x) => x.id === modelId) : null
94
+ return (m && Array.isArray(m.efforts) && m.efforts.length) ? m.efforts : ((cat && cat.efforts) || [])
95
+ }
96
+
97
+ // 群里列给人看的型号:别名在前(短、好打),再列完整 id
98
+ function modelChoices(cat) {
99
+ if (!cat || !Array.isArray(cat.models)) return []
100
+ return [...(cat.aliases || []), ...cat.models.map((m) => m.id)]
101
+ }
102
+
103
+ // 某型号的推荐深度:清单里它自带默认档(Codex defaultReasoningEffort)就用它的;否则这家的推荐档
104
+ // (cat.recommended.effort,Claude 系 xhigh);都不在这型号可选的档里 ⇒ high ⇒ 第一档。没有清单 ⇒ ''。
105
+ function recommendEffort(cat, modelId) {
106
+ if (!cat) return ''
107
+ const list = effortsFor(cat, modelId)
108
+ const m = modelId && Array.isArray(cat.models) ? cat.models.find((x) => x.id === modelId) : null
109
+ for (const e of [m && m.defEffort, cat.recommended && cat.recommended.effort, 'high']) if (e && list.includes(e)) return e
110
+ return list[0] || ''
111
+ }
112
+
113
+ // 补齐本群在目标后端上缺的选择(;群里 /model、模型卡、控制台群卡同一份)。没有「默认」之后,缺项不再静静跟随模型卡:
114
+ // 换后端且那一家**实际生效的**型号是空的(没选过、也没有迁移前遗留的卡上默认;第三方除外)⇒ 推荐型号;
115
+ // 换后端 / 换型号时实际生效的深度是空的、或新型号没有这一档 ⇒ 这型号的推荐档。
116
+ // 只调深度不补型号(补了就换脑、开新对话,与「只调深度接着聊」冲突)。返回 { model, effort, filled: ['型号','深度'] }。
117
+ function fillPicks({ switching = false, model, effort, currentModel = '', currentEffort = '', third = false, cat = null } = {}) {
118
+ const filled = []
119
+ const rec = cat && cat.ok !== false && cat.recommended ? cat.recommended : null
120
+ if (switching && model === undefined && !currentModel && !third && rec && rec.model) { model = rec.model; filled.push('型号') }
121
+ const next = model === undefined ? currentModel : model
122
+ if (effort === undefined && cat && cat.ok !== false && (switching || model !== undefined)) {
123
+ const effs = effortsFor(cat, next)
124
+ if (!currentEffort || (effs.length && !effs.includes(currentEffort))) {
125
+ const e = recommendEffort(cat, next)
126
+ if (e && e !== currentEffort) { effort = e; if (!currentEffort) filled.push('深度') }
127
+ }
128
+ }
129
+ return { model, effort, filled }
130
+ }
131
+
132
+ module.exports = { parseModelWords, resolveModel, effortsFor, modelChoices, recommendEffort, fillPicks, EFFORT_WORDS }
@@ -0,0 +1,61 @@
1
+ 'use strict'
2
+ // 型号的显示名与输入比对。**只管给人看与人怎么打**:配置里存的、传给 CLI 的、账本 / 审计 / 日志 / 自知清单里写的
3
+ // 一律仍是正式 id(`claude-opus-5-5`)—— 正式 id 不能省 `claude-`、版本号是连字符不是点,写成 `opus-5.5` 交给 CLI 不保证认。
4
+ //
5
+ // 显示规则统一一种短写法:
6
+ // claude-<家族>-<主版本>[-<次版本>][-<日期>][[变体]] → 「Opus 5.5」;日期后缀不显示;变体 [1m] → 「Fable 5.1 (1M)」
7
+ // Claude Code 官方别名 opus / sonnet / haiku(指向当前最新)→「Opus(最新)」
8
+ // deepseek / GLM / MiniMax 同一规则;GPT 等其余原样(本来就短)
9
+ // ⚠ 控制台同一份:server 把 modelDisplayName.toString() 注入 console.html(占位 /*__SBOT_MODELNAME__*/)⇒ 这个函数必须**自包含**
10
+ // (不引用模块里别的变量 / 函数),改它时单测会在注入后的页面里再跑一遍同一组例子。
11
+
12
+ function modelDisplayName(id) {
13
+ const raw = String(id == null ? '' : id).trim()
14
+ if (!raw) return ''
15
+ const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()
16
+ const title = (s) => s.split('-').map((x) => x.charAt(0).toUpperCase() + x.slice(1)).join(' ')
17
+ const vt = (v) => (v ? ' (' + v.toUpperCase() + ')' : '')
18
+ const al = raw.match(/^(opus|sonnet|haiku)$/i)
19
+ if (al) return cap(al[1]) + '(最新)'
20
+ // 次版本最多 3 位且后面不再跟数字:带日期的老 id(claude-sonnet-4-20250514)不会被读成 4.20250514
21
+ const cm = raw.match(/^claude-([a-z]+)-(\d+)(?:-(\d{1,3})(?!\d))?(?:-\d{4,})?(?:\[(\w+)\])?$/i)
22
+ if (cm) return cap(cm[1]) + ' ' + cm[2] + (cm[3] ? '.' + cm[3] : '') + vt(cm[4])
23
+ const dm = raw.match(/^deepseek-(v\d+(?:-\w+)?|chat|reasoner)(?:\[(\w+)\])?$/i)
24
+ if (dm) return 'DeepSeek ' + title(dm[1]) + vt(dm[2])
25
+ const gm = raw.match(/^(glm|chatglm)[-_]([\w.]+(?:-\w+)?)(?:\[(\w+)\])?$/i)
26
+ if (gm) return (gm[1].toLowerCase() === 'chatglm' ? 'ChatGLM' : 'GLM') + ' ' + title(gm[2]) + vt(gm[3])
27
+ const mm = raw.match(/^(MiniMax|abab)(?:-(\w[\w.-]*))?(?:\[(\w+)\])?$/)
28
+ if (mm) return (mm[1] === 'abab' ? 'ABAB' : 'MiniMax') + (mm[2] ? ' ' + title(mm[2]) : '') + vt(mm[3])
29
+ return raw
30
+ }
31
+
32
+ // 输入比对键:大小写、开头的 claude-、点 / 连字符 / 空格都不算;结尾的变体 [1m] / (1M) 单独保留(有没有 1M 是两个型号)。
33
+ // claude-opus-5-5 · opus-5.5 · opus5.5 · Opus 5.5 → opus55;claude-fable-5-1[1m] · Fable 5.1 (1M) → fable51[1m]
34
+ // 显示名「Opus(最新)」里的中文括注不是变体 ⇒ 去掉后就是别名 opus。
35
+ function modelKey(s) {
36
+ let t = String(s == null ? '' : s).trim().toLowerCase()
37
+ let v = ''
38
+ const vm = t.match(/\s*[[(]\s*([a-z0-9]+)\s*[\])]\s*$/)
39
+ if (vm) { v = vm[1]; t = t.slice(0, vm.index) }
40
+ t = t.replace(/^claude[-_\s]+/, '').replace(/[^a-z0-9]/g, '')
41
+ return t ? t + (v ? '[' + v + ']' : '') : ''
42
+ }
43
+
44
+ // 官方显示名登记:两家 CLI 的实时清单拿到就登记(src/modelcatalog.js)—— 有登记的按官方名称显示
45
+ // (Claude「Opus 5.5」、Codex「GPT-6.1-Sol」),没登记的(第三方、清单外的老型号)再按上面的规则拼。
46
+ // 进程内一份:守护进程里引擎与控制台同进程,共用;控制台页面拿 /api/state 的 modelNames。
47
+ const NAMES = new Map()
48
+ function setModelNames(list) {
49
+ for (const x of list || []) {
50
+ const id = x && String(x.id || '').trim()
51
+ const name = x && String(x.name || '').trim()
52
+ if (id && name && name.length <= 60) NAMES.set(id, name)
53
+ }
54
+ }
55
+ function modelName(id) {
56
+ const k = String(id == null ? '' : id).trim()
57
+ return (k && NAMES.get(k)) || modelDisplayName(k)
58
+ }
59
+ const knownModelNames = () => Object.fromEntries(NAMES)
60
+
61
+ module.exports = { modelDisplayName, modelKey, modelName, setModelNames, knownModelNames }