@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.
Files changed (221) 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 +24 -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 +129 -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/dist/utils/snapshot-writer.js +162 -2
  156. package/dist/utils/snapshot-writer.js.map +1 -1
  157. package/docs/INSTALLATION_AND_QUICKSTART.md +14 -1
  158. package/docs/PORTS.md +12 -0
  159. package/docs/lmstudio-tool-calling.md +25 -0
  160. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwenchat-web-request.d.ts +3 -0
  161. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/qwenchat-web-request.js +62 -0
  162. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwenchat-web.json +47 -0
  163. package/node_modules/@jsonstudio/llms/dist/conversion/hub/node-support.js +5 -2
  164. package/node_modules/@jsonstudio/llms/dist/conversion/hub/operation-table/operation-table-runner.js +68 -7
  165. package/node_modules/@jsonstudio/llms/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper-from-chat.js +138 -3
  166. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-chat-process-request-utils.js +24 -0
  167. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-execute-chat-process-entry.js +7 -1
  168. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-execute-request-stage.js +7 -0
  169. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-heavy-input-fastpath.d.ts +24 -0
  170. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-heavy-input-fastpath.js +203 -0
  171. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline-route-and-outbound.js +17 -12
  172. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +11 -0
  173. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-stage-timing.js +82 -1
  174. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +47 -14
  175. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +43 -0
  176. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/client-remap-protocol-switch.js +222 -19
  177. package/node_modules/@jsonstudio/llms/dist/conversion/hub/policy/policy-engine.js +2 -2
  178. package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process-pending-tool-sync.js +24 -7
  179. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +90 -1
  180. package/node_modules/@jsonstudio/llms/dist/conversion/hub/snapshot-recorder.d.ts +1 -0
  181. package/node_modules/@jsonstudio/llms/dist/conversion/hub/snapshot-recorder.js +252 -1
  182. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge/utils.js +5 -3
  183. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +44 -4
  184. package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils-openai-request.d.ts +3 -1
  185. package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils-openai-request.js +20 -23
  186. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.js +68 -30
  187. package/node_modules/@jsonstudio/llms/dist/conversion/snapshot-utils.js +194 -10
  188. package/node_modules/@jsonstudio/llms/dist/native/router_hotpath_napi.node +0 -0
  189. package/node_modules/@jsonstudio/llms/dist/quota/quota-state.js +2 -2
  190. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine/routing-state/store.js +35 -2
  191. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +9 -9
  192. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/sticky-session-store.js +104 -18
  193. package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +79 -32
  194. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +49 -0
  195. package/node_modules/@jsonstudio/llms/dist/servertool/pending-session.js +48 -2
  196. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +14 -1
  197. package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +1 -0
  198. package/node_modules/@jsonstudio/llms/package.json +1 -1
  199. package/node_modules/ajv/dist/compile/jtd/serialize.js +9 -2
  200. package/node_modules/ajv/dist/compile/jtd/serialize.js.map +1 -1
  201. package/node_modules/ajv/dist/core.d.ts +1 -0
  202. package/node_modules/ajv/dist/core.js.map +1 -1
  203. package/node_modules/ajv/dist/vocabularies/validation/pattern.js +13 -4
  204. package/node_modules/ajv/dist/vocabularies/validation/pattern.js.map +1 -1
  205. package/node_modules/ajv/lib/compile/jtd/serialize.ts +13 -2
  206. package/node_modules/ajv/lib/core.ts +1 -0
  207. package/node_modules/ajv/lib/vocabularies/validation/pattern.ts +15 -4
  208. package/node_modules/ajv/package.json +2 -1
  209. package/package.json +15 -10
  210. package/scripts/ci/repo-sanity.mjs +23 -2
  211. package/scripts/ci/secrets-check.mjs +48 -0
  212. package/scripts/ci/silent-failure-audit.mjs +192 -0
  213. package/scripts/mock-provider/run-regressions.mjs +1 -0
  214. package/scripts/monitor/memory-guard.mjs +207 -0
  215. package/scripts/pack-mode.mjs +32 -36
  216. package/scripts/publish-rcc.mjs +38 -60
  217. package/scripts/tests/apply-patch-loop.mjs +1 -0
  218. package/scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs +2 -0
  219. package/scripts/tools-dev/responses-debug-client/src/index.ts +8 -3
  220. package/scripts/verify-e2e-toolcall.mjs +1 -0
  221. package/scripts/verify-install-e2e.mjs +2 -1
@@ -1,5 +1,16 @@
1
1
  export declare function isHubStageTimingDetailEnabled(): boolean;
2
2
  export declare function clearHubStageTiming(requestId: string | undefined | null): void;
3
+ export type HubStageTopSummaryEntry = {
4
+ stage: string;
5
+ totalMs: number;
6
+ count: number;
7
+ avgMs: number;
8
+ maxMs: number;
9
+ };
10
+ export declare function peekHubStageTopSummary(requestId: string | undefined | null, options?: {
11
+ topN?: number;
12
+ minMs?: number;
13
+ }): HubStageTopSummaryEntry[];
3
14
  export declare function logHubStageTiming(requestId: string, stage: string, phase: 'start' | 'completed' | 'error', details?: Record<string, unknown>): void;
4
15
  export declare function measureHubStage<T>(requestId: string, stage: string, fn: () => Promise<T> | T, options?: {
5
16
  startDetails?: Record<string, unknown>;
@@ -1,9 +1,13 @@
1
1
  const truthy = new Set(['1', 'true', 'yes', 'on']);
2
2
  const falsy = new Set(['0', 'false', 'no', 'off']);
3
+ // Native alignment note: timing integrates with *WithNative stage orchestration flow.
3
4
  const REQUEST_TIMELINES = new Map();
4
5
  const REQUEST_TIMELINE_TTL_MS = 30 * 60 * 1000;
5
6
  const REQUEST_TIMELINE_MAX = 4096;
6
7
  const DEFAULT_HUB_STAGE_LOG_MIN_MS = 50;
8
+ const DEFAULT_HUB_STAGE_TOP_N = 5;
9
+ const DEFAULT_HUB_STAGE_TOP_MIN_MS = 5;
10
+ const REQUEST_STAGE_BREAKDOWNS = new Map();
7
11
  function resolveBool(raw, fallback) {
8
12
  if (raw === undefined) {
9
13
  return fallback;
@@ -62,6 +66,7 @@ function prune(nowMs) {
62
66
  for (const [key, timeline] of REQUEST_TIMELINES.entries()) {
63
67
  if (nowMs - timeline.lastAtMs >= REQUEST_TIMELINE_TTL_MS) {
64
68
  REQUEST_TIMELINES.delete(key);
69
+ REQUEST_STAGE_BREAKDOWNS.delete(key);
65
70
  }
66
71
  }
67
72
  while (REQUEST_TIMELINES.size > REQUEST_TIMELINE_MAX) {
@@ -70,6 +75,7 @@ function prune(nowMs) {
70
75
  break;
71
76
  }
72
77
  REQUEST_TIMELINES.delete(oldestKey);
78
+ REQUEST_STAGE_BREAKDOWNS.delete(oldestKey);
73
79
  }
74
80
  }
75
81
  function touchTiming(requestId) {
@@ -125,8 +131,82 @@ export function clearHubStageTiming(requestId) {
125
131
  return;
126
132
  }
127
133
  REQUEST_TIMELINES.delete(requestId);
134
+ REQUEST_STAGE_BREAKDOWNS.delete(requestId);
135
+ }
136
+ function recordHubStageElapsed(requestId, stage, elapsedMs) {
137
+ if (!requestId || !stage || !Number.isFinite(elapsedMs) || elapsedMs < 0) {
138
+ return;
139
+ }
140
+ const nowMs = Date.now();
141
+ prune(nowMs);
142
+ const byStage = REQUEST_STAGE_BREAKDOWNS.get(requestId) ?? new Map();
143
+ if (!REQUEST_STAGE_BREAKDOWNS.has(requestId)) {
144
+ REQUEST_STAGE_BREAKDOWNS.set(requestId, byStage);
145
+ }
146
+ const existing = byStage.get(stage);
147
+ if (!existing) {
148
+ byStage.set(stage, {
149
+ totalMs: elapsedMs,
150
+ count: 1,
151
+ maxMs: elapsedMs
152
+ });
153
+ return;
154
+ }
155
+ existing.totalMs += elapsedMs;
156
+ existing.count += 1;
157
+ existing.maxMs = Math.max(existing.maxMs, elapsedMs);
158
+ }
159
+ function readIntEnv(name, fallback) {
160
+ const raw = process.env[name];
161
+ const parsed = Number.parseInt(String(raw ?? '').trim(), 10);
162
+ if (Number.isFinite(parsed) && parsed > 0) {
163
+ return parsed;
164
+ }
165
+ return fallback;
166
+ }
167
+ export function peekHubStageTopSummary(requestId, options) {
168
+ if (!requestId) {
169
+ return [];
170
+ }
171
+ const byStage = REQUEST_STAGE_BREAKDOWNS.get(requestId);
172
+ if (!byStage || !byStage.size) {
173
+ return [];
174
+ }
175
+ const topN = Math.max(1, options?.topN ?? readIntEnv('ROUTECODEX_HUB_STAGE_TOP_N', DEFAULT_HUB_STAGE_TOP_N));
176
+ const minMs = Math.max(0, options?.minMs ?? readIntEnv('ROUTECODEX_HUB_STAGE_TOP_MIN_MS', DEFAULT_HUB_STAGE_TOP_MIN_MS));
177
+ return Array.from(byStage.entries())
178
+ .map(([stage, stats]) => {
179
+ const totalMs = Math.max(0, Math.round(stats.totalMs));
180
+ const count = Math.max(0, Math.floor(stats.count));
181
+ const maxMs = Math.max(0, Math.round(stats.maxMs));
182
+ const avgMs = count > 0 ? Math.max(0, Math.round(totalMs / count)) : 0;
183
+ return {
184
+ stage,
185
+ totalMs,
186
+ count,
187
+ avgMs,
188
+ maxMs
189
+ };
190
+ })
191
+ .filter((entry) => entry.totalMs >= minMs)
192
+ .sort((a, b) => b.totalMs - a.totalMs)
193
+ .slice(0, topN);
128
194
  }
129
195
  export function logHubStageTiming(requestId, stage, phase, details) {
196
+ const stageElapsedMs = phase === 'completed' || phase === 'error'
197
+ ? (typeof details?.elapsedMs === 'number'
198
+ ? details.elapsedMs
199
+ : typeof details?.nativeMs === 'number'
200
+ ? details.nativeMs
201
+ : undefined)
202
+ : undefined;
203
+ if (requestId &&
204
+ stage &&
205
+ typeof stageElapsedMs === 'number' &&
206
+ Number.isFinite(stageElapsedMs) &&
207
+ stageElapsedMs >= 0) {
208
+ recordHubStageElapsed(requestId, stage, stageElapsedMs);
209
+ }
130
210
  if (!isHubStageTimingEnabled() || !requestId || !stage) {
131
211
  return;
132
212
  }
@@ -200,10 +280,11 @@ export async function measureHubStage(requestId, stage, fn, options) {
200
280
  return value;
201
281
  }
202
282
  catch (error) {
283
+ const elapsedMs = Math.max(0, Date.now() - startedAt);
203
284
  const mapped = options?.mapErrorDetails?.(error);
204
285
  const message = error instanceof Error ? error.message : String(error ?? 'unknown');
205
286
  logHubStageTiming(requestId, stage, 'error', mapped ?? {
206
- elapsedMs: Math.max(0, Date.now() - startedAt),
287
+ elapsedMs,
207
288
  message
208
289
  });
209
290
  throw error;
@@ -1,4 +1,4 @@
1
- import { isJsonObject, jsonClone, } from "../../../../types/json.js";
1
+ import { isJsonObject, } from "../../../../types/json.js";
2
2
  import { applyHubOperationTableInbound } from "../../../../operation-table/operation-table-runner.js";
3
3
  import { recordStage } from "../../../stages/utils.js";
4
4
  import { liftReqInboundSemantics } from "./semantic-lift.js";
@@ -7,6 +7,28 @@ import { chatEnvelopeToStandardizedWithNative } from "../../../../../../router/v
7
7
  import { normalizeReqInboundShellLikeToolCallsWithNative } from "../../../../../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics-tools.js";
8
8
  import { fixApplyPatchToolCallsWithNative } from "../../../../../../router/virtual-router/engine-selection/native-compat-action-semantics.js";
9
9
  import { isHubStageTimingDetailEnabled, logHubStageTiming, } from "../../../hub-stage-timing.js";
10
+ function buildSlimResponsesContextForSemantics(context) {
11
+ if (!context || typeof context !== "object" || Array.isArray(context)) {
12
+ return undefined;
13
+ }
14
+ // Keep semantic essentials only; avoid carrying full `input` history through
15
+ // chat_process and req_process stages (it can be huge and is not required for
16
+ // non-responses outbound paths).
17
+ //
18
+ // IMPORTANT:
19
+ // Do not spread-clone first and then delete heavy keys. For large /v1/responses
20
+ // payloads that would deep-copy gigantic arrays/strings into a temporary object.
21
+ // Build a filtered object directly to keep this step O(selected fields).
22
+ const src = context;
23
+ const out = {};
24
+ for (const [key, value] of Object.entries(src)) {
25
+ if (key === "input" || key === "__captured_tool_results") {
26
+ continue;
27
+ }
28
+ out[key] = value;
29
+ }
30
+ return out;
31
+ }
10
32
  export async function runReqInboundStage2SemanticMap(options) {
11
33
  const requestId = options.adapterContext.requestId || "unknown";
12
34
  const forceDetailLog = isHubStageTimingDetailEnabled();
@@ -25,8 +47,12 @@ export async function runReqInboundStage2SemanticMap(options) {
25
47
  const contextNode = responsesNode && isJsonObject(responsesNode.context)
26
48
  ? responsesNode.context
27
49
  : undefined;
28
- return contextNode ? jsonClone(contextNode) : undefined;
50
+ // Perf: keep reference instead of deep clone to avoid multi-pass cloning on
51
+ // heavy /v1/responses histories.
52
+ return contextNode;
29
53
  })();
54
+ const semanticsResponsesContext = buildSlimResponsesContextForSemantics(preservedResponsesContext) ??
55
+ preservedResponsesContext;
30
56
  logHubStageTiming(requestId, "req_inbound.stage2_operation_table_inbound", "start");
31
57
  const operationTableStart = Date.now();
32
58
  applyHubOperationTableInbound({
@@ -52,11 +78,11 @@ export async function runReqInboundStage2SemanticMap(options) {
52
78
  elapsedMs: Date.now() - semanticLiftStart,
53
79
  forceLog: forceDetailLog,
54
80
  });
55
- if (preservedResponsesContext) {
81
+ if (semanticsResponsesContext) {
56
82
  const currentSemantics = chatEnvelope.semantics;
57
83
  if (!currentSemantics || typeof currentSemantics !== "object") {
58
84
  chatEnvelope.semantics = {
59
- responses: { context: jsonClone(preservedResponsesContext) },
85
+ responses: { context: semanticsResponsesContext },
60
86
  };
61
87
  }
62
88
  else {
@@ -69,19 +95,24 @@ export async function runReqInboundStage2SemanticMap(options) {
69
95
  ...semantics,
70
96
  responses: {
71
97
  ...responsesNode,
72
- context: jsonClone(preservedResponsesContext),
98
+ context: semanticsResponsesContext,
73
99
  },
74
100
  };
75
101
  }
76
102
  }
77
103
  }
78
- normalizeReqInboundShellLikeToolCallsWithNative(chatEnvelope);
79
- const fixedApplyPatch = fixApplyPatchToolCallsWithNative({
80
- messages: (Array.isArray(chatEnvelope.messages)
81
- ? chatEnvelope.messages
82
- : []),
83
- });
84
- chatEnvelope.messages = fixedApplyPatch.messages;
104
+ // openai-responses path already ran request_inbound bridge policy in
105
+ // buildChatRequestFromResponses (including call-id/apply-patch compat actions).
106
+ // Skip duplicate message-wide normalization passes here to reduce heavy-input cost.
107
+ if (options.formatEnvelope.protocol !== "openai-responses") {
108
+ normalizeReqInboundShellLikeToolCallsWithNative(chatEnvelope);
109
+ const fixedApplyPatch = fixApplyPatchToolCallsWithNative({
110
+ messages: (Array.isArray(chatEnvelope.messages)
111
+ ? chatEnvelope.messages
112
+ : []),
113
+ });
114
+ chatEnvelope.messages = fixedApplyPatch.messages;
115
+ }
85
116
  logHubStageTiming(requestId, "req_inbound.stage2_validate_chat_envelope", "start");
86
117
  const validateStart = Date.now();
87
118
  validateChatEnvelopeWithNative(chatEnvelope, {
@@ -107,7 +138,7 @@ export async function runReqInboundStage2SemanticMap(options) {
107
138
  const envelopeSemantics = chatEnvelope.semantics;
108
139
  const existing = standardizedRequest.semantics;
109
140
  if (!existing || typeof existing !== "object") {
110
- standardizedRequest.semantics = jsonClone(envelopeSemantics);
141
+ standardizedRequest.semantics = envelopeSemantics;
111
142
  }
112
143
  else {
113
144
  const existingObj = existing;
@@ -118,11 +149,13 @@ export async function runReqInboundStage2SemanticMap(options) {
118
149
  ? envelopeResponses.context
119
150
  : undefined;
120
151
  if (envelopeContext) {
152
+ const slimContext = buildSlimResponsesContextForSemantics(envelopeContext) ??
153
+ envelopeContext;
121
154
  const nextResponses = {
122
155
  ...(isJsonObject(existingObj.responses)
123
156
  ? existingObj.responses
124
157
  : {}),
125
- context: jsonClone(envelopeContext),
158
+ context: slimContext,
126
159
  };
127
160
  standardizedRequest.semantics = {
128
161
  ...existingObj,
@@ -19,6 +19,49 @@ export async function runReqOutboundStage1SemanticMap(options) {
19
19
  request: options.request,
20
20
  adapterContext: options.adapterContext
21
21
  });
22
+ // Perf: when outbound target is not /v1/responses, the large responses.context
23
+ // semantic snapshot is not needed for provider request mapping and can cause
24
+ // expensive deep traversals in downstream native mappers/policy actions.
25
+ if (providerProtocol !== 'openai-responses') {
26
+ const semantics = chatEnvelope.semantics;
27
+ const responsesNode = semantics && typeof semantics.responses === 'object' && semantics.responses !== null && !Array.isArray(semantics.responses)
28
+ ? semantics.responses
29
+ : undefined;
30
+ if (responsesNode && Object.prototype.hasOwnProperty.call(responsesNode, 'context')) {
31
+ const { context: _unusedContext, ...restResponses } = responsesNode;
32
+ if (Object.keys(restResponses).length > 0) {
33
+ chatEnvelope.semantics = {
34
+ ...(semantics ?? {}),
35
+ responses: restResponses
36
+ };
37
+ }
38
+ else if (semantics && Object.keys(semantics).length > 0) {
39
+ const { responses: _unusedResponses, ...restSemantics } = semantics;
40
+ chatEnvelope.semantics =
41
+ Object.keys(restSemantics).length > 0
42
+ ? restSemantics
43
+ : undefined;
44
+ }
45
+ }
46
+ }
47
+ if (providerProtocol === 'openai-responses'
48
+ && options.contextSnapshot
49
+ && typeof options.contextSnapshot === 'object'
50
+ && !Array.isArray(options.contextSnapshot)) {
51
+ const semantics = chatEnvelope.semantics && typeof chatEnvelope.semantics === 'object' && !Array.isArray(chatEnvelope.semantics)
52
+ ? chatEnvelope.semantics
53
+ : {};
54
+ const responsesNode = semantics.responses && typeof semantics.responses === 'object' && !Array.isArray(semantics.responses)
55
+ ? semantics.responses
56
+ : {};
57
+ chatEnvelope.semantics = {
58
+ ...semantics,
59
+ responses: {
60
+ ...responsesNode,
61
+ context: options.contextSnapshot
62
+ }
63
+ };
64
+ }
22
65
  logHubStageTiming(requestId, 'req_outbound.stage1_native_to_chat_envelope', 'completed', {
23
66
  elapsedMs: Date.now() - toChatStart,
24
67
  forceLog: forceDetailLog
@@ -9,8 +9,60 @@ function asRecord(value) {
9
9
  ? value
10
10
  : undefined;
11
11
  }
12
- function extractClientToolNameMap(clientToolsRaw) {
13
- const map = new Map();
12
+ function stripFunctionNamespace(raw) {
13
+ const trimmed = raw.trim();
14
+ if (!trimmed) {
15
+ return '';
16
+ }
17
+ const lowered = trimmed.toLowerCase();
18
+ if (lowered.startsWith('functions.')) {
19
+ return trimmed.slice('functions.'.length).trim();
20
+ }
21
+ if (lowered.startsWith('function.')) {
22
+ return trimmed.slice('function.'.length).trim();
23
+ }
24
+ return trimmed;
25
+ }
26
+ function toCanonicalToolName(raw) {
27
+ const stripped = stripFunctionNamespace(raw).toLowerCase().trim();
28
+ if (!stripped) {
29
+ return '';
30
+ }
31
+ return stripped
32
+ .replace(/[\s_-]+/g, '.')
33
+ .replace(/\.{2,}/g, '.')
34
+ .replace(/^\.+|\.+$/g, '');
35
+ }
36
+ function toCompactToolName(raw) {
37
+ const canonical = toCanonicalToolName(raw);
38
+ if (!canonical) {
39
+ return '';
40
+ }
41
+ return canonical.replace(/[._-]/g, '');
42
+ }
43
+ function resolveToolFamily(raw) {
44
+ const canonicalLower = toCanonicalToolName(raw);
45
+ if (!canonicalLower) {
46
+ return '';
47
+ }
48
+ const shellCandidate = canonicalLower.replace(/\./g, '_');
49
+ if (isShellToolName(canonicalLower) || isShellToolName(shellCandidate) || canonicalLower === 'terminal') {
50
+ return 'shell_like';
51
+ }
52
+ if (canonicalLower === 'apply.patch' || canonicalLower === 'apply_patch') {
53
+ return 'apply_patch';
54
+ }
55
+ if (canonicalLower === 'write.stdin' || canonicalLower === 'write_stdin') {
56
+ return 'write_stdin';
57
+ }
58
+ return canonicalLower;
59
+ }
60
+ function extractClientToolIndex(clientToolsRaw) {
61
+ const byExactLower = new Map();
62
+ const byStrippedLower = new Map();
63
+ const byCanonicalLower = new Map();
64
+ const byCompactLower = new Map();
65
+ const byFamily = new Map();
14
66
  for (const tool of clientToolsRaw ?? []) {
15
67
  const functionBag = asRecord(tool.function);
16
68
  const rawName = (typeof functionBag?.name === 'string' ? functionBag.name : undefined)
@@ -19,15 +71,70 @@ function extractClientToolNameMap(clientToolsRaw) {
19
71
  if (!normalizedName) {
20
72
  continue;
21
73
  }
22
- map.set(normalizedName.toLowerCase(), tool);
74
+ const entry = {
75
+ declaredName: normalizedName,
76
+ tool
77
+ };
78
+ const exactLower = normalizedName.toLowerCase();
79
+ const strippedLower = stripFunctionNamespace(normalizedName).toLowerCase();
80
+ const canonicalLower = toCanonicalToolName(normalizedName);
81
+ const compactLower = toCompactToolName(normalizedName);
82
+ const family = resolveToolFamily(normalizedName);
83
+ if (!byExactLower.has(exactLower)) {
84
+ byExactLower.set(exactLower, entry);
85
+ }
86
+ if (strippedLower && !byStrippedLower.has(strippedLower)) {
87
+ byStrippedLower.set(strippedLower, entry);
88
+ }
89
+ if (canonicalLower && !byCanonicalLower.has(canonicalLower)) {
90
+ byCanonicalLower.set(canonicalLower, entry);
91
+ }
92
+ if (compactLower && !byCompactLower.has(compactLower)) {
93
+ byCompactLower.set(compactLower, entry);
94
+ }
95
+ if (family && !byFamily.has(family)) {
96
+ byFamily.set(family, entry);
97
+ }
98
+ }
99
+ return { byExactLower, byStrippedLower, byCanonicalLower, byCompactLower, byFamily };
100
+ }
101
+ function resolveClientToolFromIndex(index, rawName) {
102
+ const trimmed = rawName.trim();
103
+ if (!trimmed) {
104
+ return undefined;
23
105
  }
24
- return map;
106
+ const exactLower = trimmed.toLowerCase();
107
+ const strippedLower = stripFunctionNamespace(trimmed).toLowerCase();
108
+ const canonicalLower = toCanonicalToolName(trimmed);
109
+ const compactLower = toCompactToolName(trimmed);
110
+ const family = resolveToolFamily(trimmed);
111
+ return (index.byExactLower.get(exactLower)
112
+ ?? index.byExactLower.get(strippedLower)
113
+ ?? index.byStrippedLower.get(strippedLower)
114
+ ?? (canonicalLower ? index.byCanonicalLower.get(canonicalLower) : undefined)
115
+ ?? (canonicalLower ? index.byStrippedLower.get(canonicalLower) : undefined)
116
+ ?? (compactLower ? index.byCompactLower.get(compactLower) : undefined)
117
+ ?? (family ? index.byFamily.get(family) : undefined));
25
118
  }
26
119
  function remapChatToolCallsToClientNames(payload, clientToolsRaw) {
27
- const toolMap = extractClientToolNameMap(clientToolsRaw);
28
- if (!toolMap.size) {
29
- return;
120
+ const toolIndex = extractClientToolIndex(clientToolsRaw);
121
+ if (!toolIndex.byExactLower.size) {
122
+ return [];
30
123
  }
124
+ const unknownNames = [];
125
+ const seenUnknown = new Set();
126
+ const pushUnknown = (name) => {
127
+ const key = name.trim();
128
+ if (!key || seenUnknown.has(key)) {
129
+ return;
130
+ }
131
+ seenUnknown.add(key);
132
+ unknownNames.push(key);
133
+ };
134
+ const readSchema = (entry) => {
135
+ const functionBag = asRecord(entry?.tool.function);
136
+ return functionBag?.parameters;
137
+ };
31
138
  const choices = Array.isArray(payload.choices)
32
139
  ? payload.choices
33
140
  : [];
@@ -40,20 +147,13 @@ function remapChatToolCallsToClientNames(payload, clientToolsRaw) {
40
147
  if (!currentName) {
41
148
  continue;
42
149
  }
43
- const normalizedCurrentName = currentName.toLowerCase();
44
- const matchedTool = toolMap.get(normalizedCurrentName)
45
- ?? (isShellToolName(normalizedCurrentName)
46
- ? Array.from(toolMap.entries()).find(([toolName]) => isShellToolName(toolName))?.[1]
47
- : undefined);
150
+ const matchedTool = resolveClientToolFromIndex(toolIndex, currentName);
48
151
  if (!matchedTool) {
152
+ pushUnknown(currentName);
49
153
  continue;
50
154
  }
51
- const matchedFunction = asRecord(matchedTool.function);
52
- const clientName = (typeof matchedFunction?.name === 'string' ? matchedFunction.name : undefined)
53
- ?? (typeof matchedTool.name === 'string' ? matchedTool.name : undefined)
54
- ?? currentName;
55
- functionBag.name = clientName;
56
- const schema = matchedFunction?.parameters;
155
+ functionBag.name = matchedTool.declaredName;
156
+ const schema = readSchema(matchedTool);
57
157
  const rawArgs = functionBag?.arguments;
58
158
  let parsedArgs = rawArgs;
59
159
  if (typeof rawArgs === 'string') {
@@ -75,13 +175,115 @@ function remapChatToolCallsToClientNames(payload, clientToolsRaw) {
75
175
  }
76
176
  }
77
177
  }
178
+ return unknownNames;
179
+ }
180
+ function remapResponsesToolCallsToClientNames(payload, clientToolsRaw) {
181
+ const toolIndex = extractClientToolIndex(clientToolsRaw);
182
+ if (!toolIndex.byExactLower.size) {
183
+ return [];
184
+ }
185
+ const unknownNames = [];
186
+ const seenUnknown = new Set();
187
+ const pushUnknown = (name) => {
188
+ const key = name.trim();
189
+ if (!key || seenUnknown.has(key)) {
190
+ return;
191
+ }
192
+ seenUnknown.add(key);
193
+ unknownNames.push(key);
194
+ };
195
+ const requiredActionCalls = Array.isArray(payload?.required_action?.submit_tool_outputs?.tool_calls)
196
+ ? payload.required_action.submit_tool_outputs.tool_calls
197
+ : [];
198
+ for (const call of requiredActionCalls) {
199
+ const callBag = asRecord(call);
200
+ if (!callBag) {
201
+ continue;
202
+ }
203
+ const rawName = typeof callBag.name === 'string' ? callBag.name.trim() : '';
204
+ if (!rawName) {
205
+ continue;
206
+ }
207
+ const matched = resolveClientToolFromIndex(toolIndex, rawName);
208
+ if (!matched) {
209
+ pushUnknown(rawName);
210
+ continue;
211
+ }
212
+ callBag.name = matched.declaredName;
213
+ }
214
+ const outputItems = Array.isArray(payload?.output) ? payload.output : [];
215
+ for (const item of outputItems) {
216
+ const itemBag = asRecord(item);
217
+ if (!itemBag) {
218
+ continue;
219
+ }
220
+ const type = typeof itemBag.type === 'string' ? itemBag.type.trim().toLowerCase() : '';
221
+ if (type !== 'function_call') {
222
+ continue;
223
+ }
224
+ const rawName = typeof itemBag.name === 'string' ? itemBag.name.trim() : '';
225
+ if (!rawName) {
226
+ continue;
227
+ }
228
+ const matched = resolveClientToolFromIndex(toolIndex, rawName);
229
+ if (!matched) {
230
+ pushUnknown(rawName);
231
+ continue;
232
+ }
233
+ itemBag.name = matched.declaredName;
234
+ }
235
+ return unknownNames;
236
+ }
237
+ function assertNoUnknownToolNames(args) {
238
+ const uniqueUnknown = Array.from(new Set(args.unknownNames.map((name) => name.trim()).filter(Boolean)));
239
+ if (!uniqueUnknown.length) {
240
+ return;
241
+ }
242
+ const declaredNames = (args.clientToolsRaw ?? [])
243
+ .map((tool) => {
244
+ const fn = asRecord(tool.function);
245
+ const fnName = typeof fn?.name === 'string' ? fn.name.trim() : '';
246
+ if (fnName) {
247
+ return fnName;
248
+ }
249
+ const topName = typeof tool.name === 'string' ? tool.name.trim() : '';
250
+ return topName || '';
251
+ })
252
+ .filter(Boolean);
253
+ const declaredPreview = declaredNames.slice(0, 20).join(', ');
254
+ const error = new Error(`[client-remap] tool name mismatch after remap: unknown=[${uniqueUnknown.join(', ')}]` +
255
+ ` protocol=${args.clientProtocol} requestId=${args.requestId}` +
256
+ (declaredPreview ? ` declared=[${declaredPreview}]` : ' declared=[none]'));
257
+ error.code = 'CLIENT_TOOL_NAME_MISMATCH';
258
+ error.statusCode = 502;
259
+ error.retryable = true;
260
+ error.details = {
261
+ unknownToolNames: uniqueUnknown,
262
+ declaredToolNames: declaredNames,
263
+ protocol: args.clientProtocol,
264
+ requestId: args.requestId
265
+ };
266
+ throw error;
267
+ }
268
+ function enforceClientToolNameContract(options, payload, toolsRaw) {
269
+ const hasClientTools = Array.isArray(toolsRaw) && toolsRaw.length > 0;
270
+ if (!hasClientTools) {
271
+ return;
272
+ }
273
+ const unknownFromChat = remapChatToolCallsToClientNames(payload, toolsRaw);
274
+ const unknownFromResponses = remapResponsesToolCallsToClientNames(payload, toolsRaw);
275
+ assertNoUnknownToolNames({
276
+ requestId: options.requestId,
277
+ clientProtocol: options.clientProtocol,
278
+ unknownNames: [...unknownFromChat, ...unknownFromResponses],
279
+ clientToolsRaw: toolsRaw
280
+ });
78
281
  }
79
282
  export function buildClientPayloadForProtocol(options) {
80
283
  let clientPayload;
81
284
  const toolsRaw = resolveClientToolsRawFromSemantics(options.requestSemantics);
82
285
  if (options.clientProtocol === 'openai-chat') {
83
286
  clientPayload = options.payload;
84
- remapChatToolCallsToClientNames(clientPayload, toolsRaw);
85
287
  }
86
288
  else if (options.clientProtocol === 'anthropic-messages') {
87
289
  clientPayload = buildAnthropicResponseFromChat(options.payload, {
@@ -99,5 +301,6 @@ export function buildClientPayloadForProtocol(options) {
99
301
  if (options.clientProtocol === 'openai-responses') {
100
302
  normalizeResponsesToolCallIds(clientPayload);
101
303
  }
304
+ enforceClientToolNameContract(options, clientPayload, toolsRaw);
102
305
  return clientPayload;
103
306
  }
@@ -212,7 +212,7 @@ export function recordHubPolicyObservation(options) {
212
212
  return;
213
213
  }
214
214
  const compatibilityProfile = typeof options.compatibilityProfile === 'string' ? options.compatibilityProfile.trim().toLowerCase() : '';
215
- if (compatibilityProfile === 'chat:deepseek-web') {
215
+ if (compatibilityProfile === 'chat:deepseek-web' || compatibilityProfile === 'chat:qwenchat-web') {
216
216
  return;
217
217
  }
218
218
  if (!shouldSample(effectivePolicy?.sampleRate)) {
@@ -247,7 +247,7 @@ export function applyHubProviderOutboundPolicy(options) {
247
247
  }
248
248
  const normalizedProviderProtocol = normalizeHubProviderProtocol(options.providerProtocol);
249
249
  const compatibilityProfile = typeof options.compatibilityProfile === 'string' ? options.compatibilityProfile.trim().toLowerCase() : '';
250
- if (compatibilityProfile === 'chat:deepseek-web') {
250
+ if (compatibilityProfile === 'chat:deepseek-web' || compatibilityProfile === 'chat:qwenchat-web') {
251
251
  return options.payload;
252
252
  }
253
253
  const result = applyProviderOutboundPolicy(normalizedProviderProtocol, options.payload);
@@ -3,19 +3,34 @@ import { analyzePendingToolSync } from '../../../router/virtual-router/engine-se
3
3
  function readString(value) {
4
4
  return typeof value === 'string' ? value : undefined;
5
5
  }
6
- function resolveSessionIdForPending(metadata, request) {
7
- const candidate = readString(metadata.sessionId) ?? readString(request.metadata?.sessionId);
8
- return candidate && candidate.trim() ? candidate.trim() : null;
6
+ function resolvePendingSessionCandidates(metadata, request) {
7
+ const candidates = [
8
+ readString(metadata.sessionId),
9
+ readString(request.metadata?.sessionId),
10
+ readString(metadata.conversationId),
11
+ readString(request.metadata?.conversationId)
12
+ ]
13
+ .filter((value) => typeof value === 'string' && value.trim().length > 0)
14
+ .map((value) => value.trim());
15
+ return Array.from(new Set(candidates));
9
16
  }
10
17
  export async function maybeInjectPendingServerToolResultsAfterClientTools(request, metadata, deps = {}) {
11
18
  const loadFn = deps.loadPendingServerToolInjectionFn ?? loadPendingServerToolInjection;
12
19
  const clearFn = deps.clearPendingServerToolInjectionFn ?? clearPendingServerToolInjection;
13
20
  const analyzeFn = deps.analyzePendingToolSyncFn ?? analyzePendingToolSync;
14
- const sessionId = resolveSessionIdForPending(metadata, request);
15
- if (!sessionId) {
21
+ const sessionCandidates = resolvePendingSessionCandidates(metadata, request);
22
+ if (!sessionCandidates.length) {
16
23
  return request;
17
24
  }
18
- const pending = await loadFn(sessionId);
25
+ let loadedSessionId = null;
26
+ let pending = null;
27
+ for (const sessionId of sessionCandidates) {
28
+ pending = await loadFn(sessionId);
29
+ if (pending) {
30
+ loadedSessionId = sessionId;
31
+ break;
32
+ }
33
+ }
19
34
  if (!pending) {
20
35
  return request;
21
36
  }
@@ -43,7 +58,9 @@ export async function maybeInjectPendingServerToolResultsAfterClientTools(reques
43
58
  const nextMessages = messages.slice();
44
59
  nextMessages.splice(analysis.insertAt + 1, 0, ...inject);
45
60
  try {
46
- await clearFn(sessionId);
61
+ if (loadedSessionId) {
62
+ await clearFn(loadedSessionId);
63
+ }
47
64
  }
48
65
  catch {
49
66
  // best-effort