@jsonstudio/llms 0.6.1892 → 0.6.2172
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/dist/conversion/compat/actions/deepseek-web-request.js +16 -2
- package/dist/conversion/compat/actions/deepseek-web-response.d.ts +7 -1
- package/dist/conversion/compat/actions/deepseek-web-response.js +302 -40
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.d.ts +5 -0
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +7 -4
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +1 -0
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +12 -0
- package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +1 -1
- package/dist/conversion/compat/actions/tool-text-request-guidance.d.ts +9 -0
- package/dist/conversion/compat/actions/tool-text-request-guidance.js +177 -0
- package/dist/conversion/compat/antigravity-session-signature.d.ts +6 -0
- package/dist/conversion/compat/antigravity-session-signature.js +15 -0
- package/dist/conversion/compat/profiles/chat-deepseek-web.json +52 -1
- package/dist/conversion/compat/profiles/chat-glm.json +22 -0
- package/dist/conversion/compat/profiles/chat-iflow.json +4 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +13 -27
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +10 -1
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +13 -4
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -53
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +8 -4
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +191 -9
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +118 -15
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +65 -2
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.d.ts +34 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.js +75 -0
- package/dist/conversion/hub/process/chat-process.js +85 -18
- package/dist/conversion/hub/response/provider-response.js +21 -50
- package/dist/conversion/hub/response/response-runtime.js +71 -10
- package/dist/conversion/responses/responses-openai-bridge/response-payload.d.ts +3 -0
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +576 -0
- package/dist/conversion/responses/responses-openai-bridge/types.d.ts +42 -0
- package/dist/conversion/responses/responses-openai-bridge/types.js +1 -0
- package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -44
- package/dist/conversion/responses/responses-openai-bridge.js +193 -504
- package/dist/conversion/shared/anthropic-message-utils.js +82 -2
- package/dist/conversion/shared/bridge-message-utils.js +92 -39
- package/dist/conversion/shared/snapshot-hooks.js +8 -13
- package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.js +129 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-json.d.ts +4 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-json.js +637 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-shared.d.ts +21 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-shared.js +177 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.d.ts +5 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.js +385 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-xml.d.ts +10 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-xml.js +602 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors.d.ts +5 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors.js +4 -0
- package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer/normalize.js +76 -0
- package/dist/conversion/shared/text-markup-normalizer.d.ts +3 -25
- package/dist/conversion/shared/text-markup-normalizer.js +2 -1386
- package/dist/conversion/shared/tool-governor.js +136 -10
- package/dist/filters/utils/snapshot-writer.js +3 -3
- package/dist/router/virtual-router/bootstrap/auth-utils.d.ts +6 -0
- package/dist/router/virtual-router/bootstrap/auth-utils.js +288 -0
- package/dist/router/virtual-router/bootstrap/claude-code-helpers.d.ts +11 -0
- package/dist/router/virtual-router/bootstrap/claude-code-helpers.js +18 -0
- package/dist/router/virtual-router/bootstrap/config-defaults.d.ts +5 -0
- package/dist/router/virtual-router/bootstrap/config-defaults.js +13 -0
- package/dist/router/virtual-router/bootstrap/config-normalizers.d.ts +4 -0
- package/dist/router/virtual-router/bootstrap/config-normalizers.js +106 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.d.ts +7 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.js +68 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +40 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +212 -0
- package/dist/router/virtual-router/bootstrap/responses-helpers.d.ts +15 -0
- package/dist/router/virtual-router/bootstrap/responses-helpers.js +65 -0
- package/dist/router/virtual-router/bootstrap/routing-config.d.ts +23 -0
- package/dist/router/virtual-router/bootstrap/routing-config.js +293 -0
- package/dist/router/virtual-router/bootstrap/streaming-helpers.d.ts +12 -0
- package/dist/router/virtual-router/bootstrap/streaming-helpers.js +128 -0
- package/dist/router/virtual-router/bootstrap/utils.d.ts +5 -0
- package/dist/router/virtual-router/bootstrap/utils.js +41 -0
- package/dist/router/virtual-router/bootstrap/web-search-config.d.ts +4 -0
- package/dist/router/virtual-router/bootstrap/web-search-config.js +131 -0
- package/dist/router/virtual-router/bootstrap.d.ts +0 -4
- package/dist/router/virtual-router/bootstrap.js +31 -1275
- package/dist/router/virtual-router/classifier.js +32 -14
- package/dist/router/virtual-router/engine/antigravity/alias-lease.js +2 -2
- package/dist/router/virtual-router/engine/cooldown-manager.d.ts +34 -0
- package/dist/router/virtual-router/engine/cooldown-manager.js +118 -0
- package/dist/router/virtual-router/engine/route-analytics.d.ts +28 -0
- package/dist/router/virtual-router/engine/route-analytics.js +44 -0
- package/dist/router/virtual-router/engine/routing-pools/index.js +165 -4
- package/dist/router/virtual-router/engine/sticky-session-manager.d.ts +29 -0
- package/dist/router/virtual-router/engine/sticky-session-manager.js +55 -0
- package/dist/router/virtual-router/engine-logging.d.ts +42 -1
- package/dist/router/virtual-router/engine-logging.js +82 -15
- package/dist/router/virtual-router/engine-selection/multimodal-capability.d.ts +3 -0
- package/dist/router/virtual-router/engine-selection/multimodal-capability.js +26 -0
- package/dist/router/virtual-router/engine-selection/route-utils.js +6 -2
- package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.js +31 -1
- package/dist/router/virtual-router/engine.d.ts +21 -7
- package/dist/router/virtual-router/engine.js +198 -194
- package/dist/router/virtual-router/features.js +12 -4
- package/dist/router/virtual-router/message-utils.d.ts +8 -0
- package/dist/router/virtual-router/message-utils.js +170 -45
- package/dist/router/virtual-router/pre-command-file-resolver.js +40 -2
- package/dist/router/virtual-router/routing-instructions.d.ts +8 -0
- package/dist/router/virtual-router/routing-instructions.js +18 -2
- package/dist/router/virtual-router/routing-stop-message-actions.js +34 -10
- package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +2 -0
- package/dist/router/virtual-router/routing-stop-message-state-codec.js +50 -1
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
- package/dist/router/virtual-router/stop-message-state-sync.js +3 -0
- package/dist/router/virtual-router/token-counter.js +51 -10
- package/dist/router/virtual-router/tool-signals.js +4 -0
- package/dist/router/virtual-router/types.d.ts +15 -0
- package/dist/servertool/clock/session-scope.d.ts +3 -0
- package/dist/servertool/clock/session-scope.js +52 -0
- package/dist/servertool/clock/state.js +9 -0
- package/dist/servertool/clock/tasks.js +12 -1
- package/dist/servertool/clock/types.d.ts +3 -0
- package/dist/servertool/engine.js +177 -31
- package/dist/servertool/handlers/clock-auto.js +2 -8
- package/dist/servertool/handlers/clock.js +6 -9
- package/dist/servertool/handlers/recursive-detection-guard.js +53 -14
- package/dist/servertool/handlers/stop-message-auto/blocked-report.d.ts +16 -0
- package/dist/servertool/handlers/stop-message-auto/blocked-report.js +349 -0
- package/dist/servertool/handlers/stop-message-auto/iflow-followup.d.ts +23 -0
- package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +503 -0
- package/dist/servertool/handlers/stop-message-auto/routing-state.d.ts +38 -0
- package/dist/servertool/handlers/stop-message-auto/routing-state.js +149 -0
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +67 -0
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +387 -0
- package/dist/servertool/handlers/stop-message-auto.d.ts +1 -1
- package/dist/servertool/handlers/stop-message-auto.js +80 -556
- package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.d.ts +18 -0
- package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.js +398 -0
- package/dist/servertool/handlers/stop-message-stage-policy/decision.d.ts +9 -0
- package/dist/servertool/handlers/stop-message-stage-policy/decision.js +127 -0
- package/dist/servertool/handlers/stop-message-stage-policy/observation.d.ts +2 -0
- package/dist/servertool/handlers/stop-message-stage-policy/observation.js +179 -0
- package/dist/servertool/handlers/stop-message-stage-policy/templates.d.ts +4 -0
- package/dist/servertool/handlers/stop-message-stage-policy/templates.js +96 -0
- package/dist/servertool/handlers/stop-message-stage-policy/text-utils.d.ts +9 -0
- package/dist/servertool/handlers/stop-message-stage-policy/text-utils.js +89 -0
- package/dist/servertool/handlers/stop-message-stage-policy/types.d.ts +59 -0
- package/dist/servertool/handlers/stop-message-stage-policy/types.js +1 -0
- package/dist/servertool/handlers/stop-message-stage-policy.d.ts +3 -43
- package/dist/servertool/handlers/stop-message-stage-policy.js +2 -684
- package/dist/servertool/handlers/web-search.js +117 -0
- package/dist/servertool/server-side-tools.d.ts +0 -1
- package/dist/servertool/server-side-tools.js +4 -3
- package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +110 -37
- package/dist/telemetry/stats-center.d.ts +9 -0
- package/dist/telemetry/stats-center.js +29 -1
- package/dist/tools/apply-patch/structured/coercion.js +3 -11
- package/dist/tools/exec-command/validator.d.ts +1 -0
- package/dist/tools/exec-command/validator.js +132 -0
- package/dist/tools/tool-registry.d.ts +1 -0
- package/dist/tools/tool-registry.js +1 -1
- package/package.json +1 -1
|
@@ -107,6 +107,112 @@ function injectNestedApplyPatchPolicyNotice(messages, rewriteCount) {
|
|
|
107
107
|
content: buildNestedApplyPatchPolicyNotice(rewriteCount)
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
|
+
function shellSingleQuote(text) {
|
|
111
|
+
return `'${String(text || '').replace(/'/g, `'\\''`)}'`;
|
|
112
|
+
}
|
|
113
|
+
function buildExecCommandGuardScript(reason, message) {
|
|
114
|
+
const fallback = 'blocked by exec_command guard policy.';
|
|
115
|
+
const detail = reason === 'forbidden_git_reset_hard'
|
|
116
|
+
? 'blocked by exec_command guard: git reset --hard is forbidden. Use git reset --mixed REF or git restore --source REF -- FILE.'
|
|
117
|
+
: reason === 'forbidden_git_checkout_scope'
|
|
118
|
+
? 'blocked by exec_command guard: git checkout is allowed only for a single file. Use git checkout -- FILE or git checkout REF -- FILE.'
|
|
119
|
+
: message && message.trim()
|
|
120
|
+
? `blocked by exec_command guard: ${message.trim()}`
|
|
121
|
+
: fallback;
|
|
122
|
+
const compact = detail.replace(/\s+/g, ' ').trim() || fallback;
|
|
123
|
+
return `bash -lc "printf '%s\\n' ${shellSingleQuote(compact)} >&2; exit 2"`;
|
|
124
|
+
}
|
|
125
|
+
function buildBlockedExecCommandArgs(rawArgs, reason, message) {
|
|
126
|
+
let parsed = {};
|
|
127
|
+
try {
|
|
128
|
+
const repaired = repairArgumentsToString(rawArgs);
|
|
129
|
+
try {
|
|
130
|
+
parsed = JSON.parse(repaired);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
parsed = parseLenient(repaired);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
parsed = {};
|
|
138
|
+
}
|
|
139
|
+
const out = {};
|
|
140
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
141
|
+
const workdir = typeof parsed.workdir === 'string'
|
|
142
|
+
? parsed.workdir
|
|
143
|
+
: typeof parsed.cwd === 'string'
|
|
144
|
+
? parsed.cwd
|
|
145
|
+
: undefined;
|
|
146
|
+
if (workdir && workdir.trim().length > 0) {
|
|
147
|
+
out.workdir = workdir.trim();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
out.cmd = buildExecCommandGuardScript(reason, message);
|
|
151
|
+
try {
|
|
152
|
+
return JSON.stringify(out);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return JSON.stringify({
|
|
156
|
+
cmd: `bash -lc 'printf "%s\\n" "blocked by exec_command guard policy." >&2; exit 2'`
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const EXEC_COMMAND_NAME_AS_COMMAND_PATTERN = /^(?:rg|wc|cat|ls|find|grep|git|sed|head|tail|awk|bash|sh|zsh|node|npm|pnpm|yarn|bd|echo|cp|mv|rm|mkdir|python|python3|perl|ruby)\b/i;
|
|
161
|
+
function repairCommandNameAsExecToolCall(fn) {
|
|
162
|
+
try {
|
|
163
|
+
if (!fn)
|
|
164
|
+
return false;
|
|
165
|
+
const rawName = typeof fn.name === 'string' ? String(fn.name).trim() : '';
|
|
166
|
+
if (!rawName)
|
|
167
|
+
return false;
|
|
168
|
+
const lowered = rawName.toLowerCase();
|
|
169
|
+
if (lowered === 'exec_command' || lowered === 'shell_command' || lowered === 'shell' || lowered === 'bash') {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
// Malformed shape seen in the wild: command string is put into function.name.
|
|
173
|
+
if (!EXEC_COMMAND_NAME_AS_COMMAND_PATTERN.test(rawName)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
const repaired = repairArgumentsToString(fn.arguments);
|
|
177
|
+
let parsed;
|
|
178
|
+
try {
|
|
179
|
+
parsed = JSON.parse(repaired);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
parsed = parseLenient(repaired);
|
|
183
|
+
}
|
|
184
|
+
const argsObj = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
185
|
+
? { ...parsed }
|
|
186
|
+
: {};
|
|
187
|
+
const existingCmd = typeof argsObj.cmd === 'string' && String(argsObj.cmd).trim().length
|
|
188
|
+
? String(argsObj.cmd).trim()
|
|
189
|
+
: typeof argsObj.command === 'string' && String(argsObj.command).trim().length
|
|
190
|
+
? String(argsObj.command).trim()
|
|
191
|
+
: '';
|
|
192
|
+
const cmd = existingCmd || rawName;
|
|
193
|
+
argsObj.cmd = cmd;
|
|
194
|
+
argsObj.command = cmd;
|
|
195
|
+
if (typeof argsObj.cwd === 'string' && (!argsObj.workdir || typeof argsObj.workdir !== 'string')) {
|
|
196
|
+
argsObj.workdir = String(argsObj.cwd);
|
|
197
|
+
}
|
|
198
|
+
const validation = validateToolCall('exec_command', JSON.stringify(argsObj));
|
|
199
|
+
if (validation.ok && typeof validation.normalizedArgs === 'string') {
|
|
200
|
+
fn.arguments = validation.normalizedArgs;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
const fallback = { cmd, command: cmd };
|
|
204
|
+
if (typeof argsObj.workdir === 'string' && String(argsObj.workdir).trim().length > 0) {
|
|
205
|
+
fallback.workdir = String(argsObj.workdir).trim();
|
|
206
|
+
}
|
|
207
|
+
fn.arguments = JSON.stringify(fallback);
|
|
208
|
+
}
|
|
209
|
+
fn.name = 'exec_command';
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
110
216
|
function tryWriteSnapshot(options, stage, data) {
|
|
111
217
|
try {
|
|
112
218
|
// 仅在 verbose 级别保存快照(环境变量)
|
|
@@ -332,6 +438,7 @@ export function normalizeApplyPatchToolCallsOnResponse(chat) {
|
|
|
332
438
|
for (const tc of tcs) {
|
|
333
439
|
try {
|
|
334
440
|
const fn = tc && tc.function ? tc.function : undefined;
|
|
441
|
+
repairCommandNameAsExecToolCall(fn);
|
|
335
442
|
rewriteExecCommandApplyPatchCall(fn);
|
|
336
443
|
const name = typeof fn?.name === 'string' ? String(fn.name).trim().toLowerCase() : '';
|
|
337
444
|
if (name !== 'apply_patch')
|
|
@@ -409,6 +516,7 @@ function normalizeSpecialToolCallsOnRequest(request) {
|
|
|
409
516
|
for (const tc of tcs) {
|
|
410
517
|
try {
|
|
411
518
|
const fn = tc && tc.function ? tc.function : undefined;
|
|
519
|
+
repairCommandNameAsExecToolCall(fn);
|
|
412
520
|
if (rewriteExecCommandApplyPatchCall(fn)) {
|
|
413
521
|
rewrittenNestedApplyPatchCount += 1;
|
|
414
522
|
}
|
|
@@ -447,21 +555,30 @@ function normalizeSpecialToolCallsOnRequest(request) {
|
|
|
447
555
|
// exec_command 兼容:TOON / map / string 一律收敛为 { cmd, command, workdir, ... }
|
|
448
556
|
if (name === 'exec_command') {
|
|
449
557
|
const argsStr = repairArgumentsToString(rawArgs);
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
558
|
+
const validation = validateToolCall('exec_command', argsStr);
|
|
559
|
+
if (validation && validation.ok && typeof validation.normalizedArgs === 'string') {
|
|
560
|
+
fn.arguments = validation.normalizedArgs;
|
|
453
561
|
}
|
|
454
|
-
|
|
455
|
-
|
|
562
|
+
else if (validation && !validation.ok) {
|
|
563
|
+
fn.arguments = buildBlockedExecCommandArgs(rawArgs, validation.reason, validation.message);
|
|
456
564
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const next = normalized.ok ? normalized.normalized : parsed;
|
|
565
|
+
else {
|
|
566
|
+
let parsed;
|
|
460
567
|
try {
|
|
461
|
-
|
|
568
|
+
parsed = JSON.parse(argsStr);
|
|
462
569
|
}
|
|
463
570
|
catch {
|
|
464
|
-
|
|
571
|
+
parsed = parseLenient(argsStr);
|
|
572
|
+
}
|
|
573
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
574
|
+
const normalized = normalizeExecCommandArgs(parsed);
|
|
575
|
+
const next = normalized.ok ? normalized.normalized : parsed;
|
|
576
|
+
try {
|
|
577
|
+
fn.arguments = JSON.stringify(next ?? {});
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
fn.arguments = '{}';
|
|
581
|
+
}
|
|
465
582
|
}
|
|
466
583
|
}
|
|
467
584
|
}
|
|
@@ -568,6 +685,15 @@ function enhanceResponseToolArguments(chat) {
|
|
|
568
685
|
}
|
|
569
686
|
}
|
|
570
687
|
}
|
|
688
|
+
else if (name === 'exec_command') {
|
|
689
|
+
const validation = validateToolCall('exec_command', repaired);
|
|
690
|
+
if (validation.ok && typeof validation.normalizedArgs === 'string') {
|
|
691
|
+
finalStr = validation.normalizedArgs;
|
|
692
|
+
}
|
|
693
|
+
else if (!validation.ok) {
|
|
694
|
+
finalStr = buildBlockedExecCommandArgs(repaired, validation.reason, validation.message);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
571
697
|
if (fn)
|
|
572
698
|
fn.arguments = finalStr;
|
|
573
699
|
}
|
|
@@ -2,10 +2,10 @@ import os from 'node:os';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import * as fsp from 'node:fs/promises';
|
|
4
4
|
function mapEndpointToFolder(ep) {
|
|
5
|
-
const e = String(ep || '').toLowerCase();
|
|
6
|
-
if (e.includes('/v1/responses'))
|
|
5
|
+
const e = String(ep || '').trim().toLowerCase();
|
|
6
|
+
if (e.includes('/v1/responses') || e.includes('/responses.submit'))
|
|
7
7
|
return 'openai-responses';
|
|
8
|
-
if (e.includes('/v1/messages')
|
|
8
|
+
if (e.includes('/v1/messages'))
|
|
9
9
|
return 'anthropic-messages';
|
|
10
10
|
return 'openai-chat';
|
|
11
11
|
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { scanDeepSeekAccountTokenFiles, scanOAuthTokenFiles } from '../token-file-scanner.js';
|
|
2
|
+
import { VirtualRouterError, VirtualRouterErrorCode } from '../types.js';
|
|
3
|
+
import { asRecord, normalizeAlias, pushUnique, readOptionalString } from './utils.js';
|
|
4
|
+
const MULTI_TOKEN_OAUTH_PROVIDERS = new Set(['iflow', 'qwen', 'gemini-cli', 'antigravity']);
|
|
5
|
+
export function extractProviderAuthEntries(providerId, raw) {
|
|
6
|
+
const provider = asRecord(raw);
|
|
7
|
+
const auth = asRecord(provider.auth);
|
|
8
|
+
const entries = [];
|
|
9
|
+
const aliasSet = new Set();
|
|
10
|
+
const baseTypeInfo = interpretAuthType(auth.type);
|
|
11
|
+
const baseType = baseTypeInfo.type;
|
|
12
|
+
const baseTypeSource = typeof auth.type === 'string' ? auth.type : undefined;
|
|
13
|
+
const defaults = collectAuthDefaults(auth);
|
|
14
|
+
const buildAuthCandidate = (typeHint, extras = {}) => {
|
|
15
|
+
const source = typeof typeHint === 'string' && typeHint.trim() ? typeHint.trim() : baseTypeSource;
|
|
16
|
+
const typeInfo = interpretAuthType(source ?? baseType);
|
|
17
|
+
const rawType = typeInfo.raw ?? source ?? baseTypeSource;
|
|
18
|
+
return {
|
|
19
|
+
...extras,
|
|
20
|
+
type: typeInfo.type,
|
|
21
|
+
rawType,
|
|
22
|
+
oauthProviderId: extras.oauthProviderId ?? typeInfo.oauthProviderId
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const pushEntry = (candidateAlias, authConfig) => {
|
|
26
|
+
const alias = normalizeAlias(candidateAlias, aliasSet);
|
|
27
|
+
const typeSource = authConfig.rawType ?? authConfig.type ?? baseTypeSource ?? baseType;
|
|
28
|
+
const typeInfo = interpretAuthType(typeSource);
|
|
29
|
+
const entryType = typeInfo.type;
|
|
30
|
+
const oauthProviderId = authConfig.oauthProviderId ?? typeInfo.oauthProviderId ?? baseTypeInfo.oauthProviderId;
|
|
31
|
+
if (entryType === 'oauth' && !oauthProviderId) {
|
|
32
|
+
throw new VirtualRouterError(`Provider ${providerId} OAuth auth entries must declare provider-specific type (e.g. "qwen-oauth")`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
33
|
+
}
|
|
34
|
+
const normalized = {
|
|
35
|
+
type: entryType,
|
|
36
|
+
rawType: typeof typeSource === 'string' ? typeSource : undefined,
|
|
37
|
+
oauthProviderId,
|
|
38
|
+
value: readOptionalString(authConfig.value ?? authConfig.apiKey),
|
|
39
|
+
secretRef: readOptionalString(authConfig.secretRef)
|
|
40
|
+
};
|
|
41
|
+
normalized.tokenFile = readOptionalString(authConfig.tokenFile);
|
|
42
|
+
normalized.tokenUrl = readOptionalString(authConfig.tokenUrl ?? authConfig.token_url);
|
|
43
|
+
normalized.deviceCodeUrl = readOptionalString(authConfig.deviceCodeUrl ?? authConfig.device_code_url);
|
|
44
|
+
normalized.clientId = readOptionalString(authConfig.clientId ?? authConfig.client_id);
|
|
45
|
+
normalized.clientSecret = readOptionalString(authConfig.clientSecret ?? authConfig.client_secret);
|
|
46
|
+
normalized.authorizationUrl = readOptionalString(authConfig.authorizationUrl ??
|
|
47
|
+
authConfig.authorization_url ??
|
|
48
|
+
authConfig.authUrl);
|
|
49
|
+
normalized.userInfoUrl = readOptionalString(authConfig.userInfoUrl ?? authConfig.user_info_url);
|
|
50
|
+
normalized.refreshUrl = readOptionalString(authConfig.refreshUrl ?? authConfig.refresh_url);
|
|
51
|
+
normalized.scopes = normalizeScopeList(authConfig.scopes ?? authConfig.scope);
|
|
52
|
+
normalized.secretRef ??= defaults.secretRef;
|
|
53
|
+
normalized.tokenFile ??= defaults.tokenFile;
|
|
54
|
+
normalized.tokenUrl ??= defaults.tokenUrl;
|
|
55
|
+
normalized.deviceCodeUrl ??= defaults.deviceCodeUrl;
|
|
56
|
+
normalized.clientId ??= defaults.clientId;
|
|
57
|
+
normalized.clientSecret ??= defaults.clientSecret;
|
|
58
|
+
normalized.authorizationUrl ??= defaults.authorizationUrl;
|
|
59
|
+
normalized.userInfoUrl ??= defaults.userInfoUrl;
|
|
60
|
+
normalized.refreshUrl ??= defaults.refreshUrl;
|
|
61
|
+
normalized.scopes = mergeScopes(normalized.scopes, defaults.scopes);
|
|
62
|
+
if (entryType === 'apiKey' && !normalized.secretRef) {
|
|
63
|
+
normalized.secretRef = `${providerId}.${alias}`;
|
|
64
|
+
}
|
|
65
|
+
entries.push({ keyAlias: alias, auth: normalized });
|
|
66
|
+
aliasSet.add(alias);
|
|
67
|
+
};
|
|
68
|
+
const fromRecord = (record) => {
|
|
69
|
+
const data = asRecord(record);
|
|
70
|
+
const alias = readOptionalString(data.alias);
|
|
71
|
+
const typeValue = data.type ?? baseTypeSource ?? baseType;
|
|
72
|
+
pushEntry(alias, buildAuthCandidate(typeValue, {
|
|
73
|
+
value: data.value ?? data.apiKey,
|
|
74
|
+
secretRef: data.secretRef,
|
|
75
|
+
tokenFile: data.tokenFile,
|
|
76
|
+
tokenUrl: data.tokenUrl ?? data.token_url,
|
|
77
|
+
deviceCodeUrl: data.deviceCodeUrl ?? data.device_code_url,
|
|
78
|
+
clientId: data.clientId ?? data.client_id,
|
|
79
|
+
clientSecret: data.clientSecret ?? data.client_secret,
|
|
80
|
+
authorizationUrl: data.authorizationUrl ??
|
|
81
|
+
data.authorization_url ??
|
|
82
|
+
data.authUrl,
|
|
83
|
+
userInfoUrl: data.userInfoUrl ?? data.user_info_url,
|
|
84
|
+
refreshUrl: data.refreshUrl ?? data.refresh_url,
|
|
85
|
+
scopes: data.scopes ?? data.scope
|
|
86
|
+
}));
|
|
87
|
+
};
|
|
88
|
+
if (Array.isArray(auth.entries)) {
|
|
89
|
+
for (const entry of auth.entries) {
|
|
90
|
+
fromRecord(entry);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(auth.keys)) {
|
|
94
|
+
for (const entry of auth.keys) {
|
|
95
|
+
fromRecord(entry);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const keysObject = asRecord(auth.keys);
|
|
100
|
+
for (const [alias, entry] of Object.entries(keysObject)) {
|
|
101
|
+
if (entry && typeof entry === 'object') {
|
|
102
|
+
fromRecord({ alias, ...entry });
|
|
103
|
+
}
|
|
104
|
+
else if (typeof entry === 'string') {
|
|
105
|
+
pushEntry(alias, buildAuthCandidate(baseTypeSource, { value: entry }));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const apiKeyField = provider.apiKey ?? provider.apiKeys ?? auth.apiKey;
|
|
110
|
+
if (Array.isArray(apiKeyField)) {
|
|
111
|
+
for (const item of apiKeyField) {
|
|
112
|
+
if (typeof item === 'string' && item.trim()) {
|
|
113
|
+
pushEntry(undefined, buildAuthCandidate(baseTypeSource, { value: item.trim() }));
|
|
114
|
+
}
|
|
115
|
+
else if (item && typeof item === 'object') {
|
|
116
|
+
fromRecord(item);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (typeof apiKeyField === 'string' && apiKeyField.trim()) {
|
|
121
|
+
pushEntry(undefined, buildAuthCandidate(baseTypeSource, { value: apiKeyField.trim() }));
|
|
122
|
+
}
|
|
123
|
+
const hasExplicitEntries = entries.length > 0;
|
|
124
|
+
if (baseType === 'oauth' && !hasExplicitEntries) {
|
|
125
|
+
const scanCandidates = new Set();
|
|
126
|
+
const pushCandidate = (value) => {
|
|
127
|
+
if (typeof value === 'string' && value.trim()) {
|
|
128
|
+
scanCandidates.add(value.trim().toLowerCase());
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
pushCandidate(auth.oauthProviderId);
|
|
132
|
+
pushCandidate(baseTypeInfo.oauthProviderId);
|
|
133
|
+
pushCandidate(providerId);
|
|
134
|
+
for (const candidate of scanCandidates) {
|
|
135
|
+
if (!MULTI_TOKEN_OAUTH_PROVIDERS.has(candidate)) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const tokenFiles = scanOAuthTokenFiles(candidate);
|
|
139
|
+
if (!tokenFiles.length) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const baseTypeAlias = baseTypeInfo.oauthProviderId?.toLowerCase();
|
|
143
|
+
for (const match of tokenFiles) {
|
|
144
|
+
const alias = match.alias && match.alias !== 'default'
|
|
145
|
+
? `${match.sequence}-${match.alias}`
|
|
146
|
+
: String(match.sequence);
|
|
147
|
+
const typeHint = baseTypeSource && baseTypeAlias === candidate
|
|
148
|
+
? baseTypeSource
|
|
149
|
+
: `${candidate}-oauth`;
|
|
150
|
+
const authConfig = {
|
|
151
|
+
...defaults,
|
|
152
|
+
type: typeHint,
|
|
153
|
+
tokenFile: match.filePath,
|
|
154
|
+
oauthProviderId: candidate
|
|
155
|
+
};
|
|
156
|
+
pushEntry(alias, authConfig);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const baseRawType = String(baseTypeInfo.raw ?? baseTypeSource ?? '').trim().toLowerCase();
|
|
161
|
+
if (baseType === 'apiKey' && baseRawType === 'deepseek-account' && !hasExplicitEntries) {
|
|
162
|
+
const tokenFiles = scanDeepSeekAccountTokenFiles();
|
|
163
|
+
for (const match of tokenFiles) {
|
|
164
|
+
const authConfig = {
|
|
165
|
+
...defaults,
|
|
166
|
+
type: baseTypeSource ?? 'deepseek-account',
|
|
167
|
+
tokenFile: match.filePath
|
|
168
|
+
};
|
|
169
|
+
pushEntry(match.alias, authConfig);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!entries.length) {
|
|
173
|
+
const fallbackExtras = {
|
|
174
|
+
value: readOptionalString(auth.value),
|
|
175
|
+
secretRef: readOptionalString(auth.secretRef),
|
|
176
|
+
tokenFile: readOptionalString(auth.tokenFile ?? auth.file),
|
|
177
|
+
tokenUrl: readOptionalString(auth.tokenUrl ?? auth.token_url),
|
|
178
|
+
deviceCodeUrl: readOptionalString(auth.deviceCodeUrl ?? auth.device_code_url),
|
|
179
|
+
clientId: readOptionalString(auth.clientId ?? auth.client_id),
|
|
180
|
+
clientSecret: readOptionalString(auth.clientSecret ?? auth.client_secret),
|
|
181
|
+
authorizationUrl: readOptionalString(auth.authorizationUrl ?? auth.authorization_url ?? auth.authUrl),
|
|
182
|
+
userInfoUrl: readOptionalString(auth.userInfoUrl ?? auth.user_info_url),
|
|
183
|
+
refreshUrl: readOptionalString(auth.refreshUrl ?? auth.refresh_url),
|
|
184
|
+
scopes: normalizeScopeList(auth.scopes ?? auth.scope),
|
|
185
|
+
cookieFile: readOptionalString(auth.cookieFile)
|
|
186
|
+
};
|
|
187
|
+
const fallbackHasData = Boolean(fallbackExtras.value ||
|
|
188
|
+
fallbackExtras.secretRef ||
|
|
189
|
+
fallbackExtras.tokenFile ||
|
|
190
|
+
fallbackExtras.tokenUrl ||
|
|
191
|
+
fallbackExtras.deviceCodeUrl ||
|
|
192
|
+
fallbackExtras.clientId ||
|
|
193
|
+
fallbackExtras.clientSecret ||
|
|
194
|
+
fallbackExtras.cookieFile ||
|
|
195
|
+
(fallbackExtras.scopes &&
|
|
196
|
+
Array.isArray(fallbackExtras.scopes) &&
|
|
197
|
+
fallbackExtras.scopes.length));
|
|
198
|
+
if (fallbackHasData) {
|
|
199
|
+
pushEntry(undefined, buildAuthCandidate(baseTypeSource, fallbackExtras));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!entries.length && baseType === 'apiKey') {
|
|
203
|
+
const authDeclared = Object.prototype.hasOwnProperty.call(provider, 'auth') ||
|
|
204
|
+
Object.prototype.hasOwnProperty.call(provider, 'apiKey') ||
|
|
205
|
+
Object.prototype.hasOwnProperty.call(provider, 'apiKeys') ||
|
|
206
|
+
Object.prototype.hasOwnProperty.call(provider, 'authType');
|
|
207
|
+
if (authDeclared) {
|
|
208
|
+
pushEntry(undefined, buildAuthCandidate(baseTypeSource, { value: '' }));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!entries.length) {
|
|
212
|
+
throw new VirtualRouterError(`Provider ${providerId} is missing auth configuration`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
213
|
+
}
|
|
214
|
+
return entries;
|
|
215
|
+
}
|
|
216
|
+
function collectAuthDefaults(auth) {
|
|
217
|
+
return {
|
|
218
|
+
secretRef: readOptionalString(auth.secretRef) ?? readOptionalString(auth.file),
|
|
219
|
+
tokenFile: readOptionalString(auth.tokenFile) ?? readOptionalString(auth.file),
|
|
220
|
+
tokenUrl: readOptionalString(auth.tokenUrl ?? auth.token_url),
|
|
221
|
+
deviceCodeUrl: readOptionalString(auth.deviceCodeUrl ?? auth.device_code_url),
|
|
222
|
+
clientId: readOptionalString(auth.clientId ?? auth.client_id),
|
|
223
|
+
clientSecret: readOptionalString(auth.clientSecret ?? auth.client_secret),
|
|
224
|
+
authorizationUrl: readOptionalString(auth.authorizationUrl ?? auth.authorization_url ?? auth.authUrl),
|
|
225
|
+
userInfoUrl: readOptionalString(auth.userInfoUrl ?? auth.user_info_url),
|
|
226
|
+
refreshUrl: readOptionalString(auth.refreshUrl ?? auth.refresh_url),
|
|
227
|
+
scopes: normalizeScopeList(auth.scopes ?? auth.scope)
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function normalizeScopeList(value) {
|
|
231
|
+
if (Array.isArray(value)) {
|
|
232
|
+
const normalized = [];
|
|
233
|
+
for (const item of value) {
|
|
234
|
+
const str = readOptionalString(item);
|
|
235
|
+
if (str) {
|
|
236
|
+
pushUnique(normalized, str);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return normalized.length ? normalized : undefined;
|
|
240
|
+
}
|
|
241
|
+
if (typeof value === 'string' && value.trim()) {
|
|
242
|
+
const normalized = value
|
|
243
|
+
.split(/[,\s]+/)
|
|
244
|
+
.map((item) => item.trim())
|
|
245
|
+
.filter(Boolean);
|
|
246
|
+
return normalized.length ? normalized : undefined;
|
|
247
|
+
}
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
function mergeScopes(primary, fallback) {
|
|
251
|
+
if ((!primary || !primary.length) && (!fallback || !fallback.length)) {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
const merged = new Set();
|
|
255
|
+
for (const scope of primary ?? []) {
|
|
256
|
+
if (scope.trim())
|
|
257
|
+
merged.add(scope.trim());
|
|
258
|
+
}
|
|
259
|
+
for (const scope of fallback ?? []) {
|
|
260
|
+
if (scope.trim())
|
|
261
|
+
merged.add(scope.trim());
|
|
262
|
+
}
|
|
263
|
+
return merged.size ? Array.from(merged) : undefined;
|
|
264
|
+
}
|
|
265
|
+
function interpretAuthType(value) {
|
|
266
|
+
if (typeof value !== 'string') {
|
|
267
|
+
return { type: 'apiKey' };
|
|
268
|
+
}
|
|
269
|
+
const trimmed = value.trim();
|
|
270
|
+
if (!trimmed) {
|
|
271
|
+
return { type: 'apiKey' };
|
|
272
|
+
}
|
|
273
|
+
const lower = trimmed.toLowerCase();
|
|
274
|
+
if (lower === 'apikey' || lower === 'api-key') {
|
|
275
|
+
return { type: 'apiKey', raw: trimmed };
|
|
276
|
+
}
|
|
277
|
+
if (lower === 'oauth') {
|
|
278
|
+
return { type: 'oauth', raw: trimmed };
|
|
279
|
+
}
|
|
280
|
+
const match = lower.match(/^([a-z0-9._-]+)-oauth$/);
|
|
281
|
+
if (match) {
|
|
282
|
+
return { type: 'oauth', oauthProviderId: match[1], raw: trimmed };
|
|
283
|
+
}
|
|
284
|
+
if (lower.includes('oauth')) {
|
|
285
|
+
return { type: 'oauth', raw: trimmed };
|
|
286
|
+
}
|
|
287
|
+
return { type: 'apiKey', raw: trimmed };
|
|
288
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code specific helpers for Virtual Router bootstrap.
|
|
3
|
+
*/
|
|
4
|
+
export declare const CLAUDE_CODE_DEFAULT_USER_AGENT = "claude-cli/2.0.76 (external, cli)";
|
|
5
|
+
export declare const CLAUDE_CODE_DEFAULT_X_APP = "claude-cli";
|
|
6
|
+
export declare const CLAUDE_CODE_DEFAULT_ANTHROPIC_BETA = "claude-code";
|
|
7
|
+
/**
|
|
8
|
+
* Parse the Claude Code app version from a user agent string.
|
|
9
|
+
* Returns the version string (e.g., "2.0.76") or null if not found.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseClaudeCodeAppVersionFromUserAgent(userAgent: string): string | null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code specific helpers for Virtual Router bootstrap.
|
|
3
|
+
*/
|
|
4
|
+
export const CLAUDE_CODE_DEFAULT_USER_AGENT = 'claude-cli/2.0.76 (external, cli)';
|
|
5
|
+
export const CLAUDE_CODE_DEFAULT_X_APP = 'claude-cli';
|
|
6
|
+
export const CLAUDE_CODE_DEFAULT_ANTHROPIC_BETA = 'claude-code';
|
|
7
|
+
/**
|
|
8
|
+
* Parse the Claude Code app version from a user agent string.
|
|
9
|
+
* Returns the version string (e.g., "2.0.76") or null if not found.
|
|
10
|
+
*/
|
|
11
|
+
export function parseClaudeCodeAppVersionFromUserAgent(userAgent) {
|
|
12
|
+
const ua = typeof userAgent === 'string' ? userAgent.trim() : '';
|
|
13
|
+
if (!ua)
|
|
14
|
+
return null;
|
|
15
|
+
// Example: 'claude-cli/2.0.76 (external, cli)'
|
|
16
|
+
const match = /claude-cli\/([\d.]+)/.exec(ua);
|
|
17
|
+
return match?.[1] ?? null;
|
|
18
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { LoadBalancingPolicy, ProviderHealthConfig, VirtualRouterClassifierConfig, VirtualRouterContextRoutingConfig } from '../types.js';
|
|
2
|
+
export declare const DEFAULT_CLASSIFIER: Required<VirtualRouterClassifierConfig>;
|
|
3
|
+
export declare const DEFAULT_LOAD_BALANCING: LoadBalancingPolicy;
|
|
4
|
+
export declare const DEFAULT_HEALTH: ProviderHealthConfig;
|
|
5
|
+
export declare const DEFAULT_CONTEXT_ROUTING: VirtualRouterContextRoutingConfig;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const DEFAULT_CLASSIFIER = {
|
|
2
|
+
longContextThresholdTokens: 180000,
|
|
3
|
+
thinkingKeywords: ['think step', 'analysis', 'reasoning', '仔细分析', '深度思考'],
|
|
4
|
+
codingKeywords: ['apply_patch', 'write_file', 'create_file', 'shell', '修改文件', '写入文件'],
|
|
5
|
+
backgroundKeywords: ['background', 'context dump', '上下文'],
|
|
6
|
+
visionKeywords: ['vision', 'image', 'picture', 'photo']
|
|
7
|
+
};
|
|
8
|
+
export const DEFAULT_LOAD_BALANCING = { strategy: 'round-robin' };
|
|
9
|
+
export const DEFAULT_HEALTH = { failureThreshold: 3, cooldownMs: 30_000, fatalCooldownMs: 300_000 };
|
|
10
|
+
export const DEFAULT_CONTEXT_ROUTING = {
|
|
11
|
+
warnRatio: 0.9,
|
|
12
|
+
hardLimit: false
|
|
13
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { VirtualRouterClockConfig, VirtualRouterConfig, VirtualRouterContextRoutingConfig } from '../types.js';
|
|
2
|
+
export declare function normalizeExecCommandGuard(input: unknown): VirtualRouterConfig['execCommandGuard'] | undefined;
|
|
3
|
+
export declare function normalizeClock(raw: unknown): VirtualRouterClockConfig | undefined;
|
|
4
|
+
export declare function normalizeContextRouting(input: unknown): VirtualRouterContextRoutingConfig;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { DEFAULT_CONTEXT_ROUTING } from './config-defaults.js';
|
|
2
|
+
export function normalizeExecCommandGuard(input) {
|
|
3
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const record = input;
|
|
7
|
+
const enabledRaw = record.enabled;
|
|
8
|
+
const enabled = enabledRaw === true ||
|
|
9
|
+
(typeof enabledRaw === 'string' && enabledRaw.trim().toLowerCase() === 'true') ||
|
|
10
|
+
(typeof enabledRaw === 'number' && enabledRaw === 1);
|
|
11
|
+
if (!enabled) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const policyFileRaw = record.policyFile ?? record.policy_file;
|
|
15
|
+
const policyFile = typeof policyFileRaw === 'string' && policyFileRaw.trim().length ? policyFileRaw.trim() : undefined;
|
|
16
|
+
return {
|
|
17
|
+
enabled: true,
|
|
18
|
+
...(policyFile ? { policyFile } : {})
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function normalizeClock(raw) {
|
|
22
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const record = raw;
|
|
26
|
+
const enabled = record.enabled === true ||
|
|
27
|
+
(typeof record.enabled === 'string' && record.enabled.trim().toLowerCase() === 'true') ||
|
|
28
|
+
(typeof record.enabled === 'number' && record.enabled === 1);
|
|
29
|
+
if (!enabled) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const out = { enabled: true };
|
|
33
|
+
if (typeof record.retentionMs === 'number' && Number.isFinite(record.retentionMs) && record.retentionMs >= 0) {
|
|
34
|
+
out.retentionMs = Math.floor(record.retentionMs);
|
|
35
|
+
}
|
|
36
|
+
if (typeof record.dueWindowMs === 'number' && Number.isFinite(record.dueWindowMs) && record.dueWindowMs >= 0) {
|
|
37
|
+
out.dueWindowMs = Math.floor(record.dueWindowMs);
|
|
38
|
+
}
|
|
39
|
+
if (typeof record.tickMs === 'number' && Number.isFinite(record.tickMs) && record.tickMs >= 0) {
|
|
40
|
+
out.tickMs = Math.floor(record.tickMs);
|
|
41
|
+
}
|
|
42
|
+
if (record.holdNonStreaming === true ||
|
|
43
|
+
(typeof record.holdNonStreaming === 'string' && record.holdNonStreaming.trim().toLowerCase() === 'true') ||
|
|
44
|
+
(typeof record.holdNonStreaming === 'number' && record.holdNonStreaming === 1)) {
|
|
45
|
+
out.holdNonStreaming = true;
|
|
46
|
+
}
|
|
47
|
+
if (typeof record.holdMaxMs === 'number' && Number.isFinite(record.holdMaxMs) && record.holdMaxMs >= 0) {
|
|
48
|
+
out.holdMaxMs = Math.floor(record.holdMaxMs);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
export function normalizeContextRouting(input) {
|
|
53
|
+
if (!input || typeof input !== 'object') {
|
|
54
|
+
return { ...DEFAULT_CONTEXT_ROUTING };
|
|
55
|
+
}
|
|
56
|
+
const record = input;
|
|
57
|
+
const warnCandidate = coerceRatio(record.warnRatio) ?? coerceRatio(record.warn_ratio);
|
|
58
|
+
const hardLimitCandidate = coerceBoolean(record.hardLimit) ?? coerceBoolean(record.hard_limit);
|
|
59
|
+
const warnRatio = clampWarnRatio(warnCandidate ?? DEFAULT_CONTEXT_ROUTING.warnRatio);
|
|
60
|
+
const hardLimit = typeof hardLimitCandidate === 'boolean' ? hardLimitCandidate : DEFAULT_CONTEXT_ROUTING.hardLimit;
|
|
61
|
+
return {
|
|
62
|
+
warnRatio,
|
|
63
|
+
hardLimit
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function coerceRatio(value) {
|
|
67
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
if (typeof value === 'string') {
|
|
71
|
+
const trimmed = value.trim();
|
|
72
|
+
if (!trimmed) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const parsed = Number(trimmed);
|
|
76
|
+
if (Number.isFinite(parsed)) {
|
|
77
|
+
return parsed;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
function clampWarnRatio(value) {
|
|
83
|
+
if (!Number.isFinite(value)) {
|
|
84
|
+
return DEFAULT_CONTEXT_ROUTING.warnRatio;
|
|
85
|
+
}
|
|
86
|
+
const clamped = Math.max(0.1, Math.min(value, 0.99));
|
|
87
|
+
return Number.isFinite(clamped) ? clamped : DEFAULT_CONTEXT_ROUTING.warnRatio;
|
|
88
|
+
}
|
|
89
|
+
function coerceBoolean(value) {
|
|
90
|
+
if (typeof value === 'boolean') {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
if (typeof value === 'string') {
|
|
94
|
+
const normalized = value.trim().toLowerCase();
|
|
95
|
+
if (!normalized) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
if (['true', '1', 'yes', 'y'].includes(normalized)) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
if (['false', '0', 'no', 'n'].includes(normalized)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ProviderHealthConfig, type ProviderProfile, type ProviderRuntimeProfile } from '../types.js';
|
|
2
|
+
export declare function buildProviderProfiles(targetKeys: Set<string>, runtimeEntries: Record<string, ProviderRuntimeProfile>): {
|
|
3
|
+
profiles: Record<string, ProviderProfile>;
|
|
4
|
+
targetRuntime: Record<string, ProviderRuntimeProfile>;
|
|
5
|
+
};
|
|
6
|
+
export declare function resolveContextTokens(runtime: ProviderRuntimeProfile, modelId: string): number;
|
|
7
|
+
export declare function normalizeHealth(input: unknown): ProviderHealthConfig | undefined;
|