@jsonstudio/llms 0.6.1399 → 0.6.1402
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/codecs/gemini-openai-codec.d.ts +1 -3
- package/dist/conversion/codecs/gemini-openai-codec.js +4 -10
- package/dist/conversion/compat/actions/gemini-cli-request.d.ts +2 -0
- package/dist/conversion/compat/actions/gemini-cli-request.js +490 -0
- package/dist/conversion/compat/profiles/chat-gemini-cli.json +27 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +76 -348
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +95 -3
- package/dist/conversion/hub/pipeline/hub-pipeline.js +1365 -19
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +22 -0
- package/dist/conversion/hub/policy/policy-engine.js +50 -3
- package/dist/conversion/hub/process/chat-process.js +5 -146
- package/dist/conversion/hub/response/provider-response.js +11 -10
- package/dist/conversion/hub/response/response-mappers.d.ts +1 -3
- package/dist/conversion/hub/response/response-mappers.js +2 -20
- package/dist/conversion/hub/tool-surface/tool-surface-engine.js +2 -1
- package/dist/conversion/responses/responses-openai-bridge.js +4 -3
- package/dist/conversion/shared/gemini-tool-utils.d.ts +1 -6
- package/dist/conversion/shared/gemini-tool-utils.js +164 -542
- package/dist/conversion/shared/mcp-injection.js +29 -0
- package/dist/conversion/shared/openai-message-normalize.js +3 -17
- package/dist/filters/special/request-tool-list-filter.js +21 -13
- package/dist/filters/special/tool-filter-hooks.js +60 -3
- package/dist/router/virtual-router/bootstrap.js +8 -6
- package/dist/router/virtual-router/engine-health.d.ts +1 -1
- package/dist/router/virtual-router/engine-health.js +110 -11
- package/dist/router/virtual-router/engine-selection/alias-selection.d.ts +0 -15
- package/dist/router/virtual-router/engine-selection/alias-selection.js +4 -85
- package/dist/router/virtual-router/engine-selection/route-utils.js +6 -12
- package/dist/router/virtual-router/engine-selection/tier-selection-select.js +17 -40
- package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -5
- package/dist/router/virtual-router/engine.js +6 -21
- package/dist/router/virtual-router/types.d.ts +1 -14
- package/dist/servertool/clock/config.d.ts +1 -1
- package/dist/servertool/clock/config.js +5 -9
- package/dist/servertool/engine.js +11 -88
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +1 -2
- package/dist/sse/sse-to-json/builders/response-builder.js +0 -16
- package/package.json +1 -1
- package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.d.ts +0 -10
- package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.js +0 -142
- package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.d.ts +0 -6
- package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.js +0 -79
- package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.d.ts +0 -3
- package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.js +0 -46
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.d.ts +0 -8
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.js +0 -366
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.d.ts +0 -9
- package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.js +0 -390
- package/dist/conversion/hub/pipeline/hub-pipeline/node-results.d.ts +0 -3
- package/dist/conversion/hub/pipeline/hub-pipeline/node-results.js +0 -14
- package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.d.ts +0 -2
- package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.js +0 -144
- package/dist/conversion/hub/pipeline/hub-pipeline/policy.d.ts +0 -4
- package/dist/conversion/hub/pipeline/hub-pipeline/policy.js +0 -32
- package/dist/conversion/hub/pipeline/hub-pipeline/protocol.d.ts +0 -8
- package/dist/conversion/hub/pipeline/hub-pipeline/protocol.js +0 -63
- package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.d.ts +0 -2
- package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.js +0 -43
- package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.d.ts +0 -1
- package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.js +0 -29
- package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.d.ts +0 -2
- package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.js +0 -16
- package/dist/conversion/hub/pipeline/hub-pipeline/types.d.ts +0 -116
- package/dist/conversion/hub/pipeline/hub-pipeline/types.js +0 -1
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.d.ts +0 -10
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.js +0 -172
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.d.ts +0 -10
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.js +0 -71
- package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.d.ts +0 -14
- package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.js +0 -289
|
@@ -123,7 +123,7 @@ export function trySelectFromTier(routeName, tier, stickyKey, estimatedTokens, f
|
|
|
123
123
|
return { providerKey: null, poolTargets: [], tierId: tier.id, failureHint: `${routeName}:${tier.id}:empty` };
|
|
124
124
|
}
|
|
125
125
|
const contextResult = deps.contextAdvisor.classify(targets, estimatedTokens, (key) => deps.providerRegistry.get(key));
|
|
126
|
-
const prioritizedPools = buildContextCandidatePools(contextResult
|
|
126
|
+
const prioritizedPools = buildContextCandidatePools(contextResult);
|
|
127
127
|
const quotaView = deps.quotaView;
|
|
128
128
|
const now = quotaView ? Date.now() : 0;
|
|
129
129
|
const healthWeightedCfg = resolveHealthWeightedConfig(deps.loadBalancer.getPolicy().healthWeighted);
|
|
@@ -200,7 +200,7 @@ function recordAliasQueueFailuresFromExcludedKeys(excludedKeys, orderedTargets,
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
|
-
function buildContextCandidatePools(result
|
|
203
|
+
function buildContextCandidatePools(result) {
|
|
204
204
|
const ordered = [];
|
|
205
205
|
if (result.safe.length) {
|
|
206
206
|
ordered.push(result.safe);
|
|
@@ -208,9 +208,6 @@ function buildContextCandidatePools(result, routeName) {
|
|
|
208
208
|
if (result.risky.length) {
|
|
209
209
|
ordered.push(result.risky);
|
|
210
210
|
}
|
|
211
|
-
if (routeName === 'longcontext' && result.overflow.length) {
|
|
212
|
-
ordered.push(result.overflow);
|
|
213
|
-
}
|
|
214
211
|
return ordered;
|
|
215
212
|
}
|
|
216
213
|
function describeAttempt(routeName, poolId, result) {
|
|
@@ -10,9 +10,8 @@ import { parseRoutingInstructions, applyRoutingInstructions, cleanMessagesFromRo
|
|
|
10
10
|
import { loadRoutingInstructionStateSync, saveRoutingInstructionStateAsync, saveRoutingInstructionStateSync } from './sticky-session-store.js';
|
|
11
11
|
import { buildHitReason, formatVirtualRouterHit } from './engine-logging.js';
|
|
12
12
|
import { selectDirectProviderModel, selectFromStickyPool, selectProviderImpl } from './engine-selection.js';
|
|
13
|
-
import { applyQuotaDepletedImpl, applyQuotaRecoveryImpl, handleProviderFailureImpl, mapProviderErrorImpl } from './engine-health.js';
|
|
13
|
+
import { applyQuotaDepletedImpl, applyQuotaRecoveryImpl, applySeriesCooldownImpl, handleProviderFailureImpl, mapProviderErrorImpl } from './engine-health.js';
|
|
14
14
|
import { mergeStopMessageFromPersisted } from './stop-message-state-sync.js';
|
|
15
|
-
const ANTIGRAVITY_COOLDOWN_ALIAS_THRESHOLD_MS = 30_000;
|
|
16
15
|
export class VirtualRouterEngine {
|
|
17
16
|
routing = {};
|
|
18
17
|
providerRegistry = new ProviderRegistry();
|
|
@@ -624,18 +623,10 @@ export class VirtualRouterEngine {
|
|
|
624
623
|
// ignore persistence errors
|
|
625
624
|
}
|
|
626
625
|
}
|
|
627
|
-
//
|
|
628
|
-
//
|
|
629
|
-
//
|
|
626
|
+
// 当 Host 注入 quotaView 时,VirtualRouter 的入池/优先级决策应以 quota 为准;
|
|
627
|
+
// 此时不再在 engine-health 内部进行 429/backoff/series cooldown 等健康决策,
|
|
628
|
+
// 以避免与 daemon/quota-center 的长期熔断策略重复维护并导致日志噪声。
|
|
630
629
|
if (this.quotaView) {
|
|
631
|
-
const handledByQuota = applyQuotaRecoveryImpl(event, this.healthManager, (key) => this.clearProviderCooldown(key), this.debug);
|
|
632
|
-
if (handledByQuota) {
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
const handledByQuotaDepleted = applyQuotaDepletedImpl(event, this.healthManager, (key, ttl) => this.markProviderCooldown(key, ttl), this.debug);
|
|
636
|
-
if (handledByQuotaDepleted) {
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
630
|
return;
|
|
640
631
|
}
|
|
641
632
|
// 配额恢复事件优先处理:一旦识别到 virtualRouterQuotaRecovery,
|
|
@@ -648,6 +639,7 @@ export class VirtualRouterEngine {
|
|
|
648
639
|
if (handledByQuotaDepleted) {
|
|
649
640
|
return;
|
|
650
641
|
}
|
|
642
|
+
applySeriesCooldownImpl(event, this.providerRegistry, this.healthManager, (key, ttl) => this.markProviderCooldown(key, ttl), this.debug);
|
|
651
643
|
const derived = mapProviderErrorImpl(event, this.providerHealthConfig());
|
|
652
644
|
if (!derived) {
|
|
653
645
|
return;
|
|
@@ -1469,17 +1461,10 @@ export class VirtualRouterEngine {
|
|
|
1469
1461
|
if (!expiry) {
|
|
1470
1462
|
return false;
|
|
1471
1463
|
}
|
|
1472
|
-
|
|
1473
|
-
if (now >= expiry) {
|
|
1464
|
+
if (Date.now() >= expiry) {
|
|
1474
1465
|
this.providerCooldowns.delete(providerKey);
|
|
1475
1466
|
return false;
|
|
1476
1467
|
}
|
|
1477
|
-
if (providerKey.startsWith('antigravity.')) {
|
|
1478
|
-
const remaining = expiry - now;
|
|
1479
|
-
if (remaining < ANTIGRAVITY_COOLDOWN_ALIAS_THRESHOLD_MS) {
|
|
1480
|
-
return false;
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
1468
|
return true;
|
|
1484
1469
|
}
|
|
1485
1470
|
restoreHealthFromStore() {
|
|
@@ -152,7 +152,7 @@ export interface HealthWeightedLoadBalancingConfig {
|
|
|
152
152
|
*/
|
|
153
153
|
recoverToBestOnRetry?: boolean;
|
|
154
154
|
}
|
|
155
|
-
export type AliasSelectionStrategy = 'none' | 'sticky-queue'
|
|
155
|
+
export type AliasSelectionStrategy = 'none' | 'sticky-queue';
|
|
156
156
|
export interface AliasSelectionConfig {
|
|
157
157
|
/**
|
|
158
158
|
* Global on/off switch. When false, no alias-level selection is applied.
|
|
@@ -514,19 +514,6 @@ export interface ProviderQuotaViewEntry {
|
|
|
514
514
|
inPool: boolean;
|
|
515
515
|
reason?: string;
|
|
516
516
|
priorityTier?: number;
|
|
517
|
-
/**
|
|
518
|
-
* Optional remaining quota fraction for the provider key (0..1).
|
|
519
|
-
* Used by alias-selection strategies that prefer higher remaining quota.
|
|
520
|
-
*/
|
|
521
|
-
remainingFraction?: number | null;
|
|
522
|
-
/**
|
|
523
|
-
* Optional quota reset timestamp (ms since epoch) for the provider key.
|
|
524
|
-
*/
|
|
525
|
-
quotaResetAtMs?: number | null;
|
|
526
|
-
/**
|
|
527
|
-
* Optional quota fetch timestamp (ms since epoch) for the provider key.
|
|
528
|
-
*/
|
|
529
|
-
quotaFetchedAtMs?: number | null;
|
|
530
517
|
/**
|
|
531
518
|
* Optional soft penalty hint for selection ordering.
|
|
532
519
|
* - 0 / undefined means no penalty
|
|
@@ -9,7 +9,7 @@ export declare function normalizeClockConfig(raw: unknown): ClockConfigSnapshot
|
|
|
9
9
|
* Resolve the effective clock config for a request/session.
|
|
10
10
|
*
|
|
11
11
|
* - If a config object exists and enabled=true -> return normalized config.
|
|
12
|
+
* - If the config is absent (undefined) -> default-enable using CLOCK_CONFIG_DEFAULTS.
|
|
12
13
|
* - If the config is explicitly present but disabled/invalid -> return null.
|
|
13
|
-
* - If the config is absent (undefined) -> return null (opt-in only).
|
|
14
14
|
*/
|
|
15
15
|
export declare function resolveClockConfig(raw: unknown): ClockConfigSnapshot | null;
|
|
@@ -3,11 +3,6 @@ export const CLOCK_CONFIG_DEFAULTS = {
|
|
|
3
3
|
dueWindowMs: 60_000,
|
|
4
4
|
tickMs: 60_000
|
|
5
5
|
};
|
|
6
|
-
function isClockDisabledByEnv() {
|
|
7
|
-
const raw = process.env.ROUTECODEX_DISABLE_CLOCK ?? process.env.LLMSWITCH_DISABLE_CLOCK ?? '';
|
|
8
|
-
const v = String(raw).trim().toLowerCase();
|
|
9
|
-
return v === '1' || v === 'true' || v === 'yes' || v === 'on';
|
|
10
|
-
}
|
|
11
6
|
export function normalizeClockConfig(raw) {
|
|
12
7
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
13
8
|
return null;
|
|
@@ -34,16 +29,17 @@ export function normalizeClockConfig(raw) {
|
|
|
34
29
|
* Resolve the effective clock config for a request/session.
|
|
35
30
|
*
|
|
36
31
|
* - If a config object exists and enabled=true -> return normalized config.
|
|
32
|
+
* - If the config is absent (undefined) -> default-enable using CLOCK_CONFIG_DEFAULTS.
|
|
37
33
|
* - If the config is explicitly present but disabled/invalid -> return null.
|
|
38
|
-
* - If the config is absent (undefined) -> return null (opt-in only).
|
|
39
34
|
*/
|
|
40
35
|
export function resolveClockConfig(raw) {
|
|
41
|
-
if (isClockDisabledByEnv()) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
36
|
const normalized = normalizeClockConfig(raw);
|
|
45
37
|
if (normalized) {
|
|
46
38
|
return normalized;
|
|
47
39
|
}
|
|
40
|
+
// Important: only default-enable when the config is absent, not when it's explicitly disabled.
|
|
41
|
+
if (raw === undefined) {
|
|
42
|
+
return normalizeClockConfig({ enabled: true });
|
|
43
|
+
}
|
|
48
44
|
return null;
|
|
49
45
|
}
|
|
@@ -8,32 +8,6 @@ import { applyHubFollowupPolicyShadow } from './followup-shadow.js';
|
|
|
8
8
|
import { buildServerToolFollowupChatPayloadFromInjection } from './handlers/followup-request-builder.js';
|
|
9
9
|
import { findNextUndeliveredDueAtMs, listClockTasks, resolveClockConfig } from './clock/task-store.js';
|
|
10
10
|
import { savePendingServerToolInjection } from './pending-session.js';
|
|
11
|
-
function stripToolHistoryFromFollowupMessages(raw) {
|
|
12
|
-
const messages = Array.isArray(raw) ? raw : null;
|
|
13
|
-
if (!messages) {
|
|
14
|
-
return raw;
|
|
15
|
-
}
|
|
16
|
-
const out = [];
|
|
17
|
-
for (const msg of messages) {
|
|
18
|
-
if (!msg || typeof msg !== 'object' || Array.isArray(msg)) {
|
|
19
|
-
out.push(msg);
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
const record = msg;
|
|
23
|
-
const role = typeof record.role === 'string' ? record.role.trim().toLowerCase() : '';
|
|
24
|
-
// Drop tool-role messages entirely for a "no-tools" recovery followup.
|
|
25
|
-
if (role === 'tool') {
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
// Remove OpenAI tool call fields that could trigger Gemini strict validation.
|
|
29
|
-
const cloned = { ...record };
|
|
30
|
-
delete cloned.tool_calls;
|
|
31
|
-
delete cloned.tool_call_id;
|
|
32
|
-
delete cloned.name;
|
|
33
|
-
out.push(cloned);
|
|
34
|
-
}
|
|
35
|
-
return out;
|
|
36
|
-
}
|
|
37
11
|
function parseTimeoutMs(raw, fallback) {
|
|
38
12
|
const n = typeof raw === 'string' ? Number(raw.trim()) : typeof raw === 'number' ? raw : NaN;
|
|
39
13
|
if (!Number.isFinite(n) || n <= 0) {
|
|
@@ -93,8 +67,10 @@ function coerceFollowupPayloadStream(payload, stream) {
|
|
|
93
67
|
if (!payload || typeof payload !== 'object') {
|
|
94
68
|
return payload;
|
|
95
69
|
}
|
|
96
|
-
|
|
97
|
-
|
|
70
|
+
// ServerTool followup requests must be non-streaming to keep parsing deterministic and avoid
|
|
71
|
+
// provider-side SSE wrappers leaking into internal reenter calls.
|
|
72
|
+
if (stream === false) {
|
|
73
|
+
payload.stream = false;
|
|
98
74
|
}
|
|
99
75
|
return payload;
|
|
100
76
|
}
|
|
@@ -378,41 +354,7 @@ export async function runServerToolOrchestration(options) {
|
|
|
378
354
|
}
|
|
379
355
|
return null;
|
|
380
356
|
})();
|
|
381
|
-
|
|
382
|
-
// Followup responses should still be eligible for servertool triggers (e.g. clock/web_search parsing),
|
|
383
|
-
// but they must not start a new followup flow inside an existing followup hop.
|
|
384
|
-
//
|
|
385
|
-
// Exception: allow continuing the same flow when serverToolLoopState.flowId matches.
|
|
386
|
-
const followupSeedPayload = (() => {
|
|
387
|
-
if (!followupPayloadRaw) {
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
try {
|
|
391
|
-
const rt = readRuntimeMetadata(options.adapterContext);
|
|
392
|
-
const followupFlagRaw = rt?.serverToolFollowup;
|
|
393
|
-
const isFollowup = followupFlagRaw === true ||
|
|
394
|
-
(typeof followupFlagRaw === 'string' && followupFlagRaw.trim().toLowerCase() === 'true');
|
|
395
|
-
if (!isFollowup) {
|
|
396
|
-
return followupPayloadRaw;
|
|
397
|
-
}
|
|
398
|
-
const loopState = rt?.serverToolLoopState;
|
|
399
|
-
const loopFlowId = loopState && typeof loopState === 'object' && !Array.isArray(loopState)
|
|
400
|
-
? String(loopState.flowId || '').trim()
|
|
401
|
-
: '';
|
|
402
|
-
const flowId = typeof engineResult.execution?.flowId === 'string' && engineResult.execution.flowId.trim().length
|
|
403
|
-
? engineResult.execution.flowId.trim()
|
|
404
|
-
: '';
|
|
405
|
-
if (loopFlowId && flowId && loopFlowId === flowId) {
|
|
406
|
-
return followupPayloadRaw;
|
|
407
|
-
}
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
catch {
|
|
411
|
-
// best-effort: if metadata is malformed, avoid nested followups
|
|
412
|
-
return null;
|
|
413
|
-
}
|
|
414
|
-
})();
|
|
415
|
-
if (!followupSeedPayload) {
|
|
357
|
+
if (!followupPayloadRaw) {
|
|
416
358
|
logProgress(5, totalSteps, 'completed (missing followup payload)', { flowId });
|
|
417
359
|
return {
|
|
418
360
|
chat: engineResult.finalChatResponse,
|
|
@@ -420,7 +362,7 @@ export async function runServerToolOrchestration(options) {
|
|
|
420
362
|
flowId: engineResult.execution.flowId
|
|
421
363
|
};
|
|
422
364
|
}
|
|
423
|
-
const loopState = buildServerToolLoopState(options.adapterContext, engineResult.execution.flowId,
|
|
365
|
+
const loopState = buildServerToolLoopState(options.adapterContext, engineResult.execution.flowId, followupPayloadRaw);
|
|
424
366
|
if (applyAutoLimit && loopState && typeof loopState.repeatCount === 'number' && loopState.repeatCount >= 3) {
|
|
425
367
|
logProgress(5, totalSteps, 'completed (auto limit hit)', { flowId });
|
|
426
368
|
return {
|
|
@@ -438,6 +380,7 @@ export async function runServerToolOrchestration(options) {
|
|
|
438
380
|
};
|
|
439
381
|
}
|
|
440
382
|
const metadata = {
|
|
383
|
+
stream: false,
|
|
441
384
|
...(engineResult.execution.followup.metadata ?? {})
|
|
442
385
|
};
|
|
443
386
|
const rt = ensureRuntimeMetadata(metadata);
|
|
@@ -460,10 +403,9 @@ export async function runServerToolOrchestration(options) {
|
|
|
460
403
|
(typeof options.entryEndpoint === 'string' && options.entryEndpoint.trim().length
|
|
461
404
|
? options.entryEndpoint
|
|
462
405
|
: followupEntryEndpoint);
|
|
463
|
-
// For stateful auto-followups, keep the same providerKey/alias.
|
|
464
|
-
// Otherwise the followup requestId suffix
|
|
465
|
-
|
|
466
|
-
if (isStopMessageFlow || isGeminiEmptyReplyContinue) {
|
|
406
|
+
// For stateful auto-followups (e.g. stop_message_flow), keep the same providerKey/alias.
|
|
407
|
+
// Otherwise the followup requestId suffix would cause round-robin alias switching.
|
|
408
|
+
if (isStopMessageFlow) {
|
|
467
409
|
const providerKeyRaw = options.adapterContext.providerKey;
|
|
468
410
|
const providerKey = typeof providerKeyRaw === 'string' && providerKeyRaw.trim().length ? providerKeyRaw.trim() : '';
|
|
469
411
|
if (providerKey) {
|
|
@@ -473,26 +415,7 @@ export async function runServerToolOrchestration(options) {
|
|
|
473
415
|
const retryEmptyFollowupOnce = isStopMessageFlow || isGeminiEmptyReplyContinue;
|
|
474
416
|
const maxAttempts = retryEmptyFollowupOnce ? 2 : 1;
|
|
475
417
|
const followupRequestId = buildFollowupRequestId(options.requestId, engineResult.execution.followup.requestIdSuffix);
|
|
476
|
-
|
|
477
|
-
? (metadata.stream)
|
|
478
|
-
: undefined;
|
|
479
|
-
let followupPayload = coerceFollowupPayloadStream(followupSeedPayload, followupStream);
|
|
480
|
-
if (isGeminiEmptyReplyContinue) {
|
|
481
|
-
// For gemini_empty_reply_continue, the goal is to recover text output from an empty/malformed reply.
|
|
482
|
-
// Force the followup to be non-tool-calling to avoid repeated MALFORMED_FUNCTION_CALL loops.
|
|
483
|
-
const paramsRaw = followupPayload.parameters;
|
|
484
|
-
const params = paramsRaw && typeof paramsRaw === 'object' && !Array.isArray(paramsRaw) ? { ...paramsRaw } : {};
|
|
485
|
-
params.tool_choice = 'none';
|
|
486
|
-
params.parallel_tool_calls = false;
|
|
487
|
-
// Ensure we don't override the tool_choice->toolConfig mapping with an inherited tool_config.
|
|
488
|
-
delete params.tool_config;
|
|
489
|
-
delete params.toolConfig;
|
|
490
|
-
followupPayload.parameters = params;
|
|
491
|
-
// Additionally, strip tool-call history. Gemini/CloudCode can strict-validate
|
|
492
|
-
// (history tool calls) ↔ (current tool declarations). We keep tools declared (so the
|
|
493
|
-
// session can continue), but remove history tool artifacts to avoid malformed loops.
|
|
494
|
-
followupPayload.messages = stripToolHistoryFromFollowupMessages(followupPayload.messages);
|
|
495
|
-
}
|
|
418
|
+
let followupPayload = coerceFollowupPayloadStream(followupPayloadRaw, metadata.stream === true);
|
|
496
419
|
followupPayload = applyHubFollowupPolicyShadow({
|
|
497
420
|
requestId: followupRequestId,
|
|
498
421
|
entryEndpoint: followupEntryEndpoint,
|
|
@@ -92,9 +92,8 @@ const handler = async (ctx) => {
|
|
|
92
92
|
entryEndpoint: ctx.entryEndpoint,
|
|
93
93
|
injection: {
|
|
94
94
|
ops: [
|
|
95
|
-
{ op: 'trim_openai_messages', maxNonSystemMessages: 16 },
|
|
96
95
|
{ op: 'append_assistant_message', required: false },
|
|
97
|
-
{ op: 'append_user_text', text: '
|
|
96
|
+
{ op: 'append_user_text', text: '继续执行' }
|
|
98
97
|
]
|
|
99
98
|
},
|
|
100
99
|
metadata: (() => {
|
|
@@ -825,22 +825,6 @@ export class ResponsesResponseBuilder {
|
|
|
825
825
|
}
|
|
826
826
|
return { success: true, response: this.response };
|
|
827
827
|
}
|
|
828
|
-
// 进一步容错:少数上游会在输出流结束前没有发送 output_item.done/response.completed/response.done,
|
|
829
|
-
// 但已发送 output_item.added/content_part.* 等事件。此时 outputItemBuilders 中已存在内容,
|
|
830
|
-
// 直接按当前聚合结果构建 output 并视为 completed,避免 SSE_TO_JSON_ERROR: Building not completed。
|
|
831
|
-
if (this.outputItemBuilders.size > 0) {
|
|
832
|
-
this.response.status = 'completed';
|
|
833
|
-
try {
|
|
834
|
-
const cur = this.response.output;
|
|
835
|
-
if (!Array.isArray(cur) || cur.length === 0) {
|
|
836
|
-
this.response.output = this.buildOutputItems();
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
catch {
|
|
840
|
-
this.response.output = this.buildOutputItems();
|
|
841
|
-
}
|
|
842
|
-
return { success: true, response: this.response };
|
|
843
|
-
}
|
|
844
828
|
}
|
|
845
829
|
catch { /* ignore */ }
|
|
846
830
|
return { success: false, error: new Error('Building not completed') };
|
package/package.json
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { StandardizedRequest, ProcessedRequest } from '../../types/standardized.js';
|
|
2
|
-
import type { AdapterContext } from '../../types/chat-envelope.js';
|
|
3
|
-
import type { StageRecorder } from '../../format-adapters/index.js';
|
|
4
|
-
import type { NormalizedRequest, TargetMetadata } from './types.js';
|
|
5
|
-
export declare function buildAdapterContext(normalized: NormalizedRequest, target?: TargetMetadata): AdapterContext;
|
|
6
|
-
export declare function maybeCreateStageRecorder(context: AdapterContext, endpoint?: string, options?: {
|
|
7
|
-
disableSnapshots?: boolean;
|
|
8
|
-
}): StageRecorder | undefined;
|
|
9
|
-
export declare function resolveOutboundStreamIntent(providerPreference?: TargetMetadata['streaming']): boolean | undefined;
|
|
10
|
-
export declare function applyOutboundStreamPreference(request: StandardizedRequest | ProcessedRequest, stream: boolean | undefined): StandardizedRequest | ProcessedRequest;
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { jsonClone } from '../../types/json.js';
|
|
2
|
-
import { applyHubOperations } from '../../ops/operations.js';
|
|
3
|
-
import { createSnapshotRecorder } from '../../snapshot-recorder.js';
|
|
4
|
-
import { shouldRecordSnapshots } from '../../../shared/snapshot-utils.js';
|
|
5
|
-
import { cloneRuntimeMetadata } from '../../../shared/runtime-metadata.js';
|
|
6
|
-
function normalizeToolCallIdStyleCandidate(value) {
|
|
7
|
-
if (typeof value !== 'string') {
|
|
8
|
-
return undefined;
|
|
9
|
-
}
|
|
10
|
-
const normalized = value.trim().toLowerCase();
|
|
11
|
-
if (normalized === 'fc') {
|
|
12
|
-
return 'fc';
|
|
13
|
-
}
|
|
14
|
-
if (normalized === 'preserve') {
|
|
15
|
-
return 'preserve';
|
|
16
|
-
}
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
19
|
-
export function buildAdapterContext(normalized, target) {
|
|
20
|
-
const metadata = normalized.metadata || {};
|
|
21
|
-
const providerProtocol = target?.outboundProfile || normalized.providerProtocol;
|
|
22
|
-
const providerId = (target?.providerKey || metadata.providerKey);
|
|
23
|
-
const routeId = metadata.routeName;
|
|
24
|
-
const profileId = (target?.providerKey || metadata.pipelineId);
|
|
25
|
-
const streamingHint = normalized.stream === true ? 'force' : normalized.stream === false ? 'disable' : 'auto';
|
|
26
|
-
const toolCallIdStyle = normalizeToolCallIdStyleCandidate(metadata.toolCallIdStyle);
|
|
27
|
-
const adapterContext = {
|
|
28
|
-
requestId: normalized.id,
|
|
29
|
-
entryEndpoint: normalized.entryEndpoint || '/v1/chat/completions',
|
|
30
|
-
providerProtocol,
|
|
31
|
-
providerId,
|
|
32
|
-
routeId,
|
|
33
|
-
profileId,
|
|
34
|
-
streamingHint,
|
|
35
|
-
toolCallIdStyle
|
|
36
|
-
};
|
|
37
|
-
const runtime = metadata.runtime;
|
|
38
|
-
if (runtime && typeof runtime === 'object' && !Array.isArray(runtime)) {
|
|
39
|
-
adapterContext.runtime = jsonClone(runtime);
|
|
40
|
-
}
|
|
41
|
-
const clientRequestId = typeof metadata.clientRequestId === 'string'
|
|
42
|
-
? metadata.clientRequestId.trim()
|
|
43
|
-
: '';
|
|
44
|
-
if (clientRequestId) {
|
|
45
|
-
adapterContext.clientRequestId = clientRequestId;
|
|
46
|
-
}
|
|
47
|
-
const groupRequestId = typeof metadata.groupRequestId === 'string'
|
|
48
|
-
? metadata.groupRequestId.trim()
|
|
49
|
-
: '';
|
|
50
|
-
if (groupRequestId) {
|
|
51
|
-
adapterContext.groupRequestId = groupRequestId;
|
|
52
|
-
}
|
|
53
|
-
if (typeof metadata.originalModelId === 'string') {
|
|
54
|
-
adapterContext.originalModelId = metadata.originalModelId;
|
|
55
|
-
}
|
|
56
|
-
if (typeof metadata.clientModelId === 'string') {
|
|
57
|
-
adapterContext.clientModelId = metadata.clientModelId;
|
|
58
|
-
}
|
|
59
|
-
if (typeof metadata.assignedModelId === 'string') {
|
|
60
|
-
adapterContext.modelId = metadata.assignedModelId;
|
|
61
|
-
}
|
|
62
|
-
const rt = cloneRuntimeMetadata(metadata);
|
|
63
|
-
if (rt) {
|
|
64
|
-
adapterContext.__rt = rt;
|
|
65
|
-
}
|
|
66
|
-
const sessionId = typeof metadata.sessionId === 'string'
|
|
67
|
-
? metadata.sessionId.trim()
|
|
68
|
-
: '';
|
|
69
|
-
if (sessionId) {
|
|
70
|
-
adapterContext.sessionId = sessionId;
|
|
71
|
-
}
|
|
72
|
-
const conversationId = typeof metadata.conversationId === 'string'
|
|
73
|
-
? metadata.conversationId.trim()
|
|
74
|
-
: '';
|
|
75
|
-
if (conversationId) {
|
|
76
|
-
adapterContext.conversationId = conversationId;
|
|
77
|
-
}
|
|
78
|
-
const projectIdRaw = typeof metadata.projectId === 'string'
|
|
79
|
-
? metadata.projectId.trim()
|
|
80
|
-
: typeof metadata.project_id === 'string'
|
|
81
|
-
? metadata.project_id.trim()
|
|
82
|
-
: '';
|
|
83
|
-
if (projectIdRaw) {
|
|
84
|
-
adapterContext.projectId = projectIdRaw;
|
|
85
|
-
}
|
|
86
|
-
const clientConnectionState = metadata.clientConnectionState;
|
|
87
|
-
if (clientConnectionState && typeof clientConnectionState === 'object' && !Array.isArray(clientConnectionState)) {
|
|
88
|
-
const stateRecord = clientConnectionState;
|
|
89
|
-
adapterContext.clientConnectionState = clientConnectionState;
|
|
90
|
-
if (typeof stateRecord.disconnected === 'boolean') {
|
|
91
|
-
adapterContext.clientDisconnected = stateRecord.disconnected;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
const clientDisconnectedRaw = metadata.clientDisconnected;
|
|
95
|
-
if (clientDisconnectedRaw === true ||
|
|
96
|
-
(typeof clientDisconnectedRaw === 'string' && clientDisconnectedRaw.trim().toLowerCase() === 'true')) {
|
|
97
|
-
adapterContext.clientDisconnected = true;
|
|
98
|
-
}
|
|
99
|
-
if (target?.compatibilityProfile && typeof target.compatibilityProfile === 'string') {
|
|
100
|
-
adapterContext.compatibilityProfile = target.compatibilityProfile;
|
|
101
|
-
}
|
|
102
|
-
return adapterContext;
|
|
103
|
-
}
|
|
104
|
-
export function maybeCreateStageRecorder(context, endpoint, options) {
|
|
105
|
-
if (options?.disableSnapshots === true) {
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
if (!shouldRecordSnapshots()) {
|
|
109
|
-
return undefined;
|
|
110
|
-
}
|
|
111
|
-
const effectiveEndpoint = endpoint || context.entryEndpoint || '/v1/chat/completions';
|
|
112
|
-
try {
|
|
113
|
-
return createSnapshotRecorder(context, effectiveEndpoint);
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
export function resolveOutboundStreamIntent(providerPreference) {
|
|
120
|
-
if (providerPreference === 'always') {
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
if (providerPreference === 'never') {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
128
|
-
export function applyOutboundStreamPreference(request, stream) {
|
|
129
|
-
if (!request || typeof request !== 'object') {
|
|
130
|
-
return request;
|
|
131
|
-
}
|
|
132
|
-
const ops = [];
|
|
133
|
-
if (typeof stream === 'boolean') {
|
|
134
|
-
ops.push({ op: 'set_request_parameter_fields', fields: { stream } });
|
|
135
|
-
ops.push({ op: 'set_request_metadata_fields', fields: { outboundStream: stream } });
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
ops.push({ op: 'unset_request_parameter_keys', keys: ['stream'] });
|
|
139
|
-
ops.push({ op: 'unset_request_metadata_keys', keys: ['outboundStream'] });
|
|
140
|
-
}
|
|
141
|
-
return applyHubOperations(request, ops);
|
|
142
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { isJsonObject, jsonClone } from '../../types/json.js';
|
|
2
|
-
function coerceAliasMap(candidate) {
|
|
3
|
-
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
4
|
-
return undefined;
|
|
5
|
-
}
|
|
6
|
-
const normalized = {};
|
|
7
|
-
for (const [key, value] of Object.entries(candidate)) {
|
|
8
|
-
if (typeof key !== 'string' || typeof value !== 'string') {
|
|
9
|
-
continue;
|
|
10
|
-
}
|
|
11
|
-
const trimmedKey = key.trim();
|
|
12
|
-
const trimmedValue = value.trim();
|
|
13
|
-
if (!trimmedKey.length || !trimmedValue.length) {
|
|
14
|
-
continue;
|
|
15
|
-
}
|
|
16
|
-
normalized[trimmedKey] = trimmedValue;
|
|
17
|
-
}
|
|
18
|
-
return Object.keys(normalized).length ? normalized : undefined;
|
|
19
|
-
}
|
|
20
|
-
function readAliasMapFromSemantics(chatEnvelope) {
|
|
21
|
-
if (!chatEnvelope?.semantics || typeof chatEnvelope.semantics !== 'object') {
|
|
22
|
-
return undefined;
|
|
23
|
-
}
|
|
24
|
-
const node = chatEnvelope.semantics.tools;
|
|
25
|
-
if (!node || !isJsonObject(node)) {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
const candidate = node.toolNameAliasMap ?? node.toolAliasMap;
|
|
29
|
-
return coerceAliasMap(candidate);
|
|
30
|
-
}
|
|
31
|
-
function shouldCapture(entryEndpoint) {
|
|
32
|
-
return typeof entryEndpoint === 'string' && entryEndpoint.toLowerCase().includes('/v1/messages');
|
|
33
|
-
}
|
|
34
|
-
function resolveAliasMapFromSources(adapterContext, chatEnvelope) {
|
|
35
|
-
const fromContext = coerceAliasMap(adapterContext.anthropicToolNameMap);
|
|
36
|
-
if (fromContext) {
|
|
37
|
-
return fromContext;
|
|
38
|
-
}
|
|
39
|
-
const metadataNode = chatEnvelope.metadata;
|
|
40
|
-
const direct = metadataNode ? coerceAliasMap(metadataNode.anthropicToolNameMap) : undefined;
|
|
41
|
-
if (direct) {
|
|
42
|
-
return direct;
|
|
43
|
-
}
|
|
44
|
-
const contextNode = metadataNode && metadataNode.context && typeof metadataNode.context === 'object'
|
|
45
|
-
? metadataNode.context
|
|
46
|
-
: undefined;
|
|
47
|
-
const fromContextNode = coerceAliasMap(contextNode?.anthropicToolNameMap);
|
|
48
|
-
if (fromContextNode) {
|
|
49
|
-
return fromContextNode;
|
|
50
|
-
}
|
|
51
|
-
return readAliasMapFromSemantics(chatEnvelope);
|
|
52
|
-
}
|
|
53
|
-
export function captureAnthropicToolNameAliasMap(options) {
|
|
54
|
-
if (!shouldCapture(options.entryEndpoint)) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const aliasMap = resolveAliasMapFromSources(options.adapterContext, options.chatEnvelope);
|
|
58
|
-
if (!aliasMap) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
// Tool name alias map is mappable semantics and must live in chat.semantics (never metadata).
|
|
62
|
-
try {
|
|
63
|
-
const chatEnvelope = options.chatEnvelope;
|
|
64
|
-
if (!chatEnvelope.semantics || typeof chatEnvelope.semantics !== 'object' || Array.isArray(chatEnvelope.semantics)) {
|
|
65
|
-
chatEnvelope.semantics = {};
|
|
66
|
-
}
|
|
67
|
-
const semantics = chatEnvelope.semantics;
|
|
68
|
-
if (!semantics.tools || !isJsonObject(semantics.tools)) {
|
|
69
|
-
semantics.tools = {};
|
|
70
|
-
}
|
|
71
|
-
const toolsNode = semantics.tools;
|
|
72
|
-
if (!isJsonObject(toolsNode.toolNameAliasMap) && !isJsonObject(toolsNode.toolAliasMap)) {
|
|
73
|
-
toolsNode.toolNameAliasMap = jsonClone(aliasMap);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
// best-effort: never block request handling due to alias map propagation failures
|
|
78
|
-
}
|
|
79
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
function isTruthyEnv(value) {
|
|
2
|
-
const v = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
3
|
-
return v === '1' || v === 'true' || v === 'yes' || v === 'on';
|
|
4
|
-
}
|
|
5
|
-
export function resolveApplyPatchToolModeFromEnv() {
|
|
6
|
-
const rawMode = String(process.env.RCC_APPLY_PATCH_TOOL_MODE || process.env.ROUTECODEX_APPLY_PATCH_TOOL_MODE || '')
|
|
7
|
-
.trim()
|
|
8
|
-
.toLowerCase();
|
|
9
|
-
if (rawMode === 'freeform')
|
|
10
|
-
return 'freeform';
|
|
11
|
-
if (rawMode === 'schema' || rawMode === 'json_schema')
|
|
12
|
-
return 'schema';
|
|
13
|
-
const freeformFlag = process.env.RCC_APPLY_PATCH_FREEFORM || process.env.ROUTECODEX_APPLY_PATCH_FREEFORM;
|
|
14
|
-
if (isTruthyEnv(freeformFlag))
|
|
15
|
-
return 'freeform';
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
export function resolveApplyPatchToolModeFromTools(toolsRaw) {
|
|
19
|
-
if (!Array.isArray(toolsRaw) || toolsRaw.length === 0) {
|
|
20
|
-
return undefined;
|
|
21
|
-
}
|
|
22
|
-
for (const entry of toolsRaw) {
|
|
23
|
-
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
24
|
-
continue;
|
|
25
|
-
const record = entry;
|
|
26
|
-
const type = typeof record.type === 'string' ? record.type.trim().toLowerCase() : '';
|
|
27
|
-
if (type && type !== 'function')
|
|
28
|
-
continue;
|
|
29
|
-
const fn = record.function && typeof record.function === 'object' && !Array.isArray(record.function)
|
|
30
|
-
? record.function
|
|
31
|
-
: undefined;
|
|
32
|
-
const name = typeof fn?.name === 'string' ? fn.name.trim().toLowerCase() : '';
|
|
33
|
-
if (name !== 'apply_patch')
|
|
34
|
-
continue;
|
|
35
|
-
const format = typeof record.format === 'string'
|
|
36
|
-
? record.format.trim().toLowerCase()
|
|
37
|
-
: typeof fn?.format === 'string'
|
|
38
|
-
? String(fn.format).trim().toLowerCase()
|
|
39
|
-
: '';
|
|
40
|
-
if (format === 'freeform')
|
|
41
|
-
return 'freeform';
|
|
42
|
-
// If apply_patch is present without explicit freeform marker, default to schema mode.
|
|
43
|
-
return 'schema';
|
|
44
|
-
}
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { VirtualRouterEngine } from '../../../../router/virtual-router/engine.js';
|
|
2
|
-
import type { HubPipelineConfig, HubPipelineResult, NormalizedRequest, ProviderProtocol, RequestStageHooks } from './types.js';
|
|
3
|
-
export declare function executeChatProcessEntryPipeline(options: {
|
|
4
|
-
config: HubPipelineConfig;
|
|
5
|
-
routerEngine: VirtualRouterEngine;
|
|
6
|
-
normalized: NormalizedRequest;
|
|
7
|
-
resolveProtocolHooks: (protocol: ProviderProtocol) => RequestStageHooks<Record<string, unknown>> | undefined;
|
|
8
|
-
}): Promise<HubPipelineResult>;
|