@jsonstudio/llms 0.6.2172 → 0.6.2979

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 (362) hide show
  1. package/README.md +2 -0
  2. package/dist/conversion/compat/antigravity-session-signature.js +35 -1
  3. package/dist/conversion/compat/profiles/chat-gemini-cli.json +0 -7
  4. package/dist/conversion/compat/profiles/chat-gemini.json +0 -6
  5. package/dist/conversion/hub/core/detour-registry.d.ts +2 -0
  6. package/dist/conversion/hub/core/hub-context.d.ts +3 -1
  7. package/dist/conversion/hub/core/index.d.ts +1 -0
  8. package/dist/conversion/hub/core/stage-driver.js +2 -0
  9. package/dist/conversion/hub/format-adapters/anthropic-format-adapter.js +15 -4
  10. package/dist/conversion/hub/format-adapters/chat-format-adapter.js +15 -4
  11. package/dist/conversion/hub/format-adapters/gemini-format-adapter.js +15 -4
  12. package/dist/conversion/hub/format-adapters/responses-format-adapter.js +15 -4
  13. package/dist/conversion/hub/hub-feature.js +3 -2
  14. package/dist/conversion/hub/node-support.js +9 -4
  15. package/dist/conversion/hub/operation-table/operation-table-runner.js +10 -6
  16. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +2 -2
  17. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +10 -10
  18. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +4 -8
  19. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +19 -65
  20. package/dist/conversion/hub/ops/operations.js +2 -121
  21. package/dist/conversion/hub/pipeline/compat/compat-engine.js +6 -0
  22. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.d.ts +1 -1
  23. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +33 -1042
  24. package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +2 -0
  25. package/dist/conversion/hub/pipeline/compat/compat-profile-store.js +2 -0
  26. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +14 -17
  27. package/dist/conversion/hub/pipeline/compat/native-adapter-context.d.ts +3 -0
  28. package/dist/conversion/hub/pipeline/compat/native-adapter-context.js +39 -0
  29. package/dist/conversion/hub/pipeline/hub-pipeline.js +115 -262
  30. package/dist/conversion/hub/pipeline/session-identifiers.js +6 -196
  31. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +1 -2
  32. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +37 -1
  33. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +12 -86
  34. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/semantic-lift.d.ts +14 -0
  35. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/semantic-lift.js +24 -0
  36. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/archive/shell-like-tool-call-normalization-fallback.d.ts +2 -0
  37. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/archive/shell-like-tool-call-normalization-fallback.js +157 -0
  38. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.d.ts +16 -0
  39. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-capture-orchestration.js +29 -0
  40. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js +3 -1
  41. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.d.ts +2 -15
  42. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +8 -595
  43. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.d.ts +8 -0
  44. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/responses-context-snapshot.js +28 -0
  45. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/tool-output-diagnostics.d.ts +2 -0
  46. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/tool-output-diagnostics.js +4 -0
  47. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/tool-output-snapshot.d.ts +10 -0
  48. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/tool-output-snapshot.js +12 -0
  49. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.d.ts +3 -0
  50. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/context-merge.js +30 -0
  51. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +9 -129
  52. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +1 -4
  53. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +9 -26
  54. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +32 -14
  55. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.d.ts +2 -2
  56. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +48 -8
  57. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +18 -3
  58. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +10 -198
  59. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/stream-json-sniffer.d.ts +3 -0
  60. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/stream-json-sniffer.js +81 -0
  61. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.d.ts +1 -2
  62. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +36 -1
  63. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +3 -1
  64. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/chat-process-semantics-bridge.d.ts +6 -0
  65. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/chat-process-semantics-bridge.js +17 -0
  66. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/client-remap-protocol-switch.d.ts +9 -0
  67. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/client-remap-protocol-switch.js +28 -0
  68. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +1 -2
  69. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +14 -102
  70. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +3 -1
  71. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +12 -10
  72. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +6 -5
  73. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.d.ts +11 -1
  74. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.js +10 -32
  75. package/dist/conversion/hub/pipeline/stages/utils.js +17 -1
  76. package/dist/conversion/hub/pipeline/target-utils.js +14 -91
  77. package/dist/conversion/hub/pipelines/inbound.js +3 -1
  78. package/dist/conversion/hub/pipelines/outbound.js +2 -0
  79. package/dist/conversion/hub/policy/policy-engine.js +9 -3
  80. package/dist/conversion/hub/policy/protocol-spec.js +20 -148
  81. package/dist/conversion/hub/process/chat-process-anthropic-alias.d.ts +2 -0
  82. package/dist/conversion/hub/process/chat-process-anthropic-alias.js +36 -0
  83. package/dist/conversion/hub/process/chat-process-clock-directive-parser.d.ts +5 -0
  84. package/dist/conversion/hub/process/chat-process-clock-directive-parser.js +48 -0
  85. package/dist/conversion/hub/process/chat-process-clock-directives.d.ts +24 -0
  86. package/dist/conversion/hub/process/chat-process-clock-directives.js +98 -0
  87. package/dist/conversion/hub/process/chat-process-clock-reminder-directives.d.ts +8 -0
  88. package/dist/conversion/hub/process/chat-process-clock-reminder-directives.js +42 -0
  89. package/dist/conversion/hub/process/chat-process-clock-reminder-finalize.d.ts +14 -0
  90. package/dist/conversion/hub/process/chat-process-clock-reminder-finalize.js +10 -0
  91. package/dist/conversion/hub/process/chat-process-clock-reminder-messages.d.ts +5 -0
  92. package/dist/conversion/hub/process/chat-process-clock-reminder-messages.js +10 -0
  93. package/dist/conversion/hub/process/chat-process-clock-reminder-orchestration.d.ts +30 -0
  94. package/dist/conversion/hub/process/chat-process-clock-reminder-orchestration.js +68 -0
  95. package/dist/conversion/hub/process/chat-process-clock-reminder-time-tag.d.ts +9 -0
  96. package/dist/conversion/hub/process/chat-process-clock-reminder-time-tag.js +18 -0
  97. package/dist/conversion/hub/process/chat-process-clock-reminders.d.ts +2 -0
  98. package/dist/conversion/hub/process/chat-process-clock-reminders.js +104 -0
  99. package/dist/conversion/hub/process/chat-process-clock-tool-schemas.d.ts +3 -0
  100. package/dist/conversion/hub/process/chat-process-clock-tool-schemas.js +233 -0
  101. package/dist/conversion/hub/process/chat-process-clock-tools.d.ts +6 -0
  102. package/dist/conversion/hub/process/chat-process-clock-tools.js +41 -0
  103. package/dist/conversion/hub/process/chat-process-continue-execution.d.ts +11 -0
  104. package/dist/conversion/hub/process/chat-process-continue-execution.js +82 -0
  105. package/dist/conversion/hub/process/chat-process-governance-context.d.ts +15 -0
  106. package/dist/conversion/hub/process/chat-process-governance-context.js +7 -0
  107. package/dist/conversion/hub/process/chat-process-governance-finalize.d.ts +16 -0
  108. package/dist/conversion/hub/process/chat-process-governance-finalize.js +11 -0
  109. package/dist/conversion/hub/process/chat-process-governance-orchestration.d.ts +9 -0
  110. package/dist/conversion/hub/process/chat-process-governance-orchestration.js +47 -0
  111. package/dist/conversion/hub/process/chat-process-governed-control-ops.d.ts +8 -0
  112. package/dist/conversion/hub/process/chat-process-governed-control-ops.js +5 -0
  113. package/dist/conversion/hub/process/chat-process-governed-filter-call.d.ts +12 -0
  114. package/dist/conversion/hub/process/chat-process-governed-filter-call.js +18 -0
  115. package/dist/conversion/hub/process/chat-process-governed-merge.d.ts +8 -0
  116. package/dist/conversion/hub/process/chat-process-governed-merge.js +11 -0
  117. package/dist/conversion/hub/process/chat-process-media.d.ts +3 -0
  118. package/dist/conversion/hub/process/chat-process-media.js +18 -0
  119. package/dist/conversion/hub/process/chat-process-node-result.d.ts +23 -0
  120. package/dist/conversion/hub/process/chat-process-node-result.js +24 -0
  121. package/dist/conversion/hub/process/chat-process-pending-tool-sync.d.ts +14 -0
  122. package/dist/conversion/hub/process/chat-process-pending-tool-sync.js +52 -0
  123. package/dist/conversion/hub/process/chat-process-post-governed-normalization.d.ts +8 -0
  124. package/dist/conversion/hub/process/chat-process-post-governed-normalization.js +16 -0
  125. package/dist/conversion/hub/process/chat-process-review.d.ts +2 -0
  126. package/dist/conversion/hub/process/chat-process-review.js +8 -0
  127. package/dist/conversion/hub/process/chat-process-servertool-orchestration.d.ts +8 -0
  128. package/dist/conversion/hub/process/chat-process-servertool-orchestration.js +22 -0
  129. package/dist/conversion/hub/process/chat-process-tool-normalization.d.ts +2 -0
  130. package/dist/conversion/hub/process/chat-process-tool-normalization.js +4 -0
  131. package/dist/conversion/hub/process/chat-process-web-search-intent.d.ts +12 -0
  132. package/dist/conversion/hub/process/chat-process-web-search-intent.js +13 -0
  133. package/dist/conversion/hub/process/chat-process-web-search-tool-schema.d.ts +3 -0
  134. package/dist/conversion/hub/process/chat-process-web-search-tool-schema.js +4 -0
  135. package/dist/conversion/hub/process/chat-process-web-search.d.ts +8 -0
  136. package/dist/conversion/hub/process/chat-process-web-search.js +26 -0
  137. package/dist/conversion/hub/process/chat-process.d.ts +2 -19
  138. package/dist/conversion/hub/process/chat-process.js +12 -1701
  139. package/dist/conversion/hub/process/client-inject-readiness.d.ts +1 -0
  140. package/dist/conversion/hub/process/client-inject-readiness.js +4 -0
  141. package/dist/conversion/hub/registry.js +5 -2
  142. package/dist/conversion/hub/response/chat-response-utils.js +5 -86
  143. package/dist/conversion/hub/response/provider-response.d.ts +9 -0
  144. package/dist/conversion/hub/response/provider-response.js +6 -21
  145. package/dist/conversion/hub/response/response-mappers.js +2 -26
  146. package/dist/conversion/hub/response/response-runtime.js +2 -93
  147. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -0
  148. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -0
  149. package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -0
  150. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -0
  151. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -0
  152. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -0
  153. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -0
  154. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -0
  155. package/dist/conversion/hub/snapshot-recorder.js +10 -3
  156. package/dist/conversion/hub/standardized-bridge.js +11 -288
  157. package/dist/conversion/hub/tool-governance/engine.js +5 -0
  158. package/dist/conversion/hub/tool-governance/rules.js +10 -10
  159. package/dist/conversion/hub/tool-session-compat.d.ts +2 -2
  160. package/dist/conversion/hub/tool-session-compat.js +17 -231
  161. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +5 -3
  162. package/dist/conversion/responses/responses-host-policy.js +2 -12
  163. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +6 -82
  164. package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -0
  165. package/dist/conversion/responses/responses-openai-bridge.js +21 -54
  166. package/dist/conversion/shared/anthropic-message-utils.js +151 -13
  167. package/dist/conversion/shared/args-mapping.js +2 -146
  168. package/dist/conversion/shared/bridge-actions.js +203 -718
  169. package/dist/conversion/shared/bridge-id-utils.js +5 -71
  170. package/dist/conversion/shared/bridge-instructions.js +2 -1
  171. package/dist/conversion/shared/bridge-message-types.d.ts +2 -0
  172. package/dist/conversion/shared/bridge-message-utils.js +1 -2
  173. package/dist/conversion/shared/bridge-metadata.d.ts +1 -0
  174. package/dist/conversion/shared/bridge-metadata.js +4 -0
  175. package/dist/conversion/shared/bridge-policies.js +5 -189
  176. package/dist/conversion/shared/chat-envelope-validator.js +2 -126
  177. package/dist/conversion/shared/chat-output-normalizer.js +2 -54
  178. package/dist/conversion/shared/compaction-detect.js +2 -57
  179. package/dist/conversion/shared/gemini-tool-utils.js +9 -524
  180. package/dist/conversion/shared/jsonish.js +3 -160
  181. package/dist/conversion/shared/mcp-injection.js +3 -169
  182. package/dist/conversion/shared/media.js +2 -7
  183. package/dist/conversion/shared/metadata-passthrough.js +9 -46
  184. package/dist/conversion/shared/openai-finalizer.js +2 -1
  185. package/dist/conversion/shared/openai-message-normalize.js +11 -283
  186. package/dist/conversion/shared/output-content-normalizer.js +9 -112
  187. package/dist/conversion/shared/payload-budget.js +2 -85
  188. package/dist/conversion/shared/protocol-state.js +11 -7
  189. package/dist/conversion/shared/reasoning-mapping.js +2 -6
  190. package/dist/conversion/shared/reasoning-normalizer.js +4 -1
  191. package/dist/conversion/shared/reasoning-tool-normalizer.js +14 -126
  192. package/dist/conversion/shared/reasoning-tool-parser.js +4 -87
  193. package/dist/conversion/shared/reasoning-utils.js +2 -6
  194. package/dist/conversion/shared/responses-conversation-store.js +4 -82
  195. package/dist/conversion/shared/responses-output-builder.js +11 -47
  196. package/dist/conversion/shared/responses-reasoning-registry.js +7 -1
  197. package/dist/conversion/shared/responses-request-adapter.d.ts +7 -1
  198. package/dist/conversion/shared/responses-request-adapter.js +14 -1
  199. package/dist/conversion/shared/responses-response-utils.js +6 -7
  200. package/dist/conversion/shared/responses-tool-utils.d.ts +1 -0
  201. package/dist/conversion/shared/responses-tool-utils.js +90 -14
  202. package/dist/conversion/shared/runtime-metadata.js +13 -5
  203. package/dist/conversion/shared/streaming-text-extractor.js +2 -7
  204. package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +1 -1
  205. package/dist/conversion/shared/text-markup-normalizer/normalize.js +43 -17
  206. package/dist/conversion/shared/text-markup-normalizer.d.ts +1 -0
  207. package/dist/conversion/shared/text-markup-normalizer.js +1 -0
  208. package/dist/conversion/shared/thought-signature-validator.js +3 -2
  209. package/dist/conversion/shared/tool-argument-repairer.js +2 -2
  210. package/dist/conversion/shared/tool-call-id-manager.js +5 -7
  211. package/dist/conversion/shared/tool-call-utils.js +3 -45
  212. package/dist/conversion/shared/tool-canonicalizer.js +25 -29
  213. package/dist/conversion/shared/tool-filter-pipeline.js +4 -99
  214. package/dist/conversion/shared/tool-governor.d.ts +6 -0
  215. package/dist/conversion/shared/tool-governor.js +43 -125
  216. package/dist/conversion/shared/tool-harvester.js +2 -8
  217. package/dist/conversion/shared/tool-mapping.js +2 -5
  218. package/dist/conversion/shared/tooling.d.ts +0 -4
  219. package/dist/conversion/shared/tooling.js +18 -0
  220. package/dist/native/router_hotpath_napi.node +0 -0
  221. package/dist/router/virtual-router/engine/provider-key/parse.d.ts +1 -6
  222. package/dist/router/virtual-router/engine/provider-key/parse.js +1 -43
  223. package/dist/router/virtual-router/engine/routing-state/store.js +48 -12
  224. package/dist/router/virtual-router/engine-logging.js +4 -3
  225. package/dist/router/virtual-router/engine-selection/alias-selection.js +45 -83
  226. package/dist/router/virtual-router/engine-selection/key-parsing.js +9 -23
  227. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-directive-parser.d.ts +20 -0
  228. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-directive-parser.js +163 -0
  229. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-directives.d.ts +7 -0
  230. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-directives.js +103 -0
  231. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-orchestration-semantics.d.ts +10 -0
  232. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-orchestration-semantics.js +110 -0
  233. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-semantics.d.ts +8 -0
  234. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-semantics.js +281 -0
  235. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-time-tag-semantics.d.ts +1 -0
  236. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminder-time-tag-semantics.js +25 -0
  237. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminders-semantics.d.ts +4 -0
  238. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-reminders-semantics.js +44 -0
  239. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-tool-schema-semantics.d.ts +2 -0
  240. package/dist/router/virtual-router/engine-selection/native-chat-process-clock-tool-schema-semantics.js +62 -0
  241. package/dist/router/virtual-router/engine-selection/native-chat-process-governance-semantics.d.ts +40 -0
  242. package/dist/router/virtual-router/engine-selection/native-chat-process-governance-semantics.js +484 -0
  243. package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.d.ts +9 -0
  244. package/dist/router/virtual-router/engine-selection/native-chat-process-governed-filter-semantics.js +64 -0
  245. package/dist/router/virtual-router/engine-selection/native-chat-process-node-result-semantics.d.ts +5 -0
  246. package/dist/router/virtual-router/engine-selection/native-chat-process-node-result-semantics.js +163 -0
  247. package/dist/router/virtual-router/engine-selection/native-chat-process-post-governed-normalization-semantics.d.ts +1 -0
  248. package/dist/router/virtual-router/engine-selection/native-chat-process-post-governed-normalization-semantics.js +49 -0
  249. package/dist/router/virtual-router/engine-selection/native-chat-process-servertool-orchestration-semantics.d.ts +30 -0
  250. package/dist/router/virtual-router/engine-selection/native-chat-process-servertool-orchestration-semantics.js +446 -0
  251. package/dist/router/virtual-router/engine-selection/native-chat-process-web-search-intent-semantics.d.ts +1 -0
  252. package/dist/router/virtual-router/engine-selection/native-chat-process-web-search-intent-semantics.js +49 -0
  253. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +134 -0
  254. package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +729 -0
  255. package/dist/router/virtual-router/engine-selection/native-hub-bridge-policy-semantics.d.ts +62 -0
  256. package/dist/router/virtual-router/engine-selection/native-hub-bridge-policy-semantics.js +338 -0
  257. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.d.ts +18 -0
  258. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.js +317 -0
  259. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.d.ts +22 -0
  260. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-inbound-outbound-semantics.js +426 -0
  261. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-orchestration-semantics.d.ts +57 -0
  262. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-orchestration-semantics.js +705 -0
  263. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.d.ts +46 -0
  264. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js +503 -0
  265. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.d.ts +146 -0
  266. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js +570 -0
  267. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.d.ts +25 -0
  268. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.js +148 -0
  269. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.d.ts +25 -0
  270. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js +637 -0
  271. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-session-identifiers-semantics.d.ts +11 -0
  272. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-session-identifiers-semantics.js +207 -0
  273. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-target-semantics.d.ts +3 -0
  274. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-target-semantics.js +128 -0
  275. package/dist/router/virtual-router/engine-selection/native-router-hotpath-analysis.d.ts +57 -0
  276. package/dist/router/virtual-router/engine-selection/native-router-hotpath-analysis.js +217 -0
  277. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.d.ts +5 -0
  278. package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +284 -0
  279. package/dist/router/virtual-router/engine-selection/native-router-hotpath-policy.d.ts +5 -0
  280. package/dist/router/virtual-router/engine-selection/native-router-hotpath-policy.js +18 -0
  281. package/dist/router/virtual-router/engine-selection/native-router-hotpath-quota-buckets.d.ts +25 -0
  282. package/dist/router/virtual-router/engine-selection/native-router-hotpath-quota-buckets.js +85 -0
  283. package/dist/router/virtual-router/engine-selection/native-router-hotpath.d.ts +59 -0
  284. package/dist/router/virtual-router/engine-selection/native-router-hotpath.js +117 -0
  285. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +76 -0
  286. package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +1166 -0
  287. package/dist/router/virtual-router/engine-selection/native-virtual-router-alias-selection-semantics.d.ts +16 -0
  288. package/dist/router/virtual-router/engine-selection/native-virtual-router-alias-selection-semantics.js +96 -0
  289. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-actions-semantics.d.ts +6 -0
  290. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-actions-semantics.js +85 -0
  291. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-semantics.d.ts +9 -0
  292. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-semantics.js +70 -0
  293. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-state-semantics.d.ts +2 -0
  294. package/dist/router/virtual-router/engine-selection/native-virtual-router-stop-message-state-semantics.js +76 -0
  295. package/dist/router/virtual-router/engine-selection/route-utils.js +1 -1
  296. package/dist/router/virtual-router/engine-selection/tier-selection-antigravity-session-lease.d.ts +10 -0
  297. package/dist/router/virtual-router/engine-selection/tier-selection-antigravity-session-lease.js +231 -0
  298. package/dist/router/virtual-router/engine-selection/tier-selection-antigravity-target-split.d.ts +4 -0
  299. package/dist/router/virtual-router/engine-selection/tier-selection-antigravity-target-split.js +43 -0
  300. package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.d.ts +27 -0
  301. package/dist/router/virtual-router/engine-selection/tier-selection-quota-integration.js +116 -0
  302. package/dist/router/virtual-router/engine-selection/tier-selection-select.d.ts +1 -1
  303. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +29 -129
  304. package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -265
  305. package/dist/router/virtual-router/engine.js +258 -249
  306. package/dist/router/virtual-router/features.js +2 -2
  307. package/dist/router/virtual-router/routing-instructions.d.ts +5 -7
  308. package/dist/router/virtual-router/routing-instructions.js +93 -66
  309. package/dist/router/virtual-router/routing-stop-message-actions.js +91 -112
  310. package/dist/router/virtual-router/routing-stop-message-parser.js +9 -132
  311. package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +1 -0
  312. package/dist/router/virtual-router/routing-stop-message-state-codec.js +58 -71
  313. package/dist/router/virtual-router/sticky-session-store.js +4 -2
  314. package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
  315. package/dist/router/virtual-router/stop-message-file-resolver.js +10 -0
  316. package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
  317. package/dist/router/virtual-router/stop-message-state-sync.js +3 -7
  318. package/dist/router/virtual-router/token-counter.js +0 -9
  319. package/dist/router/virtual-router/types.d.ts +9 -7
  320. package/dist/servertool/clock/config.js +23 -51
  321. package/dist/servertool/clock/io.js +1 -0
  322. package/dist/servertool/clock/session-scope.d.ts +2 -2
  323. package/dist/servertool/clock/session-scope.js +5 -47
  324. package/dist/servertool/engine.d.ts +9 -0
  325. package/dist/servertool/engine.js +196 -79
  326. package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +2 -2
  327. package/dist/servertool/handlers/clock.js +1 -1
  328. package/dist/servertool/handlers/continue-execution.js +8 -4
  329. package/dist/servertool/handlers/followup-request-builder.js +18 -1
  330. package/dist/servertool/handlers/gemini-empty-reply-continue.js +7 -1
  331. package/dist/servertool/handlers/review.js +180 -0
  332. package/dist/servertool/handlers/stop-message-auto/blocked-report.js +59 -1
  333. package/dist/servertool/handlers/stop-message-auto/iflow-followup.d.ts +23 -2
  334. package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +397 -89
  335. package/dist/servertool/handlers/stop-message-auto/routing-state.d.ts +5 -15
  336. package/dist/servertool/handlers/stop-message-auto/routing-state.js +29 -55
  337. package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +6 -0
  338. package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +35 -61
  339. package/dist/servertool/handlers/stop-message-auto.js +392 -76
  340. package/dist/servertool/server-side-tools.d.ts +1 -0
  341. package/dist/servertool/server-side-tools.js +90 -52
  342. package/dist/servertool/types.d.ts +17 -0
  343. package/dist/tools/apply-patch/patch-text/normalize.js +11 -0
  344. package/dist/tools/exec-command/validator.d.ts +4 -1
  345. package/dist/tools/exec-command/validator.js +87 -3
  346. package/dist/tools/tool-registry.d.ts +7 -1
  347. package/dist/tools/tool-registry.js +3 -2
  348. package/package.json +115 -7
  349. package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.d.ts +0 -18
  350. package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.js +0 -398
  351. package/dist/servertool/handlers/stop-message-stage-policy/decision.d.ts +0 -9
  352. package/dist/servertool/handlers/stop-message-stage-policy/decision.js +0 -127
  353. package/dist/servertool/handlers/stop-message-stage-policy/observation.d.ts +0 -2
  354. package/dist/servertool/handlers/stop-message-stage-policy/observation.js +0 -179
  355. package/dist/servertool/handlers/stop-message-stage-policy/templates.d.ts +0 -4
  356. package/dist/servertool/handlers/stop-message-stage-policy/templates.js +0 -96
  357. package/dist/servertool/handlers/stop-message-stage-policy/text-utils.d.ts +0 -9
  358. package/dist/servertool/handlers/stop-message-stage-policy/text-utils.js +0 -89
  359. package/dist/servertool/handlers/stop-message-stage-policy/types.d.ts +0 -59
  360. package/dist/servertool/handlers/stop-message-stage-policy.d.ts +0 -3
  361. package/dist/servertool/handlers/stop-message-stage-policy.js +0 -2
  362. /package/dist/servertool/handlers/{stop-message-stage-policy/types.js → review.d.ts} +0 -0
@@ -1,35 +1,18 @@
1
- import { runChatRequestToolFilters } from '../../shared/tool-filter-pipeline.js';
2
- import { ToolGovernanceEngine } from '../tool-governance/index.js';
3
- import { readRuntimeMetadata } from '../../shared/runtime-metadata.js';
4
- import { ensureApplyPatchSchema } from '../../shared/tool-mapping.js';
5
- import { normalizeApplyPatchToolCallsOnRequest } from '../../shared/tool-governor.js';
6
- import { buildAnthropicToolAliasMap } from '../../shared/anthropic-message-utils.js';
7
- import { clearClockSession, parseDueAtMs, resolveClockConfig, reserveDueTasksForRequest, scheduleClockTasks, startClockDaemonIfNeeded } from '../../../servertool/clock/task-store.js';
8
- import { logClock } from '../../../servertool/clock/log.js';
9
- import { logContinueExecution } from '../../../servertool/continue-execution/log.js';
10
- import { buildTimeTagLine, getClockTimeSnapshot } from '../../../servertool/clock/ntp.js';
11
- import { resolveClockSessionScope } from '../../../servertool/clock/session-scope.js';
12
- import { clearPendingServerToolInjection, loadPendingServerToolInjection } from '../../../servertool/pending-session.js';
13
- import { loadRoutingInstructionStateSync } from '../../../router/virtual-router/sticky-session-store.js';
14
- import { isJsonObject } from '../types/json.js';
15
- import { applyHubOperations } from '../ops/operations.js';
16
- const toolGovernanceEngine = new ToolGovernanceEngine();
17
- const CONTINUE_EXECUTION_SYSTEM_DIRECTIVE_MARKER = '[routecodex:continue_execution_directive]';
18
- const CONTINUE_EXECUTION_SYSTEM_DIRECTIVE = [
19
- CONTINUE_EXECUTION_SYSTEM_DIRECTIVE_MARKER,
20
- 'Execution policy (must follow):',
21
- '- Do not output status-talk placeholders such as \"继续执行中\" / \"I will continue\" before real work.',
22
- '- Perform at least one real action first (prefer tool calls), then report concise results when needed.',
23
- '- If you are about to return finish_reason=stop (or protocol-equivalent end_turn), do not end directly; emit a continue_execution tool call first.'
24
- ].join('\n');
1
+ import { buildErrorResult, buildProcessedRequest, buildSuccessResult } from './chat-process-node-result.js';
2
+ import { applyRequestToolGovernance } from './chat-process-governance-orchestration.js';
3
+ import { shouldRunHubChatProcessWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
25
4
  export async function runHubChatProcess(options) {
26
5
  const startTime = Date.now();
27
6
  try {
28
- const governed = await applyRequestToolGovernance(options.request, {
29
- entryEndpoint: options.entryEndpoint,
30
- requestId: options.requestId,
31
- metadata: options.metadata
32
- });
7
+ const shouldRunGovernance = shouldRunHubChatProcessWithNative(options.requestId, options.entryEndpoint);
8
+ const governed = shouldRunGovernance
9
+ ? await applyRequestToolGovernance(options.request, {
10
+ entryEndpoint: options.entryEndpoint,
11
+ requestId: options.requestId,
12
+ metadata: options.metadata,
13
+ rawPayload: options.rawPayload
14
+ })
15
+ : options.request;
33
16
  const processedRequest = buildProcessedRequest(governed);
34
17
  return {
35
18
  processedRequest,
@@ -42,1675 +25,3 @@ export async function runHubChatProcess(options) {
42
25
  };
43
26
  }
44
27
  }
45
- async function applyRequestToolGovernance(request, context) {
46
- const entryEndpoint = context.entryEndpoint && context.entryEndpoint.trim()
47
- ? context.entryEndpoint
48
- : '/v1/chat/completions';
49
- const metadata = normalizeRecord(context.metadata);
50
- const providerProtocol = readString(metadata.providerProtocol) ?? readString(metadata.provider) ?? 'openai-chat';
51
- const metadataToolHints = metadata.toolFilterHints;
52
- const metadataStreamFlag = metadata.stream;
53
- const inboundStreamIntent = typeof metadataStreamFlag === 'boolean' ? metadataStreamFlag : request.parameters?.stream === true;
54
- // /v1/messages: preserve the caller's tool naming (Bash/Glob/Read, etc.) so we can
55
- // remap tool calls back to the client protocol shape after routing through providers
56
- // that canonicalize tool names (e.g. OpenAI function names lowercased).
57
- //
58
- // NOTE: This is mappable semantics and must live in request.semantics (never metadata).
59
- try {
60
- if (entryEndpoint.toLowerCase().includes('/v1/messages')) {
61
- request.semantics = request.semantics && typeof request.semantics === 'object' && !Array.isArray(request.semantics)
62
- ? request.semantics
63
- : {};
64
- const semanticsTools = request.semantics.tools && typeof request.semantics.tools === 'object' && !Array.isArray(request.semantics.tools)
65
- ? request.semantics.tools
66
- : (request.semantics.tools = {});
67
- const hasAlias = isJsonObject(semanticsTools.toolNameAliasMap) ||
68
- isJsonObject(semanticsTools.toolAliasMap);
69
- if (!hasAlias) {
70
- const sourceTools = Array.isArray(semanticsTools.clientToolsRaw) && semanticsTools.clientToolsRaw.length
71
- ? semanticsTools.clientToolsRaw
72
- : request.tools;
73
- const aliasMap = buildAnthropicToolAliasMap(sourceTools);
74
- if (aliasMap) {
75
- semanticsTools.toolNameAliasMap = aliasMap;
76
- }
77
- }
78
- }
79
- }
80
- catch {
81
- // best-effort: never block request handling due to alias map propagation failures
82
- }
83
- const shaped = {
84
- model: request.model,
85
- messages: deepClone(request.messages),
86
- tools: request.tools ? deepClone(request.tools) : undefined,
87
- tool_choice: request.parameters?.tool_choice,
88
- stream: request.parameters?.stream === true,
89
- parameters: { ...request.parameters }
90
- };
91
- const governedPayload = await runChatRequestToolFilters(shaped, {
92
- entryEndpoint,
93
- requestId: context.requestId,
94
- model: request.model,
95
- profile: providerProtocol,
96
- stream: inboundStreamIntent,
97
- toolFilterHints: metadataToolHints,
98
- rawPayload: context.metadata?.__raw_request_body && typeof context.metadata.__raw_request_body === 'object'
99
- ? context.metadata.__raw_request_body
100
- : undefined
101
- });
102
- const governed = normalizeRecord(governedPayload);
103
- const providerStreamIntent = typeof governed.stream === 'boolean' ? governed.stream : undefined;
104
- let merged = {
105
- ...request,
106
- messages: Array.isArray(governed.messages)
107
- ? governed.messages
108
- : request.messages,
109
- tools: governed.tools !== undefined ? castGovernedTools(governed.tools) : request.tools,
110
- parameters: {
111
- ...request.parameters,
112
- ...(isRecord(governed.parameters) ? governed.parameters : {})
113
- },
114
- metadata: {
115
- ...request.metadata,
116
- toolChoice: readToolChoice(governed.tool_choice),
117
- originalStream: inboundStreamIntent,
118
- stream: inboundStreamIntent,
119
- providerStream: providerStreamIntent,
120
- governedTools: governed.tools !== undefined,
121
- governanceTimestamp: Date.now()
122
- }
123
- };
124
- // 清理历史图片:仅保留「最新一条 user 消息」中的图片分段,
125
- // 避免历史对话中的图片在后续多轮工具 / 普通对话中继续作为多模态负载发给不支持图片的模型。
126
- merged = {
127
- ...merged,
128
- messages: stripHistoricalImageAttachments(merged.messages)
129
- };
130
- merged = normalizeApplyPatchToolCallsOnRequest(merged);
131
- // Mixed-tools support: if previous response executed servertools alongside client tools,
132
- // inject servertool tool_call + tool_result messages after the client's tool results on next request.
133
- merged = await maybeInjectPendingServerToolResultsAfterClientTools(merged, metadata);
134
- if (containsImageAttachment(merged.messages)) {
135
- if (!merged.metadata) {
136
- merged.metadata = {
137
- originalEndpoint: request.metadata?.originalEndpoint ?? '/v1/chat/completions'
138
- };
139
- }
140
- merged.metadata.hasImageAttachment = true;
141
- }
142
- if (typeof inboundStreamIntent === 'boolean') {
143
- merged = applyHubOperations(merged, buildInboundStreamingOperations(inboundStreamIntent));
144
- }
145
- if (typeof governed.stream === 'boolean') {
146
- merged = applyHubOperations(merged, buildOutboundStreamingOperations(governed.stream));
147
- }
148
- if (governed.tool_choice !== undefined) {
149
- merged = applyHubOperations(merged, buildToolChoiceOperations(governed.tool_choice));
150
- }
151
- if (typeof governed.model === 'string' && governed.model.trim()) {
152
- merged.model = governed.model.trim();
153
- }
154
- // Server-side web_search tool injection (config-driven, best-effort).
155
- merged = applyHubOperations(merged, buildWebSearchOperations(merged, metadata));
156
- // Server-side clock tool + scheduled reminders injection (config-driven, best-effort).
157
- merged = applyHubOperations(merged, buildClockOperations(metadata));
158
- // Server-side no-op continue tool injection (always-on, lightweight).
159
- merged = applyHubOperations(merged, buildContinueExecutionOperations(metadata));
160
- merged = injectContinueExecutionDirectiveIntoUserMessage(merged, metadata);
161
- merged = await maybeInjectClockRemindersAndApplyDirectives(merged, metadata, context.requestId);
162
- const { request: sanitized, summary } = toolGovernanceEngine.governRequest(merged, providerProtocol);
163
- if (summary.applied) {
164
- sanitized.metadata = {
165
- ...sanitized.metadata,
166
- toolGovernance: {
167
- ...sanitized.metadata?.toolGovernance,
168
- request: summary
169
- }
170
- };
171
- }
172
- return sanitized;
173
- }
174
- function resolveSessionIdForPending(metadata, request) {
175
- const candidate = readString(metadata.sessionId) ?? readString(request.metadata?.sessionId);
176
- return candidate && candidate.trim() ? candidate.trim() : null;
177
- }
178
- function extractToolCallIdsFromToolMessages(messages) {
179
- const ids = new Set();
180
- for (const msg of messages) {
181
- if (!msg || typeof msg !== 'object')
182
- continue;
183
- if (msg.role !== 'tool')
184
- continue;
185
- const toolCallId = typeof msg.tool_call_id === 'string'
186
- ? String(msg.tool_call_id).trim()
187
- : typeof msg.toolCallId === 'string'
188
- ? String(msg.toolCallId).trim()
189
- : '';
190
- if (toolCallId)
191
- ids.add(toolCallId);
192
- }
193
- return ids;
194
- }
195
- function findLastToolMessageIndex(messages) {
196
- for (let i = messages.length - 1; i >= 0; i -= 1) {
197
- const msg = messages[i];
198
- if (msg && typeof msg === 'object' && msg.role === 'tool') {
199
- return i;
200
- }
201
- }
202
- return -1;
203
- }
204
- async function maybeInjectPendingServerToolResultsAfterClientTools(request, metadata) {
205
- const sessionId = resolveSessionIdForPending(metadata, request);
206
- if (!sessionId) {
207
- return request;
208
- }
209
- const pending = await loadPendingServerToolInjection(sessionId);
210
- if (!pending) {
211
- return request;
212
- }
213
- const afterIds = Array.isArray(pending.afterToolCallIds) ? pending.afterToolCallIds : [];
214
- if (!afterIds.length) {
215
- return request;
216
- }
217
- const messages = Array.isArray(request.messages) ? request.messages : [];
218
- const toolIds = extractToolCallIdsFromToolMessages(messages);
219
- const ready = afterIds.every((id) => toolIds.has(id));
220
- if (!ready) {
221
- return request;
222
- }
223
- const insertAt = findLastToolMessageIndex(messages);
224
- if (insertAt < 0) {
225
- return request;
226
- }
227
- const inject = Array.isArray(pending.messages) ? pending.messages : [];
228
- if (!inject.length) {
229
- return request;
230
- }
231
- const nextMessages = messages.slice();
232
- nextMessages.splice(insertAt + 1, 0, ...inject);
233
- try {
234
- await clearPendingServerToolInjection(sessionId);
235
- }
236
- catch {
237
- // best-effort
238
- }
239
- return { ...request, messages: nextMessages };
240
- }
241
- function buildProcessedRequest(request) {
242
- const timestamp = Date.now();
243
- const streamingEnabled = request.parameters?.stream === true;
244
- return {
245
- ...request,
246
- processed: {
247
- timestamp,
248
- appliedRules: ['tool-governance'],
249
- status: 'success'
250
- },
251
- processingMetadata: {
252
- streaming: {
253
- enabled: streamingEnabled,
254
- chunkCount: 0
255
- },
256
- context: buildContextMetadata(request.metadata)
257
- }
258
- };
259
- }
260
- function buildContextMetadata(metadata) {
261
- if (!metadata?.capturedContext || typeof metadata.capturedContext !== 'object') {
262
- return undefined;
263
- }
264
- return { ...metadata.capturedContext };
265
- }
266
- function buildSuccessResult(startTime, processedRequest) {
267
- const endTime = Date.now();
268
- return {
269
- success: true,
270
- metadata: {
271
- node: 'hub-chat-process',
272
- executionTime: endTime - startTime,
273
- startTime,
274
- endTime,
275
- dataProcessed: {
276
- messages: processedRequest.messages.length,
277
- tools: processedRequest.tools?.length ?? 0
278
- }
279
- }
280
- };
281
- }
282
- function buildErrorResult(startTime, error) {
283
- const endTime = Date.now();
284
- return {
285
- success: false,
286
- metadata: {
287
- node: 'hub-chat-process',
288
- executionTime: endTime - startTime,
289
- startTime,
290
- endTime
291
- },
292
- error: {
293
- message: error instanceof Error ? error.message : String(error)
294
- }
295
- };
296
- }
297
- function castGovernedTools(tools) {
298
- if (!Array.isArray(tools)) {
299
- return undefined;
300
- }
301
- const normalized = [];
302
- for (const tool of tools) {
303
- const converted = castSingleTool(tool) ?? castCustomTool(tool);
304
- if (converted) {
305
- normalized.push(converted);
306
- }
307
- }
308
- return normalized.length ? normalized : [];
309
- }
310
- function castSingleTool(tool) {
311
- if (!isRecord(tool)) {
312
- return null;
313
- }
314
- const fn = tool.function;
315
- if (!isRecord(fn) || typeof fn.name !== 'string') {
316
- return null;
317
- }
318
- const parameters = castToolParameters(fn.parameters);
319
- const description = typeof fn.description === 'string' ? fn.description : undefined;
320
- const strictFlag = fn.strict === true || tool.strict === true;
321
- return {
322
- type: 'function',
323
- function: {
324
- name: fn.name,
325
- description,
326
- parameters,
327
- strict: strictFlag ? true : undefined
328
- }
329
- };
330
- }
331
- function stripHistoricalImageAttachments(messages) {
332
- if (!Array.isArray(messages) || !messages.length) {
333
- return messages;
334
- }
335
- const placeholderText = '[Image omitted]';
336
- const last = messages[messages.length - 1];
337
- const lastRole = typeof last?.role === 'string' ? String(last.role) : '';
338
- const isNewUserTurn = lastRole === 'user';
339
- // 找到最新一条 user 消息:
340
- // - 若本轮是 user 输入(最后一条消息 role=user),则仅允许这一条保留图片分段;
341
- // - 若本轮是 followup(最后一条消息非 user,例如 tool/assistant),则所有 user 消息的图片都应替换为占位符,
342
- // 避免在工具循环/多次 followup 中反复透传大体积图片数据。
343
- let latestUserIndex = -1;
344
- if (isNewUserTurn) {
345
- for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
346
- const candidate = messages[idx];
347
- if (candidate && typeof candidate === 'object' && candidate.role === 'user') {
348
- latestUserIndex = idx;
349
- break;
350
- }
351
- }
352
- if (latestUserIndex < 0) {
353
- return messages;
354
- }
355
- }
356
- let changed = false;
357
- const next = messages.slice();
358
- for (let idx = 0; idx < messages.length; idx += 1) {
359
- if (isNewUserTurn && idx === latestUserIndex) {
360
- continue;
361
- }
362
- const message = messages[idx];
363
- if (!message || typeof message !== 'object') {
364
- continue;
365
- }
366
- if (message.role !== 'user') {
367
- continue;
368
- }
369
- const content = message.content;
370
- if (!Array.isArray(content) || !content.length) {
371
- continue;
372
- }
373
- const filtered = [];
374
- let removed = false;
375
- for (const part of content) {
376
- if (part && typeof part === 'object' && !Array.isArray(part)) {
377
- const typeValue = part.type;
378
- if (typeof typeValue === 'string' && typeValue.toLowerCase().includes('image')) {
379
- removed = true;
380
- filtered.push({ type: 'text', text: placeholderText });
381
- continue;
382
- }
383
- }
384
- filtered.push(part);
385
- }
386
- if (removed) {
387
- const cloned = {
388
- ...message,
389
- content: filtered
390
- };
391
- next[idx] = cloned;
392
- changed = true;
393
- }
394
- }
395
- return changed ? next : messages;
396
- }
397
- function containsImageAttachment(messages) {
398
- if (!Array.isArray(messages) || !messages.length) {
399
- return false;
400
- }
401
- // 仅检查“当前这一轮用户输入”(messages 最后一条必须是 user)是否携带图片。
402
- // 避免在 tool/assistant followup 请求中,因为历史 user 消息含图片而反复触发 image 标记。
403
- const last = messages[messages.length - 1];
404
- if (!last || typeof last !== 'object' || Array.isArray(last) || last.role !== 'user') {
405
- return false;
406
- }
407
- const content = last.content;
408
- if (!Array.isArray(content)) {
409
- return false;
410
- }
411
- for (const part of content) {
412
- if (!part || typeof part !== 'object') {
413
- continue;
414
- }
415
- const typeValue = part.type;
416
- if (typeof typeValue !== 'string') {
417
- continue;
418
- }
419
- const normalized = typeValue.toLowerCase();
420
- if (!normalized.includes('image')) {
421
- continue;
422
- }
423
- const record = part;
424
- let imageCandidate = '';
425
- if (typeof record.image_url === 'string') {
426
- imageCandidate = record.image_url;
427
- }
428
- else if (record.image_url && typeof record.image_url.url === 'string') {
429
- imageCandidate = record.image_url.url ?? '';
430
- }
431
- else if (typeof record.url === 'string') {
432
- imageCandidate = record.url;
433
- }
434
- else if (typeof record.uri === 'string') {
435
- imageCandidate = record.uri;
436
- }
437
- else if (typeof record.data === 'string') {
438
- imageCandidate = record.data;
439
- }
440
- if (imageCandidate.trim().length > 0) {
441
- return true;
442
- }
443
- }
444
- return false;
445
- }
446
- function castCustomTool(tool) {
447
- if (!isRecord(tool)) {
448
- return null;
449
- }
450
- const type = typeof tool.type === 'string' ? tool.type.toLowerCase() : '';
451
- const name = typeof tool.name === 'string' ? tool.name : '';
452
- if (type !== 'custom' || !name) {
453
- return null;
454
- }
455
- if (name.trim() !== 'apply_patch') {
456
- return null;
457
- }
458
- const description = typeof tool.description === 'string' ? tool.description : undefined;
459
- return {
460
- type: 'function',
461
- function: {
462
- name: 'apply_patch',
463
- description,
464
- parameters: ensureApplyPatchSchema(),
465
- strict: true
466
- }
467
- };
468
- }
469
- function castToolParameters(value) {
470
- if (isRecord(value)) {
471
- const cloned = deepClone(value);
472
- if (cloned && typeof cloned === 'object' && !Array.isArray(cloned)) {
473
- const schema = cloned;
474
- if (!('type' in schema)) {
475
- schema.type = 'object';
476
- }
477
- if (!isRecord(schema.properties)) {
478
- schema.properties = {};
479
- }
480
- return schema;
481
- }
482
- }
483
- return {
484
- type: 'object',
485
- properties: {},
486
- additionalProperties: true
487
- };
488
- }
489
- function normalizeRecord(value) {
490
- if (value && typeof value === 'object' && !Array.isArray(value)) {
491
- return value;
492
- }
493
- return {};
494
- }
495
- function deepClone(value) {
496
- return JSON.parse(JSON.stringify(value));
497
- }
498
- function readString(value) {
499
- return typeof value === 'string' && value.trim() ? value.trim() : undefined;
500
- }
501
- function readToolChoice(value) {
502
- if (!value) {
503
- return undefined;
504
- }
505
- if (typeof value === 'string') {
506
- return value;
507
- }
508
- if (typeof value === 'object') {
509
- return value;
510
- }
511
- return undefined;
512
- }
513
- function isRecord(value) {
514
- return !!value && typeof value === 'object' && !Array.isArray(value);
515
- }
516
- function maybeInjectWebSearchTool(request, metadata) {
517
- const ops = buildWebSearchOperations(request, metadata);
518
- if (!ops.length) {
519
- return request;
520
- }
521
- return applyHubOperations(request, ops);
522
- }
523
- function buildInboundStreamingOperations(intent) {
524
- return [
525
- {
526
- op: 'set_request_metadata_fields',
527
- fields: { inboundStream: intent }
528
- }
529
- ];
530
- }
531
- function buildOutboundStreamingOperations(stream) {
532
- return [
533
- {
534
- op: 'set_request_parameter_fields',
535
- fields: { stream }
536
- }
537
- ];
538
- }
539
- function buildToolChoiceOperations(toolChoice) {
540
- return [
541
- {
542
- op: 'set_request_parameter_fields',
543
- fields: { tool_choice: toolChoice }
544
- }
545
- ];
546
- }
547
- function buildWebSearchOperations(request, metadata) {
548
- const rt = readRuntimeMetadata(metadata);
549
- // ServerTool 二/三跳(serverToolFollowup=true)不再注入 web_search 工具,
550
- // 以避免在 web_search 流程内部形成循环命中。
551
- if (rt?.serverToolFollowup === true) {
552
- return [];
553
- }
554
- const rawConfig = rt?.webSearch;
555
- if (!rawConfig || !Array.isArray(rawConfig.engines) || rawConfig.engines.length === 0) {
556
- return [];
557
- }
558
- const semanticsWebSearch = extractWebSearchSemantics(request.semantics);
559
- if (semanticsWebSearch?.disable === true) {
560
- return [];
561
- }
562
- const injectPolicy = semanticsWebSearch?.force === true
563
- ? 'always'
564
- : rawConfig.injectPolicy === 'always' || rawConfig.injectPolicy === 'selective'
565
- ? rawConfig.injectPolicy
566
- : 'selective';
567
- const intent = detectWebSearchIntent(request);
568
- if (injectPolicy === 'selective') {
569
- // 仅当当前这一轮用户输入明确表达“联网搜索”意图时才注入 web_search。
570
- // 不再依赖上一轮工具分类(read/search/websearch),避免形成隐式续写语义。
571
- if (!intent.hasIntent) {
572
- return [];
573
- }
574
- }
575
- const existingTools = Array.isArray(request.tools) ? request.tools : [];
576
- let engines = rawConfig.engines.filter((engine) => typeof engine?.id === 'string' && !!engine.id.trim() && !engine.serverToolsDisabled);
577
- // 当用户明确要求「谷歌搜索」时,只暴露 Gemini / Antigravity 类搜索后端:
578
- // - providerKey 以 gemini-cli. 或 antigravity. 开头;
579
- // - 或 engine id 中包含 "google"(向前兼容配置中用 id 标识 google 引擎的场景)。
580
- if (intent.googlePreferred) {
581
- const preferred = engines.filter((engine) => {
582
- const id = engine.id.trim().toLowerCase();
583
- const providerKey = (engine.providerKey || '').toLowerCase();
584
- if (providerKey.startsWith('gemini-cli.') || providerKey.startsWith('antigravity.')) {
585
- return true;
586
- }
587
- if (id.includes('google')) {
588
- return true;
589
- }
590
- return false;
591
- });
592
- if (preferred.length > 0) {
593
- engines = preferred;
594
- }
595
- }
596
- // DeepSeek native search path:
597
- // when deepseek web_search engine is configured as top priority,
598
- // route directly to deepseek-search model instead of servertool tool injection.
599
- if (shouldBypassServerToolWebSearch(intent, engines, semanticsWebSearch)) {
600
- return [];
601
- }
602
- if (!engines.length) {
603
- return [];
604
- }
605
- const engineIds = engines.map((engine) => engine.id.trim());
606
- const engineDescriptions = engines
607
- .map((engine) => {
608
- const id = engine.id.trim();
609
- const desc = typeof engine.description === 'string' && engine.description.trim()
610
- ? engine.description.trim()
611
- : '';
612
- return desc ? `${id}: ${desc}` : id;
613
- })
614
- .join('; ');
615
- const parameters = {
616
- type: 'object',
617
- properties: {
618
- engine: {
619
- type: 'string',
620
- enum: engineIds,
621
- description: engineDescriptions
622
- },
623
- query: {
624
- type: 'string',
625
- description: 'Search query or user question.'
626
- },
627
- recency: {
628
- type: 'string',
629
- enum: ['oneDay', 'oneWeek', 'oneMonth', 'oneYear', 'noLimit'],
630
- description: 'Optional recency filter for web search results.'
631
- },
632
- count: {
633
- type: 'integer',
634
- minimum: 1,
635
- maximum: 50,
636
- description: 'Number of results to retrieve.'
637
- }
638
- },
639
- // 对于 Responses 内建 web_search,required 需要覆盖 properties 中的所有字段,
640
- // 否则上游会报 "required is required to be supplied and to be an array including every key in properties"。
641
- required: ['engine', 'query', 'recency', 'count'],
642
- additionalProperties: false
643
- };
644
- const webSearchTool = {
645
- type: 'function',
646
- function: {
647
- name: 'web_search',
648
- description: 'Perform web search using configured search engines. Use this when the user asks for up-to-date information or news.',
649
- parameters,
650
- strict: true
651
- }
652
- };
653
- const ops = [
654
- {
655
- op: 'set_request_metadata_fields',
656
- fields: { webSearchEnabled: true }
657
- }
658
- ];
659
- ops.push({
660
- op: 'append_tool_if_missing',
661
- toolName: 'web_search',
662
- tool: webSearchTool
663
- });
664
- return ops;
665
- }
666
- function shouldBypassServerToolWebSearch(intent, engines, semanticsWebSearch) {
667
- if (!intent.hasIntent || intent.googlePreferred) {
668
- return false;
669
- }
670
- // Explicit semantic force should keep servertool orchestration.
671
- if (semanticsWebSearch?.force === true) {
672
- return false;
673
- }
674
- const deepseekIndex = engines.findIndex(isDeepSeekNativeWebSearchEngine);
675
- if (deepseekIndex < 0) {
676
- return false;
677
- }
678
- const deepseekEngine = engines[deepseekIndex];
679
- // Treat explicit default as highest priority; otherwise require first position.
680
- if (deepseekEngine.default !== true && deepseekIndex !== 0) {
681
- return false;
682
- }
683
- return true;
684
- }
685
- function isDeepSeekNativeWebSearchEngine(engine) {
686
- const id = typeof engine.id === 'string' ? engine.id.trim().toLowerCase() : '';
687
- const providerKey = typeof engine.providerKey === 'string' ? engine.providerKey.trim().toLowerCase() : '';
688
- if (id === 'deepseek:web_search') {
689
- return true;
690
- }
691
- if (!providerKey.startsWith('deepseek-web.')) {
692
- return false;
693
- }
694
- return providerKey.endsWith('.deepseek-chat-search') || providerKey.endsWith('.deepseek-reasoner-search');
695
- }
696
- function buildClockOperations(metadata) {
697
- const rt = readRuntimeMetadata(metadata);
698
- // Do not inject clock tool into internal servertool followup hops.
699
- const allowClockFollowupToolInjection = rt?.clockFollowupInjectTool === true;
700
- if (rt?.serverToolFollowup === true && !allowClockFollowupToolInjection) {
701
- return [];
702
- }
703
- const rawConfig = rt?.clock;
704
- const clockConfig = resolveClockConfig(rawConfig);
705
- if (!clockConfig) {
706
- return [];
707
- }
708
- const sessionId = readString(metadata.sessionId);
709
- const hasSessionId = Boolean(sessionId && sessionId.trim());
710
- logClock('inject_schema', { hasSessionId });
711
- const parameters = {
712
- type: 'object',
713
- properties: {
714
- action: {
715
- type: 'string',
716
- enum: ['get', 'schedule', 'update', 'list', 'cancel', 'clear'],
717
- description: 'Get current time, or schedule/update/list/cancel/clear session-scoped reminders. For any wait/later requirement, schedule immediately.'
718
- },
719
- items: {
720
- type: 'array',
721
- description: 'For schedule/update: list of reminder payloads. update uses items[0] as patch source.',
722
- items: {
723
- type: 'object',
724
- properties: {
725
- dueAt: {
726
- type: 'string',
727
- description: 'ISO8601 datetime with timezone (e.g. 2026-01-21T20:30:00-08:00).'
728
- },
729
- task: {
730
- type: 'string',
731
- description: 'Reminder text that states the exact action to execute on wake-up (no vague placeholders).'
732
- },
733
- tool: {
734
- type: 'string',
735
- description: 'Optional suggested tool name (hint only).'
736
- },
737
- arguments: {
738
- type: 'string',
739
- description: 'Optional suggested tool arguments as a JSON string (hint only). Use "{}" when unsure.'
740
- }
741
- },
742
- // For strict tool schemas (OpenAI Responses), required must include every key in properties.
743
- // We keep tool/arguments as hints but allow empty string when not used.
744
- required: ['dueAt', 'task', 'tool', 'arguments'],
745
- additionalProperties: false
746
- }
747
- },
748
- taskId: {
749
- type: 'string',
750
- description: 'For cancel/update: target taskId.'
751
- }
752
- },
753
- // For strict tool schemas (OpenAI Responses), required must include every key in properties.
754
- // Fields unused for certain actions can be set to empty string / empty list.
755
- required: ['action', 'items', 'taskId'],
756
- additionalProperties: false
757
- };
758
- const clockTool = {
759
- type: 'function',
760
- function: {
761
- name: 'clock',
762
- description: 'Time + Alarm for this session. MUST use clock.schedule whenever waiting/delay is required. Never promise to wait without scheduling. Use get/schedule/update/list/cancel/clear. Scheduled reminders are injected into future requests.',
763
- parameters,
764
- strict: true
765
- }
766
- };
767
- return [
768
- {
769
- op: 'set_request_metadata_fields',
770
- fields: { clockEnabled: true, ...(hasSessionId ? { serverToolRequired: true } : {}) }
771
- },
772
- { op: 'append_tool_if_missing', toolName: 'clock', tool: clockTool }
773
- ];
774
- }
775
- function hasActiveStopMessageStateForContinueExecution(metadata) {
776
- const rt = readRuntimeMetadata(metadata);
777
- if (isStopMessageStateActive(rt?.stopMessageState)) {
778
- return true;
779
- }
780
- return hasPersistedActiveStopMessageState(metadata);
781
- }
782
- function hasPersistedActiveStopMessageState(metadata) {
783
- const sessionScope = resolveStopMessageSessionScope(metadata);
784
- if (!sessionScope) {
785
- return false;
786
- }
787
- try {
788
- const persisted = loadRoutingInstructionStateSync(sessionScope);
789
- return isStopMessageStateActive(persisted);
790
- }
791
- catch {
792
- return false;
793
- }
794
- }
795
- function resolveStopMessageSessionScope(metadata) {
796
- const sessionId = readString(metadata.sessionId);
797
- if (sessionId) {
798
- return 'session:' + sessionId;
799
- }
800
- const conversationId = readString(metadata.conversationId);
801
- if (conversationId) {
802
- return 'conversation:' + conversationId;
803
- }
804
- return undefined;
805
- }
806
- function isStopMessageStateActive(raw) {
807
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
808
- return false;
809
- }
810
- const record = raw;
811
- const text = typeof record.stopMessageText === 'string' ? record.stopMessageText.trim() : '';
812
- const maxRepeats = typeof record.stopMessageMaxRepeats === 'number' && Number.isFinite(record.stopMessageMaxRepeats)
813
- ? Math.max(1, Math.floor(record.stopMessageMaxRepeats))
814
- : 0;
815
- const stageMode = typeof record.stopMessageStageMode === 'string' ? record.stopMessageStageMode.trim().toLowerCase() : '';
816
- if (stageMode === 'off') {
817
- return false;
818
- }
819
- if (text && maxRepeats > 0) {
820
- return true;
821
- }
822
- return stageMode === 'on' || stageMode === 'auto';
823
- }
824
- function buildContinueExecutionOperations(metadata) {
825
- const rt = readRuntimeMetadata(metadata);
826
- if (rt?.serverToolFollowup === true) {
827
- return [];
828
- }
829
- const sessionId = readString(metadata.sessionId);
830
- const hasSessionId = Boolean(sessionId && sessionId.trim());
831
- if (hasActiveStopMessageStateForContinueExecution(metadata)) {
832
- logContinueExecution('skip_schema_due_stopmessage', { hasSessionId });
833
- return [];
834
- }
835
- logContinueExecution('inject_schema', { hasSessionId });
836
- const parameters = {
837
- type: 'object',
838
- properties: {},
839
- required: [],
840
- additionalProperties: false
841
- };
842
- const continueTool = {
843
- type: 'function',
844
- function: {
845
- name: 'continue_execution',
846
- description: 'No-op control tool. Use this before status summaries or before ending the turn. If you are about to send finish_reason=stop (or protocol-equivalent end_turn), do not end directly; emit a continue_execution tool call instead. Keep it lightweight. The server will auto-followup so execution continues, avoiding interruption and improving execution efficiency.',
847
- parameters,
848
- strict: true
849
- }
850
- };
851
- return [
852
- {
853
- op: 'set_request_metadata_fields',
854
- fields: { continueExecutionEnabled: true }
855
- },
856
- { op: 'append_tool_if_missing', toolName: 'continue_execution', tool: continueTool }
857
- ];
858
- }
859
- function injectContinueExecutionDirectiveIntoUserMessage(request, metadata) {
860
- const rt = readRuntimeMetadata(metadata);
861
- if (rt?.serverToolFollowup === true) {
862
- return request;
863
- }
864
- if (hasActiveStopMessageStateForContinueExecution(metadata)) {
865
- const sessionId = readString(metadata.sessionId);
866
- const hasSessionId = Boolean(sessionId && sessionId.trim());
867
- logContinueExecution('skip_user_directive_due_stopmessage', { hasSessionId });
868
- return request;
869
- }
870
- const messages = Array.isArray(request.messages) ? request.messages : [];
871
- if (messages.length === 0) {
872
- return request;
873
- }
874
- const hasDirective = messages.some((message) => {
875
- if (!message || typeof message !== 'object' || message.role !== 'user') {
876
- return false;
877
- }
878
- return messageContentContainsDirectiveMarker(message.content);
879
- });
880
- if (hasDirective) {
881
- return request;
882
- }
883
- const lastUserIdx = findLastUserMessageIndex(messages);
884
- if (lastUserIdx < 0) {
885
- return request;
886
- }
887
- const nextMessages = messages.slice();
888
- const target = nextMessages[lastUserIdx];
889
- const nextContent = appendDirectiveToUserContent(target.content, CONTINUE_EXECUTION_SYSTEM_DIRECTIVE);
890
- nextMessages[lastUserIdx] = {
891
- ...target,
892
- content: nextContent
893
- };
894
- const sessionId = readString(metadata.sessionId);
895
- const hasSessionId = Boolean(sessionId && sessionId.trim());
896
- logContinueExecution('inject_user_directive', { hasSessionId });
897
- return {
898
- ...request,
899
- messages: nextMessages
900
- };
901
- }
902
- function messageContentContainsDirectiveMarker(content) {
903
- if (typeof content === 'string') {
904
- return content.includes(CONTINUE_EXECUTION_SYSTEM_DIRECTIVE_MARKER);
905
- }
906
- if (!Array.isArray(content)) {
907
- return false;
908
- }
909
- for (const part of content) {
910
- if (!part || typeof part !== 'object') {
911
- continue;
912
- }
913
- const text = typeof part.text === 'string' ? String(part.text) : '';
914
- if (text.includes(CONTINUE_EXECUTION_SYSTEM_DIRECTIVE_MARKER)) {
915
- return true;
916
- }
917
- }
918
- return false;
919
- }
920
- function appendDirectiveToUserContent(content, directive) {
921
- if (typeof content === 'string') {
922
- const base = content.trimEnd();
923
- return base.length ? `${base}\n\n${directive}` : directive;
924
- }
925
- if (!Array.isArray(content)) {
926
- return directive;
927
- }
928
- const next = content.slice();
929
- for (let idx = next.length - 1; idx >= 0; idx -= 1) {
930
- const part = next[idx];
931
- if (!part || typeof part !== 'object' || typeof part.text !== 'string') {
932
- continue;
933
- }
934
- const base = String(part.text).trimEnd();
935
- next[idx] = {
936
- ...part,
937
- text: base.length ? `${base}\n\n${directive}` : directive
938
- };
939
- return next;
940
- }
941
- next.push({ type: 'input_text', text: directive });
942
- return next;
943
- }
944
- function resolveSessionIdForClock(metadata, request) {
945
- const requestMetadata = request.metadata && typeof request.metadata === 'object' && !Array.isArray(request.metadata)
946
- ? request.metadata
947
- : null;
948
- return resolveClockSessionScope(metadata, requestMetadata);
949
- }
950
- function stripClockClearDirectiveFromText(text) {
951
- const pattern = /<\*\*\s*clock\s*:\s*clear\s*\*\*>/gi;
952
- const hadClear = pattern.test(text);
953
- if (!hadClear) {
954
- return { hadClear: false, next: text };
955
- }
956
- const replaced = text.replace(pattern, '');
957
- // Clean up leftover excessive blank lines to keep prompts tidy.
958
- const next = replaced.replace(/\n{3,}/g, '\n\n').trim();
959
- return { hadClear: true, next };
960
- }
961
- function unquoteMarkerToken(value) {
962
- const trimmed = String(value || '').trim();
963
- if (!trimmed) {
964
- return '';
965
- }
966
- if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
967
- (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
968
- return trimmed.slice(1, -1).trim();
969
- }
970
- return trimmed;
971
- }
972
- function parseMarkerRecurrenceKind(raw) {
973
- const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
974
- if (!value) {
975
- return undefined;
976
- }
977
- if (value === 'daily' || value === 'day') {
978
- return 'daily';
979
- }
980
- if (value === 'weekly' || value === 'week') {
981
- return 'weekly';
982
- }
983
- if (value === 'interval' || value === 'every_minutes' || value === 'every-minutes' || value === 'everyminutes') {
984
- return 'interval';
985
- }
986
- return undefined;
987
- }
988
- function parseMarkerRecurrence(record) {
989
- const recurrenceRaw = record.recurrence ?? record.repeat ?? record.cycle;
990
- if (recurrenceRaw === undefined || recurrenceRaw === null || recurrenceRaw === false) {
991
- return undefined;
992
- }
993
- let kind;
994
- let everyMinutesRaw;
995
- let maxRunsRaw;
996
- if (typeof recurrenceRaw === 'string') {
997
- kind = parseMarkerRecurrenceKind(recurrenceRaw);
998
- everyMinutesRaw = record.everyMinutes;
999
- maxRunsRaw = record.maxRuns;
1000
- }
1001
- else if (recurrenceRaw && typeof recurrenceRaw === 'object' && !Array.isArray(recurrenceRaw)) {
1002
- const recurrenceRecord = recurrenceRaw;
1003
- kind = parseMarkerRecurrenceKind(recurrenceRecord.kind ?? recurrenceRecord.type ?? recurrenceRecord.mode ?? recurrenceRecord.every);
1004
- everyMinutesRaw = recurrenceRecord.everyMinutes ?? recurrenceRecord.minutes ?? record.everyMinutes;
1005
- maxRunsRaw = recurrenceRecord.maxRuns ?? record.maxRuns;
1006
- }
1007
- if (!kind) {
1008
- return undefined;
1009
- }
1010
- const maxRuns = Number(maxRunsRaw);
1011
- if (!Number.isFinite(maxRuns) || Math.floor(maxRuns) <= 0) {
1012
- return undefined;
1013
- }
1014
- if (kind === 'interval') {
1015
- const everyMinutes = Number(everyMinutesRaw);
1016
- if (!Number.isFinite(everyMinutes) || Math.floor(everyMinutes) <= 0) {
1017
- return undefined;
1018
- }
1019
- return { kind: 'interval', maxRuns: Math.floor(maxRuns), everyMinutes: Math.floor(everyMinutes) };
1020
- }
1021
- return { kind, maxRuns: Math.floor(maxRuns) };
1022
- }
1023
- function parseClockScheduleDirectivePayload(payload) {
1024
- const raw = String(payload || '').trim();
1025
- if (!raw || /^clear$/i.test(raw)) {
1026
- return null;
1027
- }
1028
- const parseFromRecord = (record) => {
1029
- const timeRaw = typeof record.time === 'string' ? record.time :
1030
- typeof record.dueAt === 'string' ? record.dueAt :
1031
- typeof record.due_at === 'string' ? record.due_at :
1032
- '';
1033
- const messageRaw = typeof record.message === 'string' ? record.message :
1034
- typeof record.task === 'string' ? record.task :
1035
- '';
1036
- const dueAt = String(timeRaw || '').trim();
1037
- const task = String(messageRaw || '').trim();
1038
- const dueAtMs = parseDueAtMs(dueAt);
1039
- if (!dueAtMs || !task) {
1040
- return null;
1041
- }
1042
- const hasRecurrenceRaw = Object.prototype.hasOwnProperty.call(record, 'recurrence')
1043
- || Object.prototype.hasOwnProperty.call(record, 'repeat')
1044
- || Object.prototype.hasOwnProperty.call(record, 'cycle');
1045
- const recurrence = parseMarkerRecurrence(record);
1046
- if (hasRecurrenceRaw && !recurrence) {
1047
- return null;
1048
- }
1049
- return {
1050
- dueAtMs,
1051
- dueAt: new Date(dueAtMs).toISOString(),
1052
- task,
1053
- ...(recurrence ? { recurrence } : {})
1054
- };
1055
- };
1056
- try {
1057
- const parsed = JSON.parse(raw);
1058
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
1059
- const fromJson = parseFromRecord(parsed);
1060
- if (fromJson) {
1061
- return fromJson;
1062
- }
1063
- }
1064
- }
1065
- catch {
1066
- // fall through to loose parser
1067
- }
1068
- const loose = /^\{\s*time\s*:\s*([^,}]+)\s*,\s*message\s*:\s*([^}]+)\s*\}$/i.exec(raw);
1069
- if (!loose) {
1070
- return null;
1071
- }
1072
- const dueAt = unquoteMarkerToken(loose[1]);
1073
- const task = unquoteMarkerToken(loose[2]);
1074
- const dueAtMs = parseDueAtMs(dueAt);
1075
- if (!dueAtMs || !task) {
1076
- return null;
1077
- }
1078
- return {
1079
- dueAtMs,
1080
- dueAt: new Date(dueAtMs).toISOString(),
1081
- task
1082
- };
1083
- }
1084
- function extractClockScheduleDirectivesFromText(text) {
1085
- const pattern = /<\*\*\s*clock\s*:\s*([\s\S]*?)\s*\*\*>/gi;
1086
- const directives = [];
1087
- const next = String(text || '').replace(pattern, (full, payload) => {
1088
- const parsed = parseClockScheduleDirectivePayload(String(payload || ''));
1089
- if (!parsed) {
1090
- return full;
1091
- }
1092
- directives.push(parsed);
1093
- return '';
1094
- });
1095
- return {
1096
- directives,
1097
- next: next.replace(/\n{3,}/g, '\n\n').trim()
1098
- };
1099
- }
1100
- function extractClockScheduleDirectivesFromContent(content) {
1101
- if (typeof content === 'string') {
1102
- const result = extractClockScheduleDirectivesFromText(content);
1103
- return { directives: result.directives, next: result.next };
1104
- }
1105
- if (Array.isArray(content)) {
1106
- const directives = [];
1107
- const next = content.map((part) => {
1108
- if (typeof part === 'string') {
1109
- const result = extractClockScheduleDirectivesFromText(part);
1110
- if (result.directives.length) {
1111
- directives.push(...result.directives);
1112
- }
1113
- return result.next;
1114
- }
1115
- if (part && typeof part === 'object' && !Array.isArray(part)) {
1116
- const block = part;
1117
- const text = typeof block.text === 'string' ? block.text : undefined;
1118
- if (!text) {
1119
- return part;
1120
- }
1121
- const result = extractClockScheduleDirectivesFromText(text);
1122
- if (result.directives.length) {
1123
- directives.push(...result.directives);
1124
- }
1125
- return { ...block, text: result.next };
1126
- }
1127
- return part;
1128
- });
1129
- return { directives, next };
1130
- }
1131
- return { directives: [], next: content };
1132
- }
1133
- let clockMarkerCallSeq = 0;
1134
- function buildClockMarkerCallId(requestId, index) {
1135
- clockMarkerCallSeq += 1;
1136
- const token = String(requestId || 'req').replace(/[^a-zA-Z0-9_-]+/g, '_') || 'req';
1137
- return `call_clock_marker_${token}_${index + 1}_${clockMarkerCallSeq}`;
1138
- }
1139
- function buildClockMarkerScheduleMessages(requestId, markerIndex, marker, payload) {
1140
- const callId = buildClockMarkerCallId(requestId, markerIndex);
1141
- const args = {
1142
- action: 'schedule',
1143
- items: [
1144
- {
1145
- dueAt: marker.dueAt,
1146
- task: marker.task,
1147
- ...(marker.recurrence ? { recurrence: marker.recurrence } : {})
1148
- }
1149
- ]
1150
- };
1151
- return [
1152
- {
1153
- role: 'assistant',
1154
- content: null,
1155
- tool_calls: [
1156
- {
1157
- id: callId,
1158
- type: 'function',
1159
- function: {
1160
- name: 'clock',
1161
- arguments: JSON.stringify(args)
1162
- }
1163
- }
1164
- ]
1165
- },
1166
- {
1167
- role: 'tool',
1168
- tool_call_id: callId,
1169
- name: 'clock',
1170
- content: JSON.stringify(payload)
1171
- }
1172
- ];
1173
- }
1174
- function stripClockClearDirectiveFromContent(content) {
1175
- if (typeof content === 'string') {
1176
- const { hadClear, next } = stripClockClearDirectiveFromText(content);
1177
- return { hadClear, next };
1178
- }
1179
- if (Array.isArray(content)) {
1180
- let hadClear = false;
1181
- const next = content.map((part) => {
1182
- if (typeof part === 'string') {
1183
- const stripped = stripClockClearDirectiveFromText(part);
1184
- if (stripped.hadClear)
1185
- hadClear = true;
1186
- return stripped.next;
1187
- }
1188
- if (part && typeof part === 'object' && !Array.isArray(part)) {
1189
- const block = part;
1190
- const text = typeof block.text === 'string' ? block.text : undefined;
1191
- if (!text)
1192
- return part;
1193
- const stripped = stripClockClearDirectiveFromText(text);
1194
- if (stripped.hadClear)
1195
- hadClear = true;
1196
- return { ...block, text: stripped.next };
1197
- }
1198
- return part;
1199
- });
1200
- return { hadClear, next };
1201
- }
1202
- return { hadClear: false, next: content };
1203
- }
1204
- function findLastUserMessageIndex(messages) {
1205
- if (!Array.isArray(messages) || messages.length === 0) {
1206
- return -1;
1207
- }
1208
- for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
1209
- const candidate = messages[idx];
1210
- if (candidate && candidate.role === 'user') {
1211
- return idx;
1212
- }
1213
- }
1214
- return -1;
1215
- }
1216
- async function maybeInjectClockRemindersAndApplyDirectives(request, metadata, requestId) {
1217
- const rt = readRuntimeMetadata(metadata);
1218
- // Do not inject reminders or apply clock directives during internal servertool followup hops.
1219
- const allowClockFollowupReminderInjection = rt?.clockFollowupInjectReminders === true;
1220
- if (rt?.serverToolFollowup === true && !allowClockFollowupReminderInjection) {
1221
- return request;
1222
- }
1223
- const rawConfig = rt?.clock;
1224
- const clockConfig = resolveClockConfig(rawConfig);
1225
- if (!clockConfig) {
1226
- return request;
1227
- }
1228
- try {
1229
- await startClockDaemonIfNeeded(clockConfig);
1230
- }
1231
- catch {
1232
- // best-effort
1233
- }
1234
- const sessionId = resolveSessionIdForClock(metadata, request);
1235
- const messages = Array.isArray(request.messages) ? request.messages : [];
1236
- const lastUserIdx = findLastUserMessageIndex(messages);
1237
- // 1) Apply <**clock:clear**> directive (latest user message only).
1238
- let hadClear = false;
1239
- let clockScheduleDirectives = [];
1240
- let baseMessages = messages;
1241
- if (lastUserIdx >= 0) {
1242
- const lastUser = messages[lastUserIdx];
1243
- const stripped = stripClockClearDirectiveFromContent(lastUser.content);
1244
- hadClear = stripped.hadClear;
1245
- const extracted = extractClockScheduleDirectivesFromContent(stripped.next);
1246
- clockScheduleDirectives = extracted.directives;
1247
- if (hadClear || clockScheduleDirectives.length > 0) {
1248
- baseMessages = messages.slice();
1249
- baseMessages[lastUserIdx] = { ...lastUser, content: extracted.next };
1250
- }
1251
- }
1252
- if (hadClear) {
1253
- if (sessionId) {
1254
- try {
1255
- await clearClockSession(sessionId);
1256
- logClock('cleared', { sessionId });
1257
- }
1258
- catch {
1259
- // best-effort: user directive should not crash request
1260
- }
1261
- }
1262
- // Continue: still inject per-request time tag (but skip due reminders).
1263
- }
1264
- // 2) Apply private schedule directives: <**clock:{time,message}**>
1265
- // Convert to actual clock scheduling and append synthetic tool_call/tool_result messages
1266
- // so downstream model sees canonical tool semantics.
1267
- let markerToolMessages = [];
1268
- if (clockScheduleDirectives.length > 0) {
1269
- for (let index = 0; index < clockScheduleDirectives.length; index += 1) {
1270
- const marker = clockScheduleDirectives[index];
1271
- const itemBase = {
1272
- dueAtMs: marker.dueAtMs,
1273
- setBy: 'user',
1274
- task: marker.task,
1275
- ...(marker.recurrence ? { recurrence: marker.recurrence } : {})
1276
- };
1277
- const now = Date.now();
1278
- const guardedItem = marker.dueAtMs <= now + clockConfig.dueWindowMs
1279
- ? { ...itemBase, notBeforeRequestId: requestId }
1280
- : itemBase;
1281
- if (!sessionId) {
1282
- markerToolMessages = markerToolMessages.concat(buildClockMarkerScheduleMessages(requestId, index, marker, {
1283
- ok: false,
1284
- action: 'schedule',
1285
- message: 'clock requires session scope (sessionId/conversationId or clockDaemonId).'
1286
- }));
1287
- continue;
1288
- }
1289
- try {
1290
- const scheduled = await scheduleClockTasks(sessionId, [guardedItem], clockConfig);
1291
- markerToolMessages = markerToolMessages.concat(buildClockMarkerScheduleMessages(requestId, index, marker, {
1292
- ok: true,
1293
- action: 'schedule',
1294
- scheduled: scheduled.map((entry) => ({
1295
- taskId: entry.taskId,
1296
- dueAt: new Date(entry.dueAtMs).toISOString(),
1297
- task: entry.task,
1298
- deliveryCount: entry.deliveryCount
1299
- }))
1300
- }));
1301
- logClock('schedule', { sessionId, count: scheduled.length, source: 'marker' });
1302
- }
1303
- catch (error) {
1304
- markerToolMessages = markerToolMessages.concat(buildClockMarkerScheduleMessages(requestId, index, marker, {
1305
- ok: false,
1306
- action: 'schedule',
1307
- message: `clock.schedule failed: ${error instanceof Error ? error.message : String(error ?? 'unknown')}`
1308
- }));
1309
- }
1310
- }
1311
- }
1312
- // 3) Inject due reminders as a user message + attach reservation for response-side commit.
1313
- let reservation = null;
1314
- let dueInjectText = '';
1315
- if (!hadClear && sessionId) {
1316
- try {
1317
- const reserved = await reserveDueTasksForRequest({
1318
- reservationId: `${requestId}:clock`,
1319
- sessionId,
1320
- config: clockConfig,
1321
- requestId
1322
- });
1323
- reservation = reserved.reservation;
1324
- dueInjectText = typeof reserved.injectText === 'string' ? reserved.injectText.trim() : '';
1325
- }
1326
- catch {
1327
- // best-effort
1328
- reservation = null;
1329
- dueInjectText = '';
1330
- }
1331
- }
1332
- const dueUserMessage = (() => {
1333
- if (!reservation || !dueInjectText)
1334
- return null;
1335
- return {
1336
- role: 'user',
1337
- content: [
1338
- '[Clock Reminder]: scheduled tasks are due.',
1339
- dueInjectText,
1340
- 'You may call tools to complete these tasks. If the tool list is incomplete, standard tools have been injected. MANDATORY: if waiting is needed, use the clock tool to schedule wake-up (clock.schedule) now; do not only promise to wait.'
1341
- ].join('\n')
1342
- };
1343
- })();
1344
- // 4) When we have due tasks, ensure a standard tool set is present (best-effort).
1345
- let nextRequest = request;
1346
- if (dueUserMessage) {
1347
- const ensureToolsOps = buildClockStandardToolsOperations();
1348
- nextRequest = applyHubOperations(nextRequest, ensureToolsOps);
1349
- }
1350
- // 5) Per-request time injection (user time tag or paired clock.get tool result).
1351
- let snapshot = null;
1352
- try {
1353
- snapshot = await getClockTimeSnapshot();
1354
- }
1355
- catch {
1356
- snapshot = null;
1357
- }
1358
- const timeTagLine = snapshot
1359
- ? buildTimeTagLine(snapshot)
1360
- : '[Time/Date]: utc=`1970-01-01T00:00:00.000Z` local=`1970-01-01 00:00:00.000 +00:00` tz=`unknown` nowMs=`0` ntpOffsetMs=`0`';
1361
- const baseMetadata = nextRequest.metadata && typeof nextRequest.metadata === 'object'
1362
- ? nextRequest.metadata
1363
- : {
1364
- originalEndpoint: readString(metadata.originalEndpoint) ?? '/v1/chat/completions'
1365
- };
1366
- const withReservationMetadata = dueUserMessage && reservation
1367
- ? {
1368
- ...baseMetadata,
1369
- __clockReservation: reservation
1370
- }
1371
- : baseMetadata;
1372
- const messagesWithMarkers = markerToolMessages.length > 0
1373
- ? [...baseMessages, ...markerToolMessages]
1374
- : baseMessages.slice();
1375
- const messagesWithDue = dueUserMessage ? [...messagesWithMarkers, dueUserMessage] : messagesWithMarkers;
1376
- // Always inject time via user-role content to keep the tag visible without adding
1377
- // extra tool-call semantics that may distract the model.
1378
- //
1379
- // IMPORTANT: do not append an extra trailing user message, otherwise the Virtual Router
1380
- // sees `latestMessageFromUser=true` and will force "thinking:user-input" even during
1381
- // tool followups (last message role=tool), which breaks `coding/search/tools` routing.
1382
- const timeInjectedMessages = (() => {
1383
- const idx = findLastUserMessageIndex(messagesWithDue);
1384
- if (idx < 0) {
1385
- return [...messagesWithDue, { role: 'user', content: timeTagLine }];
1386
- }
1387
- const msg = messagesWithDue[idx];
1388
- const content = msg.content;
1389
- const nextContent = (() => {
1390
- if (typeof content === 'string') {
1391
- const base = content.trimEnd();
1392
- return base ? `${base}\n${timeTagLine}` : timeTagLine;
1393
- }
1394
- if (Array.isArray(content)) {
1395
- return [...content, timeTagLine];
1396
- }
1397
- return timeTagLine;
1398
- })();
1399
- const cloned = messagesWithDue.slice();
1400
- cloned[idx] = { ...msg, content: nextContent };
1401
- return cloned;
1402
- })();
1403
- return {
1404
- ...nextRequest,
1405
- messages: timeInjectedMessages,
1406
- metadata: withReservationMetadata
1407
- };
1408
- }
1409
- function buildClockStandardToolsOperations() {
1410
- const tools = [
1411
- {
1412
- type: 'function',
1413
- function: {
1414
- name: 'clock',
1415
- description: 'Time + Alarm for this session. Use schedule/update/list/cancel/clear. For any wait requirement, call clock.schedule now.',
1416
- parameters: {
1417
- type: 'object',
1418
- properties: {
1419
- action: {
1420
- type: 'string',
1421
- enum: ['get', 'schedule', 'update', 'list', 'cancel', 'clear']
1422
- },
1423
- items: {
1424
- type: 'array',
1425
- items: {
1426
- type: 'object',
1427
- properties: {
1428
- dueAt: { type: 'string' },
1429
- task: { type: 'string' },
1430
- tool: { type: 'string' },
1431
- arguments: { type: 'string' }
1432
- },
1433
- required: ['dueAt', 'task', 'tool', 'arguments'],
1434
- additionalProperties: false
1435
- }
1436
- },
1437
- taskId: { type: 'string' }
1438
- },
1439
- required: ['action', 'items', 'taskId'],
1440
- additionalProperties: false
1441
- }
1442
- }
1443
- },
1444
- {
1445
- type: 'function',
1446
- function: {
1447
- name: 'shell',
1448
- description: 'Runs a shell command and returns its output.',
1449
- parameters: {
1450
- type: 'object',
1451
- properties: {
1452
- command: { oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }] },
1453
- workdir: { type: 'string' }
1454
- },
1455
- required: ['command'],
1456
- additionalProperties: false
1457
- }
1458
- }
1459
- },
1460
- {
1461
- type: 'function',
1462
- function: {
1463
- name: 'exec_command',
1464
- description: 'Execute a command in a PTY and return output.',
1465
- parameters: {
1466
- type: 'object',
1467
- properties: {
1468
- cmd: { type: 'string' },
1469
- workdir: { type: 'string' },
1470
- timeout_ms: { type: 'number' },
1471
- max_output_tokens: { type: 'number' },
1472
- yield_time_ms: { type: 'number' }
1473
- },
1474
- required: ['cmd'],
1475
- additionalProperties: false
1476
- }
1477
- }
1478
- },
1479
- {
1480
- type: 'function',
1481
- function: {
1482
- name: 'apply_patch',
1483
- description: 'Apply a patch to repository files.',
1484
- parameters: {
1485
- type: 'object',
1486
- properties: {
1487
- patch: { type: 'string' }
1488
- },
1489
- required: ['patch'],
1490
- additionalProperties: false
1491
- }
1492
- }
1493
- },
1494
- {
1495
- type: 'function',
1496
- function: {
1497
- name: 'update_plan',
1498
- description: 'Update the task plan.',
1499
- parameters: {
1500
- type: 'object',
1501
- properties: {
1502
- explanation: { type: 'string' },
1503
- plan: {
1504
- type: 'array',
1505
- items: {
1506
- type: 'object',
1507
- properties: {
1508
- step: { type: 'string' },
1509
- status: { type: 'string' }
1510
- },
1511
- required: ['step', 'status'],
1512
- additionalProperties: false
1513
- }
1514
- }
1515
- },
1516
- required: ['plan'],
1517
- additionalProperties: false
1518
- }
1519
- }
1520
- },
1521
- {
1522
- type: 'function',
1523
- function: {
1524
- name: 'view_image',
1525
- description: 'View a local image by file path.',
1526
- parameters: {
1527
- type: 'object',
1528
- properties: {
1529
- path: { type: 'string' }
1530
- },
1531
- required: ['path'],
1532
- additionalProperties: false
1533
- }
1534
- }
1535
- },
1536
- {
1537
- type: 'function',
1538
- function: {
1539
- name: 'list_mcp_resources',
1540
- description: 'List resources exposed by MCP servers.',
1541
- parameters: {
1542
- type: 'object',
1543
- properties: {
1544
- server: { type: 'string' },
1545
- filter: { type: 'string' },
1546
- root: { type: 'string' }
1547
- },
1548
- additionalProperties: false
1549
- }
1550
- }
1551
- },
1552
- {
1553
- type: 'function',
1554
- function: {
1555
- name: 'list_mcp_resource_templates',
1556
- description: 'List resource templates exposed by MCP servers.',
1557
- parameters: {
1558
- type: 'object',
1559
- properties: {
1560
- server: { type: 'string' },
1561
- cursor: { type: 'string' }
1562
- },
1563
- additionalProperties: false
1564
- }
1565
- }
1566
- },
1567
- {
1568
- type: 'function',
1569
- function: {
1570
- name: 'read_mcp_resource',
1571
- description: 'Read a specific MCP resource by { server, uri }.',
1572
- parameters: {
1573
- type: 'object',
1574
- properties: {
1575
- server: { type: 'string' },
1576
- uri: { type: 'string' }
1577
- },
1578
- required: ['server', 'uri'],
1579
- additionalProperties: false
1580
- }
1581
- }
1582
- }
1583
- ];
1584
- return tools.map((tool) => ({
1585
- op: 'append_tool_if_missing',
1586
- toolName: tool.function.name,
1587
- tool
1588
- }));
1589
- }
1590
- function extractWebSearchSemantics(semantics) {
1591
- if (!semantics || typeof semantics !== 'object') {
1592
- return undefined;
1593
- }
1594
- const extras = semantics.providerExtras;
1595
- if (!extras || !isJsonObject(extras)) {
1596
- return undefined;
1597
- }
1598
- const hint = extras.webSearch;
1599
- if (typeof hint === 'boolean') {
1600
- return hint ? { force: true } : { disable: true };
1601
- }
1602
- if (isJsonObject(hint)) {
1603
- const normalized = {};
1604
- if (hint.force === true) {
1605
- normalized.force = true;
1606
- }
1607
- if (hint.disable === true) {
1608
- normalized.disable = true;
1609
- }
1610
- return Object.keys(normalized).length ? normalized : undefined;
1611
- }
1612
- return undefined;
1613
- }
1614
- function detectWebSearchIntent(request) {
1615
- const messages = Array.isArray(request.messages) ? request.messages : [];
1616
- if (!messages.length) {
1617
- return { hasIntent: false, googlePreferred: false };
1618
- }
1619
- // 仅使用“当前请求最后一条消息且角色为 user”的文本判断 web_search 意图:
1620
- // - 不读取系统提示词(system/developer)。
1621
- // - 不读取历史 user 消息(避免历史上下文误触发)。
1622
- const lastMessage = messages[messages.length - 1];
1623
- if (!lastMessage || lastMessage.role !== 'user') {
1624
- return { hasIntent: false, googlePreferred: false };
1625
- }
1626
- // 支持多模态 content:既可能是纯文本字符串,也可能是带 image_url 的分段数组。
1627
- let content = '';
1628
- if (typeof lastMessage.content === 'string') {
1629
- content = lastMessage.content;
1630
- }
1631
- else if (Array.isArray(lastMessage.content)) {
1632
- const parts = lastMessage.content;
1633
- const texts = [];
1634
- for (const part of parts) {
1635
- if (typeof part === 'string') {
1636
- texts.push(part);
1637
- }
1638
- else if (part && typeof part === 'object') {
1639
- const maybeText = part.text;
1640
- if (typeof maybeText === 'string' && maybeText.trim()) {
1641
- texts.push(maybeText);
1642
- }
1643
- }
1644
- }
1645
- content = texts.join('\n');
1646
- }
1647
- if (!content) {
1648
- return { hasIntent: false, googlePreferred: false };
1649
- }
1650
- // Hard 100% keywords (中文):明确说明“谷歌搜索 / 谷歌一下 / 百度一下”均视为搜索意图。
1651
- // 其中“谷歌搜索 / 谷歌一下”会偏向 Google/Gemini 搜索后端。
1652
- const zh = content;
1653
- const hasGoogleExplicit = zh.includes('谷歌搜索') ||
1654
- zh.includes('谷歌一下');
1655
- const hasBaiduExplicit = zh.includes('百度一下');
1656
- if (hasGoogleExplicit || hasBaiduExplicit) {
1657
- // 谷歌 / 百度关键字都会优先尝试走“谷歌搜索”引擎;
1658
- // 只有在 Virtual Router 未配置任何谷歌相关 engine 时,才回退为普通联网搜索。
1659
- return {
1660
- hasIntent: true,
1661
- googlePreferred: true
1662
- };
1663
- }
1664
- // English intent: simple substring match on lowercased text.
1665
- const text = content.toLowerCase();
1666
- // 1) Direct patterns like "web search" / "internet search" / "/search".
1667
- const englishDirect = [
1668
- 'web search',
1669
- 'web_search',
1670
- 'websearch',
1671
- 'internet search',
1672
- 'search the web',
1673
- 'web-search',
1674
- 'internet-search',
1675
- '/search'
1676
- ];
1677
- if (englishDirect.some((keyword) => text.includes(keyword))) {
1678
- return { hasIntent: true, googlePreferred: text.includes('google') };
1679
- }
1680
- // 2) Verb + noun combinations, similar to the Chinese rule:
1681
- // - verb: search / find / look up / look for / google
1682
- // - noun: web / internet / online / news / information / info / report / reports / article / articles
1683
- const verbTokensEn = ['search', 'find', 'look up', 'look for', 'google'];
1684
- const nounTokensEn = [
1685
- 'web',
1686
- 'internet',
1687
- 'online',
1688
- 'news',
1689
- 'information',
1690
- 'info',
1691
- 'report',
1692
- 'reports',
1693
- 'article',
1694
- 'articles'
1695
- ];
1696
- const hasVerbEn = verbTokensEn.some((token) => text.includes(token));
1697
- const hasNounEn = nounTokensEn.some((token) => text.includes(token));
1698
- if (hasVerbEn && hasNounEn) {
1699
- return { hasIntent: true, googlePreferred: text.includes('google') };
1700
- }
1701
- // 中文规则:
1702
- // 1. 只要文本中包含“上网”,直接命中(例如“帮我上网看看今天的新闻”)。
1703
- // 2. 否则,如果同时包含「搜索/查找/搜」中的任意一个动词 + 「网络/联网/新闻/信息/报道」中的任意一个名词,也判定为联网搜索意图。
1704
- const chineseText = content; // 中文大小写不敏感,这里直接用原文。
1705
- if (chineseText.includes('上网')) {
1706
- return { hasIntent: true, googlePreferred: false };
1707
- }
1708
- const verbTokens = ['搜索', '查找', '搜'];
1709
- const nounTokens = ['网络', '联网', '新闻', '信息', '报道'];
1710
- const hasVerb = verbTokens.some((token) => chineseText.includes(token));
1711
- const hasNoun = nounTokens.some((token) => chineseText.includes(token));
1712
- if (hasVerb && hasNoun) {
1713
- return { hasIntent: true, googlePreferred: false };
1714
- }
1715
- return { hasIntent: false, googlePreferred: false };
1716
- }