@jsonstudio/llms 0.6.1739 → 0.6.1890

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 (107) hide show
  1. package/dist/conversion/compat/actions/deepseek-web-request.d.ts +3 -0
  2. package/dist/conversion/compat/actions/deepseek-web-request.js +350 -0
  3. package/dist/conversion/compat/actions/deepseek-web-response.d.ts +3 -0
  4. package/dist/conversion/compat/actions/deepseek-web-response.js +886 -0
  5. package/dist/conversion/compat/actions/gemini-cli-request.js +3 -1
  6. package/dist/conversion/compat/profiles/chat-deepseek-web.json +18 -0
  7. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +166 -2
  8. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +169 -0
  9. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +6 -0
  10. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +12 -0
  11. package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -0
  12. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +4 -0
  13. package/dist/conversion/hub/pipeline/hub-pipeline.js +365 -144
  14. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +9 -0
  15. package/dist/conversion/hub/policy/policy-engine.d.ts +2 -0
  16. package/dist/conversion/hub/policy/policy-engine.js +8 -0
  17. package/dist/conversion/hub/process/chat-process.js +466 -16
  18. package/dist/conversion/hub/response/provider-response.js +0 -35
  19. package/dist/conversion/responses/responses-openai-bridge.d.ts +2 -0
  20. package/dist/conversion/responses/responses-openai-bridge.js +166 -8
  21. package/dist/conversion/shared/anthropic-message-utils.js +10 -1
  22. package/dist/conversion/shared/protocol-field-allowlists.d.ts +2 -2
  23. package/dist/conversion/shared/protocol-field-allowlists.js +4 -0
  24. package/dist/conversion/shared/tool-governor.js +102 -0
  25. package/dist/guidance/index.js +17 -0
  26. package/dist/router/virtual-router/bootstrap.js +46 -1
  27. package/dist/router/virtual-router/classifier.js +59 -4
  28. package/dist/router/virtual-router/engine/health/index.js +6 -6
  29. package/dist/router/virtual-router/engine/routing-state/store.js +16 -3
  30. package/dist/router/virtual-router/engine-logging.js +62 -24
  31. package/dist/router/virtual-router/engine-selection/route-utils.js +20 -20
  32. package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -2
  33. package/dist/router/virtual-router/engine.d.ts +3 -1
  34. package/dist/router/virtual-router/engine.js +359 -39
  35. package/dist/router/virtual-router/features.js +2 -1
  36. package/dist/router/virtual-router/pre-command-file-resolver.d.ts +2 -0
  37. package/dist/router/virtual-router/pre-command-file-resolver.js +90 -0
  38. package/dist/router/virtual-router/provider-registry.js +3 -1
  39. package/dist/router/virtual-router/routing-instructions.d.ts +15 -1
  40. package/dist/router/virtual-router/routing-instructions.js +110 -151
  41. package/dist/router/virtual-router/routing-pre-command-actions.d.ts +3 -0
  42. package/dist/router/virtual-router/routing-pre-command-actions.js +26 -0
  43. package/dist/router/virtual-router/routing-pre-command-parser.d.ts +2 -0
  44. package/dist/router/virtual-router/routing-pre-command-parser.js +85 -0
  45. package/dist/router/virtual-router/routing-pre-command-state-codec.d.ts +3 -0
  46. package/dist/router/virtual-router/routing-pre-command-state-codec.js +24 -0
  47. package/dist/router/virtual-router/routing-stop-message-actions.d.ts +2 -0
  48. package/dist/router/virtual-router/routing-stop-message-actions.js +96 -0
  49. package/dist/router/virtual-router/routing-stop-message-parser.d.ts +3 -0
  50. package/dist/router/virtual-router/routing-stop-message-parser.js +142 -0
  51. package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +4 -0
  52. package/dist/router/virtual-router/routing-stop-message-state-codec.js +85 -0
  53. package/dist/router/virtual-router/sticky-session-store.js +206 -57
  54. package/dist/router/virtual-router/stop-message-stage-template-files.d.ts +12 -0
  55. package/dist/router/virtual-router/stop-message-stage-template-files.js +67 -0
  56. package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
  57. package/dist/router/virtual-router/stop-message-state-sync.js +5 -0
  58. package/dist/router/virtual-router/token-file-scanner.d.ts +9 -0
  59. package/dist/router/virtual-router/token-file-scanner.js +64 -3
  60. package/dist/router/virtual-router/tool-signals.d.ts +5 -0
  61. package/dist/router/virtual-router/tool-signals.js +42 -3
  62. package/dist/router/virtual-router/types.d.ts +19 -1
  63. package/dist/router/virtual-router/types.js +1 -0
  64. package/dist/servertool/clock/config.d.ts +1 -1
  65. package/dist/servertool/clock/config.js +27 -4
  66. package/dist/servertool/clock/state.js +41 -2
  67. package/dist/servertool/clock/task-store.d.ts +2 -2
  68. package/dist/servertool/clock/task-store.js +1 -1
  69. package/dist/servertool/clock/tasks.d.ts +3 -1
  70. package/dist/servertool/clock/tasks.js +209 -18
  71. package/dist/servertool/clock/types.d.ts +17 -0
  72. package/dist/servertool/continue-execution/log.d.ts +3 -0
  73. package/dist/servertool/continue-execution/log.js +13 -0
  74. package/dist/servertool/engine.js +414 -68
  75. package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +6 -6
  76. package/dist/servertool/handlers/clock-auto.js +54 -71
  77. package/dist/servertool/handlers/clock.js +121 -6
  78. package/dist/servertool/handlers/continue-execution.d.ts +1 -0
  79. package/dist/servertool/handlers/continue-execution.js +91 -0
  80. package/dist/servertool/handlers/followup-request-builder.js +13 -0
  81. package/dist/servertool/handlers/gemini-empty-reply-continue.js +1 -1
  82. package/dist/servertool/handlers/iflow-model-error-retry.js +1 -1
  83. package/dist/servertool/handlers/recursive-detection-guard.js +1 -1
  84. package/dist/servertool/handlers/stop-message-auto.js +386 -257
  85. package/dist/servertool/handlers/stop-message-stage-policy.d.ts +43 -0
  86. package/dist/servertool/handlers/stop-message-stage-policy.js +684 -0
  87. package/dist/servertool/handlers/vision.js +1 -1
  88. package/dist/servertool/log/progress-file.d.ts +14 -0
  89. package/dist/servertool/log/progress-file.js +88 -0
  90. package/dist/servertool/pre-command-hooks.d.ts +17 -0
  91. package/dist/servertool/pre-command-hooks.js +491 -0
  92. package/dist/servertool/registry.d.ts +23 -6
  93. package/dist/servertool/registry.js +66 -1
  94. package/dist/servertool/server-side-tools.d.ts +1 -0
  95. package/dist/servertool/server-side-tools.js +216 -14
  96. package/dist/servertool/stop-gateway-context.d.ts +14 -0
  97. package/dist/servertool/stop-gateway-context.js +167 -0
  98. package/dist/servertool/stop-message-compare-context.d.ts +24 -0
  99. package/dist/servertool/stop-message-compare-context.js +133 -0
  100. package/dist/servertool/types.d.ts +12 -0
  101. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
  102. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +36 -1
  103. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +3 -0
  104. package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +3 -0
  105. package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +118 -1
  106. package/dist/tools/apply-patch/args-normalizer/default-actions.js +1 -1
  107. package/package.json +1 -1
@@ -1,5 +1,36 @@
1
1
  const SERVER_TOOL_HANDLERS = Object.create(null);
2
2
  const AUTO_SERVER_TOOL_HANDLERS = [];
3
+ const DEFAULT_AUTO_HOOK_PRIORITY = 100;
4
+ let autoHookRegistrationOrder = 0;
5
+ function normalizeAutoHookPhase(value) {
6
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
7
+ if (normalized === 'pre' || normalized === 'before') {
8
+ return 'pre';
9
+ }
10
+ if (normalized === 'post' || normalized === 'after') {
11
+ return 'post';
12
+ }
13
+ return 'default';
14
+ }
15
+ function normalizeAutoHookPriority(value) {
16
+ if (typeof value === 'number' && Number.isFinite(value)) {
17
+ return Math.floor(value);
18
+ }
19
+ if (typeof value === 'string' && value.trim()) {
20
+ const parsed = Number.parseInt(value.trim(), 10);
21
+ if (Number.isFinite(parsed)) {
22
+ return Math.floor(parsed);
23
+ }
24
+ }
25
+ return DEFAULT_AUTO_HOOK_PRIORITY;
26
+ }
27
+ function resolveAutoHookPhaseRank(phase) {
28
+ if (phase === 'pre')
29
+ return 0;
30
+ if (phase === 'post')
31
+ return 2;
32
+ return 1;
33
+ }
3
34
  export function registerServerToolHandler(name, handler, options) {
4
35
  if (!name || typeof name !== 'string' || typeof handler !== 'function')
5
36
  return;
@@ -9,6 +40,14 @@ export function registerServerToolHandler(name, handler, options) {
9
40
  const trigger = options?.trigger ?? 'tool_call';
10
41
  const entry = { name: key, trigger, handler };
11
42
  if (trigger === 'auto') {
43
+ const priority = normalizeAutoHookPriority(options?.hook?.priority ?? options?.priority);
44
+ const phase = normalizeAutoHookPhase(options?.hook?.phase ?? options?.phase);
45
+ entry.autoHook = {
46
+ id: key,
47
+ phase,
48
+ priority,
49
+ order: autoHookRegistrationOrder++
50
+ };
12
51
  AUTO_SERVER_TOOL_HANDLERS.push(entry);
13
52
  return;
14
53
  }
@@ -23,5 +62,31 @@ export function getServerToolHandler(name) {
23
62
  return SERVER_TOOL_HANDLERS[key];
24
63
  }
25
64
  export function listAutoServerToolHandlers() {
26
- return [...AUTO_SERVER_TOOL_HANDLERS];
65
+ return [...AUTO_SERVER_TOOL_HANDLERS].sort((left, right) => {
66
+ const leftHook = left.autoHook;
67
+ const rightHook = right.autoHook;
68
+ const phaseRankDiff = resolveAutoHookPhaseRank(leftHook?.phase ?? 'default') -
69
+ resolveAutoHookPhaseRank(rightHook?.phase ?? 'default');
70
+ if (phaseRankDiff !== 0) {
71
+ return phaseRankDiff;
72
+ }
73
+ const priorityDiff = (leftHook?.priority ?? DEFAULT_AUTO_HOOK_PRIORITY) - (rightHook?.priority ?? DEFAULT_AUTO_HOOK_PRIORITY);
74
+ if (priorityDiff !== 0) {
75
+ return priorityDiff;
76
+ }
77
+ const orderDiff = (leftHook?.order ?? 0) - (rightHook?.order ?? 0);
78
+ if (orderDiff !== 0) {
79
+ return orderDiff;
80
+ }
81
+ return left.name.localeCompare(right.name);
82
+ });
83
+ }
84
+ export function listAutoServerToolHooks() {
85
+ return listAutoServerToolHandlers().map((entry) => ({
86
+ id: entry.name,
87
+ phase: entry.autoHook?.phase ?? 'default',
88
+ priority: entry.autoHook?.priority ?? DEFAULT_AUTO_HOOK_PRIORITY,
89
+ order: entry.autoHook?.order ?? 0,
90
+ handler: entry.handler
91
+ }));
27
92
  }
@@ -9,6 +9,7 @@ import './handlers/clock-auto.js';
9
9
  import './handlers/exec-command-guard.js';
10
10
  import './handlers/apply-patch-guard.js';
11
11
  import './handlers/recursive-detection-guard.js';
12
+ import './handlers/continue-execution.js';
12
13
  export declare function runServerSideToolEngine(options: ServerSideToolEngineOptions): Promise<ServerSideToolEngineResult>;
13
14
  export declare function extractToolCalls(chatResponse: JsonObject): ToolCall[];
14
15
  export declare function cloneJson<T>(value: T): T;
@@ -1,4 +1,4 @@
1
- import { getServerToolHandler, listAutoServerToolHandlers } from './registry.js';
1
+ import { getServerToolHandler, listAutoServerToolHooks } from './registry.js';
2
2
  import { ProviderProtocolError } from '../conversion/shared/errors.js';
3
3
  import { executeWebSearchBackendPlan } from './handlers/web-search.js';
4
4
  import { executeVisionBackendPlan } from './handlers/vision.js';
@@ -11,6 +11,77 @@ import './handlers/clock-auto.js';
11
11
  import './handlers/exec-command-guard.js';
12
12
  import './handlers/apply-patch-guard.js';
13
13
  import './handlers/recursive-detection-guard.js';
14
+ import './handlers/continue-execution.js';
15
+ import { runPreCommandHooks } from './pre-command-hooks.js';
16
+ import { readRuntimeMetadata } from '../conversion/shared/runtime-metadata.js';
17
+ function traceAutoHook(options, event) {
18
+ try {
19
+ options.onAutoHookTrace?.(event);
20
+ }
21
+ catch {
22
+ // best-effort trace callback
23
+ }
24
+ }
25
+ const OPTIONAL_PRIMARY_HOOK_ORDER = ['empty_reply_continue', 'stop_message_auto', 'clock_auto'];
26
+ const MANDATORY_HOOK_ORDER = [];
27
+ let fallbackToolCallIdSeq = 0;
28
+ function ensureToolCallId(record) {
29
+ const existing = typeof record.id === 'string' ? String(record.id).trim() : '';
30
+ if (existing) {
31
+ return existing;
32
+ }
33
+ fallbackToolCallIdSeq += 1;
34
+ const generated = `call_servertool_fallback_${Date.now()}_${fallbackToolCallIdSeq}`;
35
+ record.id = generated;
36
+ return generated;
37
+ }
38
+ function buildAutoHookQueues(hooks) {
39
+ const hookById = new Map();
40
+ for (const hook of hooks) {
41
+ if (!hook || typeof hook.id !== 'string') {
42
+ continue;
43
+ }
44
+ hookById.set(hook.id, hook);
45
+ }
46
+ const consumed = new Set();
47
+ const optionalQueue = [];
48
+ for (const hook of hooks) {
49
+ if (hook.phase !== 'pre') {
50
+ continue;
51
+ }
52
+ if (consumed.has(hook.id)) {
53
+ continue;
54
+ }
55
+ optionalQueue.push(hook);
56
+ consumed.add(hook.id);
57
+ }
58
+ for (const id of OPTIONAL_PRIMARY_HOOK_ORDER) {
59
+ const hook = hookById.get(id);
60
+ if (!hook || consumed.has(hook.id)) {
61
+ continue;
62
+ }
63
+ optionalQueue.push(hook);
64
+ consumed.add(hook.id);
65
+ }
66
+ for (const hook of hooks) {
67
+ if (consumed.has(hook.id)) {
68
+ continue;
69
+ }
70
+ optionalQueue.push(hook);
71
+ consumed.add(hook.id);
72
+ }
73
+ const mandatoryQueue = [];
74
+ const mandatorySeen = new Set();
75
+ for (const id of MANDATORY_HOOK_ORDER) {
76
+ const hook = hookById.get(id);
77
+ if (!hook || mandatorySeen.has(hook.id)) {
78
+ continue;
79
+ }
80
+ mandatoryQueue.push(hook);
81
+ mandatorySeen.add(hook.id);
82
+ }
83
+ return { optionalQueue, mandatoryQueue };
84
+ }
14
85
  function extractToolCallsFromMessage(message) {
15
86
  const toolCalls = getArray(message.tool_calls);
16
87
  const out = [];
@@ -18,7 +89,7 @@ function extractToolCallsFromMessage(message) {
18
89
  const tc = asObject(raw);
19
90
  if (!tc)
20
91
  continue;
21
- const id = typeof tc.id === 'string' && String(tc.id).trim() ? String(tc.id).trim() : '';
92
+ const id = ensureToolCallId(tc);
22
93
  const fn = asObject(tc.function) ??
23
94
  asObject(tc.functionCall) ??
24
95
  asObject(tc.function_call);
@@ -44,7 +115,7 @@ function extractToolCallsFromMessage(message) {
44
115
  else if (rawArgs !== undefined && rawArgs !== null) {
45
116
  args = String(rawArgs);
46
117
  }
47
- if (!id || !name)
118
+ if (!name)
48
119
  continue;
49
120
  out.push({ raw: tc, parsed: { id, name, arguments: args } });
50
121
  }
@@ -104,6 +175,45 @@ function replaceJsonObjectInPlace(target, next) {
104
175
  // ignore
105
176
  }
106
177
  }
178
+ function patchToolCallArgumentsById(chatResponse, toolCallId, argumentsText) {
179
+ if (!toolCallId || typeof argumentsText !== 'string') {
180
+ return;
181
+ }
182
+ const choices = getArray(chatResponse.choices);
183
+ for (const choice of choices) {
184
+ const choiceObj = asObject(choice);
185
+ if (!choiceObj)
186
+ continue;
187
+ const message = asObject(choiceObj.message);
188
+ if (!message)
189
+ continue;
190
+ const toolCalls = getArray(message.tool_calls);
191
+ for (const toolCall of toolCalls) {
192
+ const record = asObject(toolCall);
193
+ if (!record)
194
+ continue;
195
+ const id = typeof record.id === 'string' ? String(record.id).trim() : '';
196
+ if (!id || id !== toolCallId) {
197
+ continue;
198
+ }
199
+ const fn = asObject(record.function);
200
+ if (fn) {
201
+ fn.arguments = argumentsText;
202
+ }
203
+ const fnCamel = asObject(record.functionCall);
204
+ if (fnCamel) {
205
+ fnCamel.arguments = argumentsText;
206
+ }
207
+ const fnSnake = asObject(record.function_call);
208
+ if (fnSnake) {
209
+ fnSnake.arguments = argumentsText;
210
+ }
211
+ if (Object.prototype.hasOwnProperty.call(record, 'arguments')) {
212
+ record.arguments = argumentsText;
213
+ }
214
+ }
215
+ }
216
+ }
107
217
  function filterOutExecutedToolCalls(chatResponse, executedIds) {
108
218
  const choices = getArray(chatResponse.choices);
109
219
  for (const choice of choices) {
@@ -157,6 +267,10 @@ export async function runServerSideToolEngine(options) {
157
267
  const executedFlowIds = [];
158
268
  let lastExecution;
159
269
  const attemptedToolCallsByMessage = [];
270
+ const runtimeMetadata = readRuntimeMetadata(options.adapterContext);
271
+ const runtimePreCommandState = runtimeMetadata && typeof runtimeMetadata === 'object'
272
+ ? runtimeMetadata.preCommandState
273
+ : undefined;
160
274
  const choices = getArray(base.choices);
161
275
  for (const choice of choices) {
162
276
  const choiceObj = asObject(choice);
@@ -168,6 +282,23 @@ export async function runServerSideToolEngine(options) {
168
282
  attemptedToolCallsByMessage.push(...extractToolCallsFromMessage(message));
169
283
  }
170
284
  for (const { parsed: toolCall } of attemptedToolCallsByMessage) {
285
+ const preHookResult = runPreCommandHooks({
286
+ requestId: options.requestId,
287
+ entryEndpoint: options.entryEndpoint,
288
+ providerProtocol: options.providerProtocol,
289
+ toolName: toolCall.name,
290
+ toolCallId: toolCall.id,
291
+ toolArguments: toolCall.arguments,
292
+ preCommandState: runtimePreCommandState
293
+ });
294
+ for (const trace of preHookResult.traces) {
295
+ traceAutoHook(options, trace);
296
+ }
297
+ if (preHookResult.changed && preHookResult.toolArguments !== toolCall.arguments) {
298
+ toolCall.arguments = preHookResult.toolArguments;
299
+ patchToolCallArgumentsById(base, toolCall.id, preHookResult.toolArguments);
300
+ patchToolCallArgumentsById(baseForExecution, toolCall.id, preHookResult.toolArguments);
301
+ }
171
302
  const entry = getServerToolHandler(toolCall.name);
172
303
  if (!entry || entry.trigger !== 'tool_call') {
173
304
  continue;
@@ -276,18 +407,89 @@ export async function runServerSideToolEngine(options) {
276
407
  }
277
408
  };
278
409
  }
279
- for (const entry of listAutoServerToolHandlers()) {
280
- const planned = await runHandler(entry.handler, contextBase);
281
- const result = planned ? await materializePlannedResult(planned, options) : null;
410
+ const autoHookExecutionList = listAutoServerToolHooks();
411
+ const { optionalQueue, mandatoryQueue } = buildAutoHookQueues(autoHookExecutionList);
412
+ const optionalResult = await runAutoHookQueue({
413
+ queueName: 'A_optional',
414
+ hooks: optionalQueue,
415
+ options,
416
+ contextBase: contextBase
417
+ });
418
+ if (optionalResult) {
419
+ return {
420
+ mode: 'tool_flow',
421
+ finalChatResponse: optionalResult.chatResponse,
422
+ execution: optionalResult.execution
423
+ };
424
+ }
425
+ const mandatoryResult = await runAutoHookQueue({
426
+ queueName: 'B_mandatory',
427
+ hooks: mandatoryQueue,
428
+ options,
429
+ contextBase: contextBase
430
+ });
431
+ if (mandatoryResult) {
432
+ return {
433
+ mode: 'tool_flow',
434
+ finalChatResponse: mandatoryResult.chatResponse,
435
+ execution: mandatoryResult.execution
436
+ };
437
+ }
438
+ return { mode: 'passthrough', finalChatResponse: base };
439
+ }
440
+ async function runAutoHookQueue(options) {
441
+ const queueTotal = options.hooks.length;
442
+ for (let idx = 0; idx < options.hooks.length; idx += 1) {
443
+ const hook = options.hooks[idx];
444
+ const traceBase = {
445
+ hookId: hook.id,
446
+ phase: hook.phase,
447
+ priority: hook.priority,
448
+ queue: options.queueName,
449
+ queueIndex: idx + 1,
450
+ queueTotal
451
+ };
452
+ let planned = null;
453
+ try {
454
+ planned = await runHandler(hook.handler, options.contextBase);
455
+ }
456
+ catch (error) {
457
+ const message = error instanceof Error ? error.message : String(error ?? 'unknown');
458
+ traceAutoHook(options.options, {
459
+ ...traceBase,
460
+ result: 'error',
461
+ reason: message
462
+ });
463
+ throw error;
464
+ }
465
+ if (!planned) {
466
+ traceAutoHook(options.options, {
467
+ ...traceBase,
468
+ result: 'miss',
469
+ reason: 'predicate_false'
470
+ });
471
+ continue;
472
+ }
473
+ const result = await materializePlannedResult(planned, options.options);
282
474
  if (result) {
283
- return {
284
- mode: 'tool_flow',
285
- finalChatResponse: result.chatResponse,
286
- execution: result.execution
287
- };
475
+ const flowId = result.execution && typeof result.execution.flowId === 'string' && result.execution.flowId.trim()
476
+ ? result.execution.flowId.trim()
477
+ : undefined;
478
+ traceAutoHook(options.options, {
479
+ ...traceBase,
480
+ result: 'match',
481
+ reason: flowId ? 'matched' : 'matched_without_flow',
482
+ ...(flowId ? { flowId } : {})
483
+ });
484
+ return result;
288
485
  }
486
+ traceAutoHook(options.options, {
487
+ ...traceBase,
488
+ result: 'miss',
489
+ reason: 'empty_materialized_result'
490
+ });
289
491
  }
290
- return { mode: 'passthrough', finalChatResponse: base };
492
+ return null;
291
493
  }
292
494
  async function runHandler(handler, ctx) {
293
495
  try {
@@ -350,7 +552,7 @@ export function extractToolCalls(chatResponse) {
350
552
  const tc = asObject(raw);
351
553
  if (!tc)
352
554
  continue;
353
- const id = typeof tc.id === 'string' && tc.id.trim() ? tc.id.trim() : '';
555
+ const id = ensureToolCallId(tc);
354
556
  const fn = asObject(tc.function) ??
355
557
  asObject(tc.functionCall) ??
356
558
  asObject(tc.function_call);
@@ -376,7 +578,7 @@ export function extractToolCalls(chatResponse) {
376
578
  else if (rawArgs !== undefined && rawArgs !== null) {
377
579
  args = String(rawArgs);
378
580
  }
379
- if (!id || !name)
581
+ if (!name)
380
582
  continue;
381
583
  calls.push({ id, name, arguments: args });
382
584
  }
@@ -0,0 +1,14 @@
1
+ import type { AdapterContext } from '../conversion/hub/types/chat-envelope.js';
2
+ export interface StopGatewayContext {
3
+ observed: boolean;
4
+ eligible: boolean;
5
+ source: 'chat' | 'responses' | 'none';
6
+ reason: string;
7
+ choiceIndex?: number;
8
+ hasToolCalls?: boolean;
9
+ }
10
+ export declare function inspectStopGatewaySignal(base: unknown): StopGatewayContext;
11
+ export declare function attachStopGatewayContext(adapterContext: AdapterContext, context: StopGatewayContext): void;
12
+ export declare function readStopGatewayContext(adapterContext: unknown): StopGatewayContext | undefined;
13
+ export declare function resolveStopGatewayContext(base: unknown, adapterContext?: unknown): StopGatewayContext;
14
+ export declare function isStopEligibleForServerTool(base: unknown, adapterContext?: unknown): boolean;
@@ -0,0 +1,167 @@
1
+ import { ensureRuntimeMetadata, readRuntimeMetadata } from '../conversion/shared/runtime-metadata.js';
2
+ export function inspectStopGatewaySignal(base) {
3
+ if (!base || typeof base !== 'object' || Array.isArray(base)) {
4
+ return {
5
+ observed: false,
6
+ eligible: false,
7
+ source: 'none',
8
+ reason: 'invalid_payload'
9
+ };
10
+ }
11
+ const payload = base;
12
+ const choicesRaw = payload.choices;
13
+ if (Array.isArray(choicesRaw) && choicesRaw.length) {
14
+ for (let idx = 0; idx < choicesRaw.length; idx += 1) {
15
+ const choice = choicesRaw[idx];
16
+ if (!choice || typeof choice !== 'object' || Array.isArray(choice)) {
17
+ continue;
18
+ }
19
+ const finishReasonRaw = choice.finish_reason;
20
+ const finishReason = typeof finishReasonRaw === 'string' && finishReasonRaw.trim()
21
+ ? finishReasonRaw.trim().toLowerCase()
22
+ : '';
23
+ if (!finishReason || finishReason === 'tool_calls') {
24
+ continue;
25
+ }
26
+ if (finishReason !== 'stop' && finishReason !== 'length') {
27
+ continue;
28
+ }
29
+ const message = choice.message &&
30
+ typeof choice.message === 'object' &&
31
+ !Array.isArray(choice.message)
32
+ ? choice.message
33
+ : null;
34
+ const toolCalls = message && Array.isArray(message.tool_calls) ? message.tool_calls : [];
35
+ const hasToolCalls = toolCalls.length > 0;
36
+ return {
37
+ observed: true,
38
+ eligible: !hasToolCalls,
39
+ source: 'chat',
40
+ reason: `finish_reason_${finishReason}`,
41
+ choiceIndex: idx,
42
+ hasToolCalls
43
+ };
44
+ }
45
+ return {
46
+ observed: false,
47
+ eligible: false,
48
+ source: 'chat',
49
+ reason: 'no_stop_finish_reason'
50
+ };
51
+ }
52
+ const statusRaw = typeof payload.status === 'string' ? payload.status.trim().toLowerCase() : '';
53
+ if (statusRaw && statusRaw !== 'completed') {
54
+ return {
55
+ observed: false,
56
+ eligible: false,
57
+ source: 'responses',
58
+ reason: `status_${statusRaw}`
59
+ };
60
+ }
61
+ const hasRequiredAction = Boolean(payload.required_action && typeof payload.required_action === 'object');
62
+ const outputRaw = Array.isArray(payload.output) ? payload.output : [];
63
+ if (!statusRaw && outputRaw.length === 0) {
64
+ return {
65
+ observed: false,
66
+ eligible: false,
67
+ source: 'responses',
68
+ reason: 'no_status_or_output'
69
+ };
70
+ }
71
+ if (outputRaw.some((item) => hasToolLikeOutput(item))) {
72
+ return {
73
+ observed: true,
74
+ eligible: false,
75
+ source: 'responses',
76
+ reason: 'responses_tool_like_output'
77
+ };
78
+ }
79
+ if (hasRequiredAction) {
80
+ return {
81
+ observed: true,
82
+ eligible: false,
83
+ source: 'responses',
84
+ reason: 'responses_required_action'
85
+ };
86
+ }
87
+ return {
88
+ observed: true,
89
+ eligible: true,
90
+ source: 'responses',
91
+ reason: statusRaw ? `status_${statusRaw}` : 'responses_output_completed'
92
+ };
93
+ }
94
+ export function attachStopGatewayContext(adapterContext, context) {
95
+ try {
96
+ const rt = ensureRuntimeMetadata(adapterContext);
97
+ rt.stopGatewayContext = {
98
+ observed: context.observed,
99
+ eligible: context.eligible,
100
+ source: context.source,
101
+ reason: context.reason,
102
+ ...(typeof context.choiceIndex === 'number' ? { choiceIndex: context.choiceIndex } : {}),
103
+ ...(typeof context.hasToolCalls === 'boolean' ? { hasToolCalls: context.hasToolCalls } : {})
104
+ };
105
+ }
106
+ catch {
107
+ // ignore metadata write failures
108
+ }
109
+ }
110
+ export function readStopGatewayContext(adapterContext) {
111
+ if (!adapterContext || typeof adapterContext !== 'object' || Array.isArray(adapterContext)) {
112
+ return undefined;
113
+ }
114
+ const rt = readRuntimeMetadata(adapterContext);
115
+ const raw = rt && typeof rt === 'object' ? rt.stopGatewayContext : undefined;
116
+ return normalizeStopGatewayContext(raw);
117
+ }
118
+ export function resolveStopGatewayContext(base, adapterContext) {
119
+ const fromMetadata = readStopGatewayContext(adapterContext);
120
+ if (fromMetadata) {
121
+ return fromMetadata;
122
+ }
123
+ return inspectStopGatewaySignal(base);
124
+ }
125
+ export function isStopEligibleForServerTool(base, adapterContext) {
126
+ return resolveStopGatewayContext(base, adapterContext).eligible;
127
+ }
128
+ function normalizeStopGatewayContext(raw) {
129
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
130
+ return undefined;
131
+ }
132
+ const record = raw;
133
+ if (typeof record.observed !== 'boolean' || typeof record.eligible !== 'boolean') {
134
+ return undefined;
135
+ }
136
+ const sourceRaw = typeof record.source === 'string' ? record.source.trim().toLowerCase() : '';
137
+ const source = sourceRaw === 'chat' || sourceRaw === 'responses' || sourceRaw === 'none'
138
+ ? sourceRaw
139
+ : 'none';
140
+ const reason = typeof record.reason === 'string' && record.reason.trim() ? record.reason.trim() : 'unknown';
141
+ const choiceIndex = typeof record.choiceIndex === 'number' && Number.isFinite(record.choiceIndex)
142
+ ? Math.floor(record.choiceIndex)
143
+ : undefined;
144
+ const hasToolCalls = typeof record.hasToolCalls === 'boolean' ? record.hasToolCalls : undefined;
145
+ return {
146
+ observed: record.observed,
147
+ eligible: record.eligible,
148
+ source,
149
+ reason,
150
+ ...(typeof choiceIndex === 'number' ? { choiceIndex } : {}),
151
+ ...(typeof hasToolCalls === 'boolean' ? { hasToolCalls } : {})
152
+ };
153
+ }
154
+ function hasToolLikeOutput(value) {
155
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
156
+ return false;
157
+ }
158
+ const typeRaw = value.type;
159
+ const type = typeof typeRaw === 'string' ? typeRaw.trim().toLowerCase() : '';
160
+ if (!type) {
161
+ return false;
162
+ }
163
+ return (type === 'tool_call' ||
164
+ type === 'tool_use' ||
165
+ type === 'function_call' ||
166
+ type.includes('tool'));
167
+ }
@@ -0,0 +1,24 @@
1
+ export interface StopMessageCompareContext {
2
+ armed: boolean;
3
+ mode: 'off' | 'on' | 'auto';
4
+ allowModeOnly: boolean;
5
+ textLength: number;
6
+ maxRepeats: number;
7
+ used: number;
8
+ remaining: number;
9
+ active: boolean;
10
+ stopEligible: boolean;
11
+ hasCapturedRequest: boolean;
12
+ compactionRequest: boolean;
13
+ hasSeed: boolean;
14
+ decision: 'trigger' | 'skip';
15
+ reason: string;
16
+ stage?: string;
17
+ bdWorkState?: string;
18
+ observationHash?: string;
19
+ observationStableCount?: number;
20
+ toolSignatureHash?: string;
21
+ }
22
+ export declare function attachStopMessageCompareContext(adapterContext: unknown, context: StopMessageCompareContext): void;
23
+ export declare function readStopMessageCompareContext(adapterContext: unknown): StopMessageCompareContext | undefined;
24
+ export declare function formatStopMessageCompareContext(context: StopMessageCompareContext | undefined): string;