@thincoder/core 0.9.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.
Files changed (203) hide show
  1. package/abort-provenance.mjs +116 -0
  2. package/advisor/citations.mjs +139 -0
  3. package/advisor/compaction.mjs +174 -0
  4. package/advisor/convergence.mjs +80 -0
  5. package/advisor/history.mjs +77 -0
  6. package/advisor/loop.mjs +288 -0
  7. package/advisor/messages.mjs +299 -0
  8. package/advisor/notice.mjs +141 -0
  9. package/advisor/project-context.mjs +197 -0
  10. package/advisor/repos.mjs +150 -0
  11. package/advisor/run.mjs +190 -0
  12. package/advisor/truncate.mjs +57 -0
  13. package/advisor.mjs +281 -0
  14. package/agent/child-marks.mjs +24 -0
  15. package/agent/completion.mjs +145 -0
  16. package/agent/dispatch.mjs +493 -0
  17. package/agent/family-tools.mjs +174 -0
  18. package/agent/helpers.mjs +412 -0
  19. package/agent/post-turn.mjs +70 -0
  20. package/agent/record-results.mjs +174 -0
  21. package/agent/relay-prefix.mjs +39 -0
  22. package/agent/run-stages.mjs +244 -0
  23. package/agent/setup-reminders.mjs +199 -0
  24. package/agent/setup.mjs +234 -0
  25. package/agent/spawn-child.mjs +258 -0
  26. package/agent/suspension.mjs +240 -0
  27. package/agent/write-gate.mjs +87 -0
  28. package/agent-tools/advisor-async.mjs +481 -0
  29. package/agent-tools/advisor-settle.mjs +240 -0
  30. package/agent-tools/advisor.mjs +280 -0
  31. package/agent-tools/async-discard.mjs +143 -0
  32. package/agent-tools/async-settle.mjs +299 -0
  33. package/agent-tools/batch-segment.mjs +265 -0
  34. package/agent-tools/child-permission.mjs +45 -0
  35. package/agent-tools/consult.mjs +471 -0
  36. package/agent-tools/design-token.mjs +117 -0
  37. package/agent-tools/digest-budget.mjs +76 -0
  38. package/agent-tools/eng.mjs +102 -0
  39. package/agent-tools/escalate-async.mjs +302 -0
  40. package/agent-tools/goal.mjs +119 -0
  41. package/agent-tools/panel-blocks.mjs +24 -0
  42. package/agent-tools/parent-channel.mjs +231 -0
  43. package/agent-tools/plan.mjs +86 -0
  44. package/agent-tools/read-history.mjs +309 -0
  45. package/agent-tools/recent-changes.mjs +24 -0
  46. package/agent-tools/review-facts.mjs +31 -0
  47. package/agent-tools/settings.mjs +268 -0
  48. package/agent-tools/skill.mjs +63 -0
  49. package/agent-tools/spawn-gates.mjs +109 -0
  50. package/agent-tools/subagent-actions.mjs +496 -0
  51. package/agent-tools/subagent-async.mjs +456 -0
  52. package/agent-tools/subagent-panel.mjs +160 -0
  53. package/agent-tools/subagent-run.mjs +208 -0
  54. package/agent-tools/subagent-scheduler.mjs +446 -0
  55. package/agent-tools/subagent-spawn.mjs +478 -0
  56. package/agent-tools/subagent.mjs +419 -0
  57. package/agent-tools/task.mjs +87 -0
  58. package/agent-tools/timer.mjs +46 -0
  59. package/agent-tools/verify.mjs +295 -0
  60. package/agent-tools.mjs +23 -0
  61. package/agent.mjs +430 -0
  62. package/auto-think.mjs +115 -0
  63. package/compress-form.mjs +24 -0
  64. package/config-io.mjs +277 -0
  65. package/config-migrate.mjs +178 -0
  66. package/config-presets.mjs +49 -0
  67. package/config.mjs +419 -0
  68. package/context.mjs +495 -0
  69. package/conventions.mjs +223 -0
  70. package/embedding.mjs +120 -0
  71. package/escape.mjs +152 -0
  72. package/expand-home.mjs +16 -0
  73. package/explore-distill.mjs +152 -0
  74. package/generate-title.mjs +123 -0
  75. package/git/checkpoint.mjs +448 -0
  76. package/git/gitmem.mjs +100 -0
  77. package/history-window.mjs +179 -0
  78. package/hooks.mjs +108 -0
  79. package/i18n.mjs +106 -0
  80. package/index-bin.mjs +48 -0
  81. package/index-discover.mjs +176 -0
  82. package/ledger-cmd.mjs +209 -0
  83. package/ledger-db.mjs +86 -0
  84. package/ledger-surface.mjs +76 -0
  85. package/ledger.mjs +202 -0
  86. package/log.mjs +195 -0
  87. package/manifest.mjs +338 -0
  88. package/markdown.mjs +106 -0
  89. package/mcp/helpers.mjs +51 -0
  90. package/mcp/transport-http.mjs +248 -0
  91. package/mcp/transport-stdio.mjs +140 -0
  92. package/mcp/transport-ws.mjs +122 -0
  93. package/mcp.mjs +295 -0
  94. package/memory/code-index.mjs +219 -0
  95. package/memory/code-sync.mjs +427 -0
  96. package/memory/core.mjs +318 -0
  97. package/memory/delete.mjs +242 -0
  98. package/memory/docs.mjs +431 -0
  99. package/memory/file-walk.mjs +109 -0
  100. package/memory/origin.mjs +24 -0
  101. package/memory/scan.mjs +177 -0
  102. package/memory/schema.mjs +460 -0
  103. package/memory.mjs +21 -0
  104. package/model-ref.mjs +66 -0
  105. package/model-specs.mjs +277 -0
  106. package/package.json +32 -0
  107. package/peer-domains.mjs +265 -0
  108. package/peer-instances.mjs +178 -0
  109. package/permission.mjs +79 -0
  110. package/process-probe.mjs +315 -0
  111. package/prompt-files.mjs +113 -0
  112. package/prompt-overlays.mjs +78 -0
  113. package/prompts/advisor-design.md +43 -0
  114. package/prompts/advisor-round1.md +41 -0
  115. package/prompts/advisor-round2.md +46 -0
  116. package/prompts/advisor-round3.md +42 -0
  117. package/prompts/common.md +158 -0
  118. package/prompts/consult-base.md +19 -0
  119. package/prompts/discipline-engineering.md +123 -0
  120. package/prompts/discipline-normal.md +206 -0
  121. package/prompts/persona-coder.md +21 -0
  122. package/prompts/persona-eng-coder.md +41 -0
  123. package/prompts/persona-eng-designer.md +80 -0
  124. package/prompts/persona-engineering.md +160 -0
  125. package/prompts/persona-explore.md +15 -0
  126. package/prompts/persona-normal.md +35 -0
  127. package/prompts/persona-plan.md +27 -0
  128. package/provider/anthropic.mjs +225 -0
  129. package/provider/core.mjs +491 -0
  130. package/provider/errors.mjs +101 -0
  131. package/provider/google.mjs +257 -0
  132. package/provider/index.mjs +7 -0
  133. package/provider/list-models.mjs +163 -0
  134. package/provider/normalize.mjs +81 -0
  135. package/provider/rate.mjs +168 -0
  136. package/provider/responses.mjs +495 -0
  137. package/provider/retry.mjs +88 -0
  138. package/provider/sse.mjs +264 -0
  139. package/provider/wait-status.mjs +59 -0
  140. package/proxy.mjs +274 -0
  141. package/rules.mjs +53 -0
  142. package/session-gc.mjs +248 -0
  143. package/session-guard.mjs +59 -0
  144. package/session-lifecycle.mjs +305 -0
  145. package/session-migrate.mjs +48 -0
  146. package/session-rename.mjs +38 -0
  147. package/session-segments.mjs +99 -0
  148. package/session-slot-write.mjs +168 -0
  149. package/session-slots-manifest.mjs +264 -0
  150. package/session-slots.mjs +298 -0
  151. package/session-store.mjs +441 -0
  152. package/session.mjs +244 -0
  153. package/skills.mjs +234 -0
  154. package/text-budget.mjs +79 -0
  155. package/token-ttl.mjs +285 -0
  156. package/tool-docs/apply_patch.md +15 -0
  157. package/tool-docs/bash.md +38 -0
  158. package/tool-docs/delete.md +13 -0
  159. package/tool-docs/edit.md +30 -0
  160. package/tool-docs/execute.md +21 -0
  161. package/tool-docs/fetch.md +12 -0
  162. package/tool-docs/file_ops.md +17 -0
  163. package/tool-docs/get_current_time.md +8 -0
  164. package/tool-docs/git.md +54 -0
  165. package/tool-docs/glob.md +11 -0
  166. package/tool-docs/grep.md +19 -0
  167. package/tool-docs/hashline_edit.md +14 -0
  168. package/tool-docs/insert_after.md +15 -0
  169. package/tool-docs/lint.md +10 -0
  170. package/tool-docs/ls.md +12 -0
  171. package/tool-docs/lsp.md +10 -0
  172. package/tool-docs/process.md +10 -0
  173. package/tool-docs/question.md +16 -0
  174. package/tool-docs/read.md +20 -0
  175. package/tool-docs/read_image.md +8 -0
  176. package/tool-docs/tree.md +14 -0
  177. package/tool-docs/wait_for.md +22 -0
  178. package/tool-docs/websearch.md +16 -0
  179. package/tool-docs/write.md +11 -0
  180. package/tools/bash.mjs +276 -0
  181. package/tools/edit-batch.mjs +204 -0
  182. package/tools/edit-diff.mjs +388 -0
  183. package/tools/exec-run.mjs +43 -0
  184. package/tools/execute.mjs +243 -0
  185. package/tools/file.mjs +464 -0
  186. package/tools/git-checkpoint.mjs +143 -0
  187. package/tools/git-ext.mjs +173 -0
  188. package/tools/git.mjs +415 -0
  189. package/tools/glob-dialect.mjs +130 -0
  190. package/tools/index.mjs +76 -0
  191. package/tools/linter.mjs +120 -0
  192. package/tools/lsp.mjs +335 -0
  193. package/tools/ops.mjs +293 -0
  194. package/tools/patch.mjs +290 -0
  195. package/tools/question.mjs +26 -0
  196. package/tools/repomap.mjs +314 -0
  197. package/tools/search.mjs +248 -0
  198. package/tools/shared.mjs +467 -0
  199. package/tools/tree.mjs +81 -0
  200. package/tools/web.mjs +224 -0
  201. package/tools/write-path.mjs +191 -0
  202. package/traces/trace-store.mjs +303 -0
  203. package/undo-stack.mjs +47 -0
package/model-ref.mjs ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * model-ref.mjs — composite model reference parser (MODEL-MERGE-SESSION; v2 2026-09-10).
3
+ * A model is an explicit "provider:model" composite — config.defaultModel (top level,
4
+ * new-session starting point) and session slots both speak this language. 有效域 = **provider 存在
5
+ * + 双段非空**:清单不再人工维护(模型清单由 provider 运行期 `GET /models` 拉取决定——
6
+ * PROVIDER.md §16),候选成员校验**已废除**——显式 `p:m` 一律放行(含多冒号首分割)。
7
+ * providers[].model 是渠道默认模型(单值)——候选清单字段 models[] 已整字段退场。
8
+ *
9
+ * Strict first-colon split — deliberately does NOT reuse resolveChildProvider's loose
10
+ * three-state resolution: ① legacy single-provider configs and ② model-name-only refs
11
+ * are gone with activeProvider/activeModel (F-1), so a bare provider or bare model is
12
+ * invalid everywhere (裁定③——裸 provider 拒——显式 p:m)。
13
+ *
14
+ * Runtime model resolution:
15
+ * parseModelRef(ref, providers) → { ok, provider, model } | { ok:false, reason }
16
+ * resolveRuntimeProvider(providers, defaultModel)
17
+ * → provider object with `.model` set, or {} when
18
+ * defaultModel is null/invalid (D-S1: callers mark
19
+ * _providerInvalid — never throws)
20
+ * defaultModelReason(providers, defaultModel) → human reason for the D-S1 marker
21
+ *
22
+ * Consumers: config.mjs loadConfig (re-exported as the config hub), make-agent.mjs.
23
+ */
24
+ export function parseModelRef(ref, providers) {
25
+ if (typeof ref !== "string" || !ref.trim()) {
26
+ return { ok: false, reason: 'model reference expected as "provider:model" (e.g. deepseek:deepseek-v4-pro)' }
27
+ }
28
+ // 首冒号分割——`a:b:c` → provider=a、model=`b:c`(`ollama:llama3:70b` 式模型名可用,M4)
29
+ const sep = ref.indexOf(":")
30
+ if (sep <= 0) {
31
+ return { ok: false, reason: `invalid model reference "${ref}" — expected provider:model (bare provider/model names are not accepted)` }
32
+ }
33
+ const providerName = ref.slice(0, sep)
34
+ const model = ref.slice(sep + 1)
35
+ if (!model) {
36
+ return { ok: false, reason: `invalid model reference "${ref}" — model part is empty (expected provider:model)` }
37
+ }
38
+ const list = Array.isArray(providers) ? providers : []
39
+ const provider = list.find((p) => p?.name === providerName)
40
+ if (!provider) {
41
+ const available = list.map((p) => p.name).join(", ") || "(none)"
42
+ return { ok: false, reason: `unknown provider "${providerName}" in model reference (available: ${available})` }
43
+ }
44
+ return { ok: true, provider, model }
45
+ }
46
+
47
+ /** Resolve the runtime provider object for config.defaultModel: provider clone carrying
48
+ * `.model` = the parsed concrete model (API/spec consumers read provider.model unchanged).
49
+ * Invalid/missing defaultModel → {} (D-S1 shape — name/model/baseURL absent so
50
+ * validateProvider flags it; TUI/headless surface the reason from defaultModelReason). */
51
+ export function resolveRuntimeProvider(providers, defaultModel) {
52
+ const r = parseModelRef(defaultModel, providers)
53
+ if (!r.ok) return {}
54
+ return { ...r.provider, model: r.model }
55
+ }
56
+
57
+ /** Human-readable reason behind an invalid/empty runtime resolution (D-S1 原因). */
58
+ export function defaultModelReason(providers, defaultModel) {
59
+ const list = Array.isArray(providers) ? providers : []
60
+ if (!defaultModel) {
61
+ return list.length > 0
62
+ ? "defaultModel 未设置(config 顶层 defaultModel — 新会话起点;/config → 默认模型 设置)"
63
+ : "未配置任何 provider"
64
+ }
65
+ return parseModelRef(defaultModel, list).reason ?? `defaultModel "${defaultModel}" 无效`
66
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * model-specs.mjs — known model capability table + spec lookup (2026-08-31 extract).
3
+ *
4
+ * Split from config.mjs (which had grown to 358 lines, past the 300 advisory
5
+ * line — TODO #1). config.mjs re-exports specForModel so the 23 existing
6
+ * importers stay untouched. (PROVIDER_PRESETS has since moved out, to
7
+ * config-presets.mjs — the core's single preset face, re-exported by config.mjs.)
8
+ */
9
+
10
+ /**
11
+ * Known model capability spec table (prefix match, longer first).
12
+ * Used for compaction threshold derivation, continuation protocol selection, and capability-aware optimization.
13
+ *
14
+ * context: context window (tokens)
15
+ * maxOutput: max output tokens (defaults to context)
16
+ * thinking: whether thinking/reasoning mode is supported
17
+ * partialMode: Kimi/Qwen Partial Mode truncation continuation (assistant message with partial:true)
18
+ * prefixMode: DeepSeek Prefix Completion truncation continuation (uses /beta endpoint, with prefix:true)
19
+ * multimodal: whether multimodal (image/vision input supported)
20
+ * cacheMode: context caching mode: "auto"=automatic / "prompt"=needs explicit / "none"=unsupported
21
+ * thinkApi: thinking API type: "type"=thinking.type field / "effort"=reasoning_effort field
22
+ * thinkEnabledValue: when thinkApi is "type", the value used to enable thinking (default "enabled"; MiniMax uses "adaptive")
23
+ * reasoningEcho: reasoning_content cross-turn echo strategy: "required"=must echo (error if missing) / "optional"=echo optional (default: don't echo)
24
+ * reasoningEffortEnum: valid reasoning_effort enum values (if undeclared, no validation — passed through as-is)
25
+ * tempRange: valid temperature range [min, max] (if undeclared, no clamping)
26
+ */
27
+ const MODEL_SPECS = [
28
+ // DeepSeek V4.1 series (2026-09-11). `deepseek-flash` = DeepSeek-V4.1-Flash (in service):
29
+ // 1M ctx / 384K out, thinking on by default (effort low/high/max), Chat Prefix Completion
30
+ // beta, automatic disk cache, vision. The two legacy names below are RETIRED — the server
31
+ // still accepts them and serves them from V4.1-Flash today (no switch window), so their
32
+ // rows carry the V4.1-Flash parameters (v4-flash gains multimodal).
33
+ ["deepseek-flash", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2], multimodal: true }],
34
+ // deepseek-v4.1-flash = qwen-plan channel name for DeepSeek V4.1-Flash (token-plan GET /models verified 2026-09-15) — same fields as the row above; ".1" ≠ "-" so plain prefix lookup misses the existing rows (D-PR25)
35
+ ["deepseek-v4.1-flash", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2], multimodal: true }],
36
+ // deepseek-v4-pro (V4-Pro-0813): fields unchanged — vision capability NOT verified, so no
37
+ // multimodal (conservative). From 2026-09-14 12:00 Beijing all requests route to V4.1-Flash.
38
+ ["deepseek-v4-pro", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2] }],
39
+ // deepseek-v4-flash: RETIRED name — still accepted, served by V4.1-Flash today → aligned row
40
+ ["deepseek-v4-flash", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2], multimodal: true }],
41
+ // deepseek-v4-flash-vision-exp: retired experimental vision name — still accepted (V4.1-Flash)
42
+ ["deepseek-v4-flash-vision-exp", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2], multimodal: true }],
43
+ // Kimi series
44
+ ["kimi-k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
45
+ // Qwen router prefixes model IDs with provider namespace: kimi/kimi-k3 → kimi-k3 (IK7K4V)
46
+ ["kimi/kimi-k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
47
+ // Kimi For Coding endpoint uses the short model ID "k3" (same specs as kimi-k3) — IK5VGJ
48
+ ["k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
49
+ // GLM series
50
+ // GLM-5.3: thinking always-on (no "disabled"); effort converges to low/high/max — NOT the
51
+ // 7-level glm-5.2 enum (verified vs docs.bigmodel.cn GLM-5.3 page, 2026-08)
52
+ ["glm-5.3", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 1], noUsageStream: true }],
53
+ ["glm-5.3-flash", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 1], noUsageStream: true }],
54
+ // glm-5.3-flashx(GLM-5.3-FlashX,2026-09 上线)——独立行(不再蹭 flash 前缀行):maxOutput 131_072 =
55
+ // **校验级**(400「max_tokens…限制数值范围[1,131072]」);effort 枚举 ["low","high","max"] = **校验级**
56
+ // (400 原文点名:low/high/max 受理,其余拒绝)。multimodal = **实测**(8×8 纯红 PNG → 答「红色」)。
57
+ // thinking = **实测**且**始终思考**(`thinking:{type:"disabled"}` → 400;裸请求默认开)——off 动作在
58
+ // UI 侧为 no-op(服务端无关闭路径)。其余 = **族沿用** flash 行(context 网络口径未探边;
59
+ // cacheMode / thinkApi / reasoningEcho / tempRange / noUsageStream = 前缀命中路径日常在用)。
60
+ // flash 行 128_000 系其自身口径,不得对齐本行(AC-3 零回归)。
61
+ ["glm-5.3-flashx", { context: 1_000_000, maxOutput: 131_072, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 1], noUsageStream: true }],
62
+ ["glm-5.2", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["max", "xhigh", "high", "medium", "low", "minimal", "none"], tempRange: [0, 1], noUsageStream: true }],
63
+ ["glm-5", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["max", "xhigh", "high", "medium", "low", "minimal", "none"], tempRange: [0, 1], noUsageStream: true }],
64
+ ["glm-4", { context: 128_000, maxOutput: 32_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", tempRange: [0, 1], noUsageStream: true }],
65
+ // GPT series
66
+ ["gpt-5.6-sol", { context: 1_050_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
67
+ ["gpt-5.6", { context: 1_050_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
68
+ ["gpt-4.1", { context: 1_000_000, maxOutput: 128_000, thinking: false, cacheMode: "prompt" }],
69
+ ["gpt-4o", { context: 128_000, maxOutput: 16_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
70
+ // Qwen series
71
+ // qwen3.7-max rejects image parts outright (DashScope 400 "Unexpected item type in content") — text-only
72
+ ["qwen3.7-max", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "high"], tempRange: [0, 2] }],
73
+ // qwen3.7-flash / qwen3.8-flash (2026-09-20): thinking + effort enum + maxOutput + image intake
74
+ // are server-MEASURED (batch 2026-09-20-qwen-flash-specs §1.2 — bare request carries reasoning_tokens
75
+ // 166/27; 400 literal lists the enum; max_tokens range [1, 131072]). 3.7-flash enum has NO "max"
76
+ // (6 levels), 3.8-flash adds it (7); enum order = the server's literal order, first member "none"
77
+ // (D-5) — never re-sort by strength. context = doc/web statement (1M class, not API-measured);
78
+ // partialMode / cacheMode / thinkApi / tempRange = same-family carry-over (not independently
79
+ // measured on these rows); reasoningEcho stays undeclared (cross-turn echo unverified — R-4).
80
+ ["qwen3.7-flash", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh"], tempRange: [0, 2] }],
81
+ ["qwen3.8-flash", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"], tempRange: [0, 2] }],
82
+ ["qwen3.8-max", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "medium", "low"], tempRange: [0, 2] }],
83
+ // qwen3.8-omni-flash / qwen3.8-27b — parent 80-token probe (batch §1.8-①): reasoning_content
84
+ // present, reasoning_tokens 49, `effort:"none"` removes it ⇒ thinking MEASURED; enum / maxOutput
85
+ // / image intake measured the same way. omni-flash: context = doc/web statement (1M class, not
86
+ // API-measured); partialMode / cacheMode / thinkApi / tempRange = same-family carry-over;
87
+ // reasoningEcho undeclared (cross-turn echo unverified — R-4). The server also takes audio, this
88
+ // repo's call path sends images only ⇒ multimodal below = image intake (no `modalities` field, §2.7).
89
+ ["qwen3.8-omni-flash",{ context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"], tempRange: [0, 2] }],
90
+ // 27b: context 262_144 = doc/web statement, the native value (1M is YaRN extrapolation, local
91
+ // deploy only); partialMode / cacheMode / thinkApi / tempRange same-family carry-over; reasoningEcho
92
+ // undeclared (cross-turn echo unverified — R-4).
93
+ ["qwen3.8-27b", { context: 262_144, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"], tempRange: [0, 2] }],
94
+ // No generic "qwen" fallback row: unlisted qwen names deliberately resolve to DEFAULT_SPEC
95
+ // (128K / 32K, no vision) — per-name disposition in docs/core/design/MODEL-SPECS.md §2.4.
96
+ // Do not re-add a blanket prefix row (silent inheritance is how missing rows stayed invisible).
97
+
98
+ // qwen3.6 系五行(批 2026-09-20-qwen36-family-rows · 五名同形,取证叙述落此一处全量):
99
+ // effort 枚举六档 = **校验级**(`max` → 400 原文点名 none/minimal/low/medium/high/xhigh——无 max,
100
+ // ≠ 3.8 系七档);maxOutput 65_536 = **校验级**(400「Range of max_tokens should be [1, 65536]」;
101
+ // 65536 → 200)。thinking / multimodal = **实测**(裸请求 rc=143–239 默认开、none/minimal rc=0 可关;
102
+ // 64×64 纯红 PNG 五名受理)。context = **官方口径**(未探边,文档口径非 API 实测);partialMode /
103
+ // cacheMode / thinkApi / tempRange = **族沿用**(qwen3.7/3.8 行先例);reasoningEcho 不声明
104
+ // (跨轮回声未验——R-4)。日期戳快照名不登——前缀命中母名行(有意继承)。
105
+ ["qwen3.6-flash", { context: 1_000_000, maxOutput: 65_536, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh"], tempRange: [0, 2] }],
106
+ // qwen3.6-plus:同形行(共享证据块见 flash 行上)——校验级 maxOutput / 六档枚举、官方口径 context、族沿用机制位。
107
+ ["qwen3.6-plus", { context: 1_000_000, maxOutput: 65_536, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh"], tempRange: [0, 2] }],
108
+ // qwen3.6-max-preview:同形行——校验级 maxOutput / 六档枚举、官方口径 context、族沿用机制位;视觉通道实测 ✓ 但纯红图答「黑色」(抽样波动,如实记)。
109
+ ["qwen3.6-max-preview", { context: 1_000_000, maxOutput: 65_536, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh"], tempRange: [0, 2] }],
110
+ // qwen3.6-27b:同形行——校验级 maxOutput / 六档枚举、官方口径 context(未探边)、族沿用机制位。
111
+ ["qwen3.6-27b", { context: 1_000_000, maxOutput: 65_536, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh"], tempRange: [0, 2] }],
112
+ // qwen3.6-35b-a3b:MoE 后缀(a3b = 激活参数量级)——校验级 maxOutput / 六档枚举、官方口径 context、族沿用机制位。
113
+ ["qwen3.6-35b-a3b", { context: 1_000_000, maxOutput: 65_536, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh"], tempRange: [0, 2] }],
114
+ // MiniMax series
115
+ ["MiniMax-M3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
116
+ // MiMo series (Xiaomi — OpenAI-compatible https://api.xiaomimimo.com/v1;
117
+ // deep thinking via thinking.type, default ON; multi-turn tool calls MUST echo
118
+ // reasoning_content back exactly like DeepSeek V4, else 400 on follow-ups)
119
+ ["mimo-v2.5-pro", { context: 1_000_000, maxOutput: 128_000, thinking: true, thinkApi: "type", reasoningEcho: "required", tempRange: [0, 1.5] }],
120
+ ["mimo-v2.5", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, thinkApi: "type", reasoningEcho: "required", tempRange: [0, 1.5] }],
121
+ ["minimax-m3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
122
+ ["minimax-m1", { context: 256_000, maxOutput: 128_000, thinking: false, cacheMode: "auto", noUsageStream: true }],
123
+ // Grok series (xAI — OpenAI-compatible)
124
+ // grok-4.x: 500K context per xAI Grok 4.6 spec (corrected 2026-08; earlier entries said 1M)
125
+ ["grok-4.6", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
126
+ ["grok-4.5", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
127
+ ["grok-4", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
128
+ ["grok-4-mini", { context: 128_000, maxOutput: 16_000, thinking: false, tempRange: [0, 2] }],
129
+ // Mistral series (OpenAI-compatible)
130
+ ["mistral-large", { context: 128_000, maxOutput: 32_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
131
+ ["codestral", { context: 256_000, maxOutput: 32_000, thinking: false, tempRange: [0, 2] }],
132
+ // Claude series (Anthropic)
133
+ ["claude-opus-5", { context: 1_000_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
134
+ ["claude-sonnet-5", { context: 1_000_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
135
+ ["claude-opus-4", { context: 200_000, maxOutput: 32_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
136
+ ["claude-sonnet-4", { context: 200_000, maxOutput: 32_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
137
+ ["claude-3.5-haiku", { context: 200_000, maxOutput: 8_192, thinking: false, cacheMode: "none", format: "anthropic" }],
138
+ // Gemini series (Google)
139
+ ["gemini-3-pro", { context: 1_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
140
+ ["gemini-2.5-pro", { context: 2_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
141
+ ["gemini-2.5-flash", { context: 1_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
142
+ // Tencent Hunyuan (TokenHub — 聚合网关 `https://tokenhub.tencentmaas.com/v1`;批 2026-09-20-channel-onboarding)
143
+ // hy3:thinking / thinkApi / effort 枚举 = **本渠道实测**——裸请求 reasoning_content 在场(tok=16),
144
+ // `reasoning_effort:"none"` 即消失(实测唯一有效 off 路径)。枚举 = 七值**受理级探针**(七值全 200 受理;
145
+ // 乱值 400 泛化拒收、服务端不列枚举——等级低于 seed 的「服务端真校验」,两族同值集但等级不混);
146
+ // 序 = 服务端原文序、首项 "none",**禁按强度重排**(D-5)。multimodal:**不声明**——真值 32×32 纯红图
147
+ // 答 "Unknown"(对照组明说不识图)⇒ 实测无视觉。尺寸位两级逐条标(AC-2):maxOutput 128_000 =
148
+ // **参考实配**(网关对 max_tokens 不硬拒 ⇒ 上限未证);context 256_000 = **网络口径**(他仓/文档 256K,未本渠道实测)。
149
+ ["hy3", { context: 256_000, maxOutput: 128_000, thinking: true, thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"] }],
150
+ // hy3-preview(在场实测:/models + 401 前验活;能力位**未探针**)——**尺寸行**:仅登记尺寸位,取值 =
151
+ // hy3 **同族沿用**(maxOutput 参考实配 / context 网络口径)。D-11:能力位不跨名沿用 ⇒ thinking / 枚举 / 视觉全不声明。
152
+ ["hy3-preview", { context: 256_000, maxOutput: 128_000 }],
153
+ // hy4-preview(在场实测:/models + 401;能力位**未探针**)——尺寸行:取值 = hy3 族**同族沿用**
154
+ // (maxOutput 参考实配级取值 / context 网络口径;批次档 §1.2 未给该名尺寸行 ⇒ 上报清单 2,行注即标级)。
155
+ ["hy4-preview", { context: 256_000, maxOutput: 128_000 }],
156
+ // Doubao Seed (Volcano Ark — `https://ark.cn-beijing.volces.com/api/v3`;批 2026-09-20-channel-onboarding)
157
+ // 两档全针实测:裸请求 reasoning_content 在场(code tok=219 / lite tok=161)、`effort:"none"` 即消失 ⇒
158
+ // thinking / thinkApi = 实测;枚举 = **服务端真校验级**(乱值 400 明列 invalid + 七档全 200)⇒ 七值即服务端值域;
159
+ // maxOutput 131_072 = **实测**(262 144 → 400 "above maximum");context 256_000 = **官方口径**(官方 256K)
160
+ // + **实测**下界 210K 输入受理(探至账号 429 停手);multimodal = 实测(纯红图答 "Red")。
161
+ ["doubao-seed-2-0-code-preview-260215", { context: 256_000, maxOutput: 131_072, thinking: true, thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"], multimodal: true }],
162
+ // seed-lite = 同族全针(批次档 §1.2 lite 行:思考 tok=161 / 视觉 "Red" / 上限 131 072 / echo 真名)——
163
+ // context 同按 **官方口径** + **实测**下界标注;行独立(同值集 ≠ 同行:改一行不动另一行)。
164
+ ["doubao-seed-2-0-lite-260428", { context: 256_000, maxOutput: 131_072, thinking: true, thinkApi: "effort", reasoningEffortEnum: ["none", "minimal", "low", "medium", "high", "xhigh", "max"], multimodal: true }],
165
+ ]
166
+ const DEFAULT_SPEC = { context: 128_000, maxOutput: 32_000, cacheMode: "none" }
167
+
168
+ /** Look up spec by model name prefix (case-insensitive), conservative default for unknown models.
169
+ *
170
+ * Vendor-namespace prefix stripping (2026-09-04):第三方 token 市场(roapi/new-api/one-api/
171
+ * aiproxy 聚合网关)惯例在模型名前加厂商前缀(zhipu/glm-5.3、openai/gpt-4o)。完整名未命中
172
+ * 且含 "/" 时,剥掉第一个 "/" 前的 namespace 再按前缀匹配一次——ZHIPU/GLM-5.3 → glm-5.3 命中
173
+ * 真实规格,不再降级 128K 默认。kimi/kimi-k3 的显式 alias 行保留为文档锚(发送路径
174
+ * provider.core isRouter 依赖含 "/" 判定),通用机制已覆盖同类。 */
175
+ const warnedModels = new Set() // warn once per model name — spec lookup runs on every request (hot path)
176
+ // Pre-sorted once at module scope — spec lookup runs on every request (agent, provider core,
177
+ // context, auto-think, TUI rendering); re-sorting per call was wasteful.
178
+ const SORTED_SPECS = [...MODEL_SPECS].sort((a, b) => b[0].length - a[0].length)
179
+
180
+ /** Single table lookup shared by specForModel / specMatch — prefix match (case-insensitive)
181
+ * with vendor-namespace stripping. Returns null on a miss (DEFAULT_SPEC is the caller-side
182
+ * fallback and is deliberately NOT returned here — `matched` needs the miss itself). */
183
+ function lookupSpec(model) {
184
+ const m = (model ?? "").toLowerCase()
185
+ for (const [prefix, spec] of SORTED_SPECS) {
186
+ if (m.startsWith(prefix.toLowerCase())) return spec
187
+ }
188
+ // Vendor-namespace strip: vendor/model — retry the prefix match on the bare model part.
189
+ const slash = m.indexOf("/")
190
+ if (slash > 0) {
191
+ const bare = m.slice(slash + 1)
192
+ for (const [prefix, spec] of SORTED_SPECS) {
193
+ if (bare.startsWith(prefix.toLowerCase())) return spec
194
+ }
195
+ }
196
+ return null
197
+ }
198
+
199
+ /** Unknown model: warn ONCE (not per request) so a typo'd ID or a missing alias surfaces
200
+ * instead of silently degrading to the 128K default (IK5VGJ). The dedupe set is shared by
201
+ * specForModel / specMatch (PROVIDER.md §16 M5). */
202
+ function warnUnknownModel(model) {
203
+ const m = (model ?? "").toLowerCase()
204
+ if (m && !warnedModels.has(m)) {
205
+ warnedModels.add(m)
206
+ console.warn(`[config] model "${model}" not found in MODEL_SPECS — using default spec (128K context, 32K output). Check the model ID or add an alias.`)
207
+ }
208
+ }
209
+
210
+ export function specForModel(model) {
211
+ const hit = lookupSpec(model)
212
+ if (hit) return hit
213
+ warnUnknownModel(model)
214
+ return DEFAULT_SPEC
215
+ }
216
+
217
+ /** specMatch(model) → { spec, matched }(PROVIDER.md §16 M5——切换回显的来源判定)。
218
+ * `matched:false` = DEFAULT_SPEC 兜底(未知模型);与 specForModel 共享同一查表实现(单次),
219
+ * specForModel 的返回形状与共享对象契约零改(热路径零变)。 */
220
+ export function specMatch(model) {
221
+ const hit = lookupSpec(model)
222
+ if (hit) return { spec: hit, matched: true }
223
+ warnUnknownModel(model)
224
+ return { spec: DEFAULT_SPEC, matched: false }
225
+ }
226
+
227
+ /**
228
+ * providerSpec(provider) — spec with a provider-level context override (PROVIDER.md §15, 2026-09-02).
229
+ *
230
+ * providers[].context is configured in K units (128 = 128K = 131072 tokens) and overrides the
231
+ * MODEL_SPECS value for THIS provider only — the same model can have different real context
232
+ * windows on different endpoints (official vs local deployment). The ×1024 conversion happens
233
+ * HERE and nowhere else.
234
+ *
235
+ * Returns a COPY ({ ...spec, context }) — the shared spec object from the SORTED_SPECS lookup
236
+ * must never be mutated, or the override would leak across providers (T-C1).
237
+ *
238
+ * Validation is defensive (pure function): absent/invalid context falls back to the plain spec
239
+ * (config.mjs loadConfig already warns + strips invalid values; this guard covers direct callers
240
+ * and keeps the function total). specForModel stays a pure table lookup — callers without a
241
+ * provider keep using it.
242
+ */
243
+ export function providerSpec(provider) {
244
+ const spec = specForModel(provider?.model ?? "")
245
+ const k = Number(provider?.context) // Number() 接受数字字符串("128")——两端语义统一(code review #1)
246
+ if (Number.isInteger(k) && k > 0) return { ...spec, context: k * 1024 }
247
+ return spec
248
+ }
249
+
250
+ /**
251
+ * assistantToolCallMessage(response, spec) — the tool-round assistant message pushed onto the
252
+ * machine line (D-CC22, #109 — live-push face of the echo-safety family; single construction
253
+ * point, CONTEXT-COMPACTION.md §6.10 #9).
254
+ *
255
+ * `reasoningEcho:"required"` families (deepseek / kimi / mimo) MUST carry the reasoning echo on
256
+ * every tool-call assistant message: the field is NEVER omitted — an absent/non-string
257
+ * `response.reasoning` is echoed as `""` (live-shape probe 2026-09-20: missing field → 400
258
+ * "must be passed back"; empty string → 200 with reasoning still returned). `optional` /
259
+ * undeclared families (incl. DEFAULT_SPEC) never get the field (behavior byte-identical).
260
+ *
261
+ * Callers: thincoder-core/agent.mjs (main loop) · thincoder-core/advisor/loop.mjs (review
262
+ * mirror) · thincoder-vscode/src/agent.mjs (shell loop) — each passes its own spec face.
263
+ */
264
+ export function assistantToolCallMessage(response, spec) {
265
+ const msg = {
266
+ role: "assistant",
267
+ content: response.content || null,
268
+ tool_calls: response.toolCalls.map((tc) => ({
269
+ id: tc.id, type: "function",
270
+ function: { name: tc.name, arguments: tc.arguments },
271
+ })),
272
+ }
273
+ if (spec?.reasoningEcho === "required") {
274
+ msg.reasoning_content = typeof response.reasoning === "string" ? response.reasoning : ""
275
+ }
276
+ return msg
277
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@thincoder/core",
3
+ "version": "0.9.1",
4
+ "description": "ThinCoder shared core — shared mechanism modules + shared prompt content (CLI ↔ VS Code extension).",
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "node test/run.mjs",
8
+ "prepublishOnly": "npm test"
9
+ },
10
+ "license": "MIT",
11
+ "engines": {
12
+ "node": ">=22.13.0"
13
+ },
14
+ "exports": {
15
+ "./*": "./*"
16
+ },
17
+ "files": [
18
+ "*.mjs",
19
+ "advisor/",
20
+ "agent/",
21
+ "agent-tools/",
22
+ "git/",
23
+ "mcp/",
24
+ "memory/",
25
+ "provider/",
26
+ "session-*.mjs",
27
+ "tools/",
28
+ "traces/",
29
+ "prompts/",
30
+ "tool-docs/"
31
+ ]
32
+ }