@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.
Files changed (270) hide show
  1. package/README.md +240 -179
  2. package/config/modules.json +1 -11
  3. package/dist/build-info.js +2 -2
  4. package/dist/build-info.js.map +1 -1
  5. package/dist/client/gemini-cli/gemini-cli-protocol-client.d.ts +16 -0
  6. package/dist/client/gemini-cli/gemini-cli-protocol-client.js +56 -0
  7. package/dist/client/gemini-cli/gemini-cli-protocol-client.js.map +1 -0
  8. package/dist/client/openai/chat-protocol-client.js.map +1 -1
  9. package/dist/config/modules.json +1 -11
  10. package/dist/core/provider-health-manager.d.ts +17 -0
  11. package/dist/core/provider-health-manager.js +66 -0
  12. package/dist/core/provider-health-manager.js.map +1 -0
  13. package/dist/error-handling/route-error-hub.d.ts +48 -0
  14. package/dist/error-handling/route-error-hub.js +131 -0
  15. package/dist/error-handling/route-error-hub.js.map +1 -0
  16. package/dist/index.js +26 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/modules/llmswitch/bridge.d.ts +2 -0
  19. package/dist/modules/llmswitch/bridge.js +17 -0
  20. package/dist/modules/llmswitch/bridge.js.map +1 -1
  21. package/dist/modules/pipeline/utils/colored-logger.d.ts +14 -0
  22. package/dist/modules/pipeline/utils/colored-logger.js +48 -0
  23. package/dist/modules/pipeline/utils/colored-logger.js.map +1 -0
  24. package/dist/modules/pipeline/utils/debug-logger.d.ts +2 -0
  25. package/dist/modules/pipeline/utils/debug-logger.js +36 -0
  26. package/dist/modules/pipeline/utils/debug-logger.js.map +1 -1
  27. package/dist/providers/auth/gemini-cli-userinfo-helper.d.ts +53 -0
  28. package/dist/providers/auth/gemini-cli-userinfo-helper.js +152 -0
  29. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -0
  30. package/dist/providers/auth/oauth-auth.js +3 -2
  31. package/dist/providers/auth/oauth-auth.js.map +1 -1
  32. package/dist/providers/auth/oauth-lifecycle.js +21 -20
  33. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  34. package/dist/providers/auth/oauth-logger.d.ts +1 -0
  35. package/dist/providers/auth/oauth-logger.js +21 -0
  36. package/dist/providers/auth/oauth-logger.js.map +1 -0
  37. package/dist/providers/compat/compat-directory-loader.js +2 -55
  38. package/dist/providers/compat/compat-directory-loader.js.map +1 -1
  39. package/dist/providers/compat/compatibility-factory.d.ts +4 -4
  40. package/dist/providers/compat/compatibility-factory.js +108 -0
  41. package/dist/providers/compat/compatibility-factory.js.map +1 -1
  42. package/dist/providers/compat/glm/glm-compatibility.d.ts +2 -2
  43. package/dist/providers/compat/glm/glm-compatibility.js +7 -7
  44. package/dist/providers/compat/glm/glm-compatibility.js.map +1 -1
  45. package/dist/providers/compat/glm/index.js +0 -6
  46. package/dist/providers/compat/glm/index.js.map +1 -1
  47. package/dist/providers/compat/iflow/iflow-compatibility.d.ts +1 -1
  48. package/dist/providers/compat/iflow/iflow-compatibility.js +6 -6
  49. package/dist/providers/compat/iflow/iflow-compatibility.js.map +1 -1
  50. package/dist/providers/compat/index.d.ts +0 -6
  51. package/dist/providers/compat/index.js +0 -7
  52. package/dist/providers/compat/index.js.map +1 -1
  53. package/dist/providers/compat/lmstudio-compatibility.d.ts +2 -2
  54. package/dist/providers/compat/lmstudio-compatibility.js +4 -4
  55. package/dist/providers/compat/lmstudio-compatibility.js.map +1 -1
  56. package/dist/providers/compat/passthrough-compatibility.d.ts +1 -1
  57. package/dist/providers/compat/passthrough-compatibility.js +3 -3
  58. package/dist/providers/compat/passthrough-compatibility.js.map +1 -1
  59. package/dist/providers/compat/profiles/chat/glm/index.d.ts +6 -0
  60. package/dist/providers/compat/profiles/chat/glm/index.js +6 -0
  61. package/dist/providers/compat/profiles/chat/glm/index.js.map +1 -0
  62. package/dist/providers/compat/profiles/chat/iflow/index.d.ts +6 -0
  63. package/dist/providers/compat/profiles/chat/iflow/index.js +6 -0
  64. package/dist/providers/compat/profiles/chat/iflow/index.js.map +1 -0
  65. package/dist/providers/compat/profiles/chat/lmstudio/index.d.ts +6 -0
  66. package/dist/providers/compat/profiles/chat/lmstudio/index.js +6 -0
  67. package/dist/providers/compat/profiles/chat/lmstudio/index.js.map +1 -0
  68. package/dist/providers/compat/profiles/chat/qwen/index.d.ts +6 -0
  69. package/dist/providers/compat/profiles/chat/qwen/index.js +6 -0
  70. package/dist/providers/compat/profiles/chat/qwen/index.js.map +1 -0
  71. package/dist/providers/compat/profiles/compat/passthrough/index.d.ts +6 -0
  72. package/dist/providers/compat/profiles/compat/passthrough/index.js +6 -0
  73. package/dist/providers/compat/profiles/compat/passthrough/index.js.map +1 -0
  74. package/dist/providers/compat/profiles/responses/c4m/index.d.ts +6 -0
  75. package/dist/providers/compat/profiles/responses/c4m/index.js +6 -0
  76. package/dist/providers/compat/profiles/responses/c4m/index.js.map +1 -0
  77. package/dist/providers/compat/profiles/responses/default/index.d.ts +6 -0
  78. package/dist/providers/compat/profiles/responses/default/index.js +6 -0
  79. package/dist/providers/compat/profiles/responses/default/index.js.map +1 -0
  80. package/dist/providers/compat/profiles/responses/fai/index.d.ts +6 -0
  81. package/dist/providers/compat/profiles/responses/fai/index.js +6 -0
  82. package/dist/providers/compat/profiles/responses/fai/index.js.map +1 -0
  83. package/dist/providers/compat/profiles/responses/fc/index.d.ts +6 -0
  84. package/dist/providers/compat/profiles/responses/fc/index.js +6 -0
  85. package/dist/providers/compat/profiles/responses/fc/index.js.map +1 -0
  86. package/dist/providers/compat/qwen/index.js +0 -6
  87. package/dist/providers/compat/qwen/index.js.map +1 -1
  88. package/dist/providers/compat/qwen-compatibility.d.ts +2 -2
  89. package/dist/providers/compat/qwen-compatibility.js +4 -4
  90. package/dist/providers/compat/qwen-compatibility.js.map +1 -1
  91. package/dist/providers/compat/register-compat-module.d.ts +8 -0
  92. package/dist/providers/compat/register-compat-module.js +53 -0
  93. package/dist/providers/compat/register-compat-module.js.map +1 -0
  94. package/dist/providers/compat/responses/c4m-responses-compatibility.d.ts +6 -2
  95. package/dist/providers/compat/responses/c4m-responses-compatibility.js +85 -3
  96. package/dist/providers/compat/responses/c4m-responses-compatibility.js.map +1 -1
  97. package/dist/providers/compat/standard-compatibility-utils.js +45 -15
  98. package/dist/providers/compat/standard-compatibility-utils.js.map +1 -1
  99. package/dist/providers/core/api/provider-config.d.ts +1 -1
  100. package/dist/providers/core/api/provider-types.d.ts +3 -1
  101. package/dist/providers/core/api/provider-types.js +1 -0
  102. package/dist/providers/core/api/provider-types.js.map +1 -1
  103. package/dist/providers/core/config/service-profiles.js +5 -2
  104. package/dist/providers/core/config/service-profiles.js.map +1 -1
  105. package/dist/providers/core/runtime/base-provider.d.ts +3 -0
  106. package/dist/providers/core/runtime/base-provider.js +101 -6
  107. package/dist/providers/core/runtime/base-provider.js.map +1 -1
  108. package/dist/providers/core/runtime/gemini-cli-http-provider.d.ts +34 -0
  109. package/dist/providers/core/runtime/gemini-cli-http-provider.js +152 -0
  110. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -0
  111. package/dist/providers/core/runtime/http-transport-provider.d.ts +1 -0
  112. package/dist/providers/core/runtime/http-transport-provider.js +178 -123
  113. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  114. package/dist/providers/core/runtime/provider-factory.d.ts +1 -1
  115. package/dist/providers/core/runtime/provider-factory.js +8 -0
  116. package/dist/providers/core/runtime/provider-factory.js.map +1 -1
  117. package/dist/providers/core/runtime/provider-runtime-metadata.d.ts +1 -0
  118. package/dist/providers/core/runtime/provider-runtime-metadata.js.map +1 -1
  119. package/dist/providers/core/runtime/responses-provider.d.ts +7 -0
  120. package/dist/providers/core/runtime/responses-provider.js +228 -12
  121. package/dist/providers/core/runtime/responses-provider.js.map +1 -1
  122. package/dist/providers/core/strategies/oauth-auth-code-flow.js +10 -9
  123. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  124. package/dist/providers/core/strategies/oauth-device-flow.js +10 -9
  125. package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
  126. package/dist/providers/core/utils/provider-error-reporter.js +63 -15
  127. package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
  128. package/dist/providers/core/utils/provider-type-utils.d.ts +1 -1
  129. package/dist/providers/core/utils/provider-type-utils.js +6 -1
  130. package/dist/providers/core/utils/provider-type-utils.js.map +1 -1
  131. package/dist/providers/core/utils/snapshot-writer.d.ts +10 -0
  132. package/dist/providers/core/utils/snapshot-writer.js +85 -0
  133. package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
  134. package/dist/providers/mock/mock-provider-runtime.js +44 -0
  135. package/dist/providers/mock/mock-provider-runtime.js.map +1 -1
  136. package/dist/providers/profile/provider-profile-loader.js +26 -19
  137. package/dist/providers/profile/provider-profile-loader.js.map +1 -1
  138. package/dist/providers/profile/provider-profile.d.ts +2 -2
  139. package/dist/server/handlers/chat-handler.js +9 -3
  140. package/dist/server/handlers/chat-handler.js.map +1 -1
  141. package/dist/server/handlers/handler-utils.d.ts +7 -1
  142. package/dist/server/handlers/handler-utils.js +64 -52
  143. package/dist/server/handlers/handler-utils.js.map +1 -1
  144. package/dist/server/handlers/messages-handler.js +9 -3
  145. package/dist/server/handlers/messages-handler.js.map +1 -1
  146. package/dist/server/handlers/responses-handler.js +21 -13
  147. package/dist/server/handlers/responses-handler.js.map +1 -1
  148. package/dist/server/runtime/http-server/colored-logger.d.ts +1 -0
  149. package/dist/server/runtime/http-server/colored-logger.js +33 -0
  150. package/dist/server/runtime/http-server/colored-logger.js.map +1 -0
  151. package/dist/server/runtime/http-server/index.d.ts +3 -0
  152. package/dist/server/runtime/http-server/index.js +76 -19
  153. package/dist/server/runtime/http-server/index.js.map +1 -1
  154. package/dist/server/runtime/http-server/provider-utils.d.ts +3 -1
  155. package/dist/server/runtime/http-server/provider-utils.js +12 -2
  156. package/dist/server/runtime/http-server/provider-utils.js.map +1 -1
  157. package/dist/server/runtime/http-server/request-executor.js +6 -2
  158. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  159. package/dist/server/runtime/http-server/routes.js +31 -11
  160. package/dist/server/runtime/http-server/routes.js.map +1 -1
  161. package/dist/server/runtime/http-server/types.d.ts +2 -1
  162. package/dist/utils/error-center-payload.d.ts +7 -0
  163. package/dist/utils/error-center-payload.js +67 -0
  164. package/dist/utils/error-center-payload.js.map +1 -0
  165. package/dist/utils/error-handler-registry.d.ts +7 -0
  166. package/dist/utils/error-handler-registry.js +44 -12
  167. package/dist/utils/error-handler-registry.js.map +1 -1
  168. package/node_modules/@jsonstudio/llms/dist/conversion/codecs/responses-openai-codec.js +16 -1
  169. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +17 -0
  170. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +36 -0
  171. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +37 -0
  172. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +18 -0
  173. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +45 -0
  174. package/node_modules/@jsonstudio/llms/dist/conversion/config/compat-profiles.json +38 -0
  175. package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +314 -0
  176. package/node_modules/@jsonstudio/llms/dist/conversion/config/version-switch.json +150 -0
  177. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +4 -0
  178. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-engine.js +667 -0
  179. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-profile-store.d.ts +2 -0
  180. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-profile-store.js +76 -0
  181. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +62 -0
  182. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.js +1 -0
  183. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
  184. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +110 -29
  185. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.d.ts +14 -0
  186. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +23 -0
  187. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +34 -0
  188. package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +4 -1
  189. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +26 -0
  190. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.d.ts +1 -0
  191. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +71 -0
  192. package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-conversation-store.d.ts +35 -0
  193. package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-conversation-store.js +64 -19
  194. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.d.ts +21 -0
  195. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +138 -22
  196. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.d.ts +21 -0
  197. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-governor.js +116 -1
  198. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +52 -2
  199. package/node_modules/@jsonstudio/llms/dist/filters/config/openai-openai.fieldmap.json +18 -0
  200. package/node_modules/@jsonstudio/llms/dist/filters/special/request-tools-normalize.js +20 -1
  201. package/node_modules/@jsonstudio/llms/dist/guidance/index.js +6 -2
  202. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +16 -7
  203. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +40 -37
  204. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/default-thinking-keywords.d.ts +1 -0
  205. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/default-thinking-keywords.js +13 -0
  206. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +39 -0
  207. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +52 -11
  208. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +340 -11
  209. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.d.ts +2 -0
  210. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +105 -0
  211. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +8 -0
  212. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +2 -2
  213. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.d.ts +2 -0
  214. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +53 -11
  215. package/node_modules/@jsonstudio/llms/dist/test-output/virtual-router/results.json +1 -0
  216. package/node_modules/@jsonstudio/llms/dist/test-output/virtual-router/summary.json +12 -0
  217. package/node_modules/@jsonstudio/llms/dist/tools/tool-registry.js +4 -3
  218. package/node_modules/@jsonstudio/llms/package.json +3 -3
  219. package/package.json +11 -9
  220. package/scripts/analyze-routing-classifier.mjs +166 -0
  221. package/scripts/analyze-routing-samples.mjs +216 -0
  222. package/scripts/analyze-thinking-keywords.mjs +17 -2
  223. package/scripts/build-core.mjs +8 -0
  224. package/scripts/classify-sample-tools.mjs +252 -0
  225. package/scripts/ensure-llmswitch-mode.mjs +95 -0
  226. package/scripts/gen-build-info.mjs +58 -4
  227. package/scripts/install-global.sh +1 -1
  228. package/scripts/install-release.sh +7 -0
  229. package/scripts/mock-provider/run-regressions.mjs +60 -14
  230. package/scripts/tests/apply-patch-loop.mjs +100 -9
  231. package/scripts/tests/golden-provider-cycle.mjs +12 -1
  232. package/scripts/tests/responses-provider-dry-run.mjs +15 -1
  233. package/scripts/tests/virtual-router-health.mjs +12 -5
  234. package/scripts/tools/capture-provider-goldens.mjs +75 -25
  235. package/scripts/tools/responses-golden-dry-run.mjs +17 -1
  236. package/scripts/tools/responses-provider-replay.mjs +17 -1
  237. package/scripts/tools/sync-ci-goldens.mjs +131 -0
  238. package/scripts/verification/samples/openai-chat-list-local-files.json +19 -796
  239. package/scripts/verify-e2e-toolcall.mjs +52 -0
  240. package/dist/providers/compat/config/index.d.ts +0 -1
  241. package/dist/providers/compat/config/index.js +0 -5
  242. package/dist/providers/compat/config/index.js.map +0 -1
  243. package/dist/providers/compat/iflow/index.d.ts +0 -27
  244. package/dist/providers/compat/iflow/index.js +0 -32
  245. package/dist/providers/compat/iflow/index.js.map +0 -1
  246. package/dist/providers/compat/lmstudio/index.d.ts +0 -4
  247. package/dist/providers/compat/lmstudio/index.js +0 -10
  248. package/dist/providers/compat/lmstudio/index.js.map +0 -1
  249. package/dist/providers/compat/passthrough/index.d.ts +0 -4
  250. package/dist/providers/compat/passthrough/index.js +0 -9
  251. package/dist/providers/compat/passthrough/index.js.map +0 -1
  252. package/dist/providers/compat/responses/index.d.ts +0 -1
  253. package/dist/providers/compat/responses/index.js +0 -8
  254. package/dist/providers/compat/responses/index.js.map +0 -1
  255. package/dist/providers/core/composite/compat/anthropic.d.ts +0 -3
  256. package/dist/providers/core/composite/compat/anthropic.js +0 -7
  257. package/dist/providers/core/composite/compat/anthropic.js.map +0 -1
  258. package/dist/providers/core/composite/compat/gemini.d.ts +0 -3
  259. package/dist/providers/core/composite/compat/gemini.js +0 -7
  260. package/dist/providers/core/composite/compat/gemini.js.map +0 -1
  261. package/dist/providers/core/composite/compat/openai-compat-aggregator.d.ts +0 -9
  262. package/dist/providers/core/composite/compat/openai-compat-aggregator.js +0 -135
  263. package/dist/providers/core/composite/compat/openai-compat-aggregator.js.map +0 -1
  264. package/dist/providers/core/composite/compat/responses.d.ts +0 -3
  265. package/dist/providers/core/composite/compat/responses.js +0 -91
  266. package/dist/providers/core/composite/compat/responses.js.map +0 -1
  267. package/dist/providers/core/composite/provider-composite.d.ts +0 -50
  268. package/dist/providers/core/composite/provider-composite.js +0 -235
  269. package/dist/providers/core/composite/provider-composite.js.map +0 -1
  270. package/scripts/tests/apply-patch-loop.mjs.bak +0 -363
@@ -1,4 +1,4 @@
1
- const TTL_MS = 1000 * 60 * 30; // 30 minutes safety window
1
+ const TTL_MS = 1000 * 60 * 30; // 30min
2
2
  function cloneDeep(value) {
3
3
  try {
4
4
  if (typeof globalThis.structuredClone === 'function') {
@@ -49,19 +49,26 @@ function pickPersistedFields(payload) {
49
49
  'input_audio',
50
50
  'output_audio'
51
51
  ];
52
- const result = {};
52
+ const next = {};
53
53
  for (const key of fields) {
54
54
  if (payload[key] !== undefined) {
55
- result[key] = cloneDeep(payload[key]);
55
+ next[key] = cloneDeep(payload[key]);
56
56
  }
57
57
  }
58
- return result;
58
+ return next;
59
59
  }
60
60
  function normalizeOutputItemToInput(item) {
61
+ if (!item || typeof item !== 'object') {
62
+ return null;
63
+ }
61
64
  const type = typeof item.type === 'string' ? item.type : '';
62
65
  if (type === 'message' || type === 'reasoning') {
63
66
  const role = typeof item.role === 'string' ? item.role : 'assistant';
64
- const content = Array.isArray(item.content) ? cloneDeep(item.content) : (typeof item.text === 'string' ? [{ type: 'text', text: item.text }] : []);
67
+ const content = Array.isArray(item.content)
68
+ ? cloneDeep(item.content)
69
+ : typeof item.text === 'string'
70
+ ? [{ type: 'text', text: item.text }]
71
+ : [];
65
72
  return {
66
73
  type: 'message',
67
74
  role,
@@ -70,7 +77,11 @@ function normalizeOutputItemToInput(item) {
70
77
  };
71
78
  }
72
79
  if (type === 'function_call') {
73
- const callId = typeof item.call_id === 'string' ? item.call_id : (typeof item.id === 'string' ? item.id : undefined);
80
+ const callId = typeof item.call_id === 'string'
81
+ ? item.call_id
82
+ : typeof item.id === 'string'
83
+ ? item.id
84
+ : undefined;
74
85
  return {
75
86
  type: 'function_call',
76
87
  role: 'assistant',
@@ -80,9 +91,9 @@ function normalizeOutputItemToInput(item) {
80
91
  arguments: item.arguments,
81
92
  function: isRecord(item.function)
82
93
  ? cloneDeep(item.function)
83
- : (typeof item.name === 'string'
94
+ : typeof item.name === 'string'
84
95
  ? { name: item.name, arguments: item.arguments }
85
- : undefined)
96
+ : undefined
86
97
  };
87
98
  }
88
99
  return null;
@@ -139,6 +150,18 @@ function normalizeSubmittedToolOutputs(toolOutputs) {
139
150
  class ResponsesConversationStore {
140
151
  requestMap = new Map();
141
152
  responseIndex = new Map();
153
+ rebindRequestId(oldId, newId) {
154
+ if (!oldId || !newId || oldId === newId) {
155
+ return;
156
+ }
157
+ const entry = this.requestMap.get(oldId);
158
+ if (!entry) {
159
+ return;
160
+ }
161
+ this.requestMap.delete(oldId);
162
+ entry.requestId = newId;
163
+ this.requestMap.set(newId, entry);
164
+ }
142
165
  captureRequestContext(args) {
143
166
  const { requestId, payload, context } = args;
144
167
  if (!requestId || !payload)
@@ -148,7 +171,8 @@ class ResponsesConversationStore {
148
171
  requestId,
149
172
  basePayload: pickPersistedFields(payload),
150
173
  input: coerceInputArray(context.input),
151
- tools: coerceTools(context.toolsRaw || (Array.isArray(payload.tools) ? payload.tools : undefined)),
174
+ tools: coerceTools(context.toolsRaw) ||
175
+ coerceTools(Array.isArray(payload.tools) ? payload.tools : undefined),
152
176
  createdAt: Date.now(),
153
177
  updatedAt: Date.now()
154
178
  };
@@ -164,7 +188,7 @@ class ResponsesConversationStore {
164
188
  this.requestMap.set(requestId, entry);
165
189
  }
166
190
  recordResponse(args) {
167
- const entry = this.requestMap.get(args.requestId);
191
+ const entry = args.requestId ? this.requestMap.get(args.requestId) : undefined;
168
192
  if (!entry)
169
193
  return;
170
194
  const response = args.response;
@@ -188,9 +212,7 @@ class ResponsesConversationStore {
188
212
  if (!entry) {
189
213
  throw new Error('Responses conversation expired or not found');
190
214
  }
191
- const toolOutputs = Array.isArray(submitPayload.tool_outputs)
192
- ? submitPayload.tool_outputs
193
- : [];
215
+ const toolOutputs = Array.isArray(submitPayload.tool_outputs) ? submitPayload.tool_outputs : [];
194
216
  if (!toolOutputs.length) {
195
217
  throw new Error('tool_outputs array is required when submitting Responses tool results');
196
218
  }
@@ -208,7 +230,8 @@ class ResponsesConversationStore {
208
230
  payload.model = submitPayload.model.trim();
209
231
  }
210
232
  if (submitPayload.metadata && isRecord(submitPayload.metadata)) {
211
- payload.metadata = { ...payload.metadata, ...cloneDeep(submitPayload.metadata) };
233
+ const baseMeta = isRecord(payload.metadata) ? payload.metadata : {};
234
+ payload.metadata = { ...baseMeta, ...cloneDeep(submitPayload.metadata) };
212
235
  }
213
236
  delete payload.tool_outputs;
214
237
  delete payload.response_id;
@@ -225,6 +248,8 @@ class ResponsesConversationStore {
225
248
  };
226
249
  }
227
250
  clearRequest(requestId) {
251
+ if (!requestId)
252
+ return;
228
253
  const entry = this.requestMap.get(requestId);
229
254
  if (!entry)
230
255
  return;
@@ -254,10 +279,14 @@ class ResponsesConversationStore {
254
279
  }
255
280
  }
256
281
  }
257
- export const responsesConversationStore = new ResponsesConversationStore();
282
+ const store = new ResponsesConversationStore();
283
+ const RESPONSES_DEBUG = (process.env.ROUTECODEX_RESPONSES_DEBUG || '').trim() === '1';
258
284
  export function captureResponsesRequestContext(args) {
259
285
  try {
260
- responsesConversationStore.captureRequestContext(args);
286
+ if (RESPONSES_DEBUG) {
287
+ console.log('[responses-store] capture', args.requestId);
288
+ }
289
+ store.captureRequestContext(args);
261
290
  }
262
291
  catch {
263
292
  /* ignore capture failures */
@@ -265,15 +294,31 @@ export function captureResponsesRequestContext(args) {
265
294
  }
266
295
  export function recordResponsesResponse(args) {
267
296
  try {
268
- responsesConversationStore.recordResponse(args);
297
+ if (RESPONSES_DEBUG) {
298
+ console.log('[responses-store] record', args.requestId, args.response?.id);
299
+ }
300
+ store.recordResponse(args);
269
301
  }
270
302
  catch {
271
303
  /* ignore */
272
304
  }
273
305
  }
274
306
  export function resumeResponsesConversation(responseId, submitPayload, options) {
275
- return responsesConversationStore.resumeConversation(responseId, submitPayload, options);
307
+ if (RESPONSES_DEBUG) {
308
+ console.log('[responses-store] resume', responseId);
309
+ }
310
+ return store.resumeConversation(responseId, submitPayload, options);
276
311
  }
277
312
  export function clearResponsesConversationByRequestId(requestId) {
278
- responsesConversationStore.clearRequest(requestId);
313
+ if (RESPONSES_DEBUG && requestId) {
314
+ console.log('[responses-store] clear', requestId);
315
+ }
316
+ store.clearRequest(requestId);
317
+ }
318
+ export function rebindResponsesConversationRequestId(oldId, newId) {
319
+ if (RESPONSES_DEBUG && oldId && newId) {
320
+ console.log('[responses-store] rebind', oldId, '->', newId);
321
+ }
322
+ store.rebindRequestId(oldId, newId);
279
323
  }
324
+ export { store as responsesConversationStore };
@@ -0,0 +1,21 @@
1
+ import { ToolFilterHints } from '../../filters/index.js';
2
+ interface RequestFilterOptions {
3
+ entryEndpoint?: string;
4
+ requestId?: string;
5
+ model?: string;
6
+ profile?: string;
7
+ stream?: boolean;
8
+ toolFilterHints?: ToolFilterHints;
9
+ /**
10
+ * Optional raw payload snapshot for local tool governance (e.g. view_image exposure).
11
+ */
12
+ rawPayload?: Record<string, unknown>;
13
+ }
14
+ interface ResponseFilterOptions {
15
+ entryEndpoint?: string;
16
+ requestId?: string;
17
+ profile?: string;
18
+ }
19
+ export declare function runChatRequestToolFilters(chatRequest: any, options?: RequestFilterOptions): Promise<any>;
20
+ export declare function runChatResponseToolFilters(chatJson: any, options?: ResponseFilterOptions): Promise<any>;
21
+ export {};
@@ -1,6 +1,30 @@
1
1
  import { FilterEngine } from '../../filters/index.js';
2
2
  import { loadFieldMapConfig } from '../../filters/utils/fieldmap-loader.js';
3
3
  import { createSnapshotWriter } from './snapshot-utils.js';
4
+ const REQUEST_FILTER_STAGES = [
5
+ 'request_pre',
6
+ 'request_map',
7
+ 'request_post',
8
+ 'request_finalize'
9
+ ];
10
+ const RESPONSE_FILTER_STAGES = [
11
+ 'response_pre',
12
+ 'response_map',
13
+ 'response_post',
14
+ 'response_finalize'
15
+ ];
16
+ function assertStageCoverage(label, registeredStages, skeletonStages) {
17
+ const allowed = new Set(skeletonStages);
18
+ const uncovered = [];
19
+ for (const stage of registeredStages) {
20
+ if (!allowed.has(stage)) {
21
+ uncovered.push(stage);
22
+ }
23
+ }
24
+ if (uncovered.length) {
25
+ throw new Error(`[tool-filter-pipeline] ${label}: registered filter stage(s) not covered by skeleton: ${uncovered.join(', ')}`);
26
+ }
27
+ }
4
28
  export async function runChatRequestToolFilters(chatRequest, options = {}) {
5
29
  const reqCtxBase = {
6
30
  requestId: options.requestId ?? `req_${Date.now()}`,
@@ -21,24 +45,30 @@ export async function runChatRequestToolFilters(chatRequest, options = {}) {
21
45
  return;
22
46
  snapshot(stage, payload);
23
47
  };
24
- recordStage('req_process_tool_filters_input', chatRequest);
48
+ const preFiltered = applyLocalToolGovernance(chatRequest, options.rawPayload);
49
+ recordStage('req_process_tool_filters_input', preFiltered);
25
50
  const engine = new FilterEngine();
51
+ const registeredStages = new Set();
52
+ const register = (filter) => {
53
+ registeredStages.add(filter.stage);
54
+ engine.registerFilter(filter);
55
+ };
26
56
  const profile = (reqCtxBase.profile || '').toLowerCase();
27
57
  const endpoint = (reqCtxBase.endpoint || '').toLowerCase();
28
58
  const isAnthropic = profile === 'anthropic-messages' || endpoint.includes('/v1/messages');
29
59
  if (!isAnthropic) {
30
60
  try {
31
61
  const { RequestToolListFilter } = await import('../../filters/index.js');
32
- engine.registerFilter(new RequestToolListFilter());
62
+ register(new RequestToolListFilter());
33
63
  }
34
64
  catch {
35
65
  /* optional */
36
66
  }
37
67
  }
38
68
  const { RequestToolCallsStringifyFilter, RequestToolChoicePolicyFilter } = await import('../../filters/index.js');
39
- engine.registerFilter(new RequestToolCallsStringifyFilter());
69
+ register(new RequestToolCallsStringifyFilter());
40
70
  if (!isAnthropic) {
41
- engine.registerFilter(new RequestToolChoicePolicyFilter());
71
+ register(new RequestToolChoicePolicyFilter());
42
72
  }
43
73
  try {
44
74
  const cfg = await loadFieldMapConfig('openai-openai.fieldmap.json');
@@ -52,15 +82,96 @@ export async function runChatRequestToolFilters(chatRequest, options = {}) {
52
82
  } })());
53
83
  }
54
84
  catch { /* ignore */ }
55
- let staged = await engine.run('request_pre', chatRequest, reqCtxBase);
56
- recordStage('req_process_tool_filters_request_pre', staged);
57
- staged = await engine.run('request_map', staged, reqCtxBase);
58
- recordStage('req_process_tool_filters_request_map', staged);
59
- staged = await engine.run('request_post', staged, reqCtxBase);
60
- recordStage('req_process_tool_filters_request_post', staged);
85
+ try {
86
+ const { RequestOpenAIToolsNormalizeFilter, ToolPostConstraintsFilter } = await import('../../filters/index.js');
87
+ register(new RequestOpenAIToolsNormalizeFilter());
88
+ register(new ToolPostConstraintsFilter('request_finalize'));
89
+ }
90
+ catch {
91
+ // optional; keep prior behavior when filter not available
92
+ }
93
+ assertStageCoverage('request', registeredStages, REQUEST_FILTER_STAGES);
94
+ let staged = preFiltered;
95
+ for (const stage of REQUEST_FILTER_STAGES) {
96
+ staged = await engine.run(stage, staged, reqCtxBase);
97
+ recordStage(`req_process_tool_filters_${stage}`, staged);
98
+ }
61
99
  recordStage('req_process_tool_filters_output', staged);
62
100
  return staged;
63
101
  }
102
+ function applyLocalToolGovernance(chatRequest, rawPayload) {
103
+ if (!chatRequest || typeof chatRequest !== 'object') {
104
+ return chatRequest;
105
+ }
106
+ const messages = Array.isArray(chatRequest.messages) ? chatRequest.messages : undefined;
107
+ const tools = Array.isArray(chatRequest.tools) ? chatRequest.tools : undefined;
108
+ if (!tools || !tools.length) {
109
+ return chatRequest;
110
+ }
111
+ const hasImageHint = detectImageHint(messages, rawPayload);
112
+ if (hasImageHint) {
113
+ return chatRequest;
114
+ }
115
+ const filteredTools = tools.filter((tool) => {
116
+ if (!tool || typeof tool !== 'object')
117
+ return false;
118
+ const fn = tool.function;
119
+ if (!fn || typeof fn !== 'object')
120
+ return true;
121
+ const name = fn.name;
122
+ if (typeof name !== 'string')
123
+ return true;
124
+ return name.trim() !== 'view_image';
125
+ });
126
+ if (filteredTools.length === tools.length) {
127
+ return chatRequest;
128
+ }
129
+ return {
130
+ ...chatRequest,
131
+ tools: filteredTools
132
+ };
133
+ }
134
+ function detectImageHint(messages, rawPayload) {
135
+ const candidates = [];
136
+ const collect = (value) => {
137
+ if (typeof value === 'string' && value) {
138
+ candidates.push(value);
139
+ }
140
+ };
141
+ if (Array.isArray(messages)) {
142
+ for (const msg of messages) {
143
+ if (msg && typeof msg === 'object') {
144
+ const text = msg.content;
145
+ if (typeof text === 'string') {
146
+ collect(text);
147
+ }
148
+ else if (Array.isArray(text)) {
149
+ for (const part of text) {
150
+ if (part && typeof part === 'object') {
151
+ collect(part.text);
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ if (rawPayload && typeof rawPayload === 'object') {
159
+ collect(rawPayload.content);
160
+ }
161
+ if (!candidates.length) {
162
+ return false;
163
+ }
164
+ const patterns = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg'];
165
+ for (const text of candidates) {
166
+ const lower = text.toLowerCase();
167
+ for (const ext of patterns) {
168
+ if (lower.includes(ext)) {
169
+ return true;
170
+ }
171
+ }
172
+ }
173
+ return false;
174
+ }
64
175
  export async function runChatResponseToolFilters(chatJson, options = {}) {
65
176
  const resCtxBase = {
66
177
  requestId: options.requestId ?? `req_${Date.now()}`,
@@ -81,20 +192,25 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
81
192
  };
82
193
  recordStage('resp_process_tool_filters_input', chatJson);
83
194
  const engine = new FilterEngine();
195
+ const registeredStages = new Set();
196
+ const register = (filter) => {
197
+ registeredStages.add(filter.stage);
198
+ engine.registerFilter(filter);
199
+ };
84
200
  const { ResponseToolTextCanonicalizeFilter, ResponseToolArgumentsStringifyFilter, ResponseFinishInvariantsFilter } = await import('../../filters/index.js');
85
- engine.registerFilter(new ResponseToolTextCanonicalizeFilter());
201
+ register(new ResponseToolTextCanonicalizeFilter());
86
202
  try {
87
203
  const { ResponseToolArgumentsToonDecodeFilter, ResponseToolArgumentsBlacklistFilter, ResponseToolArgumentsSchemaConvergeFilter } = await import('../../filters/index.js');
88
- engine.registerFilter(new ResponseToolArgumentsToonDecodeFilter());
204
+ register(new ResponseToolArgumentsToonDecodeFilter());
89
205
  try {
90
- engine.registerFilter(new ResponseToolArgumentsSchemaConvergeFilter());
206
+ register(new ResponseToolArgumentsSchemaConvergeFilter());
91
207
  }
92
208
  catch { /* optional */ }
93
- engine.registerFilter(new ResponseToolArgumentsBlacklistFilter());
209
+ register(new ResponseToolArgumentsBlacklistFilter());
94
210
  }
95
211
  catch { /* optional */ }
96
- engine.registerFilter(new ResponseToolArgumentsStringifyFilter());
97
- engine.registerFilter(new ResponseFinishInvariantsFilter());
212
+ register(new ResponseToolArgumentsStringifyFilter());
213
+ register(new ResponseFinishInvariantsFilter());
98
214
  try {
99
215
  const cfg = await loadFieldMapConfig('openai-openai.fieldmap.json');
100
216
  if (cfg)
@@ -107,12 +223,12 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
107
223
  } })());
108
224
  }
109
225
  catch { /* ignore */ }
110
- let staged = await engine.run('response_pre', chatJson, resCtxBase);
111
- recordStage('resp_process_tool_filters_response_pre', staged);
112
- staged = await engine.run('response_map', staged, resCtxBase);
113
- recordStage('resp_process_tool_filters_response_map', staged);
114
- staged = await engine.run('response_post', staged, resCtxBase);
115
- recordStage('resp_process_tool_filters_response_post', staged);
226
+ assertStageCoverage('response', registeredStages, RESPONSE_FILTER_STAGES);
227
+ let staged = chatJson;
228
+ for (const stage of RESPONSE_FILTER_STAGES) {
229
+ staged = await engine.run(stage, staged, resCtxBase);
230
+ recordStage(`resp_process_tool_filters_${stage}`, staged);
231
+ }
116
232
  recordStage('resp_process_tool_filters_output', staged);
117
233
  return staged;
118
234
  }
@@ -0,0 +1,21 @@
1
+ type Unknown = Record<string, unknown>;
2
+ export interface ToolGovernanceOptions {
3
+ injectGuidance?: boolean;
4
+ snapshot?: {
5
+ enabled?: boolean;
6
+ endpoint?: string;
7
+ requestId?: string;
8
+ baseDir?: string;
9
+ };
10
+ }
11
+ export declare function processChatRequestTools(request: Unknown, opts?: ToolGovernanceOptions): Unknown;
12
+ export declare function processChatResponseTools(resp: Unknown): Unknown;
13
+ export interface GovernContext extends ToolGovernanceOptions {
14
+ phase: 'request' | 'response';
15
+ endpoint?: 'chat' | 'responses' | 'messages';
16
+ stream?: boolean;
17
+ produceRequiredAction?: boolean;
18
+ requestId?: string;
19
+ }
20
+ export declare function governTools(payload: Unknown, ctx: GovernContext): Unknown;
21
+ export {};
@@ -40,6 +40,114 @@ function tryWriteSnapshot(options, stage, data) {
40
40
  * - Inject/Refine system tool guidance (idempotent)
41
41
  * - Canonicalize textual tool markup to tool_calls; set content=null when applicable
42
42
  */
43
+ const IMAGE_EXT_RE = /\.(png|jpe?g|gif|webp|bmp|svg)(?:[?#].*)?$/i;
44
+ function hasImageReference(messages) {
45
+ if (!Array.isArray(messages))
46
+ return false;
47
+ for (const entry of messages) {
48
+ if (!entry || typeof entry !== 'object')
49
+ continue;
50
+ const content = entry.content;
51
+ if (!content)
52
+ continue;
53
+ if (Array.isArray(content)) {
54
+ if (content.some((part) => isImagePart(part)))
55
+ return true;
56
+ }
57
+ else if (isObject(content)) {
58
+ if (isImagePart(content))
59
+ return true;
60
+ }
61
+ else if (typeof content === 'string') {
62
+ if (stringHasImageLink(content))
63
+ return true;
64
+ }
65
+ }
66
+ return false;
67
+ }
68
+ function hasInputImage(entries) {
69
+ if (!Array.isArray(entries))
70
+ return false;
71
+ for (const entry of entries) {
72
+ if (!entry || typeof entry !== 'object')
73
+ continue;
74
+ const type = String(entry.type || '').toLowerCase();
75
+ if (type.includes('image'))
76
+ return true;
77
+ const content = entry.content;
78
+ if (!content)
79
+ continue;
80
+ if (Array.isArray(content)) {
81
+ if (content.some((part) => isImagePart(part)))
82
+ return true;
83
+ }
84
+ else if (isObject(content)) {
85
+ if (isImagePart(content))
86
+ return true;
87
+ }
88
+ }
89
+ return false;
90
+ }
91
+ function attachmentsHaveImage(payload) {
92
+ const attachments = payload?.attachments;
93
+ if (!Array.isArray(attachments))
94
+ return false;
95
+ for (const attachment of attachments) {
96
+ if (!attachment || typeof attachment !== 'object')
97
+ continue;
98
+ const mime = typeof attachment.mime === 'string' ? attachment.mime.toLowerCase() : '';
99
+ if (mime.startsWith('image/'))
100
+ return true;
101
+ const name = typeof attachment.name === 'string' ? attachment.name : '';
102
+ if (IMAGE_EXT_RE.test(name))
103
+ return true;
104
+ const url = typeof attachment.url === 'string' ? attachment.url : '';
105
+ if (stringHasImageLink(url))
106
+ return true;
107
+ }
108
+ return false;
109
+ }
110
+ function stringHasImageLink(value) {
111
+ if (!value)
112
+ return false;
113
+ if (value.includes('cid:'))
114
+ return true;
115
+ if (IMAGE_EXT_RE.test(value))
116
+ return true;
117
+ const lowered = value.toLowerCase();
118
+ return lowered.includes('image://');
119
+ }
120
+ function isImagePart(part) {
121
+ if (!part || typeof part !== 'object')
122
+ return false;
123
+ const type = String(part.type || '').toLowerCase();
124
+ if (type.includes('image'))
125
+ return true;
126
+ const imageUrl = part.image_url || part.imageUrl;
127
+ if (typeof imageUrl === 'string')
128
+ return true;
129
+ if (isObject(imageUrl) && typeof imageUrl.url === 'string')
130
+ return true;
131
+ const url = part.url;
132
+ if (typeof url === 'string' && stringHasImageLink(url))
133
+ return true;
134
+ return false;
135
+ }
136
+ function shouldExposeViewImage(payload) {
137
+ if (hasImageReference(payload?.messages))
138
+ return true;
139
+ if (hasInputImage(payload?.input))
140
+ return true;
141
+ if (attachmentsHaveImage(payload))
142
+ return true;
143
+ return false;
144
+ }
145
+ function isViewImageTool(tool) {
146
+ if (!tool || typeof tool !== 'object')
147
+ return false;
148
+ const name = String(tool.name || tool?.function?.name || '').toLowerCase();
149
+ return name === 'view_image';
150
+ }
43
151
  export function processChatRequestTools(request, opts) {
44
152
  const options = { ...(opts || {}) };
45
153
  if (!isObject(request))
@@ -48,8 +156,15 @@ export function processChatRequestTools(request, opts) {
48
156
  // tools 形状最小修复:为缺失 function.parameters 的工具补一个空对象,避免上游
49
157
  // Responses/OpenAI 校验 422(外部错误必须暴露,但这里属于规范化入口)。
50
158
  try {
51
- const tools = Array.isArray(out?.tools) ? out.tools : [];
159
+ let tools = Array.isArray(out?.tools) ? out.tools : [];
52
160
  if (tools.length > 0) {
161
+ if (!shouldExposeViewImage(out)) {
162
+ const filtered = tools.filter((tool) => !isViewImageTool(tool));
163
+ if (filtered.length !== tools.length) {
164
+ tools = filtered;
165
+ out.tools = tools;
166
+ }
167
+ }
53
168
  for (const t of tools) {
54
169
  if (!t || typeof t !== 'object')
55
170
  continue;
@@ -8,6 +8,56 @@ export function stringifyArgs(args) {
8
8
  return String(args);
9
9
  }
10
10
  }
11
+ function isPlainObject(value) {
12
+ return !!value && typeof value === 'object' && !Array.isArray(value);
13
+ }
14
+ function clonePlainObject(value) {
15
+ try {
16
+ return JSON.parse(JSON.stringify(value));
17
+ }
18
+ catch {
19
+ return { ...value };
20
+ }
21
+ }
22
+ function asSchema(value) {
23
+ if (isPlainObject(value)) {
24
+ return clonePlainObject(value);
25
+ }
26
+ return undefined;
27
+ }
28
+ function ensureApplyPatchSchema(seed) {
29
+ const schema = seed ? { ...seed } : {};
30
+ schema.type = typeof schema.type === 'string' ? schema.type : 'object';
31
+ const properties = isPlainObject(schema.properties) ? { ...schema.properties } : {};
32
+ properties.input = {
33
+ type: 'string',
34
+ description: 'Unified diff patch body between *** Begin Patch/*** End Patch.'
35
+ };
36
+ if (!properties.patch || typeof properties.patch !== 'object') {
37
+ properties.patch = {
38
+ type: 'string',
39
+ description: 'Alias of input for backwards compatibility.'
40
+ };
41
+ }
42
+ schema.properties = properties;
43
+ const requiredList = Array.isArray(schema.required) ? schema.required.filter((entry) => typeof entry === 'string') : [];
44
+ if (!requiredList.includes('input')) {
45
+ requiredList.push('input');
46
+ }
47
+ schema.required = requiredList;
48
+ if (typeof schema.additionalProperties !== 'boolean') {
49
+ schema.additionalProperties = false;
50
+ }
51
+ return schema;
52
+ }
53
+ function enforceBuiltinToolSchema(name, candidate) {
54
+ const normalizedName = typeof name === 'string' ? name.trim().toLowerCase() : '';
55
+ if (normalizedName === 'apply_patch') {
56
+ const base = asSchema(candidate);
57
+ return ensureApplyPatchSchema(base);
58
+ }
59
+ return asSchema(candidate);
60
+ }
11
61
  const DEFAULT_SANITIZER = (value) => {
12
62
  if (typeof value === 'string') {
13
63
  const trimmed = value.trim();
@@ -66,7 +116,7 @@ export function bridgeToolToChatDefinition(rawTool, options) {
66
116
  return null;
67
117
  }
68
118
  const description = resolveToolDescription(fnNode?.description ?? tool.description);
69
- const parameters = resolveToolParameters(fnNode, tool);
119
+ const parameters = enforceBuiltinToolSchema(name, resolveToolParameters(fnNode, tool));
70
120
  const strict = resolveToolStrict(fnNode, tool);
71
121
  const rawType = typeof tool.type === 'string' && tool.type.trim().length ? tool.type.trim() : 'function';
72
122
  const normalizedType = rawType.toLowerCase() === 'custom' ? 'function' : rawType;
@@ -105,7 +155,7 @@ export function chatToolToBridgeDefinition(rawTool, options) {
105
155
  return null;
106
156
  }
107
157
  const description = resolveToolDescription(fnNode?.description);
108
- const parameters = resolveToolParameters(fnNode, undefined);
158
+ const parameters = enforceBuiltinToolSchema(name, resolveToolParameters(fnNode, undefined));
109
159
  const strict = resolveToolStrict(fnNode, undefined);
110
160
  const normalizedType = typeof tool.type === 'string' && tool.type.trim().length ? tool.type.trim() : 'function';
111
161
  const responseShape = {
@@ -0,0 +1,18 @@
1
+ {
2
+ "request": [
3
+ {
4
+ "sourcePath": "messages[*].tool_calls[*].function.arguments",
5
+ "targetPath": "messages[*].tool_calls[*].function.arguments",
6
+ "type": "string",
7
+ "transform": "stringifyJson"
8
+ }
9
+ ],
10
+ "response": [
11
+ {
12
+ "sourcePath": "choices[*].message.tool_calls[*].function.arguments",
13
+ "targetPath": "choices[*].message.tool_calls[*].function.arguments",
14
+ "type": "string",
15
+ "transform": "stringifyJson"
16
+ }
17
+ ]
18
+ }