@jsonstudio/llms 0.6.938 → 0.6.1164

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 (131) hide show
  1. package/dist/conversion/hub/operation-table/operation-table-runner.d.ts +18 -0
  2. package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
  3. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
  4. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
  5. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
  6. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
  7. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
  8. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
  9. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
  10. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
  11. package/dist/conversion/hub/ops/operations.d.ts +19 -0
  12. package/dist/conversion/hub/ops/operations.js +126 -0
  13. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
  14. package/dist/conversion/hub/pipeline/hub-pipeline.js +533 -24
  15. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
  16. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +6 -3
  17. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
  18. package/dist/conversion/hub/policy/policy-engine.js +41 -9
  19. package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
  20. package/dist/conversion/hub/policy/protocol-spec.js +73 -23
  21. package/dist/conversion/hub/process/chat-process.js +252 -41
  22. package/dist/conversion/hub/response/provider-response.js +175 -2
  23. package/dist/conversion/hub/response/response-runtime.js +1 -1
  24. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
  25. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
  26. package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
  27. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -436
  28. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
  29. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -894
  30. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
  31. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
  32. package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
  33. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
  34. package/dist/conversion/responses/responses-openai-bridge.js +14 -2
  35. package/dist/conversion/shared/bridge-message-utils.js +2 -8
  36. package/dist/conversion/shared/bridge-policies.js +5 -105
  37. package/dist/conversion/shared/gemini-tool-utils.js +121 -4
  38. package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
  39. package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
  40. package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
  41. package/dist/conversion/shared/snapshot-hooks.js +166 -3
  42. package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
  43. package/dist/conversion/shared/text-markup-normalizer.js +345 -9
  44. package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
  45. package/dist/conversion/shared/thought-signature-validator.js +170 -0
  46. package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
  47. package/dist/conversion/shared/tool-argument-repairer.js +56 -0
  48. package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
  49. package/dist/conversion/shared/tool-call-id-manager.js +231 -0
  50. package/dist/conversion/shared/tool-canonicalizer.js +2 -11
  51. package/dist/router/virtual-router/bootstrap.js +54 -5
  52. package/dist/router/virtual-router/engine-selection.js +132 -42
  53. package/dist/router/virtual-router/engine.d.ts +3 -0
  54. package/dist/router/virtual-router/engine.js +142 -33
  55. package/dist/router/virtual-router/health-weighted.d.ts +25 -0
  56. package/dist/router/virtual-router/health-weighted.js +63 -0
  57. package/dist/router/virtual-router/load-balancer.d.ts +2 -0
  58. package/dist/router/virtual-router/load-balancer.js +45 -16
  59. package/dist/router/virtual-router/routing-instructions.js +17 -1
  60. package/dist/router/virtual-router/sticky-session-store.js +136 -24
  61. package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
  62. package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
  63. package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
  64. package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
  65. package/dist/router/virtual-router/types.d.ts +70 -0
  66. package/dist/servertool/clock/config.d.ts +7 -0
  67. package/dist/servertool/clock/config.js +27 -0
  68. package/dist/servertool/clock/daemon.d.ts +3 -0
  69. package/dist/servertool/clock/daemon.js +79 -0
  70. package/dist/servertool/clock/io.d.ts +2 -0
  71. package/dist/servertool/clock/io.js +13 -0
  72. package/dist/servertool/clock/paths.d.ts +4 -0
  73. package/dist/servertool/clock/paths.js +25 -0
  74. package/dist/servertool/clock/session-store.d.ts +3 -0
  75. package/dist/servertool/clock/session-store.js +56 -0
  76. package/dist/servertool/clock/state.d.ts +5 -0
  77. package/dist/servertool/clock/state.js +62 -0
  78. package/dist/servertool/clock/task-store.d.ts +5 -0
  79. package/dist/servertool/clock/task-store.js +4 -0
  80. package/dist/servertool/clock/tasks.d.ts +17 -0
  81. package/dist/servertool/clock/tasks.js +221 -0
  82. package/dist/servertool/clock/types.d.ts +36 -0
  83. package/dist/servertool/clock/types.js +1 -0
  84. package/dist/servertool/engine.d.ts +2 -0
  85. package/dist/servertool/engine.js +164 -8
  86. package/dist/servertool/followup-shadow.d.ts +16 -0
  87. package/dist/servertool/followup-shadow.js +145 -0
  88. package/dist/servertool/handlers/apply-patch-guard.js +1 -265
  89. package/dist/servertool/handlers/clock-auto.d.ts +1 -0
  90. package/dist/servertool/handlers/clock-auto.js +160 -0
  91. package/dist/servertool/handlers/clock.d.ts +1 -0
  92. package/dist/servertool/handlers/clock.js +197 -0
  93. package/dist/servertool/handlers/exec-command-guard.js +7 -555
  94. package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
  95. package/dist/servertool/handlers/followup-request-builder.js +248 -28
  96. package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
  97. package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
  98. package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
  99. package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
  100. package/dist/servertool/handlers/stop-message-auto.js +47 -175
  101. package/dist/servertool/handlers/vision.d.ts +7 -1
  102. package/dist/servertool/handlers/vision.js +61 -117
  103. package/dist/servertool/handlers/web-search.d.ts +7 -1
  104. package/dist/servertool/handlers/web-search.js +122 -105
  105. package/dist/servertool/reenter-backend.d.ts +23 -0
  106. package/dist/servertool/reenter-backend.js +18 -0
  107. package/dist/servertool/server-side-tools.d.ts +3 -2
  108. package/dist/servertool/server-side-tools.js +64 -10
  109. package/dist/servertool/types.d.ts +92 -3
  110. package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
  111. package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
  112. package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
  113. package/dist/sse/shared/writer.js +24 -7
  114. package/dist/tools/apply-patch/execution-capturer.js +3 -1
  115. package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
  116. package/dist/tools/apply-patch/json/parse-loose.js +139 -0
  117. package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
  118. package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
  119. package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
  120. package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
  121. package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
  122. package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
  123. package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
  124. package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
  125. package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
  126. package/dist/tools/apply-patch/structured/coercion.js +82 -0
  127. package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
  128. package/dist/tools/apply-patch/validation/shared.js +6 -0
  129. package/dist/tools/apply-patch/validator.d.ts +2 -2
  130. package/dist/tools/apply-patch/validator.js +6 -556
  131. package/package.json +1 -1
@@ -2,7 +2,9 @@ import { runChatRequestToolFilters } from '../../shared/tool-filter-pipeline.js'
2
2
  import { ToolGovernanceEngine } from '../tool-governance/index.js';
3
3
  import { ensureApplyPatchSchema } from '../../shared/tool-mapping.js';
4
4
  import { normalizeApplyPatchToolCallsOnRequest } from '../../shared/tool-governor.js';
5
+ import { clearClockSession, normalizeClockConfig, reserveDueTasksForRequest, startClockDaemonIfNeeded } from '../../../servertool/clock/task-store.js';
5
6
  import { isJsonObject } from '../types/json.js';
7
+ import { applyHubOperations } from '../ops/operations.js';
6
8
  const toolGovernanceEngine = new ToolGovernanceEngine();
7
9
  export async function runHubChatProcess(options) {
8
10
  const startTime = Date.now();
@@ -90,28 +92,22 @@ async function applyRequestToolGovernance(request, context) {
90
92
  merged.metadata.hasImageAttachment = true;
91
93
  }
92
94
  if (typeof inboundStreamIntent === 'boolean') {
93
- merged.metadata = {
94
- ...merged.metadata,
95
- inboundStream: inboundStreamIntent
96
- };
95
+ merged = applyHubOperations(merged, buildInboundStreamingOperations(inboundStreamIntent));
97
96
  }
98
97
  if (typeof governed.stream === 'boolean') {
99
- merged.parameters = {
100
- ...merged.parameters,
101
- stream: governed.stream
102
- };
98
+ merged = applyHubOperations(merged, buildOutboundStreamingOperations(governed.stream));
103
99
  }
104
100
  if (governed.tool_choice !== undefined) {
105
- merged.parameters = {
106
- ...merged.parameters,
107
- tool_choice: governed.tool_choice
108
- };
101
+ merged = applyHubOperations(merged, buildToolChoiceOperations(governed.tool_choice));
109
102
  }
110
103
  if (typeof governed.model === 'string' && governed.model.trim()) {
111
104
  merged.model = governed.model.trim();
112
105
  }
113
106
  // Server-side web_search tool injection (config-driven, best-effort).
114
- merged = maybeInjectWebSearchTool(merged, metadata);
107
+ merged = applyHubOperations(merged, buildWebSearchOperations(merged, metadata));
108
+ // Server-side clock tool + scheduled reminders injection (config-driven, best-effort).
109
+ merged = applyHubOperations(merged, buildClockOperations(metadata));
110
+ merged = await maybeInjectClockRemindersAndApplyDirectives(merged, metadata, context.requestId);
115
111
  const { request: sanitized, summary } = toolGovernanceEngine.governRequest(merged, providerProtocol);
116
112
  if (summary.applied) {
117
113
  sanitized.metadata = {
@@ -397,18 +393,49 @@ function isRecord(value) {
397
393
  return !!value && typeof value === 'object' && !Array.isArray(value);
398
394
  }
399
395
  function maybeInjectWebSearchTool(request, metadata) {
396
+ const ops = buildWebSearchOperations(request, metadata);
397
+ if (!ops.length) {
398
+ return request;
399
+ }
400
+ return applyHubOperations(request, ops);
401
+ }
402
+ function buildInboundStreamingOperations(intent) {
403
+ return [
404
+ {
405
+ op: 'set_request_metadata_fields',
406
+ fields: { inboundStream: intent }
407
+ }
408
+ ];
409
+ }
410
+ function buildOutboundStreamingOperations(stream) {
411
+ return [
412
+ {
413
+ op: 'set_request_parameter_fields',
414
+ fields: { stream }
415
+ }
416
+ ];
417
+ }
418
+ function buildToolChoiceOperations(toolChoice) {
419
+ return [
420
+ {
421
+ op: 'set_request_parameter_fields',
422
+ fields: { tool_choice: toolChoice }
423
+ }
424
+ ];
425
+ }
426
+ function buildWebSearchOperations(request, metadata) {
400
427
  // ServerTool 二/三跳(serverToolFollowup=true)不再注入 web_search 工具,
401
428
  // 以避免在 web_search 流程内部形成循环命中。
402
429
  if (metadata.serverToolFollowup === true) {
403
- return request;
430
+ return [];
404
431
  }
405
432
  const rawConfig = metadata.webSearch;
406
433
  if (!rawConfig || !Array.isArray(rawConfig.engines) || rawConfig.engines.length === 0) {
407
- return request;
434
+ return [];
408
435
  }
409
436
  const semanticsWebSearch = extractWebSearchSemantics(request.semantics);
410
437
  if (semanticsWebSearch?.disable === true) {
411
- return request;
438
+ return [];
412
439
  }
413
440
  const injectPolicy = semanticsWebSearch?.force === true
414
441
  ? 'always'
@@ -420,26 +447,10 @@ function maybeInjectWebSearchTool(request, metadata) {
420
447
  // 仅当当前这一轮用户输入明确表达“联网搜索”意图时才注入 web_search。
421
448
  // 不再依赖上一轮工具分类(read/search/websearch),避免形成隐式续写语义。
422
449
  if (!intent.hasIntent) {
423
- return request;
450
+ return [];
424
451
  }
425
452
  }
426
453
  const existingTools = Array.isArray(request.tools) ? request.tools : [];
427
- const hasWebSearch = existingTools.some((tool) => {
428
- if (!tool || typeof tool !== 'object')
429
- return false;
430
- const fn = tool.function;
431
- return typeof fn?.name === 'string' && fn.name.trim() === 'web_search';
432
- });
433
- if (hasWebSearch) {
434
- const nextMetadata = {
435
- ...(request.metadata ?? {}),
436
- webSearchEnabled: true
437
- };
438
- return {
439
- ...request,
440
- metadata: nextMetadata
441
- };
442
- }
443
454
  let engines = rawConfig.engines.filter((engine) => typeof engine?.id === 'string' && !!engine.id.trim() && !engine.serverToolsDisabled);
444
455
  // 当用户明确要求「谷歌搜索」时,只暴露 Gemini / Antigravity 类搜索后端:
445
456
  // - providerKey 以 gemini-cli. 或 antigravity. 开头;
@@ -461,7 +472,7 @@ function maybeInjectWebSearchTool(request, metadata) {
461
472
  }
462
473
  }
463
474
  if (!engines.length) {
464
- return request;
475
+ return [];
465
476
  }
466
477
  const engineIds = engines.map((engine) => engine.id.trim());
467
478
  const engineDescriptions = engines
@@ -511,15 +522,215 @@ function maybeInjectWebSearchTool(request, metadata) {
511
522
  strict: true
512
523
  }
513
524
  };
514
- const nextMetadata = {
515
- ...(request.metadata ?? {}),
516
- webSearchEnabled: true
525
+ const ops = [
526
+ {
527
+ op: 'set_request_metadata_fields',
528
+ fields: { webSearchEnabled: true }
529
+ }
530
+ ];
531
+ ops.push({
532
+ op: 'append_tool_if_missing',
533
+ toolName: 'web_search',
534
+ tool: webSearchTool
535
+ });
536
+ return ops;
537
+ }
538
+ function buildClockOperations(metadata) {
539
+ const rawConfig = metadata.clock;
540
+ const clockConfig = normalizeClockConfig(rawConfig);
541
+ if (!clockConfig) {
542
+ return [];
543
+ }
544
+ const parameters = {
545
+ type: 'object',
546
+ properties: {
547
+ action: {
548
+ type: 'string',
549
+ enum: ['schedule', 'list', 'cancel', 'clear'],
550
+ description: 'Schedule, list, cancel, or clear session-scoped reminders.'
551
+ },
552
+ items: {
553
+ type: 'array',
554
+ description: 'For schedule: list of reminders to add.',
555
+ items: {
556
+ type: 'object',
557
+ properties: {
558
+ dueAt: {
559
+ type: 'string',
560
+ description: 'ISO8601 datetime with timezone (e.g. 2026-01-21T20:30:00-08:00).'
561
+ },
562
+ task: {
563
+ type: 'string',
564
+ description: 'Reminder text (should include which tool to use and what to do).'
565
+ },
566
+ tool: {
567
+ type: 'string',
568
+ description: 'Optional suggested tool name (hint only).'
569
+ },
570
+ arguments: {
571
+ type: 'object',
572
+ description: 'Optional suggested tool arguments (hint only).',
573
+ additionalProperties: true
574
+ }
575
+ },
576
+ required: ['dueAt', 'task'],
577
+ additionalProperties: false
578
+ }
579
+ },
580
+ taskId: {
581
+ type: 'string',
582
+ description: 'For cancel: taskId to remove.'
583
+ }
584
+ },
585
+ required: ['action'],
586
+ additionalProperties: false
517
587
  };
518
- return {
519
- ...request,
520
- metadata: nextMetadata,
521
- tools: [...existingTools, webSearchTool]
588
+ const clockTool = {
589
+ type: 'function',
590
+ function: {
591
+ name: 'clock',
592
+ description: 'Schedule session-scoped reminders. Use schedule/list/cancel/clear. Scheduled reminders will be injected into future requests as [scheduled task:"..."].',
593
+ parameters,
594
+ strict: true
595
+ }
522
596
  };
597
+ return [
598
+ { op: 'set_request_metadata_fields', fields: { clockEnabled: true, serverToolRequired: true } },
599
+ { op: 'append_tool_if_missing', toolName: 'clock', tool: clockTool }
600
+ ];
601
+ }
602
+ function resolveSessionIdForClock(metadata, request) {
603
+ const candidate = readString(metadata.sessionId) ?? readString(request.metadata?.sessionId);
604
+ return candidate && candidate.trim() ? candidate.trim() : null;
605
+ }
606
+ function stripClockClearDirectiveFromText(text) {
607
+ const pattern = /<\*\*\s*clock\s*:\s*clear\s*\*\*>/gi;
608
+ const hadClear = pattern.test(text);
609
+ if (!hadClear) {
610
+ return { hadClear: false, next: text };
611
+ }
612
+ const replaced = text.replace(pattern, '');
613
+ // Clean up leftover excessive blank lines to keep prompts tidy.
614
+ const next = replaced.replace(/\n{3,}/g, '\n\n').trim();
615
+ return { hadClear: true, next };
616
+ }
617
+ function stripClockClearDirectiveFromContent(content) {
618
+ if (typeof content === 'string') {
619
+ const { hadClear, next } = stripClockClearDirectiveFromText(content);
620
+ return { hadClear, next };
621
+ }
622
+ if (Array.isArray(content)) {
623
+ let hadClear = false;
624
+ const next = content.map((part) => {
625
+ if (typeof part === 'string') {
626
+ const stripped = stripClockClearDirectiveFromText(part);
627
+ if (stripped.hadClear)
628
+ hadClear = true;
629
+ return stripped.next;
630
+ }
631
+ if (part && typeof part === 'object' && !Array.isArray(part)) {
632
+ const block = part;
633
+ const text = typeof block.text === 'string' ? block.text : undefined;
634
+ if (!text)
635
+ return part;
636
+ const stripped = stripClockClearDirectiveFromText(text);
637
+ if (stripped.hadClear)
638
+ hadClear = true;
639
+ return { ...block, text: stripped.next };
640
+ }
641
+ return part;
642
+ });
643
+ return { hadClear, next };
644
+ }
645
+ return { hadClear: false, next: content };
646
+ }
647
+ function findLastUserMessageIndex(messages) {
648
+ if (!Array.isArray(messages) || messages.length === 0) {
649
+ return -1;
650
+ }
651
+ for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
652
+ const candidate = messages[idx];
653
+ if (candidate && candidate.role === 'user') {
654
+ return idx;
655
+ }
656
+ }
657
+ return -1;
658
+ }
659
+ async function maybeInjectClockRemindersAndApplyDirectives(request, metadata, requestId) {
660
+ const rawConfig = metadata.clock;
661
+ const clockConfig = normalizeClockConfig(rawConfig);
662
+ if (!clockConfig) {
663
+ return request;
664
+ }
665
+ try {
666
+ await startClockDaemonIfNeeded(clockConfig);
667
+ }
668
+ catch {
669
+ // best-effort
670
+ }
671
+ const sessionId = resolveSessionIdForClock(metadata, request);
672
+ const messages = Array.isArray(request.messages) ? request.messages : [];
673
+ const lastUserIdx = findLastUserMessageIndex(messages);
674
+ // 1) Apply <**clock:clear**> directive (latest user message only).
675
+ let hadClear = false;
676
+ let nextMessages = messages;
677
+ if (lastUserIdx >= 0) {
678
+ const lastUser = messages[lastUserIdx];
679
+ const stripped = stripClockClearDirectiveFromContent(lastUser.content);
680
+ hadClear = stripped.hadClear;
681
+ if (hadClear) {
682
+ nextMessages = messages.slice();
683
+ nextMessages[lastUserIdx] = { ...lastUser, content: stripped.next };
684
+ }
685
+ }
686
+ if (hadClear) {
687
+ if (sessionId) {
688
+ try {
689
+ await clearClockSession(sessionId);
690
+ }
691
+ catch {
692
+ // best-effort: user directive should not crash request
693
+ }
694
+ }
695
+ return { ...request, messages: nextMessages };
696
+ }
697
+ // 2) Inject due reminders as a system message + attach reservation for response-side commit.
698
+ if (!sessionId) {
699
+ return request;
700
+ }
701
+ try {
702
+ const { reservation, injectText } = await reserveDueTasksForRequest({
703
+ reservationId: `${requestId}:clock`,
704
+ sessionId,
705
+ config: clockConfig
706
+ });
707
+ if (!reservation || typeof injectText !== 'string' || !injectText.trim()) {
708
+ return request;
709
+ }
710
+ const baseMetadata = request.metadata && typeof request.metadata === 'object'
711
+ ? request.metadata
712
+ : {
713
+ originalEndpoint: readString(metadata.originalEndpoint) ?? '/v1/chat/completions'
714
+ };
715
+ return {
716
+ ...request,
717
+ messages: [
718
+ ...messages,
719
+ {
720
+ role: 'system',
721
+ content: injectText.trim()
722
+ }
723
+ ],
724
+ metadata: {
725
+ ...baseMetadata,
726
+ __clockReservation: reservation
727
+ }
728
+ };
729
+ }
730
+ catch {
731
+ // best-effort: never break request due to reminder injection failures
732
+ return request;
733
+ }
523
734
  }
524
735
  function extractWebSearchSemantics(semantics) {
525
736
  if (!semantics || typeof semantics !== 'object') {
@@ -15,6 +15,7 @@ import { runRespOutboundStage1ClientRemap } from '../pipeline/stages/resp_outbou
15
15
  import { runRespOutboundStage2SseStream } from '../pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js';
16
16
  import { recordResponsesResponse } from '../../shared/responses-conversation-store.js';
17
17
  import { runServerToolOrchestration } from '../../../servertool/engine.js';
18
+ import { commitClockReservation, normalizeClockConfig } from '../../../servertool/clock/task-store.js';
18
19
  const PROVIDER_RESPONSE_REGISTRY = {
19
20
  'openai-chat': {
20
21
  createFormatAdapter: () => new ChatFormatAdapter(),
@@ -52,6 +53,123 @@ function resolveClientProtocol(entryEndpoint) {
52
53
  return 'anthropic-messages';
53
54
  return 'openai-chat';
54
55
  }
56
+ function isToolSurfaceShadowEnabled() {
57
+ const raw = String(process.env.ROUTECODEX_HUB_TOOL_SURFACE_MODE || '').trim().toLowerCase();
58
+ if (!raw)
59
+ return false;
60
+ if (raw === 'off' || raw === '0' || raw === 'false')
61
+ return false;
62
+ return raw === 'observe' || raw === 'shadow' || raw === 'enforce';
63
+ }
64
+ function isJsonRecord(value) {
65
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
66
+ }
67
+ function coerceClockReservation(value) {
68
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
69
+ return null;
70
+ }
71
+ const rec = value;
72
+ const reservationId = typeof rec.reservationId === 'string' ? rec.reservationId.trim() : '';
73
+ const sessionId = typeof rec.sessionId === 'string' ? rec.sessionId.trim() : '';
74
+ const taskIdsRaw = Array.isArray(rec.taskIds) ? rec.taskIds : [];
75
+ const taskIds = taskIdsRaw
76
+ .filter((t) => typeof t === 'string' && t.trim().length)
77
+ .map((t) => String(t).trim());
78
+ const reservedAtMs = typeof rec.reservedAtMs === 'number' && Number.isFinite(rec.reservedAtMs)
79
+ ? Math.floor(rec.reservedAtMs)
80
+ : Date.now();
81
+ if (!reservationId || !sessionId || taskIds.length === 0) {
82
+ return null;
83
+ }
84
+ return { reservationId, sessionId, taskIds, reservedAtMs };
85
+ }
86
+ async function maybeCommitClockReservationFromContext(context) {
87
+ try {
88
+ const clockConfig = normalizeClockConfig(context.clock);
89
+ if (!clockConfig) {
90
+ return;
91
+ }
92
+ const reservation = coerceClockReservation(context.__clockReservation);
93
+ if (!reservation) {
94
+ return;
95
+ }
96
+ await commitClockReservation(reservation, clockConfig);
97
+ }
98
+ catch {
99
+ // best-effort: never break response conversion due to clock persistence errors
100
+ }
101
+ }
102
+ function detectProviderResponseShape(payload) {
103
+ if (!isJsonRecord(payload))
104
+ return 'unknown';
105
+ const obj = payload;
106
+ if (Array.isArray(obj.choices))
107
+ return 'openai-chat';
108
+ if (Array.isArray(obj.output) || obj.object === 'response')
109
+ return 'openai-responses';
110
+ if (typeof obj.type === 'string' && String(obj.type).toLowerCase() === 'message' && Array.isArray(obj.content)) {
111
+ return 'anthropic-messages';
112
+ }
113
+ if (Array.isArray(obj.candidates))
114
+ return 'gemini-chat';
115
+ return 'unknown';
116
+ }
117
+ function summarizeToolCallsFromProviderResponse(payload) {
118
+ try {
119
+ if (!isJsonRecord(payload))
120
+ return {};
121
+ const obj = payload;
122
+ // openai-chat
123
+ if (Array.isArray(obj.choices)) {
124
+ const msg = obj.choices?.[0]?.message;
125
+ const tcs = Array.isArray(msg?.tool_calls) ? msg.tool_calls : [];
126
+ const names = tcs
127
+ .map((tc) => String(tc?.function?.name || '').trim())
128
+ .filter((s) => s.length)
129
+ .slice(0, 10);
130
+ return { toolCallCount: tcs.length, toolNames: names.length ? names : undefined };
131
+ }
132
+ // openai-responses
133
+ if (Array.isArray(obj.output)) {
134
+ const out = obj.output;
135
+ const fnCalls = out.filter((it) => it && typeof it === 'object' && String(it.type || '').toLowerCase() === 'function_call');
136
+ const names = fnCalls
137
+ .map((it) => String(it?.name || '').trim())
138
+ .filter((s) => s.length)
139
+ .slice(0, 10);
140
+ return { toolCallCount: fnCalls.length, toolNames: names.length ? names : undefined };
141
+ }
142
+ // anthropic-messages
143
+ if (Array.isArray(obj.content)) {
144
+ const blocks = obj.content;
145
+ const uses = blocks.filter((b) => b && typeof b === 'object' && String(b.type || '').toLowerCase() === 'tool_use');
146
+ const names = uses
147
+ .map((b) => String(b?.name || '').trim())
148
+ .filter((s) => s.length)
149
+ .slice(0, 10);
150
+ return { toolCallCount: uses.length, toolNames: names.length ? names : undefined };
151
+ }
152
+ return {};
153
+ }
154
+ catch {
155
+ return {};
156
+ }
157
+ }
158
+ function stripInternalPolicyDebugFields(payload) {
159
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
160
+ return;
161
+ }
162
+ const target = payload;
163
+ // These are internal debug/transport markers that must not leak across hub boundaries.
164
+ // They also break hub policy allowlists (Phase 0/1 observation) and confuse tool-surface detection.
165
+ delete target._transformed;
166
+ delete target._originalFormat;
167
+ delete target._targetFormat;
168
+ delete target.__responses_output_text_meta;
169
+ delete target.__responses_reasoning;
170
+ delete target.__responses_payload_snapshot;
171
+ delete target.__responses_passthrough;
172
+ }
55
173
  function supportsSseProtocol(protocol) {
56
174
  return protocol === 'openai-chat' || protocol === 'openai-responses' || protocol === 'anthropic-messages' || protocol === 'gemini-chat';
57
175
  }
@@ -68,6 +186,16 @@ function extractDisplayModel(context) {
68
186
  }
69
187
  return undefined;
70
188
  }
189
+ function extractClientFacingRequestId(context) {
190
+ const contextAny = context;
191
+ const candidates = [contextAny.clientRequestId, contextAny.groupRequestId, context.requestId];
192
+ for (const candidate of candidates) {
193
+ if (typeof candidate === 'string' && candidate.trim()) {
194
+ return candidate.trim();
195
+ }
196
+ }
197
+ return undefined;
198
+ }
71
199
  function applyModelOverride(payload, model) {
72
200
  if (!model || !payload || typeof payload !== 'object') {
73
201
  return;
@@ -107,6 +235,7 @@ export async function convertProviderResponse(options) {
107
235
  /* logging best-effort */
108
236
  }
109
237
  const displayModel = extractDisplayModel(options.context);
238
+ const clientFacingRequestId = extractClientFacingRequestId(options.context) ?? options.context.requestId;
110
239
  const plan = PROVIDER_RESPONSE_REGISTRY[options.providerProtocol];
111
240
  if (!plan) {
112
241
  throw new Error(`Unknown provider protocol: ${options.providerProtocol}`);
@@ -142,6 +271,27 @@ export async function convertProviderResponse(options) {
142
271
  adapterContext: options.context,
143
272
  stageRecorder: options.stageRecorder
144
273
  });
274
+ stripInternalPolicyDebugFields(formatEnvelope.payload);
275
+ // Phase 2 (shadow): response tool surface mismatch detection (provider inbound).
276
+ // Only records diffs; does not rewrite payload.
277
+ try {
278
+ if (options.stageRecorder && isToolSurfaceShadowEnabled()) {
279
+ const detected = detectProviderResponseShape(formatEnvelope.payload);
280
+ if (detected !== 'unknown' && detected !== options.providerProtocol) {
281
+ const summary = summarizeToolCallsFromProviderResponse(formatEnvelope.payload);
282
+ options.stageRecorder.record('hub_toolsurface.shadow.provider_inbound', {
283
+ kind: 'provider_inbound',
284
+ expectedProtocol: options.providerProtocol,
285
+ detectedProtocol: detected,
286
+ ...(summary.toolCallCount !== undefined ? { toolCallCount: summary.toolCallCount } : {}),
287
+ ...(summary.toolNames ? { toolNames: summary.toolNames } : {})
288
+ });
289
+ }
290
+ }
291
+ }
292
+ catch {
293
+ // never break response conversion
294
+ }
145
295
  // Phase 0/1: observe provider inbound payload violations (best-effort; no rewrites here).
146
296
  try {
147
297
  if (formatEnvelope.payload && typeof formatEnvelope.payload === 'object' && !Array.isArray(formatEnvelope.payload)) {
@@ -183,6 +333,7 @@ export async function convertProviderResponse(options) {
183
333
  requestId: options.context.requestId,
184
334
  entryEndpoint: options.entryEndpoint,
185
335
  providerProtocol: options.providerProtocol,
336
+ stageRecorder: options.stageRecorder,
186
337
  providerInvoker: options.providerInvoker,
187
338
  reenterPipeline: options.reenterPipeline
188
339
  });
@@ -229,11 +380,31 @@ export async function convertProviderResponse(options) {
229
380
  const clientPayload = runRespOutboundStage1ClientRemap({
230
381
  payload: finalizeResult.finalizedPayload,
231
382
  clientProtocol,
232
- requestId: options.context.requestId,
383
+ requestId: clientFacingRequestId,
233
384
  adapterContext: options.context,
234
385
  stageRecorder: options.stageRecorder
235
386
  });
236
387
  applyModelOverride(clientPayload, displayModel);
388
+ stripInternalPolicyDebugFields(clientPayload);
389
+ // Phase 2 (shadow): response tool surface mismatch detection (client outbound).
390
+ try {
391
+ if (options.stageRecorder && isToolSurfaceShadowEnabled()) {
392
+ const detected = detectProviderResponseShape(clientPayload);
393
+ if (detected !== 'unknown' && detected !== clientProtocol) {
394
+ const summary = summarizeToolCallsFromProviderResponse(clientPayload);
395
+ options.stageRecorder.record('hub_toolsurface.shadow.client_outbound', {
396
+ kind: 'client_outbound',
397
+ expectedProtocol: clientProtocol,
398
+ detectedProtocol: detected,
399
+ ...(summary.toolCallCount !== undefined ? { toolCallCount: summary.toolCallCount } : {}),
400
+ ...(summary.toolNames ? { toolNames: summary.toolNames } : {})
401
+ });
402
+ }
403
+ }
404
+ }
405
+ catch {
406
+ // never break response conversion
407
+ }
237
408
  // Phase 0/1: observe client outbound payload violations (best-effort; no rewrites here).
238
409
  try {
239
410
  if (clientPayload && typeof clientPayload === 'object' && !Array.isArray(clientPayload)) {
@@ -252,10 +423,12 @@ export async function convertProviderResponse(options) {
252
423
  const outbound = await runRespOutboundStage2SseStream({
253
424
  clientPayload,
254
425
  clientProtocol,
255
- requestId: options.context.requestId,
426
+ requestId: clientFacingRequestId,
256
427
  wantsStream,
257
428
  stageRecorder: options.stageRecorder
258
429
  });
430
+ // Commit scheduled-task delivery only after a successful client payload/stream is prepared.
431
+ await maybeCommitClockReservationFromContext(options.context);
259
432
  if (outbound.stream) {
260
433
  return { __sse_responses: outbound.stream, format: clientProtocol };
261
434
  }
@@ -189,7 +189,7 @@ export function buildOpenAIChatFromAnthropicMessage(payload, options) {
189
189
  switch (reason) {
190
190
  case 'tool_use': return 'tool_calls';
191
191
  case 'max_tokens': return 'length';
192
- case 'stop_sequence': return 'content_filter';
192
+ case 'stop_sequence': return 'stop';
193
193
  default: return 'stop';
194
194
  }
195
195
  };
@@ -1,8 +1 @@
1
- import type { SemanticMapper } from '../format-adapters/index.js';
2
- import type { AdapterContext, ChatEnvelope } from '../types/chat-envelope.js';
3
- import type { FormatEnvelope } from '../types/format-envelope.js';
4
- export declare class AnthropicSemanticMapper implements SemanticMapper {
5
- private readonly chatMapper;
6
- toChat(format: FormatEnvelope, ctx: AdapterContext): Promise<ChatEnvelope>;
7
- fromChat(chat: ChatEnvelope, ctx: AdapterContext): Promise<FormatEnvelope>;
8
- }
1
+ export { AnthropicSemanticMapper } from '../operation-table/semantic-mappers/anthropic-mapper.js';