@jsonstudio/rcc 0.90.814 → 0.90.876
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/README.md +8 -0
- package/configsamples/provider-default/ali-coding-plan/config.v2.json +76 -0
- package/configsamples/provider-default/antigravity/config.v2.json +142 -0
- package/configsamples/provider-default/ark-coding-plan/config.v2.json +64 -0
- package/configsamples/provider-default/crs/config.v2.json +54 -0
- package/configsamples/provider-default/deepseek-web/config.v2.json +56 -0
- package/configsamples/provider-default/gemini/config.v2.json +43 -0
- package/configsamples/provider-default/gemini-cli/config.v2.json +45 -0
- package/configsamples/provider-default/gemini-native/config.v2.json +208 -0
- package/configsamples/provider-default/glm/config.v2.json +33 -0
- package/configsamples/provider-default/glm-anthropic/config.v2.json +29 -0
- package/configsamples/provider-default/kimi/config.v2.json +25 -0
- package/configsamples/provider-default/lmstudio/config.v2.json +79 -0
- package/configsamples/provider-default/lmstudio-proxy/config.v2.json +78 -0
- package/configsamples/provider-default/manifest.json +31 -0
- package/configsamples/provider-default/meituan/config.v2.json +20 -0
- package/configsamples/provider-default/mimo/config.v2.json +26 -0
- package/configsamples/provider-default/modelscope/config.v2.json +81 -0
- package/configsamples/provider-default/my-openai/config.v2.json +20 -0
- package/configsamples/provider-default/nvidia/config.v2.json +32 -0
- package/configsamples/provider-default/opencode-zen-free/config.v2.json +56 -0
- package/configsamples/provider-default/openrouter/config.v2.json +210 -0
- package/configsamples/provider-default/qwen/config.v2.json +38 -0
- package/configsamples/provider-default/qwenchat/config.v2.json +53 -0
- package/configsamples/provider-default/tab/config.v2.json +26 -0
- package/configsamples/provider-default/tabglm/config.v2.json +77 -0
- package/dist/build-info.js +2 -2
- package/dist/cli/commands/config.d.ts +5 -0
- package/dist/cli/commands/config.js +369 -1
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/examples.js +3 -0
- package/dist/cli/commands/examples.js.map +1 -1
- package/dist/cli/commands/init.js +25 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/launcher-kernel.js +122 -46
- package/dist/cli/commands/launcher-kernel.js.map +1 -1
- package/dist/cli/commands/start.js +60 -3
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/config/bundled-provider-pack.d.ts +20 -0
- package/dist/cli/config/bundled-provider-pack.js +146 -0
- package/dist/cli/config/bundled-provider-pack.js.map +1 -0
- package/dist/cli/register/status-config-commands.d.ts +2 -0
- package/dist/cli/register/status-config-commands.js.map +1 -1
- package/dist/cli.js +81 -28
- package/dist/cli.js.map +1 -1
- package/dist/debug/snapshot-store.js +2 -1
- package/dist/debug/snapshot-store.js.map +1 -1
- package/dist/index.js +23 -14
- package/dist/index.js.map +1 -1
- package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +1 -1
- package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
- package/dist/manager/quota/provider-quota-center.js +1 -1
- package/dist/manager/quota/provider-quota-center.js.map +1 -1
- package/dist/manager/storage/file-store.js +10 -0
- package/dist/manager/storage/file-store.js.map +1 -1
- package/dist/modules/llmswitch/bridge/snapshot-recorder-runtime.js +18 -1
- package/dist/modules/llmswitch/bridge/snapshot-recorder-runtime.js.map +1 -1
- package/dist/modules/llmswitch/bridge/snapshot-recorder.js +132 -51
- package/dist/modules/llmswitch/bridge/snapshot-recorder.js.map +1 -1
- package/dist/provider-sdk/provider-runtime-inference.js +2 -2
- package/dist/provider-sdk/provider-runtime-inference.js.map +1 -1
- package/dist/providers/auth/deepseek-account-token-acquirer.js +32 -3
- package/dist/providers/auth/deepseek-account-token-acquirer.js.map +1 -1
- package/dist/providers/core/api/provider-types.d.ts +11 -0
- package/dist/providers/core/config/service-profiles.js +1 -1
- package/dist/providers/core/runtime/deepseek-http-provider.d.ts +2 -0
- package/dist/providers/core/runtime/deepseek-http-provider.js +31 -1
- package/dist/providers/core/runtime/deepseek-http-provider.js.map +1 -1
- package/dist/providers/core/runtime/qwenchat-http-provider-helpers.d.ts +3 -2
- package/dist/providers/core/runtime/qwenchat-http-provider-helpers.js +513 -96
- package/dist/providers/core/runtime/qwenchat-http-provider-helpers.js.map +1 -1
- package/dist/providers/core/runtime/standard-tool-text-harvest.d.ts +8 -0
- package/dist/providers/core/runtime/standard-tool-text-harvest.js +24 -0
- package/dist/providers/core/runtime/standard-tool-text-harvest.js.map +1 -0
- package/dist/providers/core/runtime/standard-tool-text-request-transform.d.ts +4 -1
- package/dist/providers/core/runtime/standard-tool-text-request-transform.js +129 -3
- package/dist/providers/core/runtime/standard-tool-text-request-transform.js.map +1 -1
- package/dist/providers/core/utils/snapshot-writer.js +5 -2
- package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
- package/dist/providers/profile/provider-profile-loader.js +52 -1
- package/dist/providers/profile/provider-profile-loader.js.map +1 -1
- package/dist/providers/profile/provider-profile.d.ts +3 -0
- package/dist/server/handlers/handler-response-utils.js +1 -0
- package/dist/server/handlers/handler-response-utils.js.map +1 -1
- package/dist/server/handlers/images-handler.d.ts +9 -0
- package/dist/server/handlers/images-handler.js +258 -0
- package/dist/server/handlers/images-handler.js.map +1 -0
- package/dist/server/handlers/types.d.ts +7 -0
- package/dist/server/runtime/http-server/antigravity-startup-tasks.d.ts +3 -0
- package/dist/server/runtime/http-server/antigravity-startup-tasks.js +16 -0
- package/dist/server/runtime/http-server/antigravity-startup-tasks.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +3 -18
- package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.d.ts +7 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.js +17 -0
- package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +50 -17
- package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
- package/dist/server/runtime/http-server/daemon-admin-routes.js +32 -2
- package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
- package/dist/server/runtime/http-server/executor/provider-response-converter.js +42 -13
- package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -1
- package/dist/server/runtime/http-server/executor/provider-response-utils.js +41 -3
- package/dist/server/runtime/http-server/executor/provider-response-utils.js.map +1 -1
- package/dist/server/runtime/http-server/executor/usage-aggregator.js +7 -7
- package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -1
- package/dist/server/runtime/http-server/executor/usage-logger.d.ts +9 -0
- package/dist/server/runtime/http-server/executor/usage-logger.js +35 -2
- package/dist/server/runtime/http-server/executor/usage-logger.js.map +1 -1
- package/dist/server/runtime/http-server/executor-metadata.js +12 -4
- package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
- package/dist/server/runtime/http-server/executor-pipeline.js +24 -15
- package/dist/server/runtime/http-server/executor-pipeline.js.map +1 -1
- package/dist/server/runtime/http-server/executor-provider.d.ts +6 -1
- package/dist/server/runtime/http-server/executor-provider.js +137 -5
- package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
- package/dist/server/runtime/http-server/http-server-bootstrap.js +6 -0
- package/dist/server/runtime/http-server/http-server-bootstrap.js.map +1 -1
- package/dist/server/runtime/http-server/http-server-lifecycle.js +23 -15
- package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -1
- package/dist/server/runtime/http-server/http-server-runtime-providers.js +14 -4
- package/dist/server/runtime/http-server/http-server-runtime-providers.js.map +1 -1
- package/dist/server/runtime/http-server/http-server-runtime-setup.js +83 -1
- package/dist/server/runtime/http-server/http-server-runtime-setup.js.map +1 -1
- package/dist/server/runtime/http-server/hub-shadow-compare.js +2 -41
- package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -1
- package/dist/server/runtime/http-server/provider-routing-scope.d.ts +9 -0
- package/dist/server/runtime/http-server/provider-routing-scope.js +20 -0
- package/dist/server/runtime/http-server/provider-routing-scope.js.map +1 -0
- package/dist/server/runtime/http-server/provider-traffic-governor.d.ts +67 -0
- package/dist/server/runtime/http-server/provider-traffic-governor.js +467 -0
- package/dist/server/runtime/http-server/provider-traffic-governor.js.map +1 -0
- package/dist/server/runtime/http-server/request-executor.d.ts +8 -0
- package/dist/server/runtime/http-server/request-executor.js +446 -21
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.js +13 -0
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/session-client-registry.js +30 -4
- package/dist/server/runtime/http-server/session-client-registry.js.map +1 -1
- package/dist/server/runtime/http-server/session-client-route-utils.d.ts +7 -0
- package/dist/server/runtime/http-server/session-client-route-utils.js +38 -0
- package/dist/server/runtime/http-server/session-client-route-utils.js.map +1 -1
- package/dist/server/runtime/http-server/session-client-routes.js +12 -2
- package/dist/server/runtime/http-server/session-client-routes.js.map +1 -1
- package/dist/server/utils/request-id-manager.js +42 -5
- package/dist/server/utils/request-id-manager.js.map +1 -1
- package/dist/server/utils/stage-logger.d.ts +1 -0
- package/dist/server/utils/stage-logger.js +27 -0
- package/dist/server/utils/stage-logger.js.map +1 -1
- package/dist/utils/errorsamples.js +3 -1
- package/dist/utils/errorsamples.js.map +1 -1
- package/dist/utils/sensitive-redaction.d.ts +1 -0
- package/dist/utils/sensitive-redaction.js +122 -0
- package/dist/utils/sensitive-redaction.js.map +1 -0
- package/dist/utils/snapshot-writer.js +162 -2
- package/dist/utils/snapshot-writer.js.map +1 -1
- package/docs/INSTALLATION_AND_QUICKSTART.md +14 -1
- package/docs/PORTS.md +12 -0
- package/docs/lmstudio-tool-calling.md +25 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwenchat-web-request.d.ts +3 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwenchat-web-request.js +62 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwenchat-web.json +47 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/node-support.js +5 -2
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/operation-table/operation-table-runner.js +68 -7
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper-from-chat.js +138 -3
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-chat-process-request-utils.js +24 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-execute-chat-process-entry.js +7 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-execute-request-stage.js +7 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-heavy-input-fastpath.d.ts +24 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-heavy-input-fastpath.js +203 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-route-and-outbound.js +17 -12
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +11 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-stage-timing.js +82 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +47 -14
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +43 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/client-remap-protocol-switch.js +222 -19
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/policy/policy-engine.js +2 -2
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process-pending-tool-sync.js +24 -7
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +90 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/snapshot-recorder.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/snapshot-recorder.js +252 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge/utils.js +5 -3
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +44 -4
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils-openai-request.d.ts +3 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils-openai-request.js +20 -23
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.js +68 -30
- package/node_modules/@jsonstudio/llms/dist/conversion/snapshot-utils.js +194 -10
- package/node_modules/@jsonstudio/llms/dist/native/router_hotpath_napi.node +0 -0
- package/node_modules/@jsonstudio/llms/dist/quota/quota-state.js +2 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine/routing-state/store.js +35 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +9 -9
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/sticky-session-store.js +104 -18
- package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +79 -32
- package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +49 -0
- package/node_modules/@jsonstudio/llms/dist/servertool/pending-session.js +48 -2
- package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +14 -1
- package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/package.json +1 -1
- package/node_modules/ajv/dist/compile/jtd/serialize.js +9 -2
- package/node_modules/ajv/dist/compile/jtd/serialize.js.map +1 -1
- package/node_modules/ajv/dist/core.d.ts +1 -0
- package/node_modules/ajv/dist/core.js.map +1 -1
- package/node_modules/ajv/dist/vocabularies/validation/pattern.js +13 -4
- package/node_modules/ajv/dist/vocabularies/validation/pattern.js.map +1 -1
- package/node_modules/ajv/lib/compile/jtd/serialize.ts +13 -2
- package/node_modules/ajv/lib/core.ts +1 -0
- package/node_modules/ajv/lib/vocabularies/validation/pattern.ts +15 -4
- package/node_modules/ajv/package.json +2 -1
- package/package.json +15 -10
- package/scripts/ci/repo-sanity.mjs +23 -2
- package/scripts/ci/secrets-check.mjs +48 -0
- package/scripts/ci/silent-failure-audit.mjs +192 -0
- package/scripts/mock-provider/run-regressions.mjs +1 -0
- package/scripts/monitor/memory-guard.mjs +207 -0
- package/scripts/pack-mode.mjs +32 -36
- package/scripts/publish-rcc.mjs +38 -60
- package/scripts/tests/apply-patch-loop.mjs +1 -0
- package/scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs +2 -0
- package/scripts/tools-dev/responses-debug-client/src/index.ts +8 -3
- package/scripts/verify-e2e-toolcall.mjs +1 -0
- package/scripts/verify-install-e2e.mjs +2 -1
|
@@ -13,8 +13,9 @@ import { isPoolExhaustedPipelineError, mergeMetadataPreservingDefined, resolvePo
|
|
|
13
13
|
import { resolveProviderRuntimeOrThrow } from './executor/provider-runtime-resolver.js';
|
|
14
14
|
import { resolveProviderRequestContext } from './executor/provider-request-context.js';
|
|
15
15
|
import { isServerToolEnabled } from './servertool-admin-state.js';
|
|
16
|
-
import { registerRequestLogContext } from '../../utils/request-log-color.js';
|
|
16
|
+
import { registerRequestLogContext, resolveSessionLogColor } from '../../utils/request-log-color.js';
|
|
17
17
|
import { STREAM_LOG_FINISH_REASON_KEY } from '../../utils/finish-reason.js';
|
|
18
|
+
import { createNoopProviderTrafficGovernor, getSharedProviderTrafficGovernor } from './provider-traffic-governor.js';
|
|
18
19
|
const DEFAULT_MAX_PROVIDER_ATTEMPTS = 6;
|
|
19
20
|
function readString(value) {
|
|
20
21
|
if (typeof value !== 'string') {
|
|
@@ -163,16 +164,304 @@ function extractRetryErrorSnapshot(error) {
|
|
|
163
164
|
reason
|
|
164
165
|
};
|
|
165
166
|
}
|
|
167
|
+
function readHubStageTop(metadata) {
|
|
168
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
const rt = metadata.__rt && typeof metadata.__rt === 'object' && !Array.isArray(metadata.__rt)
|
|
172
|
+
? metadata.__rt
|
|
173
|
+
: undefined;
|
|
174
|
+
const raw = rt?.hubStageTop;
|
|
175
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
const normalized = raw
|
|
179
|
+
.map((entry) => {
|
|
180
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
const record = entry;
|
|
184
|
+
const stage = typeof record.stage === 'string' ? record.stage.trim() : '';
|
|
185
|
+
const totalMs = typeof record.totalMs === 'number' && Number.isFinite(record.totalMs)
|
|
186
|
+
? Math.max(0, Math.round(record.totalMs))
|
|
187
|
+
: undefined;
|
|
188
|
+
if (!stage || totalMs === undefined) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
const count = typeof record.count === 'number' && Number.isFinite(record.count)
|
|
192
|
+
? Math.max(0, Math.floor(record.count))
|
|
193
|
+
: undefined;
|
|
194
|
+
const avgMs = typeof record.avgMs === 'number' && Number.isFinite(record.avgMs)
|
|
195
|
+
? Math.max(0, Math.round(record.avgMs))
|
|
196
|
+
: undefined;
|
|
197
|
+
const maxMs = typeof record.maxMs === 'number' && Number.isFinite(record.maxMs)
|
|
198
|
+
? Math.max(0, Math.round(record.maxMs))
|
|
199
|
+
: undefined;
|
|
200
|
+
return {
|
|
201
|
+
stage,
|
|
202
|
+
totalMs,
|
|
203
|
+
...(count !== undefined ? { count } : {}),
|
|
204
|
+
...(avgMs !== undefined ? { avgMs } : {}),
|
|
205
|
+
...(maxMs !== undefined ? { maxMs } : {})
|
|
206
|
+
};
|
|
207
|
+
})
|
|
208
|
+
.filter((entry) => Boolean(entry));
|
|
209
|
+
return normalized.length ? normalized : undefined;
|
|
210
|
+
}
|
|
166
211
|
function truncateReason(reason, maxLength = 220) {
|
|
167
212
|
if (reason.length <= maxLength) {
|
|
168
213
|
return reason;
|
|
169
214
|
}
|
|
170
215
|
return `${reason.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
171
216
|
}
|
|
217
|
+
function resolveTrafficRuntimeProfile(runtimeKey, handle, providerKey) {
|
|
218
|
+
const runtimeCandidate = handle.runtime;
|
|
219
|
+
if (runtimeCandidate && typeof runtimeCandidate === 'object') {
|
|
220
|
+
return runtimeCandidate;
|
|
221
|
+
}
|
|
222
|
+
const providerIdFallback = (() => {
|
|
223
|
+
if (typeof handle.providerId === 'string' && handle.providerId.trim()) {
|
|
224
|
+
return handle.providerId.trim();
|
|
225
|
+
}
|
|
226
|
+
if (typeof providerKey === 'string' && providerKey.includes('.')) {
|
|
227
|
+
const [head] = providerKey.split('.');
|
|
228
|
+
if (head && head.trim()) {
|
|
229
|
+
return head.trim();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return 'unknown';
|
|
233
|
+
})();
|
|
234
|
+
const providerTypeFallback = (typeof handle.providerType === 'string' && handle.providerType.trim()
|
|
235
|
+
? handle.providerType.trim().toLowerCase()
|
|
236
|
+
: 'openai');
|
|
237
|
+
return {
|
|
238
|
+
runtimeKey,
|
|
239
|
+
providerId: providerIdFallback,
|
|
240
|
+
providerKey,
|
|
241
|
+
providerType: providerTypeFallback,
|
|
242
|
+
providerFamily: handle.providerFamily,
|
|
243
|
+
endpoint: '',
|
|
244
|
+
auth: {
|
|
245
|
+
type: 'apikey',
|
|
246
|
+
value: ''
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function isRecord(value) {
|
|
251
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
252
|
+
}
|
|
253
|
+
function valueHasNonEmptyText(value) {
|
|
254
|
+
if (typeof value === 'string') {
|
|
255
|
+
return value.trim().length > 0;
|
|
256
|
+
}
|
|
257
|
+
if (Array.isArray(value)) {
|
|
258
|
+
return value.some((item) => valueHasNonEmptyText(item));
|
|
259
|
+
}
|
|
260
|
+
if (!isRecord(value)) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
return (valueHasNonEmptyText(value.text)
|
|
264
|
+
|| valueHasNonEmptyText(value.output_text)
|
|
265
|
+
|| valueHasNonEmptyText(value.content)
|
|
266
|
+
|| valueHasNonEmptyText(value.reasoning_content)
|
|
267
|
+
|| valueHasNonEmptyText(value.reasoning));
|
|
268
|
+
}
|
|
269
|
+
function extractTextFromResponsesOutputItem(item) {
|
|
270
|
+
if (!isRecord(item)) {
|
|
271
|
+
return '';
|
|
272
|
+
}
|
|
273
|
+
const itemType = readString(item.type)?.toLowerCase();
|
|
274
|
+
if (itemType === 'output_text' || itemType === 'text' || itemType === 'input_text') {
|
|
275
|
+
const direct = readString(item.text);
|
|
276
|
+
if (direct) {
|
|
277
|
+
return direct;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (itemType === 'message') {
|
|
281
|
+
const content = Array.isArray(item.content) ? item.content : [];
|
|
282
|
+
const chunks = [];
|
|
283
|
+
for (const part of content) {
|
|
284
|
+
if (!isRecord(part)) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const partType = readString(part.type)?.toLowerCase();
|
|
288
|
+
if (partType && partType !== 'output_text' && partType !== 'text' && partType !== 'input_text') {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const partText = readString(part.text);
|
|
292
|
+
if (partText) {
|
|
293
|
+
chunks.push(partText);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return chunks.join('');
|
|
297
|
+
}
|
|
298
|
+
return '';
|
|
299
|
+
}
|
|
300
|
+
function backfillResponsesOutputTextIfMissing(body) {
|
|
301
|
+
if (!isRecord(body)) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (valueHasNonEmptyText(body.output_text)) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const outputItems = Array.isArray(body.output) ? body.output : [];
|
|
308
|
+
if (outputItems.length <= 0) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const text = outputItems
|
|
312
|
+
.map((item) => extractTextFromResponsesOutputItem(item))
|
|
313
|
+
.join('')
|
|
314
|
+
.trim();
|
|
315
|
+
if (!text) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
body.output_text = text;
|
|
319
|
+
}
|
|
320
|
+
function emitVirtualRouterConcurrencyLog(args) {
|
|
321
|
+
const timestamp = (() => {
|
|
322
|
+
const now = new Date();
|
|
323
|
+
const hh = String(now.getHours()).padStart(2, '0');
|
|
324
|
+
const mm = String(now.getMinutes()).padStart(2, '0');
|
|
325
|
+
const ss = String(now.getSeconds()).padStart(2, '0');
|
|
326
|
+
return `${hh}:${mm}:${ss}`;
|
|
327
|
+
})();
|
|
328
|
+
const sessionLabel = args.sessionId ? ` sid=${args.sessionId}` : '';
|
|
329
|
+
const routeBase = args.routeName && args.routeName.trim() ? args.routeName.trim() : 'route';
|
|
330
|
+
const routeLabel = args.poolId && args.poolId.trim() ? `${routeBase}/${args.poolId.trim()}` : routeBase;
|
|
331
|
+
const providerLabel = args.providerKey && args.providerKey.trim() ? args.providerKey.trim() : 'unknown-provider';
|
|
332
|
+
const modelLabel = args.model && args.model.trim() ? `.${args.model.trim()}` : '';
|
|
333
|
+
const reasonLabel = args.reason && args.reason.trim() ? ` reason=${args.reason.trim()}` : '';
|
|
334
|
+
const prefixColor = '\x1b[38;5;208m';
|
|
335
|
+
const timeColor = '\x1b[90m';
|
|
336
|
+
const routeColor = resolveSessionLogColor(args.sessionId);
|
|
337
|
+
const white = '\x1b[97m';
|
|
338
|
+
const reset = '\x1b[0m';
|
|
339
|
+
const concurrencyLabel = `${white}[concurrency:${Math.max(0, Math.floor(args.activeInFlight))}/${Math.max(1, Math.floor(args.maxInFlight))}]${reset}`;
|
|
340
|
+
console.log(`${prefixColor}[virtual-router-hit]${reset} ${concurrencyLabel} ${timeColor}${timestamp}${reset}${sessionLabel} ${routeColor}${routeLabel} -> ${providerLabel}${modelLabel}${reasonLabel}${reset}`);
|
|
341
|
+
}
|
|
342
|
+
function hasNonEmptyToolCalls(value) {
|
|
343
|
+
if (!Array.isArray(value) || value.length <= 0) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
return value.some((item) => isRecord(item));
|
|
347
|
+
}
|
|
348
|
+
function hasOutputFunctionCalls(value) {
|
|
349
|
+
if (!Array.isArray(value) || value.length <= 0) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
return value.some((item) => {
|
|
353
|
+
if (!isRecord(item)) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
const itemType = readString(item.type)?.toLowerCase();
|
|
357
|
+
if (itemType === 'function_call' || itemType === 'function') {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
if (hasNonEmptyToolCalls(item.tool_calls)) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
return false;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function containsToolRegistryMissingText(value) {
|
|
367
|
+
if (!valueHasNonEmptyText(value)) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const text = String(value ?? '');
|
|
371
|
+
const pattern = /tool\s+[a-z0-9_.-]+\s+does\s+not\s+exist(?:s)?/ig;
|
|
372
|
+
let count = 0;
|
|
373
|
+
while (pattern.exec(text)) {
|
|
374
|
+
count += 1;
|
|
375
|
+
if (count >= 1) {
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
function detectRetryableEmptyAssistantResponse(body) {
|
|
382
|
+
if (!isRecord(body)) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
if (Object.prototype.hasOwnProperty.call(body, '__sse_responses')) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
const choices = Array.isArray(body.choices) ? body.choices : [];
|
|
389
|
+
if (choices.length > 0) {
|
|
390
|
+
const firstChoice = isRecord(choices[0]) ? choices[0] : undefined;
|
|
391
|
+
if (!firstChoice) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
const finishReason = readString(firstChoice.finish_reason)?.toLowerCase() ?? '';
|
|
395
|
+
const message = isRecord(firstChoice.message) ? firstChoice.message : undefined;
|
|
396
|
+
const hasToolCalls = hasNonEmptyToolCalls(message?.tool_calls);
|
|
397
|
+
const hasText = valueHasNonEmptyText(message?.content)
|
|
398
|
+
|| valueHasNonEmptyText(message?.reasoning_content)
|
|
399
|
+
|| valueHasNonEmptyText(message?.reasoning)
|
|
400
|
+
|| valueHasNonEmptyText(firstChoice.content);
|
|
401
|
+
const combinedText = [
|
|
402
|
+
message?.content,
|
|
403
|
+
message?.reasoning_content,
|
|
404
|
+
message?.reasoning,
|
|
405
|
+
firstChoice.content
|
|
406
|
+
]
|
|
407
|
+
.filter((item) => valueHasNonEmptyText(item))
|
|
408
|
+
.map((item) => String(item))
|
|
409
|
+
.join('\n');
|
|
410
|
+
if ((finishReason === 'stop' || finishReason === 'tool_calls' || !finishReason) && !hasToolCalls && !hasText) {
|
|
411
|
+
return {
|
|
412
|
+
reason: `finish_reason=${finishReason || 'unknown'} but assistant text/tool_calls are empty`,
|
|
413
|
+
marker: 'chat_empty_assistant'
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
if ((finishReason === 'stop' || finishReason === 'tool_calls' || !finishReason) && !hasToolCalls && containsToolRegistryMissingText(combinedText)) {
|
|
417
|
+
return {
|
|
418
|
+
reason: 'assistant emitted textual tool-not-found complaint without structured tool_calls',
|
|
419
|
+
marker: 'chat_textual_tool_registry_missing'
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const status = readString(body.status)?.toLowerCase() ?? '';
|
|
424
|
+
if (status === 'completed' || status === 'stop') {
|
|
425
|
+
const requiredAction = isRecord(body.required_action) ? body.required_action : undefined;
|
|
426
|
+
const submitToolOutputs = requiredAction && isRecord(requiredAction.submit_tool_outputs)
|
|
427
|
+
? requiredAction.submit_tool_outputs
|
|
428
|
+
: undefined;
|
|
429
|
+
const hasRequiredActionToolCalls = hasNonEmptyToolCalls(submitToolOutputs?.tool_calls);
|
|
430
|
+
const hasFunctionCalls = hasOutputFunctionCalls(body.output);
|
|
431
|
+
const hasText = valueHasNonEmptyText(body.output_text)
|
|
432
|
+
|| valueHasNonEmptyText(body.output)
|
|
433
|
+
|| valueHasNonEmptyText(body.reasoning);
|
|
434
|
+
if (!hasRequiredActionToolCalls && !hasFunctionCalls && !hasText) {
|
|
435
|
+
return {
|
|
436
|
+
reason: `responses status=${status} but output text/tool_calls are empty`,
|
|
437
|
+
marker: 'responses_empty_output'
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (!hasRequiredActionToolCalls &&
|
|
441
|
+
!hasFunctionCalls &&
|
|
442
|
+
containsToolRegistryMissingText(body.output_text)) {
|
|
443
|
+
return {
|
|
444
|
+
reason: 'responses completed with textual tool-not-found complaint but no function_call output',
|
|
445
|
+
marker: 'responses_textual_tool_registry_missing'
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
172
451
|
export class HubRequestExecutor {
|
|
173
452
|
deps;
|
|
453
|
+
trafficGovernor;
|
|
174
454
|
constructor(deps) {
|
|
175
455
|
this.deps = deps;
|
|
456
|
+
if (deps.trafficGovernor) {
|
|
457
|
+
this.trafficGovernor = deps.trafficGovernor;
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (process.env.NODE_ENV === 'test') {
|
|
461
|
+
this.trafficGovernor = createNoopProviderTrafficGovernor();
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
this.trafficGovernor = getSharedProviderTrafficGovernor();
|
|
176
465
|
}
|
|
177
466
|
logProviderRetrySwitch(args) {
|
|
178
467
|
const providerLabel = args.providerKey || 'unknown-provider';
|
|
@@ -249,6 +538,27 @@ export class HubRequestExecutor {
|
|
|
249
538
|
if (forcedRouteHint) {
|
|
250
539
|
metadataForAttempt.routeHint = forcedRouteHint;
|
|
251
540
|
}
|
|
541
|
+
const metadataRt = metadataForAttempt.__rt && typeof metadataForAttempt.__rt === 'object' && !Array.isArray(metadataForAttempt.__rt)
|
|
542
|
+
? metadataForAttempt.__rt
|
|
543
|
+
: {};
|
|
544
|
+
metadataForAttempt.__rt = {
|
|
545
|
+
...metadataRt,
|
|
546
|
+
disableVirtualRouterHitLog: true
|
|
547
|
+
};
|
|
548
|
+
// llmswitch Hub 仍有一条 legacy virtual-router-hit 调试日志(无 concurrency 信息)。
|
|
549
|
+
// 为保证控制台只保留一条统一格式(含 [concurrency:x/y])的命中日志,这里对
|
|
550
|
+
// metadata.logger 做最小化降噪:仅屏蔽 logVirtualRouterHit,不影响其他 logger 能力。
|
|
551
|
+
const loggerRecord = metadataForAttempt.logger &&
|
|
552
|
+
typeof metadataForAttempt.logger === 'object' &&
|
|
553
|
+
!Array.isArray(metadataForAttempt.logger)
|
|
554
|
+
? metadataForAttempt.logger
|
|
555
|
+
: undefined;
|
|
556
|
+
if (loggerRecord && typeof loggerRecord.logVirtualRouterHit === 'function') {
|
|
557
|
+
metadataForAttempt.logger = {
|
|
558
|
+
...loggerRecord,
|
|
559
|
+
logVirtualRouterHit: undefined
|
|
560
|
+
};
|
|
561
|
+
}
|
|
252
562
|
const clientHeadersForAttempt = cloneClientHeaders(metadataForAttempt?.clientHeaders) || inboundClientHeaders;
|
|
253
563
|
if (clientHeadersForAttempt) {
|
|
254
564
|
metadataForAttempt.clientHeaders = clientHeadersForAttempt;
|
|
@@ -271,6 +581,13 @@ export class HubRequestExecutor {
|
|
|
271
581
|
if (cooldownWaitMs &&
|
|
272
582
|
attempt < maxAttempts &&
|
|
273
583
|
poolCooldownWaitBudgetMs >= cooldownWaitMs) {
|
|
584
|
+
this.logStage(`${pipelineLabel}.completed`, providerRequestId, {
|
|
585
|
+
route: undefined,
|
|
586
|
+
target: undefined,
|
|
587
|
+
elapsedMs: Date.now() - hubStartedAtMs,
|
|
588
|
+
attempt,
|
|
589
|
+
recoverablePoolCooldown: true
|
|
590
|
+
});
|
|
274
591
|
this.logStage('provider.route_pool_cooldown_wait', providerRequestId, {
|
|
275
592
|
attempt,
|
|
276
593
|
waitMs: cooldownWaitMs,
|
|
@@ -308,6 +625,9 @@ export class HubRequestExecutor {
|
|
|
308
625
|
if (!initialRoutePool && Array.isArray(pipelineResult.routingDecision?.pool)) {
|
|
309
626
|
initialRoutePool = [...pipelineResult.routingDecision.pool];
|
|
310
627
|
}
|
|
628
|
+
const routePoolForAttempt = Array.isArray(pipelineResult.routingDecision?.pool)
|
|
629
|
+
? pipelineResult.routingDecision.pool
|
|
630
|
+
: (initialRoutePool ?? []);
|
|
311
631
|
const providerPayload = pipelineResult.providerPayload;
|
|
312
632
|
const target = pipelineResult.target;
|
|
313
633
|
if (!providerPayload || !target?.providerKey) {
|
|
@@ -392,11 +712,9 @@ export class HubRequestExecutor {
|
|
|
392
712
|
throw error;
|
|
393
713
|
}
|
|
394
714
|
recordAttempt({ error: true });
|
|
715
|
+
const retryBackoffMs = await waitBeforeRetry(error, { attempt });
|
|
395
716
|
const singleProviderPool = Boolean(initialRoutePool && initialRoutePool.length === 1 && initialRoutePool[0] === target.providerKey);
|
|
396
|
-
if (singleProviderPool) {
|
|
397
|
-
await waitBeforeRetry(error);
|
|
398
|
-
}
|
|
399
|
-
else if (target.providerKey) {
|
|
717
|
+
if (!singleProviderPool && target.providerKey) {
|
|
400
718
|
excludedProviderKeys.add(target.providerKey);
|
|
401
719
|
}
|
|
402
720
|
const switchAction = singleProviderPool ? 'retry_same_provider' : 'exclude_and_reroute';
|
|
@@ -422,7 +740,8 @@ export class HubRequestExecutor {
|
|
|
422
740
|
switchAction,
|
|
423
741
|
...(typeof retryError.statusCode === 'number' ? { statusCode: retryError.statusCode } : {}),
|
|
424
742
|
...(retryError.errorCode ? { errorCode: retryError.errorCode } : {}),
|
|
425
|
-
...(retryError.upstreamCode ? { upstreamCode: retryError.upstreamCode } : {})
|
|
743
|
+
...(retryError.upstreamCode ? { upstreamCode: retryError.upstreamCode } : {}),
|
|
744
|
+
retryBackoffMs
|
|
426
745
|
});
|
|
427
746
|
continue;
|
|
428
747
|
}
|
|
@@ -487,18 +806,65 @@ export class HubRequestExecutor {
|
|
|
487
806
|
runtimeKey,
|
|
488
807
|
attempt
|
|
489
808
|
});
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
providerKey: target.providerKey,
|
|
493
|
-
runtimeKey,
|
|
494
|
-
protocol: providerProtocol,
|
|
495
|
-
providerType: handle.providerType,
|
|
496
|
-
providerFamily: handle.providerFamily,
|
|
497
|
-
model: providerModel,
|
|
498
|
-
providerLabel,
|
|
499
|
-
attempt
|
|
500
|
-
});
|
|
809
|
+
let trafficPermit = null;
|
|
810
|
+
let providerSendStartedAtMs = 0;
|
|
501
811
|
try {
|
|
812
|
+
this.logStage('provider.traffic.acquire.start', input.requestId, {
|
|
813
|
+
providerKey: target.providerKey,
|
|
814
|
+
runtimeKey,
|
|
815
|
+
attempt
|
|
816
|
+
});
|
|
817
|
+
const trafficAcquired = await this.trafficGovernor.acquire({
|
|
818
|
+
runtimeKey,
|
|
819
|
+
providerKey: target.providerKey,
|
|
820
|
+
requestId: input.requestId,
|
|
821
|
+
runtime: resolveTrafficRuntimeProfile(runtimeKey, handle, target.providerKey),
|
|
822
|
+
// If current route pool has alternatives, do not stall too long on quota/RPM gating.
|
|
823
|
+
// Switch provider after 10s wait so weighted pools can continue serving.
|
|
824
|
+
softWaitTimeoutMs: routePoolForAttempt.length > 1 ? 10_000 : undefined
|
|
825
|
+
});
|
|
826
|
+
trafficPermit = trafficAcquired.permit;
|
|
827
|
+
if (trafficAcquired.waitedMs > 0) {
|
|
828
|
+
this.logStage('provider.traffic.acquire.wait', input.requestId, {
|
|
829
|
+
providerKey: target.providerKey,
|
|
830
|
+
runtimeKey,
|
|
831
|
+
waitedMs: trafficAcquired.waitedMs,
|
|
832
|
+
attempt
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
this.logStage('provider.traffic.acquire.completed', input.requestId, {
|
|
836
|
+
providerKey: target.providerKey,
|
|
837
|
+
runtimeKey,
|
|
838
|
+
maxInFlight: trafficAcquired.policy.concurrency.maxInFlight,
|
|
839
|
+
requestsPerMinute: trafficAcquired.policy.rpm.requestsPerMinute,
|
|
840
|
+
activeInFlight: trafficAcquired.activeInFlight,
|
|
841
|
+
rpmInWindow: trafficAcquired.rpmInWindow,
|
|
842
|
+
attempt
|
|
843
|
+
});
|
|
844
|
+
const routingDecisionRecord = pipelineResult.routingDecision && typeof pipelineResult.routingDecision === 'object'
|
|
845
|
+
? pipelineResult.routingDecision
|
|
846
|
+
: undefined;
|
|
847
|
+
emitVirtualRouterConcurrencyLog({
|
|
848
|
+
sessionId: readString(mergedMetadata.sessionId) ?? readString(mergedMetadata.conversationId),
|
|
849
|
+
routeName: pipelineResult.routingDecision?.routeName,
|
|
850
|
+
poolId: readString(routingDecisionRecord?.poolId),
|
|
851
|
+
providerKey: target.providerKey,
|
|
852
|
+
model: providerModel,
|
|
853
|
+
reason: readString(routingDecisionRecord?.reasoning),
|
|
854
|
+
activeInFlight: trafficAcquired.activeInFlight,
|
|
855
|
+
maxInFlight: trafficAcquired.policy.concurrency.maxInFlight
|
|
856
|
+
});
|
|
857
|
+
providerSendStartedAtMs = Date.now();
|
|
858
|
+
this.logStage('provider.send.start', input.requestId, {
|
|
859
|
+
providerKey: target.providerKey,
|
|
860
|
+
runtimeKey,
|
|
861
|
+
protocol: providerProtocol,
|
|
862
|
+
providerType: handle.providerType,
|
|
863
|
+
providerFamily: handle.providerFamily,
|
|
864
|
+
model: providerModel,
|
|
865
|
+
providerLabel,
|
|
866
|
+
attempt
|
|
867
|
+
});
|
|
502
868
|
const providerResponse = await handle.instance.processIncoming(providerPayload);
|
|
503
869
|
const responseStatus = extractResponseStatus(providerResponse);
|
|
504
870
|
this.logStage('provider.send.completed', input.requestId, {
|
|
@@ -582,6 +948,9 @@ export class HubRequestExecutor {
|
|
|
582
948
|
const convertedBodyRecord = converted.body && typeof converted.body === 'object'
|
|
583
949
|
? converted.body
|
|
584
950
|
: undefined;
|
|
951
|
+
if (convertedBodyRecord) {
|
|
952
|
+
backfillResponsesOutputTextIfMissing(convertedBodyRecord);
|
|
953
|
+
}
|
|
585
954
|
const finishReason = convertedBodyRecord && typeof convertedBodyRecord[STREAM_LOG_FINISH_REASON_KEY] === 'string'
|
|
586
955
|
? String(convertedBodyRecord[STREAM_LOG_FINISH_REASON_KEY])
|
|
587
956
|
: undefined;
|
|
@@ -683,6 +1052,22 @@ export class HubRequestExecutor {
|
|
|
683
1052
|
convertedStatus,
|
|
684
1053
|
attempt
|
|
685
1054
|
});
|
|
1055
|
+
const emptyAssistantSignal = detectRetryableEmptyAssistantResponse(converted.body);
|
|
1056
|
+
if (emptyAssistantSignal) {
|
|
1057
|
+
const bodyForError = converted.body;
|
|
1058
|
+
const errorToThrow = new Error(`Upstream returned empty assistant payload: ${emptyAssistantSignal.reason}`);
|
|
1059
|
+
errorToThrow.statusCode = 502;
|
|
1060
|
+
errorToThrow.status = 502;
|
|
1061
|
+
errorToThrow.code = 'EMPTY_ASSISTANT_RESPONSE';
|
|
1062
|
+
errorToThrow.response = { data: bodyForError };
|
|
1063
|
+
this.logStage('provider.empty_assistant_retry', input.requestId, {
|
|
1064
|
+
providerKey: target.providerKey,
|
|
1065
|
+
marker: emptyAssistantSignal.marker,
|
|
1066
|
+
reason: emptyAssistantSignal.reason,
|
|
1067
|
+
attempt
|
|
1068
|
+
});
|
|
1069
|
+
throw errorToThrow;
|
|
1070
|
+
}
|
|
686
1071
|
this.logStage('provider.usage_extract.start', input.requestId, {
|
|
687
1072
|
providerKey: target.providerKey,
|
|
688
1073
|
source: 'converted_response',
|
|
@@ -711,12 +1096,14 @@ export class HubRequestExecutor {
|
|
|
711
1096
|
attempt
|
|
712
1097
|
});
|
|
713
1098
|
recordAttempt({ usage: aggregatedUsage, error: false });
|
|
1099
|
+
const metadataHubStageTop = readHubStageTop(mergedMetadata);
|
|
714
1100
|
return {
|
|
715
1101
|
...converted,
|
|
716
1102
|
usageLogInfo: {
|
|
717
1103
|
providerKey: target.providerKey,
|
|
718
1104
|
model: providerModel,
|
|
719
1105
|
usage: aggregatedUsage,
|
|
1106
|
+
hubStageTop: metadataHubStageTop,
|
|
720
1107
|
requestStartedAtMs: requestStartedAt,
|
|
721
1108
|
timingRequestIds: Array.from(new Set([providerRequestId, input.requestId].filter((value) => Boolean(value)))),
|
|
722
1109
|
sessionId: mergedMetadata.sessionId,
|
|
@@ -839,13 +1226,11 @@ export class HubRequestExecutor {
|
|
|
839
1226
|
}
|
|
840
1227
|
// Record this failed provider attempt even if the overall request succeeds later via failover.
|
|
841
1228
|
recordAttempt({ error: true });
|
|
1229
|
+
const retryBackoffMs = await waitBeforeRetry(error, { attempt });
|
|
842
1230
|
const singleProviderPool = Boolean(initialRoutePool && initialRoutePool.length === 1 && initialRoutePool[0] === target.providerKey);
|
|
843
1231
|
if (promptTooLong && target.providerKey) {
|
|
844
1232
|
excludedProviderKeys.add(target.providerKey);
|
|
845
1233
|
}
|
|
846
|
-
else if (singleProviderPool) {
|
|
847
|
-
await waitBeforeRetry(error);
|
|
848
|
-
}
|
|
849
1234
|
if (!promptTooLong && !singleProviderPool && target.providerKey) {
|
|
850
1235
|
const is429 = status === 429;
|
|
851
1236
|
if (isAntigravityProviderKey(target.providerKey) && (isVerify || is429)) {
|
|
@@ -894,10 +1279,49 @@ export class HubRequestExecutor {
|
|
|
894
1279
|
...(typeof retryError.statusCode === 'number' ? { statusCode: retryError.statusCode } : {}),
|
|
895
1280
|
...(retryError.errorCode ? { errorCode: retryError.errorCode } : {}),
|
|
896
1281
|
...(retryError.upstreamCode ? { upstreamCode: retryError.upstreamCode } : {}),
|
|
1282
|
+
retryBackoffMs,
|
|
897
1283
|
...(promptTooLong ? { contextOverflowRetries, maxContextOverflowRetries: MAX_CONTEXT_OVERFLOW_RETRIES } : {})
|
|
898
1284
|
});
|
|
899
1285
|
continue;
|
|
900
1286
|
}
|
|
1287
|
+
finally {
|
|
1288
|
+
if (trafficPermit) {
|
|
1289
|
+
const releaseStartedAtMs = Date.now();
|
|
1290
|
+
this.logStage('provider.traffic.release.start', input.requestId, {
|
|
1291
|
+
providerKey: target.providerKey,
|
|
1292
|
+
runtimeKey,
|
|
1293
|
+
leaseId: trafficPermit.leaseId,
|
|
1294
|
+
attempt
|
|
1295
|
+
});
|
|
1296
|
+
try {
|
|
1297
|
+
const released = await this.trafficGovernor.release(trafficPermit);
|
|
1298
|
+
this.logStage('provider.traffic.release.completed', input.requestId, {
|
|
1299
|
+
providerKey: target.providerKey,
|
|
1300
|
+
runtimeKey,
|
|
1301
|
+
leaseId: trafficPermit.leaseId,
|
|
1302
|
+
released: released.released,
|
|
1303
|
+
activeInFlight: released.activeInFlight,
|
|
1304
|
+
elapsedMs: Date.now() - releaseStartedAtMs,
|
|
1305
|
+
attempt
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
catch (releaseError) {
|
|
1309
|
+
this.logStage('provider.traffic.release.error', input.requestId, {
|
|
1310
|
+
providerKey: target.providerKey,
|
|
1311
|
+
runtimeKey,
|
|
1312
|
+
leaseId: trafficPermit.leaseId,
|
|
1313
|
+
message: releaseError instanceof Error
|
|
1314
|
+
? releaseError.message
|
|
1315
|
+
: String(releaseError ?? 'Unknown release error'),
|
|
1316
|
+
elapsedMs: Date.now() - releaseStartedAtMs,
|
|
1317
|
+
attempt
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
finally {
|
|
1321
|
+
trafficPermit = null;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
901
1325
|
}
|
|
902
1326
|
throw lastError ?? new Error('Provider execution failed without response');
|
|
903
1327
|
}
|
|
@@ -924,7 +1348,8 @@ export class HubRequestExecutor {
|
|
|
924
1348
|
export const __requestExecutorTestables = {
|
|
925
1349
|
readString,
|
|
926
1350
|
extractRetryErrorSnapshot,
|
|
927
|
-
truncateReason
|
|
1351
|
+
truncateReason,
|
|
1352
|
+
detectRetryableEmptyAssistantResponse
|
|
928
1353
|
};
|
|
929
1354
|
export function createRequestExecutor(deps) {
|
|
930
1355
|
return new HubRequestExecutor(deps);
|