@jsonstudio/rcc 0.90.814 → 0.90.872

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.
Files changed (217) hide show
  1. package/README.md +8 -0
  2. package/configsamples/provider-default/ali-coding-plan/config.v2.json +76 -0
  3. package/configsamples/provider-default/antigravity/config.v2.json +142 -0
  4. package/configsamples/provider-default/ark-coding-plan/config.v2.json +64 -0
  5. package/configsamples/provider-default/crs/config.v2.json +54 -0
  6. package/configsamples/provider-default/deepseek-web/config.v2.json +56 -0
  7. package/configsamples/provider-default/gemini/config.v2.json +43 -0
  8. package/configsamples/provider-default/gemini-cli/config.v2.json +45 -0
  9. package/configsamples/provider-default/gemini-native/config.v2.json +208 -0
  10. package/configsamples/provider-default/glm/config.v2.json +33 -0
  11. package/configsamples/provider-default/glm-anthropic/config.v2.json +29 -0
  12. package/configsamples/provider-default/kimi/config.v2.json +25 -0
  13. package/configsamples/provider-default/lmstudio/config.v2.json +79 -0
  14. package/configsamples/provider-default/lmstudio-proxy/config.v2.json +78 -0
  15. package/configsamples/provider-default/manifest.json +31 -0
  16. package/configsamples/provider-default/meituan/config.v2.json +20 -0
  17. package/configsamples/provider-default/mimo/config.v2.json +26 -0
  18. package/configsamples/provider-default/modelscope/config.v2.json +81 -0
  19. package/configsamples/provider-default/my-openai/config.v2.json +20 -0
  20. package/configsamples/provider-default/nvidia/config.v2.json +32 -0
  21. package/configsamples/provider-default/opencode-zen-free/config.v2.json +56 -0
  22. package/configsamples/provider-default/openrouter/config.v2.json +210 -0
  23. package/configsamples/provider-default/qwen/config.v2.json +38 -0
  24. package/configsamples/provider-default/qwenchat/config.v2.json +53 -0
  25. package/configsamples/provider-default/tab/config.v2.json +26 -0
  26. package/configsamples/provider-default/tabglm/config.v2.json +77 -0
  27. package/dist/build-info.js +2 -2
  28. package/dist/cli/commands/config.d.ts +5 -0
  29. package/dist/cli/commands/config.js +369 -1
  30. package/dist/cli/commands/config.js.map +1 -1
  31. package/dist/cli/commands/examples.js +3 -0
  32. package/dist/cli/commands/examples.js.map +1 -1
  33. package/dist/cli/commands/init.js +25 -1
  34. package/dist/cli/commands/init.js.map +1 -1
  35. package/dist/cli/commands/launcher-kernel.js +122 -46
  36. package/dist/cli/commands/launcher-kernel.js.map +1 -1
  37. package/dist/cli/commands/start.js +60 -3
  38. package/dist/cli/commands/start.js.map +1 -1
  39. package/dist/cli/config/bundled-provider-pack.d.ts +20 -0
  40. package/dist/cli/config/bundled-provider-pack.js +146 -0
  41. package/dist/cli/config/bundled-provider-pack.js.map +1 -0
  42. package/dist/cli/register/status-config-commands.d.ts +2 -0
  43. package/dist/cli/register/status-config-commands.js.map +1 -1
  44. package/dist/cli.js +81 -28
  45. package/dist/cli.js.map +1 -1
  46. package/dist/debug/snapshot-store.js +2 -1
  47. package/dist/debug/snapshot-store.js.map +1 -1
  48. package/dist/index.js +23 -14
  49. package/dist/index.js.map +1 -1
  50. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +1 -1
  51. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
  52. package/dist/manager/quota/provider-quota-center.js +1 -1
  53. package/dist/manager/quota/provider-quota-center.js.map +1 -1
  54. package/dist/manager/storage/file-store.js +10 -0
  55. package/dist/manager/storage/file-store.js.map +1 -1
  56. package/dist/modules/llmswitch/bridge/snapshot-recorder-runtime.js +18 -1
  57. package/dist/modules/llmswitch/bridge/snapshot-recorder-runtime.js.map +1 -1
  58. package/dist/modules/llmswitch/bridge/snapshot-recorder.js +132 -51
  59. package/dist/modules/llmswitch/bridge/snapshot-recorder.js.map +1 -1
  60. package/dist/provider-sdk/provider-runtime-inference.js +2 -2
  61. package/dist/provider-sdk/provider-runtime-inference.js.map +1 -1
  62. package/dist/providers/auth/deepseek-account-token-acquirer.js +32 -3
  63. package/dist/providers/auth/deepseek-account-token-acquirer.js.map +1 -1
  64. package/dist/providers/core/api/provider-types.d.ts +11 -0
  65. package/dist/providers/core/config/service-profiles.js +1 -1
  66. package/dist/providers/core/runtime/deepseek-http-provider.d.ts +2 -0
  67. package/dist/providers/core/runtime/deepseek-http-provider.js +31 -1
  68. package/dist/providers/core/runtime/deepseek-http-provider.js.map +1 -1
  69. package/dist/providers/core/runtime/qwenchat-http-provider-helpers.d.ts +3 -2
  70. package/dist/providers/core/runtime/qwenchat-http-provider-helpers.js +513 -96
  71. package/dist/providers/core/runtime/qwenchat-http-provider-helpers.js.map +1 -1
  72. package/dist/providers/core/runtime/standard-tool-text-harvest.d.ts +8 -0
  73. package/dist/providers/core/runtime/standard-tool-text-harvest.js +16 -0
  74. package/dist/providers/core/runtime/standard-tool-text-harvest.js.map +1 -0
  75. package/dist/providers/core/runtime/standard-tool-text-request-transform.d.ts +4 -1
  76. package/dist/providers/core/runtime/standard-tool-text-request-transform.js +121 -3
  77. package/dist/providers/core/runtime/standard-tool-text-request-transform.js.map +1 -1
  78. package/dist/providers/core/utils/snapshot-writer.js +5 -2
  79. package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
  80. package/dist/providers/profile/provider-profile-loader.js +52 -1
  81. package/dist/providers/profile/provider-profile-loader.js.map +1 -1
  82. package/dist/providers/profile/provider-profile.d.ts +3 -0
  83. package/dist/server/handlers/handler-response-utils.js +1 -0
  84. package/dist/server/handlers/handler-response-utils.js.map +1 -1
  85. package/dist/server/handlers/images-handler.d.ts +9 -0
  86. package/dist/server/handlers/images-handler.js +258 -0
  87. package/dist/server/handlers/images-handler.js.map +1 -0
  88. package/dist/server/handlers/types.d.ts +7 -0
  89. package/dist/server/runtime/http-server/antigravity-startup-tasks.d.ts +3 -0
  90. package/dist/server/runtime/http-server/antigravity-startup-tasks.js +16 -0
  91. package/dist/server/runtime/http-server/antigravity-startup-tasks.js.map +1 -1
  92. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +3 -18
  93. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
  94. package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.d.ts +7 -0
  95. package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.js +17 -0
  96. package/dist/server/runtime/http-server/daemon-admin/providers-handler-utils.js.map +1 -1
  97. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +50 -17
  98. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  99. package/dist/server/runtime/http-server/daemon-admin-routes.js +32 -2
  100. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
  101. package/dist/server/runtime/http-server/executor/provider-response-converter.js +42 -13
  102. package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -1
  103. package/dist/server/runtime/http-server/executor/provider-response-utils.js +41 -3
  104. package/dist/server/runtime/http-server/executor/provider-response-utils.js.map +1 -1
  105. package/dist/server/runtime/http-server/executor/usage-aggregator.js +7 -7
  106. package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -1
  107. package/dist/server/runtime/http-server/executor/usage-logger.d.ts +9 -0
  108. package/dist/server/runtime/http-server/executor/usage-logger.js +35 -2
  109. package/dist/server/runtime/http-server/executor/usage-logger.js.map +1 -1
  110. package/dist/server/runtime/http-server/executor-metadata.js +12 -4
  111. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  112. package/dist/server/runtime/http-server/executor-pipeline.js +24 -15
  113. package/dist/server/runtime/http-server/executor-pipeline.js.map +1 -1
  114. package/dist/server/runtime/http-server/executor-provider.d.ts +6 -1
  115. package/dist/server/runtime/http-server/executor-provider.js +137 -5
  116. package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
  117. package/dist/server/runtime/http-server/http-server-bootstrap.js +6 -0
  118. package/dist/server/runtime/http-server/http-server-bootstrap.js.map +1 -1
  119. package/dist/server/runtime/http-server/http-server-lifecycle.js +23 -15
  120. package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -1
  121. package/dist/server/runtime/http-server/http-server-runtime-providers.js +14 -4
  122. package/dist/server/runtime/http-server/http-server-runtime-providers.js.map +1 -1
  123. package/dist/server/runtime/http-server/http-server-runtime-setup.js +83 -1
  124. package/dist/server/runtime/http-server/http-server-runtime-setup.js.map +1 -1
  125. package/dist/server/runtime/http-server/hub-shadow-compare.js +2 -41
  126. package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -1
  127. package/dist/server/runtime/http-server/provider-routing-scope.d.ts +9 -0
  128. package/dist/server/runtime/http-server/provider-routing-scope.js +20 -0
  129. package/dist/server/runtime/http-server/provider-routing-scope.js.map +1 -0
  130. package/dist/server/runtime/http-server/provider-traffic-governor.d.ts +67 -0
  131. package/dist/server/runtime/http-server/provider-traffic-governor.js +467 -0
  132. package/dist/server/runtime/http-server/provider-traffic-governor.js.map +1 -0
  133. package/dist/server/runtime/http-server/request-executor.d.ts +8 -0
  134. package/dist/server/runtime/http-server/request-executor.js +446 -21
  135. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  136. package/dist/server/runtime/http-server/routes.js +13 -0
  137. package/dist/server/runtime/http-server/routes.js.map +1 -1
  138. package/dist/server/runtime/http-server/session-client-registry.js +30 -4
  139. package/dist/server/runtime/http-server/session-client-registry.js.map +1 -1
  140. package/dist/server/runtime/http-server/session-client-route-utils.d.ts +7 -0
  141. package/dist/server/runtime/http-server/session-client-route-utils.js +38 -0
  142. package/dist/server/runtime/http-server/session-client-route-utils.js.map +1 -1
  143. package/dist/server/runtime/http-server/session-client-routes.js +12 -2
  144. package/dist/server/runtime/http-server/session-client-routes.js.map +1 -1
  145. package/dist/server/utils/request-id-manager.js +42 -5
  146. package/dist/server/utils/request-id-manager.js.map +1 -1
  147. package/dist/server/utils/stage-logger.d.ts +1 -0
  148. package/dist/server/utils/stage-logger.js +27 -0
  149. package/dist/server/utils/stage-logger.js.map +1 -1
  150. package/dist/utils/errorsamples.js +3 -1
  151. package/dist/utils/errorsamples.js.map +1 -1
  152. package/dist/utils/sensitive-redaction.d.ts +1 -0
  153. package/dist/utils/sensitive-redaction.js +122 -0
  154. package/dist/utils/sensitive-redaction.js.map +1 -0
  155. package/docs/INSTALLATION_AND_QUICKSTART.md +14 -1
  156. package/docs/PORTS.md +12 -0
  157. package/docs/lmstudio-tool-calling.md +25 -0
  158. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwenchat-web-request.d.ts +3 -0
  159. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwenchat-web-request.js +62 -0
  160. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwenchat-web.json +47 -0
  161. package/node_modules/@jsonstudio/llms/dist/conversion/hub/operation-table/operation-table-runner.js +68 -7
  162. package/node_modules/@jsonstudio/llms/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper-from-chat.js +138 -3
  163. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-chat-process-request-utils.js +24 -0
  164. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-execute-chat-process-entry.js +7 -1
  165. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-execute-request-stage.js +7 -0
  166. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-heavy-input-fastpath.d.ts +24 -0
  167. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-heavy-input-fastpath.js +203 -0
  168. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-route-and-outbound.js +17 -12
  169. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +11 -0
  170. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-stage-timing.js +82 -1
  171. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +47 -14
  172. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +43 -0
  173. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/client-remap-protocol-switch.js +222 -19
  174. package/node_modules/@jsonstudio/llms/dist/conversion/hub/policy/policy-engine.js +2 -2
  175. package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process-pending-tool-sync.js +24 -7
  176. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +90 -1
  177. package/node_modules/@jsonstudio/llms/dist/conversion/hub/snapshot-recorder.d.ts +1 -0
  178. package/node_modules/@jsonstudio/llms/dist/conversion/hub/snapshot-recorder.js +252 -1
  179. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge/utils.js +5 -3
  180. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +44 -4
  181. package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils-openai-request.d.ts +3 -1
  182. package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils-openai-request.js +20 -23
  183. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.js +68 -30
  184. package/node_modules/@jsonstudio/llms/dist/conversion/snapshot-utils.js +48 -8
  185. package/node_modules/@jsonstudio/llms/dist/native/router_hotpath_napi.node +0 -0
  186. package/node_modules/@jsonstudio/llms/dist/quota/quota-state.js +2 -2
  187. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine/routing-state/store.js +35 -2
  188. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +9 -9
  189. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/sticky-session-store.js +104 -18
  190. package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +79 -32
  191. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +49 -0
  192. package/node_modules/@jsonstudio/llms/dist/servertool/pending-session.js +48 -2
  193. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +14 -1
  194. package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +1 -0
  195. package/node_modules/@jsonstudio/llms/package.json +1 -1
  196. package/node_modules/ajv/dist/compile/jtd/serialize.js +9 -2
  197. package/node_modules/ajv/dist/compile/jtd/serialize.js.map +1 -1
  198. package/node_modules/ajv/dist/core.d.ts +1 -0
  199. package/node_modules/ajv/dist/core.js.map +1 -1
  200. package/node_modules/ajv/dist/vocabularies/validation/pattern.js +13 -4
  201. package/node_modules/ajv/dist/vocabularies/validation/pattern.js.map +1 -1
  202. package/node_modules/ajv/lib/compile/jtd/serialize.ts +13 -2
  203. package/node_modules/ajv/lib/core.ts +1 -0
  204. package/node_modules/ajv/lib/vocabularies/validation/pattern.ts +15 -4
  205. package/node_modules/ajv/package.json +2 -1
  206. package/package.json +15 -10
  207. package/scripts/ci/repo-sanity.mjs +23 -2
  208. package/scripts/ci/secrets-check.mjs +48 -0
  209. package/scripts/ci/silent-failure-audit.mjs +192 -0
  210. package/scripts/mock-provider/run-regressions.mjs +1 -0
  211. package/scripts/pack-mode.mjs +32 -36
  212. package/scripts/publish-rcc.mjs +38 -60
  213. package/scripts/tests/apply-patch-loop.mjs +1 -0
  214. package/scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs +2 -0
  215. package/scripts/tools-dev/responses-debug-client/src/index.ts +8 -3
  216. package/scripts/verify-e2e-toolcall.mjs +1 -0
  217. package/scripts/verify-install-e2e.mjs +2 -1
@@ -0,0 +1,122 @@
1
+ const REDACTED = '[REDACTED]';
2
+ const MAX_DEPTH = 20;
3
+ const SENSITIVE_KEYS = new Set([
4
+ 'authorization',
5
+ 'proxyauthorization',
6
+ 'xapikey',
7
+ 'apikey',
8
+ 'apikeyvalue',
9
+ 'apikeyheader',
10
+ 'api_key',
11
+ 'api-key',
12
+ 'access_token',
13
+ 'accesstoken',
14
+ 'refresh_token',
15
+ 'refreshtoken',
16
+ 'id_token',
17
+ 'idtoken',
18
+ 'sessiontoken',
19
+ 'tokenvalue',
20
+ 'bearertoken',
21
+ 'password',
22
+ 'passwd',
23
+ 'passcode',
24
+ 'secret',
25
+ 'clientsecret',
26
+ 'client_secret',
27
+ 'cookie',
28
+ 'setcookie'
29
+ ]);
30
+ function normalizeKey(key) {
31
+ return String(key || '')
32
+ .trim()
33
+ .toLowerCase()
34
+ .replace(/[^a-z0-9_]/g, '');
35
+ }
36
+ function isSensitiveKey(key) {
37
+ const normalized = normalizeKey(key);
38
+ if (!normalized) {
39
+ return false;
40
+ }
41
+ if (SENSITIVE_KEYS.has(normalized)) {
42
+ return true;
43
+ }
44
+ return normalized.endsWith('token') && normalized !== 'tokenfile';
45
+ }
46
+ function isSafeSecretReference(value) {
47
+ const trimmed = String(value || '').trim();
48
+ if (!trimmed) {
49
+ return false;
50
+ }
51
+ if (trimmed.startsWith('authfile-')) {
52
+ return true;
53
+ }
54
+ if (/^\$\{[A-Z0-9_]+\}$/i.test(trimmed)) {
55
+ return true;
56
+ }
57
+ if (/^[A-Z][A-Z0-9_]+$/.test(trimmed)) {
58
+ return true;
59
+ }
60
+ return false;
61
+ }
62
+ function maskSecretValue(value) {
63
+ const trimmed = String(value || '').trim();
64
+ if (!trimmed) {
65
+ return REDACTED;
66
+ }
67
+ return `${REDACTED}:${trimmed.length}`;
68
+ }
69
+ function redactEmbeddedSecrets(text) {
70
+ let out = String(text || '');
71
+ out = out.replace(/(\bBearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi, '$1[REDACTED]');
72
+ out = out.replace(/\bsk-[A-Za-z0-9]{12,}\b/g, 'sk-[REDACTED]');
73
+ out = out.replace(/(\bapi[_-]?key\b\s*[:=]\s*)(["']?)[^"'\s,;]+(\2)/gi, '$1$2[REDACTED]$3');
74
+ out = out.replace(/(\bpassword\b\s*[:=]\s*)(["']?)[^"'\s,;]+(\2)/gi, '$1$2[REDACTED]$3');
75
+ out = out.replace(/(\bcookie\b\s*[:=]\s*)(["']?)[^"'\n]+(\2)/gi, '$1$2[REDACTED]$3');
76
+ return out;
77
+ }
78
+ function redactByKey(value) {
79
+ if (typeof value === 'string') {
80
+ if (isSafeSecretReference(value)) {
81
+ return value;
82
+ }
83
+ return maskSecretValue(value);
84
+ }
85
+ if (value == null) {
86
+ return REDACTED;
87
+ }
88
+ if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
89
+ return REDACTED;
90
+ }
91
+ return REDACTED;
92
+ }
93
+ function redactInternal(value, state, depth, keyHint) {
94
+ if (depth > MAX_DEPTH) {
95
+ return '[TRUNCATED_DEPTH]';
96
+ }
97
+ if (keyHint && isSensitiveKey(keyHint)) {
98
+ return redactByKey(value);
99
+ }
100
+ if (typeof value === 'string') {
101
+ return redactEmbeddedSecrets(value);
102
+ }
103
+ if (!value || typeof value !== 'object') {
104
+ return value;
105
+ }
106
+ if (state.has(value)) {
107
+ return '[CIRCULAR]';
108
+ }
109
+ state.add(value);
110
+ if (Array.isArray(value)) {
111
+ return value.map((item) => redactInternal(item, state, depth + 1));
112
+ }
113
+ const out = {};
114
+ for (const [key, child] of Object.entries(value)) {
115
+ out[key] = redactInternal(child, state, depth + 1, key);
116
+ }
117
+ return out;
118
+ }
119
+ export function redactSensitiveData(input) {
120
+ return redactInternal(input, new WeakSet(), 0);
121
+ }
122
+ //# sourceMappingURL=sensitive-redaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sensitive-redaction.js","sourceRoot":"","sources":["../../src/utils/sensitive-redaction.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,12 @@ rcc init
26
26
 
27
27
  初始化成功后,`rcc init` 会把内置文档复制到:`~/.rcc/docs`
28
28
 
29
+ 如果你希望一次性解压内置的脱敏 provider 模板到 `~/.rcc/provider`(便于后续直接填 key / tokenFile):
30
+
31
+ ```bash
32
+ rcc init default
33
+ ```
34
+
29
35
  非交互式(CI/脚本):
30
36
 
31
37
  ```bash
@@ -38,11 +44,12 @@ rcc init --providers openai,tab --default-provider tab
38
44
  rcc init --list-providers
39
45
  ```
40
46
 
41
- 生成后你需要把 `apiKey` / `tokenFile` / `cookieFile` 按需补齐(脱敏模板会用 `YOUR_API_KEY_HERE` 占位)。
47
+ 生成后你需要把 `apiKey` / `tokenFile` / `cookieFile` 按需补齐(脱敏模板会用环境变量占位,例如 `${OPENROUTER_API_KEY}`)。
42
48
 
43
49
  参考配置:
44
50
  - `configsamples/config.reference.json`
45
51
  - `configsamples/provider/*/config.v1.json`
52
+ - `configsamples/provider-default/*/config.v2.json`
46
53
 
47
54
  ## 3) 启动服务器
48
55
 
@@ -56,6 +63,12 @@ rcc start
56
63
  rcc start --config ./config.json
57
64
  ```
58
65
 
66
+ 离线单模型(例如 LM Studio)建议单独配置并独立端口运行:
67
+
68
+ ```bash
69
+ routecodex start --port 5520 --config "/Volumes/extension/.rcc/config.offline.json"
70
+ ```
71
+
59
72
  ## 4) 验证服务可用
60
73
 
61
74
  ```bash
package/docs/PORTS.md CHANGED
@@ -34,3 +34,15 @@
34
34
  rcc status
35
35
  curl http://127.0.0.1:5555/ready
36
36
  ```
37
+
38
+ ## 离线单模型模式(常用)
39
+
40
+ 当你需要把所有主路由收敛到本地模型(例如 LM Studio)时,建议使用单独配置文件(例如 `config.offline.json`)并在独立端口启动:
41
+
42
+ ```bash
43
+ routecodex start --port 5520 --config "/Volumes/extension/.rcc/config.offline.json"
44
+ ```
45
+
46
+ 说明:
47
+ - `--port` 优先级高于配置文件端口。
48
+ - 若端口已被非托管进程占用,会直接失败(fail-fast),不会静默抢占。
@@ -4,6 +4,31 @@
4
4
 
5
5
  LM Studio provides comprehensive tool calling functionality that enables Large Language Models (LLMs) to interact with external functions and APIs. All models in LM Studio support at least some degree of tool use, with two levels of support: **Native** and **Default**.
6
6
 
7
+ ## RouteCodex 集成注意(2026-04-04 更新)
8
+
9
+ 在 RouteCodex → LM Studio 的 `/v1/responses` 路径下,真实回放曾出现:
10
+
11
+ - `HTTP 400`
12
+ - `type: invalid_request_error`
13
+ - `param: tools.*.type`
14
+ - `code: invalid_string`
15
+
16
+ 根因确认(2026-04-04 实测):
17
+
18
+ - LM Studio `/v1/responses` **不接受** chat-style 嵌套工具形状
19
+ `{"type":"function","function":{...}}`(会触发 `tools.0.type invalid_string`)。
20
+ - LM Studio `/v1/responses` 接受的是 Responses-style 扁平工具形状
21
+ `{"type":"function","name":"...","parameters":{...}}`。
22
+ - `tool_choice` 需使用字符串枚举(`"none" | "auto" | "required"`),不应传对象。
23
+
24
+ 因此在 RouteCodex 侧必须保证:
25
+
26
+ 1. `tools[]` 仅保留 **function tools**。
27
+ 2. 请求工具统一归一为扁平形状:`{"type":"function","name","parameters",...}`。
28
+ 3. 当 `tools` 被清空时,移除 `tool_choice`;当 `tool_choice` 为对象时,归一为 `"required"`。
29
+
30
+ > 结论:LM Studio 文档层面的“工具支持”不代表任意 OpenAI 兼容形状都可透传;RouteCodex 必须做请求侧 schema 约束与形状修复。
31
+
7
32
  ## Tool Support Levels
8
33
 
9
34
  ### Native Tool Use Support
@@ -0,0 +1,3 @@
1
+ import type { AdapterContext } from '../../hub/types/chat-envelope.js';
2
+ import type { JsonObject } from '../../hub/types/json.js';
3
+ export declare function applyQwenChatWebRequestTransform(payload: JsonObject, adapterContext?: AdapterContext): JsonObject;
@@ -0,0 +1,62 @@
1
+ import { buildNativeReqOutboundCompatAdapterContext } from '../../hub/pipeline/compat/native-adapter-context.js';
2
+ import { runReqOutboundStage3CompatWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
3
+ const PROFILE = 'chat:qwenchat-web';
4
+ const DEFAULT_PROVIDER_PROTOCOL = 'openai-chat';
5
+ const DEFAULT_ENTRY_ENDPOINT = '/v1/chat/completions';
6
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
7
+ const readBoolean = (value) => {
8
+ if (typeof value === 'boolean') {
9
+ return value;
10
+ }
11
+ if (typeof value === 'string') {
12
+ const normalized = value.trim().toLowerCase();
13
+ if (['true', '1', 'yes', 'on'].includes(normalized)) {
14
+ return true;
15
+ }
16
+ if (['false', '0', 'no', 'off'].includes(normalized)) {
17
+ return false;
18
+ }
19
+ }
20
+ return undefined;
21
+ };
22
+ const readToolProtocol = (value) => {
23
+ if (typeof value !== 'string') {
24
+ return undefined;
25
+ }
26
+ const normalized = value.trim().toLowerCase();
27
+ return normalized === 'native' || normalized === 'text' ? normalized : undefined;
28
+ };
29
+ function resolveToolTextNode(adapterContext) {
30
+ const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
31
+ const baseNode = isRecord(nativeContext.deepseek) ? nativeContext.deepseek : {};
32
+ const baseProtocol = readToolProtocol(baseNode.toolProtocol);
33
+ const baseFallback = readBoolean(baseNode.textToolFallback);
34
+ const protocol = baseProtocol ?? (baseFallback === undefined ? undefined : baseFallback ? 'text' : 'native');
35
+ return {
36
+ ...baseNode,
37
+ strictToolRequired: readBoolean(baseNode.strictToolRequired) ?? true,
38
+ textToolFallback: protocol ? protocol === 'text' : baseFallback ?? true,
39
+ ...(protocol ? { toolProtocol: protocol } : {})
40
+ };
41
+ }
42
+ function buildCompatInput(payload, adapterContext) {
43
+ const nativeContext = buildNativeReqOutboundCompatAdapterContext(adapterContext);
44
+ const normalizedContext = {
45
+ ...nativeContext,
46
+ compatibilityProfile: PROFILE,
47
+ providerProtocol: nativeContext.providerProtocol ?? adapterContext?.providerProtocol ?? DEFAULT_PROVIDER_PROTOCOL,
48
+ entryEndpoint: nativeContext.entryEndpoint ?? adapterContext?.entryEndpoint ?? DEFAULT_ENTRY_ENDPOINT,
49
+ deepseek: resolveToolTextNode(adapterContext)
50
+ };
51
+ return {
52
+ payload,
53
+ adapterContext: normalizedContext,
54
+ explicitProfile: PROFILE
55
+ };
56
+ }
57
+ export function applyQwenChatWebRequestTransform(payload, adapterContext) {
58
+ if (!payload || typeof payload !== 'object') {
59
+ return payload;
60
+ }
61
+ return runReqOutboundStage3CompatWithNative(buildCompatInput(payload, adapterContext)).payload;
62
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "id": "chat:qwenchat-web",
3
+ "protocol": "openai-chat",
4
+ "request": {
5
+ "mappings": [
6
+ { "action": "snapshot", "phase": "compat-pre" },
7
+ {
8
+ "action": "tool_text_request_guidance",
9
+ "config": {
10
+ "marker": "Tool-call output contract (STRICT)",
11
+ "includeToolNames": true,
12
+ "requireTools": true
13
+ }
14
+ },
15
+ { "action": "deepseek_web_request" },
16
+ { "action": "snapshot", "phase": "compat-post" }
17
+ ]
18
+ },
19
+ "response": {
20
+ "mappings": [
21
+ { "action": "snapshot", "phase": "compat-pre" },
22
+ {
23
+ "action": "deepseek_web_response",
24
+ "config": {
25
+ "textNormalizer": {
26
+ "jsonToolRepair": {
27
+ "toolNameAliases": {
28
+ "shell_command": "exec_command",
29
+ "shell": "exec_command",
30
+ "bash": "exec_command",
31
+ "terminal": "exec_command"
32
+ },
33
+ "argumentAliases": {
34
+ "exec_command": {
35
+ "cmd": ["cmd", "command", "input.command", "script"],
36
+ "command": ["cmd", "command", "input.command", "script"],
37
+ "workdir": ["workdir", "cwd", "input.cwd"]
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ },
44
+ { "action": "snapshot", "phase": "compat-post" }
45
+ ]
46
+ }
47
+ }
@@ -10,7 +10,10 @@ const INBOUND_BRIDGE_SPECS = {
10
10
  'gemini-chat': { protocol: 'gemini-chat', stage: 'request_inbound', messages: 'chat_envelope' }
11
11
  };
12
12
  const OUTBOUND_BRIDGE_SPECS = {
13
- 'openai-chat': { protocol: 'openai-chat', stage: 'request_outbound', messages: 'format_payload_messages', includeCapturedToolResults: true },
13
+ // openai-chat outbound post-map hooks do not write back `state.messages` into payload.
14
+ // Feeding full payload.messages here only adds O(n) scan cost on large histories.
15
+ // Keep hooks metadata-only by not passing message arrays.
16
+ 'openai-chat': { protocol: 'openai-chat', stage: 'request_outbound', messages: 'none', includeCapturedToolResults: true },
14
17
  // Keep parity: openai-responses outbound actions should not touch normalized messages.
15
18
  'openai-responses': { protocol: 'openai-responses', stage: 'request_outbound', messages: 'none', moduleType: 'openai-responses' },
16
19
  'anthropic-messages': { protocol: 'anthropic-messages', stage: 'request_outbound', messages: 'none', includeCapturedToolResults: true },
@@ -41,24 +44,82 @@ function buildCapturedToolResults(toolOutputs) {
41
44
  name: entry.name
42
45
  }));
43
46
  }
44
- function applyBridgePolicy(spec, options) {
45
- const bridgePolicy = resolveBridgePolicy({ protocol: spec.protocol, moduleType: spec.moduleType ?? spec.protocol });
46
- const actions = resolvePolicyActions(bridgePolicy, spec.stage);
47
+ function hasToolSignalsInMessages(messages) {
48
+ if (!Array.isArray(messages) || messages.length === 0) {
49
+ return false;
50
+ }
51
+ for (const message of messages) {
52
+ if (!message || typeof message !== 'object') {
53
+ continue;
54
+ }
55
+ const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
56
+ if (role === 'tool') {
57
+ return true;
58
+ }
59
+ if (typeof message.tool_call_id === 'string' && message.tool_call_id.trim().length > 0) {
60
+ return true;
61
+ }
62
+ const toolCalls = message.tool_calls;
63
+ if (Array.isArray(toolCalls) && toolCalls.length > 0) {
64
+ return true;
65
+ }
66
+ }
67
+ return false;
68
+ }
69
+ function filterToolOnlyActionsWhenNoToolSignals(stage, actions, messages) {
47
70
  if (!actions?.length) {
48
- return;
71
+ return actions;
49
72
  }
50
- const metadata = options.chatEnvelope.metadata;
73
+ if (stage !== 'request_outbound' && stage !== 'request_inbound') {
74
+ return actions;
75
+ }
76
+ if (hasToolSignalsInMessages(messages)) {
77
+ return actions;
78
+ }
79
+ const toolOnlyActions = new Set([
80
+ 'tools.capture-results',
81
+ 'tools.normalize-call-ids',
82
+ 'compat.fix-apply-patch',
83
+ 'tools.ensure-placeholders'
84
+ ]);
85
+ return actions.filter((action) => {
86
+ const name = typeof action?.name === 'string' ? action.name.trim().toLowerCase() : '';
87
+ return !toolOnlyActions.has(name);
88
+ });
89
+ }
90
+ function applyBridgePolicy(spec, options) {
91
+ const bridgePolicy = resolveBridgePolicy({ protocol: spec.protocol, moduleType: spec.moduleType ?? spec.protocol });
92
+ const resolvedActions = resolvePolicyActions(bridgePolicy, spec.stage);
51
93
  const messages = spec.messages === 'chat_envelope'
52
94
  ? options.chatEnvelope.messages
53
95
  : spec.messages === 'format_payload_messages'
54
96
  ? extractPayloadMessages(options.payload)
55
97
  : undefined;
98
+ const actions = filterToolOnlyActionsWhenNoToolSignals(spec.stage, resolvedActions, messages);
99
+ if (!actions?.length) {
100
+ return;
101
+ }
102
+ const metadata = options.chatEnvelope.metadata;
103
+ const rawRequestForActionState = (() => {
104
+ if (spec.messages !== 'format_payload_messages') {
105
+ return options.payload;
106
+ }
107
+ if (!messages || !Array.isArray(messages) || messages.length === 0) {
108
+ return options.payload;
109
+ }
110
+ // Performance stop-bleed: avoid duplicating a very large messages[] payload in both
111
+ // `state.messages` and `state.rawRequest.messages`. Bridge actions still receive the
112
+ // canonical messages via `state.messages`.
113
+ const compact = { ...options.payload };
114
+ delete compact.messages;
115
+ return compact;
116
+ })();
56
117
  const capturedToolResults = spec.includeCapturedToolResults
57
118
  ? buildCapturedToolResults(options.chatEnvelope.toolOutputs)
58
119
  : undefined;
59
120
  const actionState = createBridgeActionState({
60
121
  ...(messages ? { messages } : {}),
61
- rawRequest: options.payload,
122
+ rawRequest: rawRequestForActionState,
62
123
  metadata,
63
124
  ...(capturedToolResults ? { capturedToolResults } : {})
64
125
  });
@@ -1,10 +1,88 @@
1
1
  import { isJsonObject, jsonClone } from '../../types/json.js';
2
2
  import { buildAnthropicRequestFromOpenAIChat } from '../../../codecs/anthropic-openai-codec.js';
3
+ import { buildAnthropicFromOpenAIChatWithNative } from '../../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
3
4
  import { encodeMetadataPassthrough } from '../../../metadata-passthrough.js';
5
+ import { isHubStageTimingDetailEnabled, logHubStageTiming } from '../../pipeline/hub-stage-timing.js';
4
6
  import { applyEffortBudget, buildAnthropicThinkingFromConfig, mergeAnthropicOutputConfig, mergeAnthropicThinkingConfig, normalizeAnthropicThinkingConfigFromUnknown, resolveConfiguredAnthropicThinkingBudgets, resolveConfiguredAnthropicThinkingConfig } from './anthropic-thinking-config.js';
5
7
  import { appendDroppedFieldAudit, appendLossyFieldAudit, hasExplicitEmptyToolsSemantics, isResponsesOrigin, } from './anthropic-semantics-audit.js';
6
8
  import { ANTHROPIC_TOP_LEVEL_FIELDS, PASSTHROUGH_METADATA_PREFIX, PASSTHROUGH_PARAMETERS, RESPONSES_DROPPED_PARAMETER_KEYS, sanitizeAnthropicPayload, } from './anthropic-mapper-config.js';
9
+ const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
10
+ const FALSY = new Set(['0', 'false', 'no', 'off']);
11
+ const DEFAULT_HEAVY_INPUT_THRESHOLD = 120_000;
12
+ function readBooleanEnv(names, fallback) {
13
+ for (const name of names) {
14
+ const raw = process.env[name];
15
+ if (raw === undefined) {
16
+ continue;
17
+ }
18
+ const normalized = String(raw).trim().toLowerCase();
19
+ if (TRUTHY.has(normalized)) {
20
+ return true;
21
+ }
22
+ if (FALSY.has(normalized)) {
23
+ return false;
24
+ }
25
+ }
26
+ return fallback;
27
+ }
28
+ function readPositiveIntEnv(names, fallback) {
29
+ for (const name of names) {
30
+ const raw = process.env[name];
31
+ if (raw === undefined) {
32
+ continue;
33
+ }
34
+ const parsed = Number.parseInt(String(raw).trim(), 10);
35
+ if (Number.isFinite(parsed) && parsed > 0) {
36
+ return parsed;
37
+ }
38
+ }
39
+ return fallback;
40
+ }
41
+ function shouldUseNativeBuild(ctx) {
42
+ const enabled = readBooleanEnv([
43
+ 'ROUTECODEX_HUB_FASTPATH_ANTHROPIC_NATIVE_BUILD',
44
+ 'RCC_HUB_FASTPATH_ANTHROPIC_NATIVE_BUILD',
45
+ // backward-compatible manual knob
46
+ 'ROUTECODEX_HUB_ANTHROPIC_NATIVE_BUILD',
47
+ 'RCC_HUB_ANTHROPIC_NATIVE_BUILD',
48
+ ], false);
49
+ if (!enabled) {
50
+ return false;
51
+ }
52
+ const threshold = readPositiveIntEnv([
53
+ 'ROUTECODEX_HUB_FASTPATH_INPUT_TOKEN_THRESHOLD',
54
+ 'RCC_HUB_FASTPATH_INPUT_TOKEN_THRESHOLD',
55
+ ], DEFAULT_HEAVY_INPUT_THRESHOLD);
56
+ const rt = ctx.__rt;
57
+ if (rt &&
58
+ typeof rt === 'object' &&
59
+ rt.hubFastpathHeavyInput === true) {
60
+ return true;
61
+ }
62
+ const estimatedInputTokens = ctx.estimatedInputTokens;
63
+ return (typeof estimatedInputTokens === 'number' &&
64
+ Number.isFinite(estimatedInputTokens) &&
65
+ estimatedInputTokens >= threshold);
66
+ }
67
+ function hasAnthropicSystemSemantic(chat) {
68
+ try {
69
+ const sysNode = chat.semantics && typeof chat.semantics === 'object'
70
+ ? chat.semantics.system
71
+ : undefined;
72
+ if (!sysNode || typeof sysNode !== 'object' || Array.isArray(sysNode)) {
73
+ return false;
74
+ }
75
+ return Object.prototype.hasOwnProperty.call(sysNode, 'blocks');
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
7
81
  export function buildAnthropicFormatEnvelopeFromChat(chat, ctx) {
82
+ const requestId = typeof ctx.requestId === 'string' && ctx.requestId.trim().length
83
+ ? ctx.requestId
84
+ : 'unknown';
85
+ const forceDetailLog = isHubStageTimingDetailEnabled();
8
86
  const model = chat.parameters?.model;
9
87
  if (typeof model !== 'string' || !model.trim()) {
10
88
  throw new Error('ChatEnvelope.parameters.model is required for anthropic-messages outbound conversion');
@@ -114,15 +192,72 @@ export function buildAnthropicFormatEnvelopeFromChat(chat, ctx) {
114
192
  catch {
115
193
  // ignore
116
194
  }
117
- const payloadSource = buildAnthropicRequestFromOpenAIChat(baseRequest);
118
- const payload = sanitizeAnthropicPayload(JSON.parse(JSON.stringify(payloadSource)));
195
+ const useNativeBuild = shouldUseNativeBuild(ctx);
196
+ let payloadSource;
197
+ if (useNativeBuild) {
198
+ logHubStageTiming(requestId, 'req_outbound.anthropic.build_request_native', 'start');
199
+ const nativeBuildStartedAt = Date.now();
200
+ try {
201
+ payloadSource = buildAnthropicFromOpenAIChatWithNative(baseRequest, {
202
+ requestId: typeof ctx.requestId === 'string' && ctx.requestId.trim().length
203
+ ? ctx.requestId
204
+ : undefined,
205
+ entryEndpoint: typeof ctx.entryEndpoint === 'string' && ctx.entryEndpoint.trim().length
206
+ ? ctx.entryEndpoint
207
+ : undefined,
208
+ });
209
+ if (hasAnthropicSystemSemantic(chat) &&
210
+ !Object.prototype.hasOwnProperty.call(payloadSource, 'system')) {
211
+ throw new Error('native_missing_system_semantic_replay');
212
+ }
213
+ logHubStageTiming(requestId, 'req_outbound.anthropic.build_request_native', 'completed', {
214
+ elapsedMs: Date.now() - nativeBuildStartedAt,
215
+ forceLog: forceDetailLog,
216
+ });
217
+ }
218
+ catch {
219
+ logHubStageTiming(requestId, 'req_outbound.anthropic.build_request_native', 'completed', {
220
+ elapsedMs: Date.now() - nativeBuildStartedAt,
221
+ forceLog: true,
222
+ fallbackToJs: true,
223
+ });
224
+ logHubStageTiming(requestId, 'req_outbound.anthropic.build_request_js_fallback', 'start');
225
+ const jsFallbackStartedAt = Date.now();
226
+ payloadSource = buildAnthropicRequestFromOpenAIChat(baseRequest, {
227
+ requestId,
228
+ });
229
+ logHubStageTiming(requestId, 'req_outbound.anthropic.build_request_js_fallback', 'completed', {
230
+ elapsedMs: Date.now() - jsFallbackStartedAt,
231
+ forceLog: forceDetailLog,
232
+ });
233
+ }
234
+ }
235
+ else {
236
+ logHubStageTiming(requestId, 'req_outbound.anthropic.build_request_js', 'start');
237
+ const jsBuildStartedAt = Date.now();
238
+ payloadSource = buildAnthropicRequestFromOpenAIChat(baseRequest, {
239
+ requestId,
240
+ });
241
+ logHubStageTiming(requestId, 'req_outbound.anthropic.build_request_js', 'completed', {
242
+ elapsedMs: Date.now() - jsBuildStartedAt,
243
+ forceLog: forceDetailLog,
244
+ });
245
+ }
246
+ logHubStageTiming(requestId, 'req_outbound.anthropic.payload_sanitize', 'start');
247
+ const sanitizeStartedAt = Date.now();
248
+ const payload = sanitizeAnthropicPayload({
249
+ ...payloadSource,
250
+ });
119
251
  if (baseRequest.thinking !== undefined) {
120
252
  payload.thinking = jsonClone(baseRequest.thinking);
121
253
  }
122
254
  if (baseRequest.output_config !== undefined) {
123
255
  payload.output_config = jsonClone(baseRequest.output_config);
124
256
  }
125
- sanitizeAnthropicPayload(payload);
257
+ logHubStageTiming(requestId, 'req_outbound.anthropic.payload_sanitize', 'completed', {
258
+ elapsedMs: Date.now() - sanitizeStartedAt,
259
+ forceLog: forceDetailLog,
260
+ });
126
261
  return {
127
262
  protocol: 'anthropic-messages',
128
263
  direction: 'response',
@@ -3,6 +3,7 @@ import { buildPassthroughAuditWithNative, readResponsesResumeFromRequestSemantic
3
3
  import { readRuntimeMetadata } from "../../runtime-metadata.js";
4
4
  import { computeRequestTokens } from "../../../router/virtual-router/token-estimator.js";
5
5
  import { estimateSessionBoundTokens } from "../process/chat-process-session-usage.js";
6
+ import { isHeavyInputFastpathEnabled, markHeavyInputFastpath, resolveHeavyInputTokenThreshold, roughEstimateInputTokensFromRequest, } from "./hub-pipeline-heavy-input-fastpath.js";
6
7
  export function sanitizeStandardizedRequestMessages(standardizedRequest) {
7
8
  return {
8
9
  ...standardizedRequest,
@@ -37,12 +38,35 @@ export function resolveActiveProcessModeAndAudit(args) {
37
38
  export function estimateInputTokensForWorkingRequest(args) {
38
39
  const { workingRequest, normalizedMetadata } = args;
39
40
  try {
41
+ const fastpathEnabled = isHeavyInputFastpathEnabled();
42
+ const threshold = resolveHeavyInputTokenThreshold();
43
+ if (fastpathEnabled && threshold > 0) {
44
+ const roughEstimate = roughEstimateInputTokensFromRequest(workingRequest);
45
+ if (roughEstimate >= threshold) {
46
+ if (normalizedMetadata && typeof normalizedMetadata === "object") {
47
+ normalizedMetadata.estimatedInputTokens = roughEstimate;
48
+ markHeavyInputFastpath({
49
+ metadata: normalizedMetadata,
50
+ estimatedInputTokens: roughEstimate,
51
+ reason: "rough_estimate",
52
+ });
53
+ }
54
+ return;
55
+ }
56
+ }
40
57
  const estimatedTokens = estimateSessionBoundTokens(workingRequest, normalizedMetadata) ?? computeRequestTokens(workingRequest, "");
41
58
  if (typeof estimatedTokens === "number" &&
42
59
  Number.isFinite(estimatedTokens) &&
43
60
  estimatedTokens > 0) {
44
61
  if (normalizedMetadata && typeof normalizedMetadata === "object") {
45
62
  normalizedMetadata.estimatedInputTokens = estimatedTokens;
63
+ if (fastpathEnabled && estimatedTokens >= threshold) {
64
+ markHeavyInputFastpath({
65
+ metadata: normalizedMetadata,
66
+ estimatedInputTokens: estimatedTokens,
67
+ reason: "full_estimate",
68
+ });
69
+ }
46
70
  }
47
71
  }
48
72
  }