@jsonstudio/rcc 0.89.3 → 0.89.164
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 +240 -179
- package/config/modules.json +1 -11
- package/dist/build-info.js +2 -2
- package/dist/build-info.js.map +1 -1
- package/dist/client/gemini-cli/gemini-cli-protocol-client.d.ts +16 -0
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js +56 -0
- package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -0
- package/dist/client/openai/chat-protocol-client.js.map +1 -1
- package/dist/config/modules.json +1 -11
- package/dist/core/provider-health-manager.d.ts +17 -0
- package/dist/core/provider-health-manager.js +66 -0
- package/dist/core/provider-health-manager.js.map +1 -0
- package/dist/error-handling/route-error-hub.d.ts +48 -0
- package/dist/error-handling/route-error-hub.js +131 -0
- package/dist/error-handling/route-error-hub.js.map +1 -0
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/llmswitch/bridge.d.ts +2 -0
- package/dist/modules/llmswitch/bridge.js +17 -0
- package/dist/modules/llmswitch/bridge.js.map +1 -1
- package/dist/modules/pipeline/utils/colored-logger.d.ts +14 -0
- package/dist/modules/pipeline/utils/colored-logger.js +48 -0
- package/dist/modules/pipeline/utils/colored-logger.js.map +1 -0
- package/dist/modules/pipeline/utils/debug-logger.d.ts +2 -0
- package/dist/modules/pipeline/utils/debug-logger.js +36 -0
- package/dist/modules/pipeline/utils/debug-logger.js.map +1 -1
- package/dist/providers/auth/gemini-cli-userinfo-helper.d.ts +53 -0
- package/dist/providers/auth/gemini-cli-userinfo-helper.js +152 -0
- package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -0
- package/dist/providers/auth/oauth-auth.js +3 -2
- package/dist/providers/auth/oauth-auth.js.map +1 -1
- package/dist/providers/auth/oauth-lifecycle.js +21 -20
- package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
- package/dist/providers/auth/oauth-logger.d.ts +1 -0
- package/dist/providers/auth/oauth-logger.js +21 -0
- package/dist/providers/auth/oauth-logger.js.map +1 -0
- package/dist/providers/compat/compat-directory-loader.js +2 -55
- package/dist/providers/compat/compat-directory-loader.js.map +1 -1
- package/dist/providers/compat/compatibility-factory.d.ts +4 -4
- package/dist/providers/compat/compatibility-factory.js +108 -0
- package/dist/providers/compat/compatibility-factory.js.map +1 -1
- package/dist/providers/compat/glm/glm-compatibility.d.ts +2 -2
- package/dist/providers/compat/glm/glm-compatibility.js +7 -7
- package/dist/providers/compat/glm/glm-compatibility.js.map +1 -1
- package/dist/providers/compat/glm/index.js +0 -6
- package/dist/providers/compat/glm/index.js.map +1 -1
- package/dist/providers/compat/iflow/iflow-compatibility.d.ts +1 -1
- package/dist/providers/compat/iflow/iflow-compatibility.js +6 -6
- package/dist/providers/compat/iflow/iflow-compatibility.js.map +1 -1
- package/dist/providers/compat/index.d.ts +0 -6
- package/dist/providers/compat/index.js +0 -7
- package/dist/providers/compat/index.js.map +1 -1
- package/dist/providers/compat/lmstudio-compatibility.d.ts +2 -2
- package/dist/providers/compat/lmstudio-compatibility.js +4 -4
- package/dist/providers/compat/lmstudio-compatibility.js.map +1 -1
- package/dist/providers/compat/passthrough-compatibility.d.ts +1 -1
- package/dist/providers/compat/passthrough-compatibility.js +3 -3
- package/dist/providers/compat/passthrough-compatibility.js.map +1 -1
- package/dist/providers/compat/profiles/chat/glm/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/glm/index.js +6 -0
- package/dist/providers/compat/profiles/chat/glm/index.js.map +1 -0
- package/dist/providers/compat/profiles/chat/iflow/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/iflow/index.js +6 -0
- package/dist/providers/compat/profiles/chat/iflow/index.js.map +1 -0
- package/dist/providers/compat/profiles/chat/lmstudio/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/lmstudio/index.js +6 -0
- package/dist/providers/compat/profiles/chat/lmstudio/index.js.map +1 -0
- package/dist/providers/compat/profiles/chat/qwen/index.d.ts +6 -0
- package/dist/providers/compat/profiles/chat/qwen/index.js +6 -0
- package/dist/providers/compat/profiles/chat/qwen/index.js.map +1 -0
- package/dist/providers/compat/profiles/compat/passthrough/index.d.ts +6 -0
- package/dist/providers/compat/profiles/compat/passthrough/index.js +6 -0
- package/dist/providers/compat/profiles/compat/passthrough/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/c4m/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/c4m/index.js +6 -0
- package/dist/providers/compat/profiles/responses/c4m/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/default/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/default/index.js +6 -0
- package/dist/providers/compat/profiles/responses/default/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/fai/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/fai/index.js +6 -0
- package/dist/providers/compat/profiles/responses/fai/index.js.map +1 -0
- package/dist/providers/compat/profiles/responses/fc/index.d.ts +6 -0
- package/dist/providers/compat/profiles/responses/fc/index.js +6 -0
- package/dist/providers/compat/profiles/responses/fc/index.js.map +1 -0
- package/dist/providers/compat/qwen/index.js +0 -6
- package/dist/providers/compat/qwen/index.js.map +1 -1
- package/dist/providers/compat/qwen-compatibility.d.ts +2 -2
- package/dist/providers/compat/qwen-compatibility.js +4 -4
- package/dist/providers/compat/qwen-compatibility.js.map +1 -1
- package/dist/providers/compat/register-compat-module.d.ts +8 -0
- package/dist/providers/compat/register-compat-module.js +53 -0
- package/dist/providers/compat/register-compat-module.js.map +1 -0
- package/dist/providers/compat/responses/c4m-responses-compatibility.d.ts +6 -2
- package/dist/providers/compat/responses/c4m-responses-compatibility.js +85 -3
- package/dist/providers/compat/responses/c4m-responses-compatibility.js.map +1 -1
- package/dist/providers/compat/standard-compatibility-utils.js +45 -15
- package/dist/providers/compat/standard-compatibility-utils.js.map +1 -1
- package/dist/providers/core/api/provider-config.d.ts +1 -1
- package/dist/providers/core/api/provider-types.d.ts +3 -1
- package/dist/providers/core/api/provider-types.js +1 -0
- package/dist/providers/core/api/provider-types.js.map +1 -1
- package/dist/providers/core/config/service-profiles.js +5 -2
- package/dist/providers/core/config/service-profiles.js.map +1 -1
- package/dist/providers/core/runtime/base-provider.d.ts +3 -0
- package/dist/providers/core/runtime/base-provider.js +101 -6
- package/dist/providers/core/runtime/base-provider.js.map +1 -1
- package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +34 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.js +152 -0
- package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -0
- package/dist/providers/core/runtime/http-transport-provider.d.ts +1 -0
- package/dist/providers/core/runtime/http-transport-provider.js +178 -123
- package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
- package/dist/providers/core/runtime/provider-factory.d.ts +1 -1
- package/dist/providers/core/runtime/provider-factory.js +8 -0
- package/dist/providers/core/runtime/provider-factory.js.map +1 -1
- package/dist/providers/core/runtime/provider-runtime-metadata.d.ts +1 -0
- package/dist/providers/core/runtime/provider-runtime-metadata.js.map +1 -1
- package/dist/providers/core/runtime/responses-provider.d.ts +7 -0
- package/dist/providers/core/runtime/responses-provider.js +228 -12
- package/dist/providers/core/runtime/responses-provider.js.map +1 -1
- package/dist/providers/core/strategies/oauth-auth-code-flow.js +10 -9
- package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
- package/dist/providers/core/strategies/oauth-device-flow.js +10 -9
- package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
- package/dist/providers/core/utils/provider-error-reporter.js +63 -15
- package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
- package/dist/providers/core/utils/provider-type-utils.d.ts +1 -1
- package/dist/providers/core/utils/provider-type-utils.js +6 -1
- package/dist/providers/core/utils/provider-type-utils.js.map +1 -1
- package/dist/providers/core/utils/snapshot-writer.d.ts +10 -0
- package/dist/providers/core/utils/snapshot-writer.js +85 -0
- package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
- package/dist/providers/mock/mock-provider-runtime.js +44 -0
- package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
- package/dist/providers/profile/provider-profile-loader.js +26 -19
- package/dist/providers/profile/provider-profile-loader.js.map +1 -1
- package/dist/providers/profile/provider-profile.d.ts +2 -2
- package/dist/server/handlers/chat-handler.js +9 -3
- package/dist/server/handlers/chat-handler.js.map +1 -1
- package/dist/server/handlers/handler-utils.d.ts +7 -1
- package/dist/server/handlers/handler-utils.js +64 -52
- package/dist/server/handlers/handler-utils.js.map +1 -1
- package/dist/server/handlers/messages-handler.js +9 -3
- package/dist/server/handlers/messages-handler.js.map +1 -1
- package/dist/server/handlers/responses-handler.js +21 -13
- package/dist/server/handlers/responses-handler.js.map +1 -1
- package/dist/server/runtime/http-server/colored-logger.d.ts +1 -0
- package/dist/server/runtime/http-server/colored-logger.js +33 -0
- package/dist/server/runtime/http-server/colored-logger.js.map +1 -0
- package/dist/server/runtime/http-server/index.d.ts +3 -0
- package/dist/server/runtime/http-server/index.js +76 -19
- package/dist/server/runtime/http-server/index.js.map +1 -1
- package/dist/server/runtime/http-server/provider-utils.d.ts +3 -1
- package/dist/server/runtime/http-server/provider-utils.js +12 -2
- package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
- package/dist/server/runtime/http-server/request-executor.js +6 -2
- package/dist/server/runtime/http-server/request-executor.js.map +1 -1
- package/dist/server/runtime/http-server/routes.js +31 -11
- package/dist/server/runtime/http-server/routes.js.map +1 -1
- package/dist/server/runtime/http-server/types.d.ts +2 -1
- package/dist/utils/error-center-payload.d.ts +7 -0
- package/dist/utils/error-center-payload.js +67 -0
- package/dist/utils/error-center-payload.js.map +1 -0
- package/dist/utils/error-handler-registry.d.ts +7 -0
- package/dist/utils/error-handler-registry.js +44 -12
- package/dist/utils/error-handler-registry.js.map +1 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/codecs/responses-openai-codec.js +16 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +17 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +36 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +37 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +18 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +45 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/config/compat-profiles.json +38 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +314 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/config/version-switch.json +150 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +4 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-engine.js +667 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-profile-store.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-profile-store.js +76 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +62 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.js +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +110 -29
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.d.ts +14 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +23 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +34 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +4 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +26 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +71 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-conversation-store.d.ts +35 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-conversation-store.js +64 -19
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.d.ts +21 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +138 -22
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.d.ts +21 -0
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.js +116 -1
- package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +52 -2
- package/node_modules/@jsonstudio/llms/dist/filters/config/openai-openai.fieldmap.json +18 -0
- package/node_modules/@jsonstudio/llms/dist/filters/special/request-tools-normalize.js +20 -1
- package/node_modules/@jsonstudio/llms/dist/guidance/index.js +6 -2
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +16 -7
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +40 -37
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/default-thinking-keywords.d.ts +1 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/default-thinking-keywords.js +13 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +39 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +52 -11
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +340 -11
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +105 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +8 -0
- package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +2 -2
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.d.ts +2 -0
- package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +53 -11
- package/node_modules/@jsonstudio/llms/dist/test-output/virtual-router/results.json +1 -0
- package/node_modules/@jsonstudio/llms/dist/test-output/virtual-router/summary.json +12 -0
- package/node_modules/@jsonstudio/llms/dist/tools/tool-registry.js +4 -3
- package/node_modules/@jsonstudio/llms/package.json +3 -3
- package/package.json +11 -9
- package/scripts/analyze-routing-classifier.mjs +166 -0
- package/scripts/analyze-routing-samples.mjs +216 -0
- package/scripts/analyze-thinking-keywords.mjs +17 -2
- package/scripts/build-core.mjs +8 -0
- package/scripts/classify-sample-tools.mjs +252 -0
- package/scripts/ensure-llmswitch-mode.mjs +95 -0
- package/scripts/gen-build-info.mjs +58 -4
- package/scripts/install-global.sh +1 -1
- package/scripts/install-release.sh +7 -0
- package/scripts/mock-provider/run-regressions.mjs +60 -14
- package/scripts/tests/apply-patch-loop.mjs +100 -9
- package/scripts/tests/golden-provider-cycle.mjs +12 -1
- package/scripts/tests/responses-provider-dry-run.mjs +15 -1
- package/scripts/tests/virtual-router-health.mjs +12 -5
- package/scripts/tools/capture-provider-goldens.mjs +75 -25
- package/scripts/tools/responses-golden-dry-run.mjs +17 -1
- package/scripts/tools/responses-provider-replay.mjs +17 -1
- package/scripts/tools/sync-ci-goldens.mjs +131 -0
- package/scripts/verification/samples/openai-chat-list-local-files.json +19 -796
- package/scripts/verify-e2e-toolcall.mjs +52 -0
- package/dist/providers/compat/config/index.d.ts +0 -1
- package/dist/providers/compat/config/index.js +0 -5
- package/dist/providers/compat/config/index.js.map +0 -1
- package/dist/providers/compat/iflow/index.d.ts +0 -27
- package/dist/providers/compat/iflow/index.js +0 -32
- package/dist/providers/compat/iflow/index.js.map +0 -1
- package/dist/providers/compat/lmstudio/index.d.ts +0 -4
- package/dist/providers/compat/lmstudio/index.js +0 -10
- package/dist/providers/compat/lmstudio/index.js.map +0 -1
- package/dist/providers/compat/passthrough/index.d.ts +0 -4
- package/dist/providers/compat/passthrough/index.js +0 -9
- package/dist/providers/compat/passthrough/index.js.map +0 -1
- package/dist/providers/compat/responses/index.d.ts +0 -1
- package/dist/providers/compat/responses/index.js +0 -8
- package/dist/providers/compat/responses/index.js.map +0 -1
- package/dist/providers/core/composite/compat/anthropic.d.ts +0 -3
- package/dist/providers/core/composite/compat/anthropic.js +0 -7
- package/dist/providers/core/composite/compat/anthropic.js.map +0 -1
- package/dist/providers/core/composite/compat/gemini.d.ts +0 -3
- package/dist/providers/core/composite/compat/gemini.js +0 -7
- package/dist/providers/core/composite/compat/gemini.js.map +0 -1
- package/dist/providers/core/composite/compat/openai-compat-aggregator.d.ts +0 -9
- package/dist/providers/core/composite/compat/openai-compat-aggregator.js +0 -135
- package/dist/providers/core/composite/compat/openai-compat-aggregator.js.map +0 -1
- package/dist/providers/core/composite/compat/responses.d.ts +0 -3
- package/dist/providers/core/composite/compat/responses.js +0 -91
- package/dist/providers/core/composite/compat/responses.js.map +0 -1
- package/dist/providers/core/composite/provider-composite.d.ts +0 -50
- package/dist/providers/core/composite/provider-composite.js +0 -235
- package/dist/providers/core/composite/provider-composite.js.map +0 -1
- package/scripts/tests/apply-patch-loop.mjs.bak +0 -363
|
@@ -1,5 +1,125 @@
|
|
|
1
|
+
import { countRequestTokens } from './token-counter.js';
|
|
1
2
|
const THINKING_KEYWORDS = ['let me think', 'chain of thought', 'cot', 'reason step', 'deliberate'];
|
|
2
3
|
const WEB_TOOL_KEYWORDS = ['websearch', 'web_search', 'web-search', 'webfetch', 'web_fetch', 'web_request', 'search_web', 'internet_search'];
|
|
4
|
+
const READ_TOOL_EXACT = new Set([
|
|
5
|
+
'read_file',
|
|
6
|
+
'read_text',
|
|
7
|
+
'view_file',
|
|
8
|
+
'view_code',
|
|
9
|
+
'view_document',
|
|
10
|
+
'open_file',
|
|
11
|
+
'get_file',
|
|
12
|
+
'download_file',
|
|
13
|
+
'describe_current_request',
|
|
14
|
+
'list_dir',
|
|
15
|
+
'list_directory',
|
|
16
|
+
'list_files',
|
|
17
|
+
'list_documents',
|
|
18
|
+
'list_resources',
|
|
19
|
+
'search_files',
|
|
20
|
+
'find_files'
|
|
21
|
+
]);
|
|
22
|
+
const WRITE_TOOL_EXACT = new Set([
|
|
23
|
+
'apply_patch',
|
|
24
|
+
'write_file',
|
|
25
|
+
'create_file',
|
|
26
|
+
'modify_file',
|
|
27
|
+
'edit_file',
|
|
28
|
+
'update_file',
|
|
29
|
+
'save_file',
|
|
30
|
+
'append_file',
|
|
31
|
+
'replace_file',
|
|
32
|
+
'delete_file',
|
|
33
|
+
'remove_file',
|
|
34
|
+
'rename_file',
|
|
35
|
+
'move_file',
|
|
36
|
+
'copy_file',
|
|
37
|
+
'mkdir',
|
|
38
|
+
'rmdir'
|
|
39
|
+
]);
|
|
40
|
+
const SEARCH_TOOL_EXACT = new Set(['websearch', 'web_search', 'search_web', 'internet_search', 'webfetch', 'web_fetch']);
|
|
41
|
+
const READ_TOOL_KEYWORDS = ['read', 'list', 'view', 'download', 'open', 'show', 'fetch', 'inspect'];
|
|
42
|
+
const WRITE_TOOL_KEYWORDS = ['write', 'patch', 'modify', 'edit', 'create', 'update', 'append', 'replace', 'delete', 'remove'];
|
|
43
|
+
const SEARCH_TOOL_KEYWORDS = ['search', 'websearch', 'web_fetch', 'webfetch', 'web-request', 'web_request', 'internet'];
|
|
44
|
+
const SHELL_TOOL_NAMES = new Set(['shell_command', 'shell', 'bash']);
|
|
45
|
+
const SHELL_HEREDOC_PATTERN = /<<\s*['"]?[a-z0-9_-]+/i;
|
|
46
|
+
const SHELL_WRITE_PATTERNS = [
|
|
47
|
+
'apply_patch',
|
|
48
|
+
'sed -i',
|
|
49
|
+
'perl -pi',
|
|
50
|
+
'tee ',
|
|
51
|
+
'cat <<',
|
|
52
|
+
'cat >',
|
|
53
|
+
'printf >',
|
|
54
|
+
'touch ',
|
|
55
|
+
'truncate',
|
|
56
|
+
'mkdir',
|
|
57
|
+
'mktemp',
|
|
58
|
+
'rmdir',
|
|
59
|
+
'rm ',
|
|
60
|
+
'rm-',
|
|
61
|
+
'unlink',
|
|
62
|
+
'mv ',
|
|
63
|
+
'cp ',
|
|
64
|
+
'ln -',
|
|
65
|
+
'chmod',
|
|
66
|
+
'chown',
|
|
67
|
+
'chgrp',
|
|
68
|
+
'tar ',
|
|
69
|
+
'git add',
|
|
70
|
+
'git commit',
|
|
71
|
+
'git apply',
|
|
72
|
+
'git am',
|
|
73
|
+
'git rebase',
|
|
74
|
+
'git checkout',
|
|
75
|
+
'git merge',
|
|
76
|
+
'patch <<',
|
|
77
|
+
'npm install',
|
|
78
|
+
'pnpm install',
|
|
79
|
+
'yarn add',
|
|
80
|
+
'yarn install',
|
|
81
|
+
'pip install',
|
|
82
|
+
'pip3 install',
|
|
83
|
+
'brew install',
|
|
84
|
+
'cargo add',
|
|
85
|
+
'cargo install',
|
|
86
|
+
'go install',
|
|
87
|
+
'make install'
|
|
88
|
+
];
|
|
89
|
+
const SHELL_SEARCH_PATTERNS = [
|
|
90
|
+
'rg ',
|
|
91
|
+
'rg-',
|
|
92
|
+
'grep ',
|
|
93
|
+
'grep-',
|
|
94
|
+
'ripgrep',
|
|
95
|
+
'find ',
|
|
96
|
+
'fd ',
|
|
97
|
+
'locate ',
|
|
98
|
+
'search ',
|
|
99
|
+
'ack ',
|
|
100
|
+
'ag ',
|
|
101
|
+
'where ',
|
|
102
|
+
'which ',
|
|
103
|
+
'codesearch'
|
|
104
|
+
];
|
|
105
|
+
const SHELL_READ_PATTERNS = [
|
|
106
|
+
'ls',
|
|
107
|
+
'dir ',
|
|
108
|
+
'pwd',
|
|
109
|
+
'cat ',
|
|
110
|
+
'type ',
|
|
111
|
+
'head ',
|
|
112
|
+
'tail ',
|
|
113
|
+
'stat',
|
|
114
|
+
'tree',
|
|
115
|
+
'wc ',
|
|
116
|
+
'du ',
|
|
117
|
+
'printf "',
|
|
118
|
+
'python - <<',
|
|
119
|
+
'python -c',
|
|
120
|
+
'node - <<',
|
|
121
|
+
'node -e'
|
|
122
|
+
];
|
|
3
123
|
export function buildRoutingFeatures(request, metadata) {
|
|
4
124
|
const latestUserMessage = getLatestUserMessage(request.messages);
|
|
5
125
|
const assistantMessages = request.messages.filter((msg) => msg.role === 'assistant');
|
|
@@ -7,17 +127,18 @@ export function buildRoutingFeatures(request, metadata) {
|
|
|
7
127
|
const normalizedUserText = latestUserText.toLowerCase();
|
|
8
128
|
const hasTools = Array.isArray(request.tools) && request.tools.length > 0;
|
|
9
129
|
const hasToolCallResponses = assistantMessages.some((msg) => Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0);
|
|
10
|
-
const estimatedTokens =
|
|
130
|
+
const estimatedTokens = computeRequestTokens(request, latestUserText);
|
|
11
131
|
const hasThinking = detectKeyword(normalizedUserText, THINKING_KEYWORDS);
|
|
12
132
|
const hasVisionTool = detectVisionTool(request);
|
|
13
133
|
const hasImageAttachment = hasVisionTool && detectImageAttachment(latestUserMessage);
|
|
14
134
|
const hasCodingTool = detectCodingTool(request);
|
|
15
135
|
const hasWebTool = detectWebTool(request);
|
|
16
136
|
const hasThinkingKeyword = hasThinking || detectExtendedThinkingKeyword(normalizedUserText);
|
|
137
|
+
const lastAssistantTool = detectLastAssistantToolCategory(assistantMessages);
|
|
17
138
|
return {
|
|
18
139
|
requestId: metadata.requestId,
|
|
19
140
|
model: request.model,
|
|
20
|
-
totalMessages:
|
|
141
|
+
totalMessages: request.messages?.length ?? 0,
|
|
21
142
|
userTextSample: latestUserText.slice(0, 2000),
|
|
22
143
|
toolCount: request.tools?.length ?? 0,
|
|
23
144
|
hasTools,
|
|
@@ -28,6 +149,8 @@ export function buildRoutingFeatures(request, metadata) {
|
|
|
28
149
|
hasCodingTool,
|
|
29
150
|
hasThinkingKeyword,
|
|
30
151
|
estimatedTokens,
|
|
152
|
+
lastAssistantToolCategory: lastAssistantTool?.category,
|
|
153
|
+
lastAssistantToolName: lastAssistantTool?.name,
|
|
31
154
|
metadata: {
|
|
32
155
|
...metadata
|
|
33
156
|
}
|
|
@@ -97,12 +220,15 @@ function detectCodingTool(request) {
|
|
|
97
220
|
return false;
|
|
98
221
|
}
|
|
99
222
|
return request.tools.some((tool) => {
|
|
100
|
-
const functionName = extractToolName(tool);
|
|
101
|
-
const description = extractToolDescription(tool);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
223
|
+
const functionName = extractToolName(tool).toLowerCase();
|
|
224
|
+
const description = (extractToolDescription(tool) || '').toLowerCase();
|
|
225
|
+
if (!functionName && !description) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
if (WRITE_TOOL_EXACT.has(functionName)) {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
return WRITE_TOOL_KEYWORDS.some((keyword) => functionName.includes(keyword.toLowerCase()) || description.includes(keyword.toLowerCase()));
|
|
106
232
|
});
|
|
107
233
|
}
|
|
108
234
|
function detectWebTool(request) {
|
|
@@ -125,12 +251,20 @@ function detectExtendedThinkingKeyword(text) {
|
|
|
125
251
|
const keywords = ['仔细分析', '思考', '超级思考', '深度思考', 'careful analysis', 'deep thinking', 'deliberate'];
|
|
126
252
|
return keywords.some((keyword) => text.includes(keyword));
|
|
127
253
|
}
|
|
128
|
-
function
|
|
254
|
+
function computeRequestTokens(request, fallbackText) {
|
|
255
|
+
try {
|
|
256
|
+
return countRequestTokens(request);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return fallbackEstimateTokens(fallbackText, request.messages?.length ?? 0);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function fallbackEstimateTokens(text, messageCount) {
|
|
129
263
|
if (!text) {
|
|
130
|
-
return Math.max(32, messageCount * 16);
|
|
264
|
+
return Math.max(32, Math.max(messageCount, 1) * 16);
|
|
131
265
|
}
|
|
132
266
|
const rough = Math.ceil(text.length / 4);
|
|
133
|
-
return Math.max(rough, messageCount * 32);
|
|
267
|
+
return Math.max(rough, Math.max(messageCount, 1) * 32);
|
|
134
268
|
}
|
|
135
269
|
function extractToolName(tool) {
|
|
136
270
|
if (!tool || typeof tool !== 'object') {
|
|
@@ -160,3 +294,198 @@ function extractToolDescription(tool) {
|
|
|
160
294
|
}
|
|
161
295
|
return '';
|
|
162
296
|
}
|
|
297
|
+
function detectLastAssistantToolCategory(messages) {
|
|
298
|
+
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
299
|
+
const msg = messages[idx];
|
|
300
|
+
if (!msg || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
let fallback;
|
|
304
|
+
for (const call of msg.tool_calls) {
|
|
305
|
+
const classification = classifyToolCall(call);
|
|
306
|
+
if (!classification) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (!fallback) {
|
|
310
|
+
fallback = classification;
|
|
311
|
+
}
|
|
312
|
+
if (classification.category !== 'other') {
|
|
313
|
+
return classification;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (fallback) {
|
|
317
|
+
return fallback;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
function classifyToolCall(call) {
|
|
323
|
+
if (!call || typeof call !== 'object') {
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
const functionName = typeof call?.function?.name === 'string' && call.function.name.trim()
|
|
327
|
+
? canonicalizeToolName(call.function.name)
|
|
328
|
+
: '';
|
|
329
|
+
if (!functionName) {
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
const argsObject = parseToolArguments(call?.function?.arguments);
|
|
333
|
+
const commandText = extractCommandText(argsObject);
|
|
334
|
+
const nameCategory = categorizeToolName(functionName);
|
|
335
|
+
if (nameCategory === 'write' || nameCategory === 'read' || nameCategory === 'search') {
|
|
336
|
+
return { category: nameCategory, name: functionName };
|
|
337
|
+
}
|
|
338
|
+
if (SHELL_TOOL_NAMES.has(functionName)) {
|
|
339
|
+
const shellCategory = classifyShellCommand(commandText);
|
|
340
|
+
return { category: shellCategory, name: functionName };
|
|
341
|
+
}
|
|
342
|
+
if (commandText) {
|
|
343
|
+
const derivedCategory = classifyShellCommand(commandText);
|
|
344
|
+
if (derivedCategory !== 'other') {
|
|
345
|
+
return { category: derivedCategory, name: functionName };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return { category: 'other', name: functionName };
|
|
349
|
+
}
|
|
350
|
+
function canonicalizeToolName(rawName) {
|
|
351
|
+
const trimmed = rawName.trim();
|
|
352
|
+
const markerIndex = trimmed.indexOf('arg_');
|
|
353
|
+
if (markerIndex > 0) {
|
|
354
|
+
return trimmed.slice(0, markerIndex);
|
|
355
|
+
}
|
|
356
|
+
return trimmed;
|
|
357
|
+
}
|
|
358
|
+
function parseToolArguments(rawArguments) {
|
|
359
|
+
if (!rawArguments) {
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
if (typeof rawArguments === 'string') {
|
|
363
|
+
try {
|
|
364
|
+
return JSON.parse(rawArguments);
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return rawArguments;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (typeof rawArguments === 'object') {
|
|
371
|
+
return rawArguments;
|
|
372
|
+
}
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
function extractCommandText(args) {
|
|
376
|
+
if (!args) {
|
|
377
|
+
return '';
|
|
378
|
+
}
|
|
379
|
+
if (typeof args === 'string') {
|
|
380
|
+
return args;
|
|
381
|
+
}
|
|
382
|
+
if (Array.isArray(args)) {
|
|
383
|
+
return args.map((item) => (typeof item === 'string' ? item : '')).filter(Boolean).join(' ');
|
|
384
|
+
}
|
|
385
|
+
if (typeof args === 'object') {
|
|
386
|
+
const record = args;
|
|
387
|
+
const command = record.command;
|
|
388
|
+
const input = record.input;
|
|
389
|
+
const nestedArgs = record.args;
|
|
390
|
+
if (typeof command === 'string') {
|
|
391
|
+
return command;
|
|
392
|
+
}
|
|
393
|
+
if (Array.isArray(command)) {
|
|
394
|
+
return command.map((item) => (typeof item === 'string' ? item : '')).filter(Boolean).join(' ');
|
|
395
|
+
}
|
|
396
|
+
if (typeof input === 'string') {
|
|
397
|
+
return input;
|
|
398
|
+
}
|
|
399
|
+
if (typeof nestedArgs === 'string') {
|
|
400
|
+
return nestedArgs;
|
|
401
|
+
}
|
|
402
|
+
if (Array.isArray(nestedArgs)) {
|
|
403
|
+
return nestedArgs.map((item) => (typeof item === 'string' ? item : '')).filter(Boolean).join(' ');
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return '';
|
|
407
|
+
}
|
|
408
|
+
function categorizeToolName(name) {
|
|
409
|
+
const normalized = name.toLowerCase();
|
|
410
|
+
if (SEARCH_TOOL_EXACT.has(normalized) ||
|
|
411
|
+
SEARCH_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
412
|
+
return 'search';
|
|
413
|
+
}
|
|
414
|
+
if (READ_TOOL_EXACT.has(normalized) ||
|
|
415
|
+
READ_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
416
|
+
return 'read';
|
|
417
|
+
}
|
|
418
|
+
if (WRITE_TOOL_EXACT.has(normalized) ||
|
|
419
|
+
WRITE_TOOL_KEYWORDS.some((keyword) => normalized.includes(keyword.toLowerCase()))) {
|
|
420
|
+
return 'write';
|
|
421
|
+
}
|
|
422
|
+
return 'other';
|
|
423
|
+
}
|
|
424
|
+
function classifyShellCommand(command) {
|
|
425
|
+
if (!command) {
|
|
426
|
+
return 'other';
|
|
427
|
+
}
|
|
428
|
+
if (SHELL_HEREDOC_PATTERN.test(command)) {
|
|
429
|
+
return 'write';
|
|
430
|
+
}
|
|
431
|
+
const segments = splitCommandSegments(command).map(stripShellWrapper);
|
|
432
|
+
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_WRITE_PATTERNS))) {
|
|
433
|
+
return 'write';
|
|
434
|
+
}
|
|
435
|
+
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_SEARCH_PATTERNS))) {
|
|
436
|
+
return 'search';
|
|
437
|
+
}
|
|
438
|
+
if (segments.some((segment) => matchesAnyPattern(segment, SHELL_READ_PATTERNS))) {
|
|
439
|
+
return 'read';
|
|
440
|
+
}
|
|
441
|
+
const stripped = stripShellWrapper(command);
|
|
442
|
+
if (matchesAnyPattern(stripped, SHELL_WRITE_PATTERNS)) {
|
|
443
|
+
return 'write';
|
|
444
|
+
}
|
|
445
|
+
if (matchesAnyPattern(stripped, SHELL_SEARCH_PATTERNS)) {
|
|
446
|
+
return 'search';
|
|
447
|
+
}
|
|
448
|
+
if (matchesAnyPattern(stripped, SHELL_READ_PATTERNS)) {
|
|
449
|
+
return 'read';
|
|
450
|
+
}
|
|
451
|
+
return 'other';
|
|
452
|
+
}
|
|
453
|
+
function splitCommandSegments(command) {
|
|
454
|
+
return command
|
|
455
|
+
.split(/(?:\r?\n|&&|\|\||;)/)
|
|
456
|
+
.map((segment) => segment.trim())
|
|
457
|
+
.filter(Boolean);
|
|
458
|
+
}
|
|
459
|
+
function matchesAnyPattern(text, patterns) {
|
|
460
|
+
if (!text) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
const trimmed = text.trim().toLowerCase();
|
|
464
|
+
const normalized = trimmed.startsWith('sudo ') ? trimmed.slice(5).trim() : trimmed;
|
|
465
|
+
return patterns.some((pattern) => {
|
|
466
|
+
const lowered = pattern.toLowerCase().trim();
|
|
467
|
+
return normalized.startsWith(lowered);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
function stripShellWrapper(segment) {
|
|
471
|
+
let normalized = segment.trim();
|
|
472
|
+
const wrappers = ['bash -lc ', 'bash -lc', 'sh -c ', 'sh -c', '/bin/sh -c ', '/bin/sh -c', 'env -i bash -lc ', 'env -i bash -lc'];
|
|
473
|
+
for (const wrapper of wrappers) {
|
|
474
|
+
if (normalized.toLowerCase().startsWith(wrapper)) {
|
|
475
|
+
normalized = normalized.slice(wrapper.length).trim();
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
normalized = trimEnclosingQuotes(normalized);
|
|
480
|
+
if (normalized.startsWith('sudo ')) {
|
|
481
|
+
normalized = normalized.slice(5).trim();
|
|
482
|
+
}
|
|
483
|
+
return normalized;
|
|
484
|
+
}
|
|
485
|
+
function trimEnclosingQuotes(value) {
|
|
486
|
+
if ((value.startsWith('"') && value.endsWith('"') && value.length > 1) ||
|
|
487
|
+
(value.startsWith("'") && value.endsWith("'") && value.length > 1)) {
|
|
488
|
+
return value.slice(1, -1).trim();
|
|
489
|
+
}
|
|
490
|
+
return value;
|
|
491
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { encoding_for_model, get_encoding } from 'tiktoken';
|
|
2
|
+
const DEFAULT_ENCODING = 'cl100k_base';
|
|
3
|
+
const encoderCache = new Map();
|
|
4
|
+
let defaultEncoder = null;
|
|
5
|
+
function getEncoder(model) {
|
|
6
|
+
if (model) {
|
|
7
|
+
const normalized = model.trim();
|
|
8
|
+
if (encoderCache.has(normalized)) {
|
|
9
|
+
return encoderCache.get(normalized);
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const encoder = encoding_for_model(normalized);
|
|
13
|
+
encoderCache.set(normalized, encoder);
|
|
14
|
+
return encoder;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// fall back to default encoder
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (!defaultEncoder) {
|
|
21
|
+
defaultEncoder = get_encoding(DEFAULT_ENCODING);
|
|
22
|
+
}
|
|
23
|
+
return defaultEncoder;
|
|
24
|
+
}
|
|
25
|
+
export function countRequestTokens(request) {
|
|
26
|
+
const encoder = getEncoder(request.model);
|
|
27
|
+
let total = 0;
|
|
28
|
+
for (const message of request.messages || []) {
|
|
29
|
+
total += countMessageTokens(message, encoder);
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(request.tools)) {
|
|
32
|
+
for (const tool of request.tools) {
|
|
33
|
+
total += encodeText(JSON.stringify(tool ?? {}), encoder);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (request.parameters) {
|
|
37
|
+
total += encodeText(JSON.stringify(request.parameters), encoder);
|
|
38
|
+
}
|
|
39
|
+
if (request.metadata) {
|
|
40
|
+
total += encodeText(JSON.stringify(request.metadata), encoder);
|
|
41
|
+
}
|
|
42
|
+
return total;
|
|
43
|
+
}
|
|
44
|
+
function countMessageTokens(message, encoder) {
|
|
45
|
+
let total = 0;
|
|
46
|
+
total += encodeText(message.role, encoder);
|
|
47
|
+
total += encodeContent(message.content, encoder);
|
|
48
|
+
if (Array.isArray(message.tool_calls)) {
|
|
49
|
+
for (const call of message.tool_calls) {
|
|
50
|
+
total += encodeText(JSON.stringify(call ?? {}), encoder);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(message.metadata?.toolRuns)) {
|
|
54
|
+
total += encodeText(JSON.stringify(message.metadata?.toolRuns), encoder);
|
|
55
|
+
}
|
|
56
|
+
if (message.name) {
|
|
57
|
+
total += encodeText(message.name, encoder);
|
|
58
|
+
}
|
|
59
|
+
if (message.metadata) {
|
|
60
|
+
total += encodeText(JSON.stringify(message.metadata), encoder);
|
|
61
|
+
}
|
|
62
|
+
if (message.tool_call_id) {
|
|
63
|
+
total += encodeText(message.tool_call_id, encoder);
|
|
64
|
+
}
|
|
65
|
+
return total;
|
|
66
|
+
}
|
|
67
|
+
function encodeContent(content, encoder) {
|
|
68
|
+
if (content === null || content === undefined) {
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
if (typeof content === 'string') {
|
|
72
|
+
return encodeText(content, encoder);
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(content)) {
|
|
75
|
+
let total = 0;
|
|
76
|
+
for (const part of content) {
|
|
77
|
+
if (typeof part === 'string') {
|
|
78
|
+
total += encodeText(part, encoder);
|
|
79
|
+
}
|
|
80
|
+
else if (part && typeof part === 'object') {
|
|
81
|
+
if (typeof part.text === 'string') {
|
|
82
|
+
total += encodeText(part.text, encoder);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
total += encodeText(JSON.stringify(part), encoder);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return total;
|
|
90
|
+
}
|
|
91
|
+
if (typeof content === 'object') {
|
|
92
|
+
return encodeText(JSON.stringify(content), encoder);
|
|
93
|
+
}
|
|
94
|
+
return encodeText(String(content), encoder);
|
|
95
|
+
}
|
|
96
|
+
function encodeText(value, encoder) {
|
|
97
|
+
if (value === null || value === undefined) {
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
const text = typeof value === 'string' ? value : String(value);
|
|
101
|
+
if (!text.trim()) {
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
return encoder.encode(text).length;
|
|
105
|
+
}
|
|
@@ -96,6 +96,11 @@ export interface RouterMetadataInput {
|
|
|
96
96
|
providerProtocol?: string;
|
|
97
97
|
stage?: 'inbound' | 'outbound' | 'response';
|
|
98
98
|
routeHint?: string;
|
|
99
|
+
responsesResume?: {
|
|
100
|
+
previousRequestId?: string;
|
|
101
|
+
restoredFromResponseId?: string;
|
|
102
|
+
[key: string]: unknown;
|
|
103
|
+
};
|
|
99
104
|
}
|
|
100
105
|
export interface RoutingFeatures {
|
|
101
106
|
requestId: string;
|
|
@@ -111,6 +116,8 @@ export interface RoutingFeatures {
|
|
|
111
116
|
hasCodingTool: boolean;
|
|
112
117
|
hasThinkingKeyword: boolean;
|
|
113
118
|
estimatedTokens: number;
|
|
119
|
+
lastAssistantToolCategory?: 'read' | 'write' | 'search' | 'other';
|
|
120
|
+
lastAssistantToolName?: string;
|
|
114
121
|
metadata: RouterMetadataInput;
|
|
115
122
|
}
|
|
116
123
|
export interface ClassificationResult {
|
|
@@ -203,6 +210,7 @@ export interface ProviderErrorEvent {
|
|
|
203
210
|
stage: string;
|
|
204
211
|
status?: number;
|
|
205
212
|
recoverable?: boolean;
|
|
213
|
+
affectsHealth?: boolean;
|
|
206
214
|
runtime: ProviderErrorRuntimeMetadata;
|
|
207
215
|
timestamp: number;
|
|
208
216
|
details?: Record<string, unknown>;
|
|
@@ -112,6 +112,7 @@ export class ResponsesResponseBuilder {
|
|
|
112
112
|
this.handleOutputItemDone(event);
|
|
113
113
|
break;
|
|
114
114
|
case 'required_action':
|
|
115
|
+
case 'response.required_action':
|
|
115
116
|
this.handleRequiredAction(event);
|
|
116
117
|
break;
|
|
117
118
|
case 'response.done':
|
|
@@ -395,6 +396,36 @@ export class ResponsesResponseBuilder {
|
|
|
395
396
|
outputItemState.arguments = '';
|
|
396
397
|
outputItemState.lastEventTime = event.timestamp;
|
|
397
398
|
}
|
|
399
|
+
coerceArgumentsChunk(raw) {
|
|
400
|
+
if (typeof raw === 'string') {
|
|
401
|
+
return raw;
|
|
402
|
+
}
|
|
403
|
+
if (raw && typeof raw === 'object') {
|
|
404
|
+
try {
|
|
405
|
+
return JSON.stringify(raw);
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
return String(raw);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
shouldOverrideArguments(current, incoming) {
|
|
414
|
+
if (!incoming) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
const trimmed = incoming.trim();
|
|
418
|
+
if (!current || !current.length) {
|
|
419
|
+
return trimmed.length > 0;
|
|
420
|
+
}
|
|
421
|
+
if (!trimmed.length) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
if (trimmed === '{}' || trimmed.toLowerCase() === 'null') {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
398
429
|
/**
|
|
399
430
|
* 处理function_call.delta事件
|
|
400
431
|
*/
|
|
@@ -404,10 +435,10 @@ export class ResponsesResponseBuilder {
|
|
|
404
435
|
if (!outputItemState) {
|
|
405
436
|
throw new Error(`Output item not found: ${data.item_id}`);
|
|
406
437
|
}
|
|
407
|
-
const chunk = (data
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (
|
|
438
|
+
const chunk = this.coerceArgumentsChunk(data?.delta?.arguments) ??
|
|
439
|
+
this.coerceArgumentsChunk(data?.delta) ??
|
|
440
|
+
this.coerceArgumentsChunk(data?.arguments);
|
|
441
|
+
if (chunk) {
|
|
411
442
|
outputItemState.arguments = (outputItemState.arguments || '') + chunk;
|
|
412
443
|
}
|
|
413
444
|
outputItemState.lastEventTime = event.timestamp;
|
|
@@ -445,8 +476,16 @@ export class ResponsesResponseBuilder {
|
|
|
445
476
|
outputItemState.name = data.name;
|
|
446
477
|
if (typeof data.call_id === 'string' && data.call_id)
|
|
447
478
|
outputItemState.callId = data.call_id;
|
|
448
|
-
|
|
449
|
-
|
|
479
|
+
const finalChunk = this.coerceArgumentsChunk(data?.arguments) ??
|
|
480
|
+
this.coerceArgumentsChunk(data?.delta?.arguments) ??
|
|
481
|
+
this.coerceArgumentsChunk(data?.delta);
|
|
482
|
+
if (this.shouldOverrideArguments(outputItemState.arguments, finalChunk)) {
|
|
483
|
+
outputItemState.arguments = finalChunk;
|
|
484
|
+
}
|
|
485
|
+
else if (!outputItemState.arguments && finalChunk) {
|
|
486
|
+
// 没有任何累计增量时,保底写入 done 事件里的值
|
|
487
|
+
outputItemState.arguments = finalChunk;
|
|
488
|
+
}
|
|
450
489
|
}
|
|
451
490
|
catch { /* ignore */ }
|
|
452
491
|
outputItemState.status = 'completed';
|
|
@@ -630,14 +669,17 @@ export class ResponsesResponseBuilder {
|
|
|
630
669
|
this.state = 'error';
|
|
631
670
|
}
|
|
632
671
|
handleResponseCompleted(event) {
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
672
|
+
const payload = event.data?.response ?? event.data;
|
|
673
|
+
const usage = (payload && payload.usage)
|
|
674
|
+
? payload.usage
|
|
675
|
+
: event.data?.usage;
|
|
676
|
+
if (usage) {
|
|
677
|
+
this.response.usage = usage;
|
|
636
678
|
}
|
|
637
679
|
// 标准化完成态:部分上游的 response.completed 不包含 status 字段
|
|
638
680
|
// 若未提供明确的完成状态,统一标记为 'completed',而不是沿用之前的 in_progress。
|
|
639
|
-
this.response.status = (
|
|
640
|
-
?
|
|
681
|
+
this.response.status = (payload && payload.status != null)
|
|
682
|
+
? payload.status
|
|
641
683
|
: 'completed';
|
|
642
684
|
// 将已聚合的输出写回并标记完成(若为空数组也重建)
|
|
643
685
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"samplesRoot": "/Users/fanzhang/.routecodex/codex-samples",
|
|
3
|
+
"configPath": "/Users/fanzhang/Documents/github/sharedmodule/llmswitch-core/test/virtual-router/virtual-router.config.json",
|
|
4
|
+
"stats": {
|
|
5
|
+
"totalSamples": 0,
|
|
6
|
+
"processed": 0,
|
|
7
|
+
"routes": {},
|
|
8
|
+
"providers": {},
|
|
9
|
+
"errors": [],
|
|
10
|
+
"scenarios": {}
|
|
11
|
+
}
|
|
12
|
+
}
|