@wudao-org/providers 0.0.0-stage → 0.4.20
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.
- package/LICENSE +21 -0
- package/README.md +17 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/anthropic.d.ts +19 -0
- package/dist/anthropic.d.ts.map +1 -0
- package/dist/anthropic.js +621 -0
- package/dist/anthropic.js.map +1 -0
- package/dist/balance.d.ts +28 -0
- package/dist/balance.d.ts.map +1 -0
- package/dist/balance.js +105 -0
- package/dist/balance.js.map +1 -0
- package/dist/config.d.ts +328 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +623 -0
- package/dist/config.js.map +1 -0
- package/dist/dispatcher.d.ts +5 -0
- package/dist/dispatcher.d.ts.map +1 -0
- package/dist/dispatcher.js +21 -0
- package/dist/dispatcher.js.map +1 -0
- package/dist/effort-catalog.d.ts +17 -0
- package/dist/effort-catalog.d.ts.map +1 -0
- package/dist/effort-catalog.js +73 -0
- package/dist/effort-catalog.js.map +1 -0
- package/dist/error-body.d.ts +32 -0
- package/dist/error-body.d.ts.map +1 -0
- package/dist/error-body.js +74 -0
- package/dist/error-body.js.map +1 -0
- package/dist/header-validation.d.ts +57 -0
- package/dist/header-validation.d.ts.map +1 -0
- package/dist/header-validation.js +130 -0
- package/dist/header-validation.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/dist/json-arguments.d.ts +17 -0
- package/dist/json-arguments.d.ts.map +1 -0
- package/dist/json-arguments.js +227 -0
- package/dist/json-arguments.js.map +1 -0
- package/dist/key-pool.d.ts +66 -0
- package/dist/key-pool.d.ts.map +1 -0
- package/dist/key-pool.js +207 -0
- package/dist/key-pool.js.map +1 -0
- package/dist/missing-reasoning-circuit.d.ts +17 -0
- package/dist/missing-reasoning-circuit.d.ts.map +1 -0
- package/dist/missing-reasoning-circuit.js +38 -0
- package/dist/missing-reasoning-circuit.js.map +1 -0
- package/dist/model-failure.d.ts +156 -0
- package/dist/model-failure.d.ts.map +1 -0
- package/dist/model-failure.js +253 -0
- package/dist/model-failure.js.map +1 -0
- package/dist/model-fallback.d.ts +18 -0
- package/dist/model-fallback.d.ts.map +1 -0
- package/dist/model-fallback.js +87 -0
- package/dist/model-fallback.js.map +1 -0
- package/dist/models.d.ts +20 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +55 -0
- package/dist/models.js.map +1 -0
- package/dist/openai-compatible.d.ts +87 -0
- package/dist/openai-compatible.d.ts.map +1 -0
- package/dist/openai-compatible.js +368 -0
- package/dist/openai-compatible.js.map +1 -0
- package/dist/openai-engine.d.ts +18 -0
- package/dist/openai-engine.d.ts.map +1 -0
- package/dist/openai-engine.js +515 -0
- package/dist/openai-engine.js.map +1 -0
- package/dist/openai-images.d.ts +38 -0
- package/dist/openai-images.d.ts.map +1 -0
- package/dist/openai-images.js +301 -0
- package/dist/openai-images.js.map +1 -0
- package/dist/openai-messages.d.ts +40 -0
- package/dist/openai-messages.d.ts.map +1 -0
- package/dist/openai-messages.js +135 -0
- package/dist/openai-messages.js.map +1 -0
- package/dist/openai-wire.d.ts +93 -0
- package/dist/openai-wire.d.ts.map +1 -0
- package/dist/openai-wire.js +151 -0
- package/dist/openai-wire.js.map +1 -0
- package/dist/persona/apply.d.ts +19 -0
- package/dist/persona/apply.d.ts.map +1 -0
- package/dist/persona/apply.js +98 -0
- package/dist/persona/apply.js.map +1 -0
- package/dist/persona/builtins.d.ts +3 -0
- package/dist/persona/builtins.d.ts.map +1 -0
- package/dist/persona/builtins.js +180 -0
- package/dist/persona/builtins.js.map +1 -0
- package/dist/persona/decorator.d.ts +4 -0
- package/dist/persona/decorator.d.ts.map +1 -0
- package/dist/persona/decorator.js +112 -0
- package/dist/persona/decorator.js.map +1 -0
- package/dist/persona/files.d.ts +20 -0
- package/dist/persona/files.d.ts.map +1 -0
- package/dist/persona/files.js +84 -0
- package/dist/persona/files.js.map +1 -0
- package/dist/persona/ids.d.ts +9 -0
- package/dist/persona/ids.d.ts.map +1 -0
- package/dist/persona/ids.js +11 -0
- package/dist/persona/ids.js.map +1 -0
- package/dist/persona/registry.d.ts +12 -0
- package/dist/persona/registry.d.ts.map +1 -0
- package/dist/persona/registry.js +54 -0
- package/dist/persona/registry.js.map +1 -0
- package/dist/persona/runtime.d.ts +23 -0
- package/dist/persona/runtime.d.ts.map +1 -0
- package/dist/persona/runtime.js +32 -0
- package/dist/persona/runtime.js.map +1 -0
- package/dist/persona/schema.d.ts +27 -0
- package/dist/persona/schema.d.ts.map +1 -0
- package/dist/persona/schema.js +165 -0
- package/dist/persona/schema.js.map +1 -0
- package/dist/persona/types.d.ts +38 -0
- package/dist/persona/types.d.ts.map +1 -0
- package/dist/persona/types.js +2 -0
- package/dist/persona/types.js.map +1 -0
- package/dist/pricing.d.ts +40 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +70 -0
- package/dist/pricing.js.map +1 -0
- package/dist/quota-park.d.ts +53 -0
- package/dist/quota-park.d.ts.map +1 -0
- package/dist/quota-park.js +135 -0
- package/dist/quota-park.js.map +1 -0
- package/dist/rate-pause.d.ts +28 -0
- package/dist/rate-pause.d.ts.map +1 -0
- package/dist/rate-pause.js +72 -0
- package/dist/rate-pause.js.map +1 -0
- package/dist/registry.d.ts +142 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +471 -0
- package/dist/registry.js.map +1 -0
- package/dist/responses.d.ts +5 -0
- package/dist/responses.d.ts.map +1 -0
- package/dist/responses.js +290 -0
- package/dist/responses.js.map +1 -0
- package/dist/retry-after.d.ts +13 -0
- package/dist/retry-after.d.ts.map +1 -0
- package/dist/retry-after.js +64 -0
- package/dist/retry-after.js.map +1 -0
- package/dist/retry-ladder.d.ts +176 -0
- package/dist/retry-ladder.d.ts.map +1 -0
- package/dist/retry-ladder.js +664 -0
- package/dist/retry-ladder.js.map +1 -0
- package/dist/split-inband-thinking.d.ts +35 -0
- package/dist/split-inband-thinking.d.ts.map +1 -0
- package/dist/split-inband-thinking.js +117 -0
- package/dist/split-inband-thinking.js.map +1 -0
- package/dist/stream-idle.d.ts +97 -0
- package/dist/stream-idle.d.ts.map +1 -0
- package/dist/stream-idle.js +127 -0
- package/dist/stream-idle.js.map +1 -0
- package/dist/tool-call-ids.d.ts +16 -0
- package/dist/tool-call-ids.d.ts.map +1 -0
- package/dist/tool-call-ids.js +36 -0
- package/dist/tool-call-ids.js.map +1 -0
- package/package.json +32 -3
package/dist/config.js
ADDED
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
// Provider profiles + env/flag resolution. All profiles share the same
|
|
2
|
+
// OpenAI-compatible engine (openai-compatible.ts); a profile only fixes the
|
|
3
|
+
// default baseUrl / default model and the reasoning format matrix entry
|
|
4
|
+
// (format + thinkingLevelMap + supportsReasoningEffort).
|
|
5
|
+
import { PROTOCOL_IDS } from "./persona/schema.js";
|
|
6
|
+
import { parsePersonaSpec } from "./persona/runtime.js";
|
|
7
|
+
export const REASONING_FORMATS = [
|
|
8
|
+
"none",
|
|
9
|
+
"deepseek",
|
|
10
|
+
"zai",
|
|
11
|
+
"qwen",
|
|
12
|
+
"qwen-chat-template",
|
|
13
|
+
"openrouter",
|
|
14
|
+
"together",
|
|
15
|
+
"baseten",
|
|
16
|
+
];
|
|
17
|
+
export const PROVIDER_PROFILES = {
|
|
18
|
+
deepseek: {
|
|
19
|
+
label: "deepseek",
|
|
20
|
+
defaultBaseUrl: "https://api.deepseek.com",
|
|
21
|
+
defaultModel: "deepseek-v4-flash",
|
|
22
|
+
reasoningFormat: "deepseek",
|
|
23
|
+
acceptsReasoningOnlyFinish: true,
|
|
24
|
+
windowMode: "shared",
|
|
25
|
+
// ω99-39: the ONLY probe-backed pin (ω99-33 §5.2 — billing + cache both
|
|
26
|
+
// inert for client-sent reasoning_content). Until another endpoint gets
|
|
27
|
+
// its own probe, this stays deepseek-only.
|
|
28
|
+
dropsClientReasoningReplay: true,
|
|
29
|
+
// P0-4 (96 队列): api-docs.deepseek.com's wallet-balance endpoint — the
|
|
30
|
+
// one builtin with a documented GET /user/balance.
|
|
31
|
+
balanceUrl: "https://api.deepseek.com/user/balance",
|
|
32
|
+
// 反馈① 收口 (2026-08-29 夜): api-docs.deepseek.com/quick_start/pricing
|
|
33
|
+
// lists ONE CONTEXT LENGTH row — 1M — for the whole v4 line. "1M" is
|
|
34
|
+
// read literally as 1_000_000 (conservative vs 1_048_576: autocompact
|
|
35
|
+
// fires a hair earlier, never later). The discontinued legacy aliases
|
|
36
|
+
// (deepseek-chat/reasoner, 128K-era) stay uncataloged → env fallback.
|
|
37
|
+
models: [
|
|
38
|
+
{ name: "deepseek-v4-flash", contextLength: 1_000_000 },
|
|
39
|
+
{ name: "deepseek-v4-pro", contextLength: 1_000_000 },
|
|
40
|
+
{ name: "deepseek-v4-flash-vision-exp", contextLength: 1_000_000 },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
// glm (zai format): the M16 `thinking:{type}` transform now lives in the
|
|
44
|
+
// matrix — the zai envelope plus a thinkingLevelMap folding each effort
|
|
45
|
+
// onto enabled/disabled. bigmodel.cn takes no reasoning_effort rider.
|
|
46
|
+
glm: {
|
|
47
|
+
label: "glm",
|
|
48
|
+
defaultBaseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
49
|
+
defaultModel: "glm-4.6",
|
|
50
|
+
reasoningFormat: "zai",
|
|
51
|
+
thinkingLevelMap: { low: "disabled", medium: "disabled", high: "enabled", max: "enabled", none: "disabled" },
|
|
52
|
+
supportsReasoningEffort: false,
|
|
53
|
+
acceptsReasoningOnlyFinish: true,
|
|
54
|
+
// 夜移 022: bigmodel endpoints may legitimately omit reasoning on
|
|
55
|
+
// thinking-enabled tool turns (Reasonix reasoning_replay_policy.go:13-24).
|
|
56
|
+
omitsReasoningOnToolTurns: true,
|
|
57
|
+
// 反馈① 收口 (2026-08-29 夜): docs.z.ai/guides/llm/glm-4.6 — 200K
|
|
58
|
+
// (= 204_800) context. Per-model, NOT a provider default: glm-4.5 and
|
|
59
|
+
// older are 128K, a profile-level 204_800 would over-window them.
|
|
60
|
+
models: [{ name: "glm-4.6", contextLength: 204_800 }],
|
|
61
|
+
},
|
|
62
|
+
// qwen (dashscope compatible-mode): enable_thinking only, no rider. The
|
|
63
|
+
// reasoning-only degenerate finish is not a known qwen shape — repair off.
|
|
64
|
+
qwen: {
|
|
65
|
+
label: "qwen",
|
|
66
|
+
defaultBaseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
67
|
+
defaultModel: "qwen3-max",
|
|
68
|
+
reasoningFormat: "qwen",
|
|
69
|
+
supportsReasoningEffort: false,
|
|
70
|
+
acceptsReasoningOnlyFinish: false,
|
|
71
|
+
// 反馈① 收口 (2026-08-29 夜): help.aliyun.com/en/model-studio/
|
|
72
|
+
// model-qwen3-max — 262_144 context (258_048 max input). The qwen
|
|
73
|
+
// catalog is huge and window-varying; only the doc-verified default
|
|
74
|
+
// model is pinned.
|
|
75
|
+
models: [{ name: "qwen3-max", contextLength: 262_144 }],
|
|
76
|
+
},
|
|
77
|
+
wudao: { label: "wudao", defaultBaseUrl: undefined, defaultModel: "wudao-preview", requiresBaseUrl: true, reasoningFormat: "deepseek", acceptsReasoningOnlyFinish: true },
|
|
78
|
+
// anthropic (0.3.12): the messages-protocol builtin — the adapter itself
|
|
79
|
+
// (anthropic.ts) predates the profile; this entry is what makes it
|
|
80
|
+
// SELECTABLE as a first-class provider (settings cards, /provider switch,
|
|
81
|
+
// --provider). x-api-key + anthropic-version headers live in the adapter;
|
|
82
|
+
// thinking rides the effort→budget_tokens map (no reasoningFormat
|
|
83
|
+
// envelope); the window is independent (max_tokens is a completion-only
|
|
84
|
+
// budget, unlike deepseek's shared window).
|
|
85
|
+
anthropic: {
|
|
86
|
+
label: "anthropic",
|
|
87
|
+
defaultBaseUrl: "https://api.anthropic.com",
|
|
88
|
+
defaultModel: "claude-sonnet-5",
|
|
89
|
+
reasoningFormat: "none",
|
|
90
|
+
acceptsReasoningOnlyFinish: false,
|
|
91
|
+
windowMode: "independent",
|
|
92
|
+
wire: "anthropic",
|
|
93
|
+
},
|
|
94
|
+
// ── 按量平台 API(0.3.30 两套拆分) ──────────────────────────────────
|
|
95
|
+
// kimi 与 minimax 的按 token 计费端点——与各自的 *-code 订阅网关成对:
|
|
96
|
+
// 同一厂商,两条计费路,用户按持有的是 API key 还是订阅来选。
|
|
97
|
+
kimi: {
|
|
98
|
+
label: "kimi",
|
|
99
|
+
defaultBaseUrl: "https://api.moonshot.cn/anthropic",
|
|
100
|
+
defaultModel: "kimi-k3",
|
|
101
|
+
reasoningFormat: "none",
|
|
102
|
+
acceptsReasoningOnlyFinish: false,
|
|
103
|
+
windowMode: "independent",
|
|
104
|
+
wire: "anthropic",
|
|
105
|
+
bearerAuth: true,
|
|
106
|
+
// platform.kimi.com/docs/guide/claude-code-kimi AUTO_COMPACT_WINDOW
|
|
107
|
+
// table, verbatim numbers. kimi-k2.6 has NO documented window — left
|
|
108
|
+
// unpinned (a wrong pin fires autocompact late → 413; env fallback is
|
|
109
|
+
// safer). Known edge: kimi-k2.7-code demands explicit thinking — effort
|
|
110
|
+
// none (no thinking block sent) 400s on it; the default kimi-k3 is
|
|
111
|
+
// unconstrained.
|
|
112
|
+
models: [
|
|
113
|
+
{ name: "kimi-k3", contextLength: 1_048_576 },
|
|
114
|
+
{ name: "kimi-k2.7-code", contextLength: 262_144 },
|
|
115
|
+
{ name: "kimi-k2.7-code-highspeed", contextLength: 262_144 },
|
|
116
|
+
{ name: "kimi-k2.6" },
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
minimax: {
|
|
120
|
+
label: "minimax",
|
|
121
|
+
defaultBaseUrl: "https://api.minimaxi.com/v1",
|
|
122
|
+
defaultModel: "MiniMax-M3",
|
|
123
|
+
reasoningFormat: "none",
|
|
124
|
+
acceptsReasoningOnlyFinish: false,
|
|
125
|
+
// 平台 API(按量)走 OpenAI chat 线(wire 省略 = openai-chat,与
|
|
126
|
+
// deepseek/glm/qwen 的按量条目同构)。窗口数未在按量文档核实——不钉
|
|
127
|
+
// (错钉比漏钉危险:晚 autocompact → 413)。
|
|
128
|
+
models: [{ name: "MiniMax-M3" }],
|
|
129
|
+
},
|
|
130
|
+
// ── Coding-plan gateways (0.3.26 弧;0.3.30 kimi-code 指向订阅端) ────
|
|
131
|
+
// 智谱 GLM Coding Plan / Kimi Coding Plan / MiniMax Token Plan — three
|
|
132
|
+
// Anthropic-messages mirrors of the SAME shape as the anthropic builtin
|
|
133
|
+
// (adapter URL joins base + /v1/messages; thinking rides the
|
|
134
|
+
// effort→budget_tokens map; independent window). glm-code/minimax-code
|
|
135
|
+
// auth via ANTHROPIC_AUTH_TOKEN so the adapter dual-sends Authorization:
|
|
136
|
+
// Bearer; kimi-code's official config uses ANTHROPIC_API_KEY — x-api-key
|
|
137
|
+
// only, no bearerAuth flag.
|
|
138
|
+
"glm-code": {
|
|
139
|
+
label: "glm-code",
|
|
140
|
+
defaultBaseUrl: "https://open.bigmodel.cn/api/anthropic",
|
|
141
|
+
defaultModel: "glm-5.3",
|
|
142
|
+
reasoningFormat: "none",
|
|
143
|
+
acceptsReasoningOnlyFinish: false,
|
|
144
|
+
windowMode: "independent",
|
|
145
|
+
wire: "anthropic",
|
|
146
|
+
bearerAuth: true,
|
|
147
|
+
// 取证弧 0921: probe-verified completion tiers (16384 base / 32768
|
|
148
|
+
// escalation) — kills the 4096-default reasoning-exhaustion death.
|
|
149
|
+
defaultMaxTokens: 16_384,
|
|
150
|
+
outputEscalateTo: 32_768,
|
|
151
|
+
// docs.bigmodel.cn/cn/coding-plan/quick-start: both cataloged models
|
|
152
|
+
// are 1M context (official model cards).
|
|
153
|
+
models: [
|
|
154
|
+
{ name: "glm-5.3", contextLength: 1_000_000 },
|
|
155
|
+
{ name: "glm-5.3-flash", contextLength: 1_000_000 },
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
// Kimi Coding Plan 订阅网关(0.3.30 重指向):官方 Claude Code 接入页
|
|
159
|
+
// 的订阅路径——api.kimi.com/coding,模型 k3-256k、窗口 262144(用户
|
|
160
|
+
// settings.json 的 AUTO_COMPACT_WINDOW 原数)。鉴权按官方配置走
|
|
161
|
+
// ANTHROPIC_API_KEY(x-api-key only——省略 bearerAuth,与 glm-code/
|
|
162
|
+
// minimax-code 的 AUTH_TOKEN 双发不同)。按量平台 API 见上方 `kimi`。
|
|
163
|
+
"kimi-code": {
|
|
164
|
+
label: "kimi-code",
|
|
165
|
+
defaultBaseUrl: "https://api.kimi.com/coding",
|
|
166
|
+
defaultModel: "k3-256k",
|
|
167
|
+
reasoningFormat: "none",
|
|
168
|
+
acceptsReasoningOnlyFinish: false,
|
|
169
|
+
windowMode: "independent",
|
|
170
|
+
wire: "anthropic",
|
|
171
|
+
models: [{ name: "k3-256k", contextLength: 262_144 }],
|
|
172
|
+
},
|
|
173
|
+
"minimax-code": {
|
|
174
|
+
label: "minimax-code",
|
|
175
|
+
defaultBaseUrl: "https://api.minimaxi.com/anthropic",
|
|
176
|
+
defaultModel: "MiniMax-M3",
|
|
177
|
+
reasoningFormat: "none",
|
|
178
|
+
acceptsReasoningOnlyFinish: false,
|
|
179
|
+
windowMode: "independent",
|
|
180
|
+
wire: "anthropic",
|
|
181
|
+
bearerAuth: true,
|
|
182
|
+
// platform.minimaxi.com/docs/token-plan/other-tools: M3 up to 1M.
|
|
183
|
+
models: [{ name: "MiniMax-M3", contextLength: 1_000_000 }],
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
const PROVIDER_NAMES = Object.keys(PROVIDER_PROFILES);
|
|
187
|
+
export const REASONING_EFFORTS = ["low", "medium", "high", "max", "none"];
|
|
188
|
+
export const REASONING_REPLAYS = ["never", "toolCallTurns", "always"];
|
|
189
|
+
/** Parse/fail-fast the replay value (WUDAO_REASONING_REPLAY). */
|
|
190
|
+
export function parseReasoningReplay(v) {
|
|
191
|
+
if (!REASONING_REPLAYS.includes(v)) {
|
|
192
|
+
throw new Error(`invalid reasoning replay "${v}" (expected one of: ${REASONING_REPLAYS.join(", ")})`);
|
|
193
|
+
}
|
|
194
|
+
return v;
|
|
195
|
+
}
|
|
196
|
+
/** Narrow to the compiled-in four (pure, compile-time table). Providers-internal
|
|
197
|
+
* and evals use this; the registry adds customs on top at the CLI layer. */
|
|
198
|
+
export function isBuiltinProviderName(v) {
|
|
199
|
+
return PROVIDER_NAMES.includes(v);
|
|
200
|
+
}
|
|
201
|
+
/** Historical spelling of `isBuiltinProviderName` — retained for existing
|
|
202
|
+
* consumers (cli, evals). Still narrows to the builtin union. */
|
|
203
|
+
export function isProviderName(v) {
|
|
204
|
+
return isBuiltinProviderName(v);
|
|
205
|
+
}
|
|
206
|
+
/** Response fields that may carry reasoning text, format-preferred first. */
|
|
207
|
+
export function reasoningResponseFields(format) {
|
|
208
|
+
switch (format) {
|
|
209
|
+
case "openrouter":
|
|
210
|
+
case "together":
|
|
211
|
+
return ["reasoning", "reasoning_content", "thinking"];
|
|
212
|
+
default:
|
|
213
|
+
// deepseek / zai / qwen / qwen-chat-template / baseten / none all favor
|
|
214
|
+
// the domestic spelling, with the other two as fallbacks.
|
|
215
|
+
return ["reasoning_content", "reasoning", "thinking"];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** First present reasoning field on a message/delta object, else undefined. */
|
|
219
|
+
export function pickReasoning(fields, source) {
|
|
220
|
+
for (const f of fields) {
|
|
221
|
+
const v = source[f];
|
|
222
|
+
if (typeof v === "string" && v !== "")
|
|
223
|
+
return v;
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
/** Native level for an effort under a map: the mapped value, the effort
|
|
228
|
+
* itself when unmapped, or null when the map explicitly unsupports it. */
|
|
229
|
+
function nativeLevel(effort, map) {
|
|
230
|
+
const mapped = map?.[effort];
|
|
231
|
+
return mapped === undefined ? effort : mapped;
|
|
232
|
+
}
|
|
233
|
+
/** The ONE request-side implementation of the matrix (M16's per-profile
|
|
234
|
+
* transforms folded in; single point, no scattered ifs). Pure: returns only
|
|
235
|
+
* the extra fields to merge into the chat-completions body; never called
|
|
236
|
+
* when no effort is configured. */
|
|
237
|
+
export function reasoningFormatBody(ctx, effort) {
|
|
238
|
+
const level = nativeLevel(effort, ctx.thinkingLevelMap);
|
|
239
|
+
const enable = effort !== "none";
|
|
240
|
+
// The reasoning_effort rider: only formats whose envelope leaves the effort
|
|
241
|
+
// VALUE unstated, and only endpoints that accept the field.
|
|
242
|
+
const rider = () => ctx.supportsReasoningEffort && level !== null ? { reasoning_effort: level } : {};
|
|
243
|
+
switch (ctx.format) {
|
|
244
|
+
case "none":
|
|
245
|
+
return {};
|
|
246
|
+
case "deepseek":
|
|
247
|
+
return level === null ? {} : { reasoning_effort: level };
|
|
248
|
+
case "zai": {
|
|
249
|
+
if (level === null)
|
|
250
|
+
return {};
|
|
251
|
+
const mapped = ctx.thinkingLevelMap?.[effort];
|
|
252
|
+
const type = mapped !== undefined ? mapped : enable ? "enabled" : "disabled";
|
|
253
|
+
return { thinking: { type }, ...rider() };
|
|
254
|
+
}
|
|
255
|
+
case "qwen":
|
|
256
|
+
return { enable_thinking: enable, ...rider() };
|
|
257
|
+
case "qwen-chat-template":
|
|
258
|
+
return { chat_template_kwargs: { enable_thinking: enable, preserve_thinking: true } };
|
|
259
|
+
case "openrouter":
|
|
260
|
+
return level === null ? {} : { reasoning: { effort: level } };
|
|
261
|
+
case "together":
|
|
262
|
+
return { reasoning: { enabled: enable }, ...rider() };
|
|
263
|
+
case "baseten":
|
|
264
|
+
return { chat_template_args: { enable_thinking: enable }, ...rider() };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/** ω99-42: the repair body a mandatory-reasoning rejection re-sends with —
|
|
268
|
+
* the matrix's EXPLICIT enable, at the medium default level (hermes' own
|
|
269
|
+
* default reasoning effort; every format in the tree can express it), with
|
|
270
|
+
* the thinkingLevelMap deliberately STRIPPED: the map is exactly what folds
|
|
271
|
+
* low/medium onto "disabled", so a mapped repair would resend the very
|
|
272
|
+
* disable the endpoint just rejected. `{}` for the "none" format — that
|
|
273
|
+
* wire cannot express reasoning, the repair is impossible, and the
|
|
274
|
+
* rejection stays terminal. Pure: returns only the extra fields to merge
|
|
275
|
+
* into the chat-completions body (same contract as reasoningFormatBody). */
|
|
276
|
+
export function mandatoryRepairReasoningBody(ctx) {
|
|
277
|
+
return reasoningFormatBody({ format: ctx.format, supportsReasoningEffort: ctx.supportsReasoningEffort }, "medium");
|
|
278
|
+
}
|
|
279
|
+
/** Resolve the reasoning request context for a config: explicit config fields
|
|
280
|
+
* beat the selected profile's defaults; unpinned rider gates sniff the baseUrl. */
|
|
281
|
+
export function resolveReasoningContext(config) {
|
|
282
|
+
const profile = config.provider !== undefined && isProviderName(config.provider)
|
|
283
|
+
? PROVIDER_PROFILES[config.provider]
|
|
284
|
+
: undefined;
|
|
285
|
+
const map = config.thinkingLevelMap ?? profile?.thinkingLevelMap;
|
|
286
|
+
return {
|
|
287
|
+
format: config.reasoningFormat ?? profile?.reasoningFormat ?? "deepseek",
|
|
288
|
+
...(map !== undefined ? { thinkingLevelMap: map } : {}),
|
|
289
|
+
supportsReasoningEffort: config.supportsReasoningEffort
|
|
290
|
+
?? profile?.supportsReasoningEffort
|
|
291
|
+
?? supportsReasoningEffort(config.baseURL),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/** URL sniff (custom baseUrls): does this endpoint take a top-level
|
|
295
|
+
* `reasoning_effort`? Mirrors pi's detection — the domestic and gateway
|
|
296
|
+
* endpoints known to reject the field say false, everything else true. */
|
|
297
|
+
export function supportsReasoningEffort(baseURL) {
|
|
298
|
+
const url = baseURL.toLowerCase();
|
|
299
|
+
const reject = [
|
|
300
|
+
"api.z.ai",
|
|
301
|
+
"open.bigmodel.cn",
|
|
302
|
+
"api.together.ai",
|
|
303
|
+
"api.together.xyz",
|
|
304
|
+
"api.moonshot.",
|
|
305
|
+
"api.x.ai",
|
|
306
|
+
"integrate.api.nvidia.com",
|
|
307
|
+
"api.ant-ling.com",
|
|
308
|
+
"gateway.ai.cloudflare.com",
|
|
309
|
+
];
|
|
310
|
+
return !reject.some((h) => url.includes(h));
|
|
311
|
+
}
|
|
312
|
+
// Validate a reasoning-effort value; throws on junk so typos fail fast at
|
|
313
|
+
// resolution time instead of silently shaping requests.
|
|
314
|
+
export function parseReasoningEffort(v) {
|
|
315
|
+
if (!REASONING_EFFORTS.includes(v)) {
|
|
316
|
+
throw new Error(`invalid reasoning effort "${v}" (expected one of: ${REASONING_EFFORTS.join(", ")})`);
|
|
317
|
+
}
|
|
318
|
+
return v;
|
|
319
|
+
}
|
|
320
|
+
// R2 T2 — the wire adapters' (anthropic/responses) effort plumbing, the same
|
|
321
|
+
// contract the openai engine applies internally (M58 §C). Exported from the
|
|
322
|
+
// module (not the package index): these are adapter-internal seams.
|
|
323
|
+
/** Validate a per-input reasoning effort ONCE at the adapter's public
|
|
324
|
+
* boundary, BEFORE the retry ladder — the fail-fast parse error must never
|
|
325
|
+
* walk the ladder (junk can never succeed on retry). The parsed value rides
|
|
326
|
+
* the returned input; absent/empty = the input unchanged (the config's own
|
|
327
|
+
* effort applies). */
|
|
328
|
+
export function validatedInputEffort(input) {
|
|
329
|
+
if (input.reasoningEffort === undefined || input.reasoningEffort === "")
|
|
330
|
+
return input;
|
|
331
|
+
return { ...input, reasoningEffort: parseReasoningEffort(input.reasoningEffort) };
|
|
332
|
+
}
|
|
333
|
+
/** The request's EFFECTIVE reasoning effort — a per-input effort (already
|
|
334
|
+
* validated at the adapter boundary) overrides the config's own; absent
|
|
335
|
+
* both = undefined (no effort field on the wire — byte-identical status
|
|
336
|
+
* quo, the prefix-cache red line). */
|
|
337
|
+
export function effectiveReasoningEffort(input, config) {
|
|
338
|
+
return input.reasoningEffort !== undefined && input.reasoningEffort !== ""
|
|
339
|
+
? input.reasoningEffort
|
|
340
|
+
: config.reasoningEffort;
|
|
341
|
+
}
|
|
342
|
+
// Same fail-fast contract for the format matrix.
|
|
343
|
+
export function parseReasoningFormat(v) {
|
|
344
|
+
if (!REASONING_FORMATS.includes(v)) {
|
|
345
|
+
throw new Error(`invalid reasoning format "${v}" (expected one of: ${REASONING_FORMATS.join(", ")})`);
|
|
346
|
+
}
|
|
347
|
+
return v;
|
|
348
|
+
}
|
|
349
|
+
// Persona Phase B (design 93 §4.2-1): the wire (protocol) a config speaks —
|
|
350
|
+
// which adapter createProvider dispatches to. Fail-fast on junk values so a
|
|
351
|
+
// typo'd WUDAO_WIRE dies at selection time, not as a silent openai request.
|
|
352
|
+
export function parseWire(v) {
|
|
353
|
+
if (!PROTOCOL_IDS.includes(v)) {
|
|
354
|
+
throw new Error(`invalid wire "${v}" (expected one of: ${PROTOCOL_IDS.join(", ")})`);
|
|
355
|
+
}
|
|
356
|
+
return v;
|
|
357
|
+
}
|
|
358
|
+
export const NO_KEY_REASON = "no API key found";
|
|
359
|
+
/** M56 §1: the key POOL from env — `WUDAO_API_KEYS` (comma-separated) riding
|
|
360
|
+
* alongside the historical single `WUDAO_API_KEY` (or `OPENAI_API_KEY`
|
|
361
|
+
* fallback). The single key stays the PRIMARY (first), the comma list adds
|
|
362
|
+
* the rest, everything deduped order-preserving. Empty when no key at all —
|
|
363
|
+
* `resolveProviderConfigResult` keeps failing with NO_KEY_REASON. */
|
|
364
|
+
export function parseApiKeyPool(env) {
|
|
365
|
+
const pool = [];
|
|
366
|
+
const push = (raw) => {
|
|
367
|
+
for (const part of (raw ?? "").split(",")) {
|
|
368
|
+
const key = part.trim();
|
|
369
|
+
if (key !== "" && !pool.includes(key))
|
|
370
|
+
pool.push(key);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
push(env.WUDAO_API_KEY ?? env.OPENAI_API_KEY);
|
|
374
|
+
push(env.WUDAO_API_KEYS);
|
|
375
|
+
return pool;
|
|
376
|
+
}
|
|
377
|
+
// Resolve provider config from env + explicit selection. Fails (ok:false) with
|
|
378
|
+
// a human-readable reason when no API key is present or the profile requires a
|
|
379
|
+
// baseUrl that was not provided — so callers can fall back to guidance instead
|
|
380
|
+
// of crashing. Throws on unknown provider names / invalid effort or format values.
|
|
381
|
+
// The optional `providers` map (provider management R2) is the effective view
|
|
382
|
+
// from the registry (builtins ⊕ overlays ⊕ customs): a selected id that hits
|
|
383
|
+
// it resolves against that profile — a custom provider builds on its registry
|
|
384
|
+
// baseUrl, a builtin overlay rides the compiled-in defaults. Absent map =
|
|
385
|
+
// the historical builtin-only behavior, byte for byte.
|
|
386
|
+
export function resolveProviderConfigResult(env = process.env, selection = {}, credentials, providers) {
|
|
387
|
+
const nameRaw = selection.provider ?? env.WUDAO_PROVIDER;
|
|
388
|
+
let provider = "deepseek";
|
|
389
|
+
if (nameRaw !== undefined && nameRaw !== "") {
|
|
390
|
+
if (!isProviderName(nameRaw) && providers?.get(nameRaw) === undefined) {
|
|
391
|
+
throw new Error(`unknown provider "${nameRaw}" (expected one of: ${PROVIDER_NAMES.join(", ")}, `
|
|
392
|
+
+ `or a custom provider from the registry (~/.wudao/providers.json))`);
|
|
393
|
+
}
|
|
394
|
+
provider = nameRaw;
|
|
395
|
+
}
|
|
396
|
+
// M56 §1: keys resolve as a POOL (single key = one-element pool). M63 §B:
|
|
397
|
+
// an empty env pool falls back to the credentials map's entry for the
|
|
398
|
+
// SELECTED provider (env wins — see the seam comment above). The file key
|
|
399
|
+
// becomes a one-element pool, preserving the historical shape. Registry ids
|
|
400
|
+
// resolve keys the same way (the credentials file accepts any ID_RE id).
|
|
401
|
+
let apiKeys = parseApiKeyPool(env);
|
|
402
|
+
if (apiKeys.length === 0 && credentials !== undefined) {
|
|
403
|
+
const fileKey = (credentials[provider] ?? "").trim();
|
|
404
|
+
if (fileKey !== "")
|
|
405
|
+
apiKeys = [fileKey];
|
|
406
|
+
}
|
|
407
|
+
const apiKey = apiKeys[0];
|
|
408
|
+
// The registry's effective view wins when present (it already folds the
|
|
409
|
+
// builtin base for overlays); otherwise the compiled-in profile.
|
|
410
|
+
const registered = providers?.get(provider);
|
|
411
|
+
let profile = registered
|
|
412
|
+
?? (isProviderName(provider) ? PROVIDER_PROFILES[provider] : undefined);
|
|
413
|
+
if (profile === undefined) {
|
|
414
|
+
// Unreachable — the unknown-provider guard above threw for exactly this
|
|
415
|
+
// shape — but fail loudly rather than guess a profile if that breaks.
|
|
416
|
+
throw new Error(`unknown provider "${provider}"`);
|
|
417
|
+
}
|
|
418
|
+
if (!apiKey)
|
|
419
|
+
return { ok: false, reason: NO_KEY_REASON };
|
|
420
|
+
const baseURL = env.WUDAO_BASE_URL ?? env.OPENAI_BASE_URL;
|
|
421
|
+
if (baseURL === undefined && profile.requiresBaseUrl) {
|
|
422
|
+
// R-M3 (T1 review): requiresBaseUrl demands an EXPLICIT url — the env
|
|
423
|
+
// override, or one the REGISTRY itself supplied (a custom's baseUrl, an
|
|
424
|
+
// overlay's own baseUrl). The builtin's compiled-in defaultBaseUrl must
|
|
425
|
+
// NOT satisfy an overlay's requiresBaseUrl (the whole point of the flag:
|
|
426
|
+
// "route me anywhere except the vendor default"). Same refusal shape as
|
|
427
|
+
// the historical wudao case; absent a registry the behavior is unchanged.
|
|
428
|
+
const builtinDefault = isProviderName(provider) ? PROVIDER_PROFILES[provider].defaultBaseUrl : undefined;
|
|
429
|
+
const registrySupplied = profile.defaultBaseUrl !== undefined && profile.defaultBaseUrl !== builtinDefault;
|
|
430
|
+
if (!registrySupplied) {
|
|
431
|
+
return { ok: false, reason: `${provider} provider needs WUDAO_BASE_URL` };
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// R3-T002 深测弧:模型来源三档(旗标 > env > profile 缺省)——哨兵只对
|
|
435
|
+
// 前两档设防(用户/环境钉的),profile 缺省永远合法。
|
|
436
|
+
const modelSource = selection.model !== undefined && selection.model !== "" ? "flag"
|
|
437
|
+
: env.WUDAO_MODEL !== undefined && env.WUDAO_MODEL !== "" || env.OPENAI_MODEL !== undefined && env.OPENAI_MODEL !== "" ? "env"
|
|
438
|
+
: "profile";
|
|
439
|
+
const model = modelSource === "flag" ? (selection.model ?? "")
|
|
440
|
+
: modelSource === "env" ? (env.WUDAO_MODEL ?? env.OPENAI_MODEL ?? "")
|
|
441
|
+
: (profile.defaultModel ?? "");
|
|
442
|
+
// R-M2 (T1 review): a CUSTOM provider saved without defaultModel and without
|
|
443
|
+
// models resolves "" here — fail fast at selection time, naming the fix.
|
|
444
|
+
// (Saving that shape stays allowed; only running it refuses.) Builtins never
|
|
445
|
+
// hit this (their defaultModel is a non-empty constant).
|
|
446
|
+
if (model === "" && registered?.custom === true) {
|
|
447
|
+
throw new Error(`custom provider "${provider}" has no model to run — set "defaultModel" or add a "models" list to its entry in ~/.wudao/providers.json (or set WUDAO_MODEL)`);
|
|
448
|
+
}
|
|
449
|
+
// R3-T002 深测弧(永续迭代):环境钉的模型静默覆盖所选 provider 的目录
|
|
450
|
+
// 缺省——实证形态:仓库树里某层 .env 带 WUDAO_MODEL=deepseek-v4-flash
|
|
451
|
+
// (CLI 从 cwd 向上自动加载),`--provider glm-code` 于是拼出
|
|
452
|
+
// glm-code · deepseek-v4-flash,到厂商端点必然 404,报错零归因。可证
|
|
453
|
+
// 伪的组合在选型时刻就拒绝、点名冲突与修法:模型确实来自 env、所选
|
|
454
|
+
// provider 的目录非空且不含它、且 base URL 没有 env 网关覆盖(网关可
|
|
455
|
+
// 路由任意模型名——config.test.ts 的网关钉守住这条豁免)。目录空的自
|
|
456
|
+
// 定义 provider(私有网关常见形)不设防;profile 缺省模型永远合法。
|
|
457
|
+
const catalog = registered?.models ?? (isProviderName(provider) ? PROVIDER_PROFILES[provider].models : []) ?? [];
|
|
458
|
+
if (selection.allowEnvModelMismatch !== true &&
|
|
459
|
+
modelSource !== "profile" &&
|
|
460
|
+
model !== undefined &&
|
|
461
|
+
model !== "" &&
|
|
462
|
+
catalog.length > 0 &&
|
|
463
|
+
!catalog.some((m) => m.name === model) &&
|
|
464
|
+
baseURL === undefined) {
|
|
465
|
+
const source = modelSource === "flag" ? "--model" : env.WUDAO_MODEL !== undefined ? "WUDAO_MODEL" : "OPENAI_MODEL";
|
|
466
|
+
throw new Error(`model "${model}" (from ${source}) is not offered by provider "${provider}" (catalog: ${catalog.map((m) => m.name).join(", ")}) — pick a catalog model, fix the env var (check a parent directory's .env), or set WUDAO_BASE_URL to a gateway that serves this model`);
|
|
467
|
+
}
|
|
468
|
+
const effortRaw = selection.reasoningEffort ?? env.WUDAO_REASONING_EFFORT;
|
|
469
|
+
const reasoningEffort = effortRaw !== undefined && effortRaw !== "" ? parseReasoningEffort(effortRaw) : undefined;
|
|
470
|
+
const formatRaw = env.WUDAO_REASONING_FORMAT;
|
|
471
|
+
const reasoningFormat = formatRaw !== undefined && formatRaw !== "" ? parseReasoningFormat(formatRaw) : undefined;
|
|
472
|
+
const replayRaw = env.WUDAO_REASONING_REPLAY;
|
|
473
|
+
const replayParsed = replayRaw !== undefined && replayRaw !== "" ? parseReasoningReplay(replayRaw) : undefined;
|
|
474
|
+
// ω99-39 (案 A, T0-rulled): the probe-verified drop fact caps the intent
|
|
475
|
+
// HERE, at the one assembly seam every downstream face reads — the wire
|
|
476
|
+
// serialization, the replayedReasoningTokens forensics, and the
|
|
477
|
+
// terminalRepairKind ladder all see the same "never" (case B's wire-only
|
|
478
|
+
// suppression would leave the forensics claiming bytes the wire never
|
|
479
|
+
// sent). The parse above still throws on junk FIRST (fail-fast red line).
|
|
480
|
+
const reasoningReplay = capClientReasoningReplay(profile, replayParsed);
|
|
481
|
+
// Phase B: selection.wire > env WUDAO_WIRE > the profile's native wire
|
|
482
|
+
// (0.3.12: the anthropic builtin carries its protocol — selecting it needs
|
|
483
|
+
// no WUDAO_WIRE; an explicit override still beats the profile) > absent
|
|
484
|
+
// (the default "openai-chat" materializes at createProvider dispatch,
|
|
485
|
+
// keeping the wire-absent config byte-identical to the historical shape).
|
|
486
|
+
const wireRaw = selection.wire ?? env.WUDAO_WIRE ?? profile.wire;
|
|
487
|
+
const wire = wireRaw !== undefined && wireRaw !== "" ? parseWire(wireRaw) : undefined;
|
|
488
|
+
// WUDAO_PERSONA parsing: <id> or <id>:layers[,layers...] format
|
|
489
|
+
let persona = undefined;
|
|
490
|
+
const personaSpec = env.WUDAO_PERSONA?.trim();
|
|
491
|
+
if (personaSpec !== undefined && personaSpec !== "") {
|
|
492
|
+
const parsed = parsePersonaSpec(personaSpec);
|
|
493
|
+
if (!parsed.ok) {
|
|
494
|
+
return { ok: false, reason: `invalid WUDAO_PERSONA: ${parsed.error}` };
|
|
495
|
+
}
|
|
496
|
+
persona =
|
|
497
|
+
parsed.layers === undefined ? { id: parsed.id } : { id: parsed.id, layers: parsed.layers };
|
|
498
|
+
}
|
|
499
|
+
const resolvedBase = baseURL ?? profile.defaultBaseUrl ?? "";
|
|
500
|
+
const supports = profile.supportsReasoningEffort ?? supportsReasoningEffort(resolvedBase);
|
|
501
|
+
return {
|
|
502
|
+
ok: true,
|
|
503
|
+
config: {
|
|
504
|
+
apiKey,
|
|
505
|
+
apiKeys,
|
|
506
|
+
baseURL: resolvedBase,
|
|
507
|
+
model,
|
|
508
|
+
provider,
|
|
509
|
+
reasoningEffort,
|
|
510
|
+
...(reasoningFormat !== undefined
|
|
511
|
+
? { reasoningFormat }
|
|
512
|
+
: registered !== undefined && registered.reasoningFormat !== undefined
|
|
513
|
+
// Registry leg: the effective profile's format rides the config when
|
|
514
|
+
// env doesn't override — custom ids have no compiled-in profile for
|
|
515
|
+
// resolveReasoningContext to fall back on, and a builtin overlay must
|
|
516
|
+
// beat the compiled-in pin. Without a registry the field stays
|
|
517
|
+
// env-only (byte-identical historical behavior).
|
|
518
|
+
? { reasoningFormat: registered.reasoningFormat }
|
|
519
|
+
: {}),
|
|
520
|
+
...(reasoningReplay !== undefined ? { reasoningReplay } : {}),
|
|
521
|
+
...(profile.thinkingLevelMap !== undefined ? { thinkingLevelMap: profile.thinkingLevelMap } : {}),
|
|
522
|
+
supportsReasoningEffort: supports,
|
|
523
|
+
// M48 §2: the reasoning-only repair gate is a profile fact (the shape
|
|
524
|
+
// is a known deepseek/zai degenerate finish) — explicit beats profile.
|
|
525
|
+
acceptsReasoningOnlyFinish: profile.acceptsReasoningOnlyFinish ?? false,
|
|
526
|
+
// 夜移 022: the tool-turn reasoning-omission fact rides the resolved
|
|
527
|
+
// config the same way (glm omits legitimately; default false).
|
|
528
|
+
omitsReasoningOnToolTurns: profile.omitsReasoningOnToolTurns ?? false,
|
|
529
|
+
// ω-2b (I1-c): the window semantics ride the resolved config (deepseek
|
|
530
|
+
// = shared; the others stay unknown until characterized).
|
|
531
|
+
...(profile.windowMode !== undefined ? { windowMode: profile.windowMode } : {}),
|
|
532
|
+
...(wire !== undefined ? { wire } : {}),
|
|
533
|
+
...(profile.bearerAuth !== undefined ? { bearerAuth: profile.bearerAuth } : {}),
|
|
534
|
+
// 取证弧 0921: the profile's completion cap rides the resolved config
|
|
535
|
+
// (same fold pattern as bearerAuth; absent = unset, per-call explicit
|
|
536
|
+
// maxTokens still wins at the adapter's input ?? config seam).
|
|
537
|
+
...(profile.defaultMaxTokens !== undefined ? { maxTokens: profile.defaultMaxTokens } : {}),
|
|
538
|
+
// P0-4 (96 队列): the wallet-balance endpoint rides the resolved config
|
|
539
|
+
// (the same fold pattern as bearerAuth).
|
|
540
|
+
...(profile.balanceUrl !== undefined ? { balanceUrl: profile.balanceUrl } : {}),
|
|
541
|
+
// ω99-64: the profile's transport-layer header baseline rides the
|
|
542
|
+
// resolved config (the registry row's `headers` column) — undefined
|
|
543
|
+
// passes through as undefined: an unconfigured card changes nothing.
|
|
544
|
+
...(profile.headers !== undefined ? { extraHeaders: profile.headers } : {}),
|
|
545
|
+
...(persona !== undefined ? { persona } : {}),
|
|
546
|
+
},
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
/** M48 §2: the reasoning-only repair gate for the engine — explicit config
|
|
550
|
+
* field, then the selected profile's default, then false. */
|
|
551
|
+
export function acceptsReasoningOnlyFinish(config) {
|
|
552
|
+
if (config.acceptsReasoningOnlyFinish !== undefined)
|
|
553
|
+
return config.acceptsReasoningOnlyFinish;
|
|
554
|
+
if (config.provider !== undefined && isProviderName(config.provider)) {
|
|
555
|
+
return PROVIDER_PROFILES[config.provider].acceptsReasoningOnlyFinish ?? false;
|
|
556
|
+
}
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
/** 夜移 022: the tool-turn reasoning-omission gate for terminal repair —
|
|
560
|
+
* the same three-step ladder as acceptsReasoningOnlyFinish (explicit config
|
|
561
|
+
* field, then the selected profile's default, then false). */
|
|
562
|
+
export function omitsReasoningOnToolTurns(config) {
|
|
563
|
+
if (config.omitsReasoningOnToolTurns !== undefined)
|
|
564
|
+
return config.omitsReasoningOnToolTurns;
|
|
565
|
+
if (config.provider !== undefined && isProviderName(config.provider)) {
|
|
566
|
+
return PROVIDER_PROFILES[config.provider].omitsReasoningOnToolTurns ?? false;
|
|
567
|
+
}
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
/** 取证弧 0921: the output-limit ladder's escalateTo from the profile (loop
|
|
571
|
+
* 夜移 013's contract: "supply it from the provider profile, never a magic
|
|
572
|
+
* 64k"). Undefined = the ladder runs nudge-only, byte-identical history. */
|
|
573
|
+
export function outputLimitEscalateFor(provider) {
|
|
574
|
+
if (provider === undefined || !isProviderName(provider))
|
|
575
|
+
return undefined;
|
|
576
|
+
return PROVIDER_PROFILES[provider].outputEscalateTo;
|
|
577
|
+
}
|
|
578
|
+
// ω99-39 — the once-per-process stderr tell. Providers has no warn/notice
|
|
579
|
+
// precedent (checked: no console.*/stderr anywhere in the package), so the
|
|
580
|
+
// pre-authorized fallback channel applies: ONE line at provider resolution,
|
|
581
|
+
// deduped process-wide (resolution runs several times per boot — run.ts,
|
|
582
|
+
// preflight, doctor — and must never spam). Tests re-arm the guard with
|
|
583
|
+
// vi.resetModules + dynamic import (a fresh module instance), so no
|
|
584
|
+
// test-only reset hook is exported.
|
|
585
|
+
let replayShutoffNoticeShown = false;
|
|
586
|
+
function noticeReasoningReplayShutoff(replay, label) {
|
|
587
|
+
if (replayShutoffNoticeShown)
|
|
588
|
+
return;
|
|
589
|
+
replayShutoffNoticeShown = true;
|
|
590
|
+
process.stderr.write(`wudao: WUDAO_REASONING_REPLAY=${replay} is forced to "never" on ${label} — probe-verified (ω99-33): this endpoint drops client-sent reasoning_content (uncounted, cache-inert); replaying it would only waste bytes\n`);
|
|
591
|
+
}
|
|
592
|
+
/** ω99-39: cap a configured reasoning replay by the provider's probe-verified
|
|
593
|
+
* drop fact. The profile fact is an UPPER BOUND on the user's intent, not a
|
|
594
|
+
* new knob: `never` and absent pass through untouched (nothing to cap), an
|
|
595
|
+
* unmarked profile passes everything through byte-identically, and only a
|
|
596
|
+
* drops-marked endpoint forces toolCallTurns/always down to "never" — with
|
|
597
|
+
* the one-line stderr tell above (the honest chain: silent ineffectiveness
|
|
598
|
+
* → explicit shutoff + why). Pure; the notice is the only side effect. */
|
|
599
|
+
export function capClientReasoningReplay(profile, replay) {
|
|
600
|
+
if (replay === undefined || replay === "never")
|
|
601
|
+
return replay;
|
|
602
|
+
if (profile?.dropsClientReasoningReplay !== true)
|
|
603
|
+
return replay;
|
|
604
|
+
noticeReasoningReplayShutoff(replay, profile.label);
|
|
605
|
+
return "never";
|
|
606
|
+
}
|
|
607
|
+
/** ω-2b (I1-c): the window semantics for the loop's admission clip — explicit
|
|
608
|
+
* config field, then the selected profile's pin, then "unknown" (the clip
|
|
609
|
+
* stays off until the endpoint is characterized). */
|
|
610
|
+
export function resolveWindowMode(config) {
|
|
611
|
+
if (config.windowMode !== undefined)
|
|
612
|
+
return config.windowMode;
|
|
613
|
+
if (config.provider !== undefined && isProviderName(config.provider)) {
|
|
614
|
+
return PROVIDER_PROFILES[config.provider].windowMode ?? "unknown";
|
|
615
|
+
}
|
|
616
|
+
return "unknown";
|
|
617
|
+
}
|
|
618
|
+
// Convenience wrapper: null when resolution failed (any reason).
|
|
619
|
+
export function resolveProviderConfig(env = process.env, selection = {}) {
|
|
620
|
+
const r = resolveProviderConfigResult(env, selection);
|
|
621
|
+
return r.ok ? r.config : null;
|
|
622
|
+
}
|
|
623
|
+
//# sourceMappingURL=config.js.map
|