@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.
- package/dist/conversion/compat/actions/deepseek-web-request.d.ts +3 -0
- package/dist/conversion/compat/actions/deepseek-web-request.js +350 -0
- package/dist/conversion/compat/actions/deepseek-web-response.d.ts +3 -0
- package/dist/conversion/compat/actions/deepseek-web-response.js +886 -0
- package/dist/conversion/compat/actions/gemini-cli-request.js +3 -1
- package/dist/conversion/compat/profiles/chat-deepseek-web.json +18 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +166 -2
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +169 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +6 -0
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +12 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +4 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +365 -144
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +9 -0
- package/dist/conversion/hub/policy/policy-engine.d.ts +2 -0
- package/dist/conversion/hub/policy/policy-engine.js +8 -0
- package/dist/conversion/hub/process/chat-process.js +466 -16
- package/dist/conversion/hub/response/provider-response.js +0 -35
- package/dist/conversion/responses/responses-openai-bridge.d.ts +2 -0
- package/dist/conversion/responses/responses-openai-bridge.js +166 -8
- package/dist/conversion/shared/anthropic-message-utils.js +10 -1
- package/dist/conversion/shared/protocol-field-allowlists.d.ts +2 -2
- package/dist/conversion/shared/protocol-field-allowlists.js +4 -0
- package/dist/conversion/shared/tool-governor.js +102 -0
- package/dist/guidance/index.js +17 -0
- package/dist/router/virtual-router/bootstrap.js +46 -1
- package/dist/router/virtual-router/classifier.js +59 -4
- package/dist/router/virtual-router/engine/health/index.js +6 -6
- package/dist/router/virtual-router/engine/routing-state/store.js +16 -3
- package/dist/router/virtual-router/engine-logging.js +62 -24
- package/dist/router/virtual-router/engine-selection/route-utils.js +20 -20
- package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -2
- package/dist/router/virtual-router/engine.d.ts +3 -1
- package/dist/router/virtual-router/engine.js +359 -39
- package/dist/router/virtual-router/features.js +2 -1
- package/dist/router/virtual-router/pre-command-file-resolver.d.ts +2 -0
- package/dist/router/virtual-router/pre-command-file-resolver.js +90 -0
- package/dist/router/virtual-router/provider-registry.js +3 -1
- package/dist/router/virtual-router/routing-instructions.d.ts +15 -1
- package/dist/router/virtual-router/routing-instructions.js +110 -151
- package/dist/router/virtual-router/routing-pre-command-actions.d.ts +3 -0
- package/dist/router/virtual-router/routing-pre-command-actions.js +26 -0
- package/dist/router/virtual-router/routing-pre-command-parser.d.ts +2 -0
- package/dist/router/virtual-router/routing-pre-command-parser.js +85 -0
- package/dist/router/virtual-router/routing-pre-command-state-codec.d.ts +3 -0
- package/dist/router/virtual-router/routing-pre-command-state-codec.js +24 -0
- package/dist/router/virtual-router/routing-stop-message-actions.d.ts +2 -0
- package/dist/router/virtual-router/routing-stop-message-actions.js +96 -0
- package/dist/router/virtual-router/routing-stop-message-parser.d.ts +3 -0
- package/dist/router/virtual-router/routing-stop-message-parser.js +142 -0
- package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +4 -0
- package/dist/router/virtual-router/routing-stop-message-state-codec.js +85 -0
- package/dist/router/virtual-router/sticky-session-store.js +206 -57
- package/dist/router/virtual-router/stop-message-stage-template-files.d.ts +12 -0
- package/dist/router/virtual-router/stop-message-stage-template-files.js +67 -0
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
- package/dist/router/virtual-router/stop-message-state-sync.js +5 -0
- package/dist/router/virtual-router/token-file-scanner.d.ts +9 -0
- package/dist/router/virtual-router/token-file-scanner.js +64 -3
- package/dist/router/virtual-router/tool-signals.d.ts +5 -0
- package/dist/router/virtual-router/tool-signals.js +42 -3
- package/dist/router/virtual-router/types.d.ts +19 -1
- package/dist/router/virtual-router/types.js +1 -0
- package/dist/servertool/clock/config.d.ts +1 -1
- package/dist/servertool/clock/config.js +27 -4
- package/dist/servertool/clock/state.js +41 -2
- package/dist/servertool/clock/task-store.d.ts +2 -2
- package/dist/servertool/clock/task-store.js +1 -1
- package/dist/servertool/clock/tasks.d.ts +3 -1
- package/dist/servertool/clock/tasks.js +209 -18
- package/dist/servertool/clock/types.d.ts +17 -0
- package/dist/servertool/continue-execution/log.d.ts +3 -0
- package/dist/servertool/continue-execution/log.js +13 -0
- package/dist/servertool/engine.js +414 -68
- package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +6 -6
- package/dist/servertool/handlers/clock-auto.js +54 -71
- package/dist/servertool/handlers/clock.js +121 -6
- package/dist/servertool/handlers/continue-execution.d.ts +1 -0
- package/dist/servertool/handlers/continue-execution.js +91 -0
- package/dist/servertool/handlers/followup-request-builder.js +13 -0
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +1 -1
- package/dist/servertool/handlers/iflow-model-error-retry.js +1 -1
- package/dist/servertool/handlers/recursive-detection-guard.js +1 -1
- package/dist/servertool/handlers/stop-message-auto.js +386 -257
- package/dist/servertool/handlers/stop-message-stage-policy.d.ts +43 -0
- package/dist/servertool/handlers/stop-message-stage-policy.js +684 -0
- package/dist/servertool/handlers/vision.js +1 -1
- package/dist/servertool/log/progress-file.d.ts +14 -0
- package/dist/servertool/log/progress-file.js +88 -0
- package/dist/servertool/pre-command-hooks.d.ts +17 -0
- package/dist/servertool/pre-command-hooks.js +491 -0
- package/dist/servertool/registry.d.ts +23 -6
- package/dist/servertool/registry.js +66 -1
- package/dist/servertool/server-side-tools.d.ts +1 -0
- package/dist/servertool/server-side-tools.js +216 -14
- package/dist/servertool/stop-gateway-context.d.ts +14 -0
- package/dist/servertool/stop-gateway-context.js +167 -0
- package/dist/servertool/stop-message-compare-context.d.ts +24 -0
- package/dist/servertool/stop-message-compare-context.js +133 -0
- package/dist/servertool/types.d.ts +12 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +36 -1
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +3 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +3 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +118 -1
- package/dist/tools/apply-patch/args-normalizer/default-actions.js +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,12 @@ import { applyHubFollowupPolicyShadow } from './followup-shadow.js';
|
|
|
8
8
|
import { buildServerToolFollowupChatPayloadFromInjection, extractCapturedChatSeed } from './handlers/followup-request-builder.js';
|
|
9
9
|
import { findNextUndeliveredDueAtMs, listClockTasks, resolveClockConfig } from './clock/task-store.js';
|
|
10
10
|
import { savePendingServerToolInjection } from './pending-session.js';
|
|
11
|
+
import { appendServerToolProgressFileEvent } from './log/progress-file.js';
|
|
12
|
+
import { attachStopGatewayContext, inspectStopGatewaySignal } from './stop-gateway-context.js';
|
|
13
|
+
import { formatStopMessageCompareContext, readStopMessageCompareContext } from './stop-message-compare-context.js';
|
|
14
|
+
const STOP_MESSAGE_STAGE_TIMEOUT_MS = 900_000;
|
|
15
|
+
const STOP_MESSAGE_LOOP_WARN_THRESHOLD = 5;
|
|
16
|
+
const STOP_MESSAGE_LOOP_FAIL_THRESHOLD = 10;
|
|
11
17
|
function parseTimeoutMs(raw, fallback) {
|
|
12
18
|
const n = typeof raw === 'string' ? Number(raw.trim()) : typeof raw === 'number' ? raw : NaN;
|
|
13
19
|
if (!Number.isFinite(n) || n <= 0) {
|
|
@@ -63,6 +69,36 @@ function createServerToolTimeoutError(options) {
|
|
|
63
69
|
err.status = 504;
|
|
64
70
|
return err;
|
|
65
71
|
}
|
|
72
|
+
function createStopMessageFetchFailedError(options) {
|
|
73
|
+
const baseMessage = options.reason === 'loop_limit'
|
|
74
|
+
? 'fetch failed: network error (stopMessage loop detected)'
|
|
75
|
+
: 'fetch failed: network error (stopMessage exceeded stage timeout)';
|
|
76
|
+
const err = new ProviderProtocolError(baseMessage, {
|
|
77
|
+
code: 'SERVERTOOL_TIMEOUT',
|
|
78
|
+
category: 'EXTERNAL_ERROR',
|
|
79
|
+
details: {
|
|
80
|
+
requestId: options.requestId,
|
|
81
|
+
reason: options.reason,
|
|
82
|
+
...(typeof options.elapsedMs === 'number' && Number.isFinite(options.elapsedMs)
|
|
83
|
+
? { elapsedMs: Math.max(0, Math.floor(options.elapsedMs)) }
|
|
84
|
+
: {}),
|
|
85
|
+
...(typeof options.repeatCount === 'number' && Number.isFinite(options.repeatCount)
|
|
86
|
+
? { repeatCount: Math.max(0, Math.floor(options.repeatCount)) }
|
|
87
|
+
: {}),
|
|
88
|
+
...(typeof options.timeoutMs === 'number' && Number.isFinite(options.timeoutMs)
|
|
89
|
+
? { timeoutMs: Math.max(0, Math.floor(options.timeoutMs)) }
|
|
90
|
+
: {}),
|
|
91
|
+
...(typeof options.attempt === 'number' && Number.isFinite(options.attempt)
|
|
92
|
+
? { attempt: Math.max(1, Math.floor(options.attempt)) }
|
|
93
|
+
: {}),
|
|
94
|
+
...(typeof options.maxAttempts === 'number' && Number.isFinite(options.maxAttempts)
|
|
95
|
+
? { maxAttempts: Math.max(1, Math.floor(options.maxAttempts)) }
|
|
96
|
+
: {})
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
err.status = 502;
|
|
100
|
+
return err;
|
|
101
|
+
}
|
|
66
102
|
function coerceFollowupPayloadStream(payload, stream) {
|
|
67
103
|
if (!payload || typeof payload !== 'object') {
|
|
68
104
|
return payload;
|
|
@@ -159,49 +195,7 @@ function isEmptyClientResponsePayload(payload) {
|
|
|
159
195
|
return true;
|
|
160
196
|
}
|
|
161
197
|
function isStopFinishReasonWithoutToolCalls(base) {
|
|
162
|
-
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
const payload = base;
|
|
166
|
-
const choicesRaw = payload.choices;
|
|
167
|
-
if (Array.isArray(choicesRaw) && choicesRaw.length) {
|
|
168
|
-
const first = choicesRaw[0];
|
|
169
|
-
if (!first || typeof first !== 'object' || Array.isArray(first)) {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
const finishReasonRaw = first.finish_reason;
|
|
173
|
-
const finishReason = typeof finishReasonRaw === 'string' && finishReasonRaw.trim()
|
|
174
|
-
? finishReasonRaw.trim().toLowerCase()
|
|
175
|
-
: '';
|
|
176
|
-
if (!finishReason || finishReason === 'tool_calls') {
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
if (finishReason !== 'stop' && finishReason !== 'length') {
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
const message = first.message &&
|
|
183
|
-
typeof first.message === 'object' &&
|
|
184
|
-
!Array.isArray(first.message)
|
|
185
|
-
? first.message
|
|
186
|
-
: null;
|
|
187
|
-
if (!message) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
191
|
-
if (toolCalls.length > 0) {
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
// Responses-like: completed without required_action generally counts as stop.
|
|
197
|
-
const statusRaw = typeof payload.status === 'string' ? payload.status.trim().toLowerCase() : '';
|
|
198
|
-
if (statusRaw && statusRaw !== 'completed') {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
if (payload.required_action && typeof payload.required_action === 'object') {
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
return true;
|
|
198
|
+
return inspectStopGatewaySignal(base).eligible;
|
|
205
199
|
}
|
|
206
200
|
async function shouldDisableServerToolTimeoutForClockHold(args) {
|
|
207
201
|
// Only relevant for stop/length responses: clock_auto may hold indefinitely.
|
|
@@ -241,19 +235,183 @@ async function shouldDisableServerToolTimeoutForClockHold(args) {
|
|
|
241
235
|
}
|
|
242
236
|
}
|
|
243
237
|
export async function runServerToolOrchestration(options) {
|
|
238
|
+
const BLUE = '\x1b[38;5;39m';
|
|
244
239
|
const YELLOW = '\x1b[38;5;214m';
|
|
240
|
+
const GOLD = '\x1b[38;5;220m';
|
|
245
241
|
const RESET = '\x1b[0m';
|
|
246
|
-
const
|
|
242
|
+
const resolveToolName = (flowId) => {
|
|
243
|
+
const normalized = flowId.trim();
|
|
244
|
+
if (!normalized)
|
|
245
|
+
return 'unknown';
|
|
246
|
+
const mapping = {
|
|
247
|
+
continue_execution_flow: 'continue_execution',
|
|
248
|
+
stop_message_flow: 'stop_message_auto',
|
|
249
|
+
empty_reply_continue: 'empty_reply_continue',
|
|
250
|
+
apply_patch_guard: 'apply_patch_guard',
|
|
251
|
+
exec_command_guard: 'exec_command_guard',
|
|
252
|
+
iflow_model_error_retry: 'iflow_model_error_retry',
|
|
253
|
+
antigravity_thought_signature_bootstrap: 'antigravity_thought_signature_bootstrap',
|
|
254
|
+
web_search_flow: 'web_search',
|
|
255
|
+
vision_flow: 'vision_auto',
|
|
256
|
+
clock_flow: 'clock',
|
|
257
|
+
clock_hold_flow: 'clock_auto',
|
|
258
|
+
recursive_detection_guard: 'recursive_detection_guard'
|
|
259
|
+
};
|
|
260
|
+
return mapping[normalized] ?? normalized;
|
|
261
|
+
};
|
|
262
|
+
const resolveStage = (step, message) => {
|
|
263
|
+
const normalized = message.trim().toLowerCase();
|
|
264
|
+
if (normalized === 'matched' || step <= 1)
|
|
265
|
+
return 'match';
|
|
266
|
+
if (normalized.startsWith('completed') || step >= 5)
|
|
267
|
+
return 'final';
|
|
268
|
+
return 'followup';
|
|
269
|
+
};
|
|
270
|
+
const normalizeResult = (message) => {
|
|
271
|
+
const normalized = message.trim().toLowerCase();
|
|
272
|
+
if (!normalized)
|
|
273
|
+
return 'unknown';
|
|
274
|
+
const group = /^completed\s*\(([^)]+)\)/.exec(normalized);
|
|
275
|
+
if (group && group[1]) {
|
|
276
|
+
return 'completed_' + group[1].trim().replace(/[^a-z0-9]+/g, '_');
|
|
277
|
+
}
|
|
278
|
+
return normalized.replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'unknown';
|
|
279
|
+
};
|
|
280
|
+
const logStopEntry = (stage, result, extra) => {
|
|
281
|
+
const color = BLUE;
|
|
282
|
+
const viewStage = stage === 'trigger' ? 'match' : 'entry';
|
|
283
|
+
const source = typeof extra?.source === 'string' ? extra.source : 'unknown';
|
|
284
|
+
const reason = typeof extra?.reason === 'string' ? extra.reason : 'unknown';
|
|
285
|
+
const eligible = typeof extra?.eligible === 'boolean' ? String(extra.eligible) : 'unknown';
|
|
286
|
+
const flowId = typeof extra?.flowId === 'string' ? extra.flowId : '';
|
|
287
|
+
const brief = stage === 'entry'
|
|
288
|
+
? `source=${source} reason=${reason} eligible=${eligible}`
|
|
289
|
+
: `result=${result} flow=${flowId || 'none'}`;
|
|
290
|
+
try {
|
|
291
|
+
// eslint-disable-next-line no-console
|
|
292
|
+
console.log(`${color}[servertool][stop_watch] requestId=${options.requestId} stage=${viewStage} ${brief}${RESET}`);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
/* best-effort logging */
|
|
296
|
+
}
|
|
297
|
+
appendServerToolProgressFileEvent({
|
|
298
|
+
requestId: options.requestId,
|
|
299
|
+
flowId: 'stop_message_flow',
|
|
300
|
+
tool: 'stop_message_auto',
|
|
301
|
+
stage,
|
|
302
|
+
result,
|
|
303
|
+
message: result,
|
|
304
|
+
step: stage === 'entry' ? 0 : 2,
|
|
305
|
+
entryEndpoint: options.entryEndpoint,
|
|
306
|
+
providerProtocol: options.providerProtocol
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
const logProgress = (step, _total, message, extra) => {
|
|
310
|
+
const flowId = typeof extra?.flowId === 'string' ? extra.flowId.trim() : '';
|
|
311
|
+
const tool = resolveToolName(flowId);
|
|
312
|
+
const stage = resolveStage(step, message);
|
|
313
|
+
const result = normalizeResult(message);
|
|
314
|
+
const color = flowId === 'continue_execution_flow' ? GOLD : YELLOW;
|
|
247
315
|
try {
|
|
248
316
|
// eslint-disable-next-line no-console
|
|
249
|
-
console.log(`${
|
|
250
|
-
(extra ? ` ${JSON.stringify(extra)}` : '') +
|
|
251
|
-
RESET);
|
|
317
|
+
console.log(`${color}[servertool] requestId=${options.requestId} tool=${tool} stage=${stage} result=${result}${RESET}`);
|
|
252
318
|
}
|
|
253
319
|
catch {
|
|
254
320
|
/* best-effort logging */
|
|
255
321
|
}
|
|
322
|
+
appendServerToolProgressFileEvent({
|
|
323
|
+
requestId: options.requestId,
|
|
324
|
+
flowId: flowId || 'none',
|
|
325
|
+
tool,
|
|
326
|
+
stage,
|
|
327
|
+
result,
|
|
328
|
+
message,
|
|
329
|
+
step,
|
|
330
|
+
entryEndpoint: options.entryEndpoint,
|
|
331
|
+
providerProtocol: options.providerProtocol
|
|
332
|
+
});
|
|
333
|
+
};
|
|
334
|
+
const logAutoHookTrace = (event) => {
|
|
335
|
+
const reasonToken = typeof event.reason === 'string' && event.reason.trim()
|
|
336
|
+
? event.reason.trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '')
|
|
337
|
+
: 'unknown';
|
|
338
|
+
appendServerToolProgressFileEvent({
|
|
339
|
+
requestId: options.requestId,
|
|
340
|
+
flowId: event.flowId || `hook:${event.hookId}`,
|
|
341
|
+
tool: event.hookId,
|
|
342
|
+
stage: 'hook',
|
|
343
|
+
result: `${event.result}_${reasonToken || 'unknown'}`,
|
|
344
|
+
message: `${event.result} (${event.reason}) queue=${event.queue}[${event.queueIndex}/${event.queueTotal}] phase=${event.phase} priority=${event.priority}`,
|
|
345
|
+
step: 2,
|
|
346
|
+
entryEndpoint: options.entryEndpoint,
|
|
347
|
+
providerProtocol: options.providerProtocol
|
|
348
|
+
});
|
|
349
|
+
try {
|
|
350
|
+
options.stageRecorder?.record('servertool.hook', {
|
|
351
|
+
hookId: event.hookId,
|
|
352
|
+
phase: event.phase,
|
|
353
|
+
priority: event.priority,
|
|
354
|
+
result: event.result,
|
|
355
|
+
reason: event.reason,
|
|
356
|
+
queue: event.queue,
|
|
357
|
+
queueIndex: event.queueIndex,
|
|
358
|
+
queueTotal: event.queueTotal,
|
|
359
|
+
...(event.flowId ? { flowId: event.flowId } : {})
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// best-effort only
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
const logStopCompare = (stage, flowId) => {
|
|
367
|
+
const compareContext = readStopMessageCompareContext(options.adapterContext);
|
|
368
|
+
const summary = formatStopMessageCompareContext(compareContext);
|
|
369
|
+
const viewStage = stage === 'trigger' ? 'match' : 'entry';
|
|
370
|
+
const flowToken = flowId && flowId.trim() ? flowId.trim() : 'none';
|
|
371
|
+
try {
|
|
372
|
+
// eslint-disable-next-line no-console
|
|
373
|
+
console.log(`${BLUE}[servertool][stop_compare] requestId=${options.requestId} stage=${viewStage} flow=${flowToken} ${summary}${RESET}`);
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// best-effort logging
|
|
377
|
+
}
|
|
378
|
+
const compareResult = compareContext
|
|
379
|
+
? `${compareContext.decision}_${compareContext.reason.toLowerCase().replace(/[^a-z0-9]+/g, '_') || 'unknown'}`
|
|
380
|
+
: 'unknown_no_context';
|
|
381
|
+
appendServerToolProgressFileEvent({
|
|
382
|
+
requestId: options.requestId,
|
|
383
|
+
flowId: flowToken,
|
|
384
|
+
tool: 'stop_message_auto',
|
|
385
|
+
stage: 'compare',
|
|
386
|
+
result: compareResult,
|
|
387
|
+
message: summary,
|
|
388
|
+
step: stage === 'entry' ? 1 : 3,
|
|
389
|
+
entryEndpoint: options.entryEndpoint,
|
|
390
|
+
providerProtocol: options.providerProtocol
|
|
391
|
+
});
|
|
392
|
+
try {
|
|
393
|
+
options.stageRecorder?.record('servertool.stop_compare', {
|
|
394
|
+
stage: viewStage,
|
|
395
|
+
flowId: flowToken,
|
|
396
|
+
summary,
|
|
397
|
+
...(compareContext ? { compare: compareContext } : {})
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
// best-effort only
|
|
402
|
+
}
|
|
256
403
|
};
|
|
404
|
+
const stopSignal = inspectStopGatewaySignal(options.chat);
|
|
405
|
+
attachStopGatewayContext(options.adapterContext, stopSignal);
|
|
406
|
+
if (stopSignal.observed) {
|
|
407
|
+
logStopEntry('entry', 'observed', {
|
|
408
|
+
reason: stopSignal.reason,
|
|
409
|
+
source: stopSignal.source,
|
|
410
|
+
eligible: stopSignal.eligible,
|
|
411
|
+
...(typeof stopSignal.choiceIndex === 'number' ? { choiceIndex: stopSignal.choiceIndex } : {}),
|
|
412
|
+
...(typeof stopSignal.hasToolCalls === 'boolean' ? { hasToolCalls: stopSignal.hasToolCalls } : {})
|
|
413
|
+
});
|
|
414
|
+
}
|
|
257
415
|
const serverToolTimeoutMs = resolveServerToolTimeoutMs();
|
|
258
416
|
const shouldDisableTimeout = await shouldDisableServerToolTimeoutForClockHold({
|
|
259
417
|
chat: options.chat,
|
|
@@ -269,7 +427,8 @@ export async function runServerToolOrchestration(options) {
|
|
|
269
427
|
requestId: options.requestId,
|
|
270
428
|
providerProtocol: options.providerProtocol,
|
|
271
429
|
providerInvoker: options.providerInvoker,
|
|
272
|
-
reenterPipeline: options.reenterPipeline
|
|
430
|
+
reenterPipeline: options.reenterPipeline,
|
|
431
|
+
onAutoHookTrace: logAutoHookTrace
|
|
273
432
|
};
|
|
274
433
|
const engineResult = await withTimeout(runServerSideToolEngine(engineOptions), effectiveServerToolTimeoutMs, () => createServerToolTimeoutError({
|
|
275
434
|
requestId: options.requestId,
|
|
@@ -277,22 +436,51 @@ export async function runServerToolOrchestration(options) {
|
|
|
277
436
|
timeoutMs: effectiveServerToolTimeoutMs || serverToolTimeoutMs
|
|
278
437
|
}));
|
|
279
438
|
if (engineResult.mode === 'passthrough' || !engineResult.execution) {
|
|
439
|
+
const skipReason = engineResult.mode === 'passthrough' ? 'passthrough' : 'no_execution';
|
|
440
|
+
if (stopSignal.observed) {
|
|
441
|
+
logStopEntry('trigger', `skipped_${skipReason}`, {
|
|
442
|
+
reason: stopSignal.reason,
|
|
443
|
+
source: stopSignal.source,
|
|
444
|
+
eligible: stopSignal.eligible
|
|
445
|
+
});
|
|
446
|
+
logStopCompare('trigger');
|
|
447
|
+
}
|
|
280
448
|
try {
|
|
281
449
|
options.stageRecorder?.record('servertool.match', {
|
|
282
450
|
matched: false,
|
|
283
451
|
mode: engineResult.mode,
|
|
284
|
-
reason:
|
|
452
|
+
reason: skipReason
|
|
285
453
|
});
|
|
286
454
|
}
|
|
287
455
|
catch {
|
|
288
456
|
// best-effort only
|
|
289
457
|
}
|
|
458
|
+
appendServerToolProgressFileEvent({
|
|
459
|
+
requestId: options.requestId,
|
|
460
|
+
flowId: 'none',
|
|
461
|
+
tool: 'none',
|
|
462
|
+
stage: 'match',
|
|
463
|
+
result: 'skipped_' + skipReason,
|
|
464
|
+
message: 'skipped (' + skipReason + ')',
|
|
465
|
+
step: 0,
|
|
466
|
+
entryEndpoint: options.entryEndpoint,
|
|
467
|
+
providerProtocol: options.providerProtocol
|
|
468
|
+
});
|
|
290
469
|
return {
|
|
291
470
|
chat: engineResult.finalChatResponse,
|
|
292
471
|
executed: false
|
|
293
472
|
};
|
|
294
473
|
}
|
|
295
474
|
const flowId = engineResult.execution.flowId ?? 'unknown';
|
|
475
|
+
if (stopSignal.observed) {
|
|
476
|
+
logStopEntry('trigger', flowId === 'stop_message_flow' ? 'activated' : 'non_stop_flow', {
|
|
477
|
+
flowId,
|
|
478
|
+
reason: stopSignal.reason,
|
|
479
|
+
source: stopSignal.source,
|
|
480
|
+
eligible: stopSignal.eligible
|
|
481
|
+
});
|
|
482
|
+
logStopCompare('trigger', flowId);
|
|
483
|
+
}
|
|
296
484
|
try {
|
|
297
485
|
options.stageRecorder?.record('servertool.match', {
|
|
298
486
|
matched: true,
|
|
@@ -337,13 +525,12 @@ export async function runServerToolOrchestration(options) {
|
|
|
337
525
|
};
|
|
338
526
|
}
|
|
339
527
|
const isStopMessageFlow = engineResult.execution.flowId === 'stop_message_flow';
|
|
528
|
+
const isClockHoldFlow = engineResult.execution.flowId === 'clock_hold_flow';
|
|
340
529
|
const isEmptyReplyContinue = engineResult.execution.flowId === 'empty_reply_continue';
|
|
341
530
|
const isApplyPatchGuard = engineResult.execution.flowId === 'apply_patch_guard';
|
|
342
531
|
const isExecCommandGuard = engineResult.execution.flowId === 'exec_command_guard';
|
|
343
|
-
const stopMessageSource = isStopMessageFlow ? getStopMessageSource(options.adapterContext) : undefined;
|
|
344
|
-
const isAutoStopMessage = isStopMessageFlow && stopMessageSource !== 'explicit';
|
|
345
532
|
const isErrorAutoFlow = engineResult.execution.flowId === 'iflow_model_error_retry';
|
|
346
|
-
const applyAutoLimit =
|
|
533
|
+
const applyAutoLimit = isErrorAutoFlow || isEmptyReplyContinue || isApplyPatchGuard || isExecCommandGuard;
|
|
347
534
|
// ServerTool followups must not inherit or inject any routeHint; always route fresh.
|
|
348
535
|
const preserveRouteHint = false;
|
|
349
536
|
const followupPlan = engineResult.execution.followup;
|
|
@@ -382,7 +569,7 @@ export async function runServerToolOrchestration(options) {
|
|
|
382
569
|
flowId: engineResult.execution.flowId
|
|
383
570
|
};
|
|
384
571
|
}
|
|
385
|
-
const loopState = buildServerToolLoopState(options.adapterContext, engineResult.execution.flowId, followupPayloadRaw);
|
|
572
|
+
const loopState = buildServerToolLoopState(options.adapterContext, engineResult.execution.flowId, followupPayloadRaw, engineResult.finalChatResponse);
|
|
386
573
|
if (applyAutoLimit && loopState && typeof loopState.repeatCount === 'number' && loopState.repeatCount >= 3) {
|
|
387
574
|
logProgress(5, totalSteps, 'completed (auto limit hit)', { flowId });
|
|
388
575
|
return {
|
|
@@ -391,6 +578,36 @@ export async function runServerToolOrchestration(options) {
|
|
|
391
578
|
flowId: engineResult.execution.flowId
|
|
392
579
|
};
|
|
393
580
|
}
|
|
581
|
+
let shouldInjectStopLoopWarning = false;
|
|
582
|
+
if (isStopMessageFlow && loopState) {
|
|
583
|
+
const elapsedMs = typeof loopState.startedAtMs === 'number' && Number.isFinite(loopState.startedAtMs)
|
|
584
|
+
? Math.max(0, Date.now() - loopState.startedAtMs)
|
|
585
|
+
: 0;
|
|
586
|
+
if (elapsedMs >= STOP_MESSAGE_STAGE_TIMEOUT_MS) {
|
|
587
|
+
throw createStopMessageFetchFailedError({
|
|
588
|
+
requestId: options.requestId,
|
|
589
|
+
reason: 'stage_timeout',
|
|
590
|
+
elapsedMs,
|
|
591
|
+
timeoutMs: STOP_MESSAGE_STAGE_TIMEOUT_MS
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
const pairRepeatCount = typeof loopState.stopPairRepeatCount === 'number' && Number.isFinite(loopState.stopPairRepeatCount)
|
|
595
|
+
? Math.max(0, Math.floor(loopState.stopPairRepeatCount))
|
|
596
|
+
: 0;
|
|
597
|
+
if (pairRepeatCount >= STOP_MESSAGE_LOOP_FAIL_THRESHOLD) {
|
|
598
|
+
throw createStopMessageFetchFailedError({
|
|
599
|
+
requestId: options.requestId,
|
|
600
|
+
reason: 'loop_limit',
|
|
601
|
+
elapsedMs,
|
|
602
|
+
repeatCount: pairRepeatCount
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
if (pairRepeatCount >= STOP_MESSAGE_LOOP_WARN_THRESHOLD && !loopState.stopPairWarned) {
|
|
606
|
+
loopState.stopPairWarned = true;
|
|
607
|
+
shouldInjectStopLoopWarning = true;
|
|
608
|
+
logProgress(2, totalSteps, 'loop warning armed', { flowId });
|
|
609
|
+
}
|
|
610
|
+
}
|
|
394
611
|
if (isAdapterClientDisconnected(options.adapterContext)) {
|
|
395
612
|
logProgress(5, totalSteps, 'completed (client disconnected)', { flowId });
|
|
396
613
|
return {
|
|
@@ -423,9 +640,9 @@ export async function runServerToolOrchestration(options) {
|
|
|
423
640
|
(typeof options.entryEndpoint === 'string' && options.entryEndpoint.trim().length
|
|
424
641
|
? options.entryEndpoint
|
|
425
642
|
: followupEntryEndpoint);
|
|
426
|
-
// For stateful auto-followups (e.g. stop_message_flow), keep the same providerKey/alias.
|
|
427
|
-
// Otherwise the followup requestId suffix would cause round-robin alias switching.
|
|
428
|
-
if (isStopMessageFlow) {
|
|
643
|
+
// For stateful auto-followups (e.g. stop_message_flow / clock_hold_flow), keep the same providerKey/alias.
|
|
644
|
+
// Otherwise the followup requestId suffix would cause round-robin alias switching and compatibility drift.
|
|
645
|
+
if (isStopMessageFlow || isClockHoldFlow) {
|
|
429
646
|
const providerKeyRaw = options.adapterContext.providerKey;
|
|
430
647
|
const providerKey = typeof providerKeyRaw === 'string' && providerKeyRaw.trim().length ? providerKeyRaw.trim() : '';
|
|
431
648
|
if (providerKey) {
|
|
@@ -436,6 +653,9 @@ export async function runServerToolOrchestration(options) {
|
|
|
436
653
|
const maxAttempts = retryEmptyFollowupOnce ? 2 : 1;
|
|
437
654
|
const followupRequestId = buildFollowupRequestId(options.requestId, engineResult.execution.followup.requestIdSuffix);
|
|
438
655
|
let followupPayload = coerceFollowupPayloadStream(followupPayloadRaw, metadata.stream === true);
|
|
656
|
+
if (shouldInjectStopLoopWarning) {
|
|
657
|
+
appendStopMessageLoopWarning(followupPayload, loopState?.stopPairRepeatCount ?? STOP_MESSAGE_LOOP_WARN_THRESHOLD);
|
|
658
|
+
}
|
|
439
659
|
followupPayload = applyHubFollowupPolicyShadow({
|
|
440
660
|
requestId: followupRequestId,
|
|
441
661
|
entryEndpoint: followupEntryEndpoint,
|
|
@@ -451,20 +671,45 @@ export async function runServerToolOrchestration(options) {
|
|
|
451
671
|
// stop_message_flow 的计数器递增由 handler 在决定触发时处理,engine 不再提前递增。
|
|
452
672
|
const stopMessageReservation = null;
|
|
453
673
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
674
|
+
const elapsedBeforeAttempt = isStopMessageFlow && loopState && typeof loopState.startedAtMs === 'number' && Number.isFinite(loopState.startedAtMs)
|
|
675
|
+
? Math.max(0, Date.now() - loopState.startedAtMs)
|
|
676
|
+
: 0;
|
|
677
|
+
if (isStopMessageFlow && elapsedBeforeAttempt >= STOP_MESSAGE_STAGE_TIMEOUT_MS) {
|
|
678
|
+
throw createStopMessageFetchFailedError({
|
|
679
|
+
requestId: options.requestId,
|
|
680
|
+
reason: 'stage_timeout',
|
|
681
|
+
elapsedMs: elapsedBeforeAttempt,
|
|
682
|
+
timeoutMs: STOP_MESSAGE_STAGE_TIMEOUT_MS,
|
|
683
|
+
attempt,
|
|
684
|
+
maxAttempts
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
const attemptTimeoutMs = isStopMessageFlow && STOP_MESSAGE_STAGE_TIMEOUT_MS > elapsedBeforeAttempt
|
|
688
|
+
? Math.max(1, Math.min(followupTimeoutMs, STOP_MESSAGE_STAGE_TIMEOUT_MS - elapsedBeforeAttempt))
|
|
689
|
+
: followupTimeoutMs;
|
|
454
690
|
try {
|
|
455
691
|
followup = await withTimeout(options.reenterPipeline({
|
|
456
692
|
entryEndpoint: followupEntryEndpoint,
|
|
457
693
|
requestId: followupRequestId,
|
|
458
694
|
body: followupPayload,
|
|
459
695
|
metadata
|
|
460
|
-
}),
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
696
|
+
}), attemptTimeoutMs, () => isStopMessageFlow
|
|
697
|
+
? createStopMessageFetchFailedError({
|
|
698
|
+
requestId: options.requestId,
|
|
699
|
+
reason: 'stage_timeout',
|
|
700
|
+
elapsedMs: elapsedBeforeAttempt,
|
|
701
|
+
timeoutMs: STOP_MESSAGE_STAGE_TIMEOUT_MS,
|
|
702
|
+
attempt,
|
|
703
|
+
maxAttempts
|
|
704
|
+
})
|
|
705
|
+
: createServerToolTimeoutError({
|
|
706
|
+
requestId: options.requestId,
|
|
707
|
+
phase: 'followup',
|
|
708
|
+
timeoutMs: attemptTimeoutMs,
|
|
709
|
+
flowId: engineResult.execution.flowId,
|
|
710
|
+
attempt,
|
|
711
|
+
maxAttempts
|
|
712
|
+
}));
|
|
468
713
|
// Treat empty followup as failure for auto followup flows:
|
|
469
714
|
// - retry once (maxAttempts=2)
|
|
470
715
|
// - if still empty, surface as HTTP error so client can retry.
|
|
@@ -784,7 +1029,7 @@ function resolveRouteHint(adapterContext, flowId) {
|
|
|
784
1029
|
}
|
|
785
1030
|
return routeId;
|
|
786
1031
|
}
|
|
787
|
-
function buildServerToolLoopState(adapterContext, flowId, payload) {
|
|
1032
|
+
function buildServerToolLoopState(adapterContext, flowId, payload, response) {
|
|
788
1033
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
789
1034
|
return null;
|
|
790
1035
|
}
|
|
@@ -800,11 +1045,33 @@ function buildServerToolLoopState(adapterContext, flowId, payload) {
|
|
|
800
1045
|
? Math.max(0, Math.floor(previous.repeatCount))
|
|
801
1046
|
: 0;
|
|
802
1047
|
const repeatCount = sameFlow && samePayload ? prevCount + 1 : 1;
|
|
803
|
-
|
|
1048
|
+
const previousStartedAtMs = sameFlow && previous && typeof previous.startedAtMs === 'number' && Number.isFinite(previous.startedAtMs)
|
|
1049
|
+
? Math.max(0, Math.floor(previous.startedAtMs))
|
|
1050
|
+
: undefined;
|
|
1051
|
+
const startedAtMs = previousStartedAtMs ?? Date.now();
|
|
1052
|
+
const base = {
|
|
804
1053
|
...(flowId ? { flowId } : {}),
|
|
805
1054
|
payloadHash,
|
|
806
|
-
repeatCount
|
|
1055
|
+
repeatCount,
|
|
1056
|
+
startedAtMs
|
|
807
1057
|
};
|
|
1058
|
+
if (flowId === 'stop_message_flow') {
|
|
1059
|
+
const pairHash = hashStopMessageRequestResponsePair(payload, response);
|
|
1060
|
+
if (pairHash) {
|
|
1061
|
+
const previousPairHash = sameFlow && previous && typeof previous.stopPairHash === 'string' ? previous.stopPairHash : undefined;
|
|
1062
|
+
const previousPairCount = sameFlow && previous && typeof previous.stopPairRepeatCount === 'number' && Number.isFinite(previous.stopPairRepeatCount)
|
|
1063
|
+
? Math.max(0, Math.floor(previous.stopPairRepeatCount))
|
|
1064
|
+
: 0;
|
|
1065
|
+
const stopPairRepeatCount = previousPairHash === pairHash ? previousPairCount + 1 : 1;
|
|
1066
|
+
const stopPairWarned = previousPairHash === pairHash && previous && typeof previous.stopPairWarned === 'boolean'
|
|
1067
|
+
? previous.stopPairWarned
|
|
1068
|
+
: false;
|
|
1069
|
+
base.stopPairHash = pairHash;
|
|
1070
|
+
base.stopPairRepeatCount = stopPairRepeatCount;
|
|
1071
|
+
base.stopPairWarned = stopPairWarned;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return base;
|
|
808
1075
|
}
|
|
809
1076
|
function readServerToolLoopState(adapterContext) {
|
|
810
1077
|
if (!adapterContext || typeof adapterContext !== 'object') {
|
|
@@ -821,13 +1088,27 @@ function readServerToolLoopState(adapterContext) {
|
|
|
821
1088
|
const repeatCount = typeof record.repeatCount === 'number' && Number.isFinite(record.repeatCount)
|
|
822
1089
|
? Math.max(0, Math.floor(record.repeatCount))
|
|
823
1090
|
: undefined;
|
|
1091
|
+
const startedAtMs = typeof record.startedAtMs === 'number' && Number.isFinite(record.startedAtMs)
|
|
1092
|
+
? Math.max(0, Math.floor(record.startedAtMs))
|
|
1093
|
+
: undefined;
|
|
1094
|
+
const stopPairHash = typeof record.stopPairHash === 'string' && record.stopPairHash.trim().length
|
|
1095
|
+
? record.stopPairHash.trim()
|
|
1096
|
+
: undefined;
|
|
1097
|
+
const stopPairRepeatCount = typeof record.stopPairRepeatCount === 'number' && Number.isFinite(record.stopPairRepeatCount)
|
|
1098
|
+
? Math.max(0, Math.floor(record.stopPairRepeatCount))
|
|
1099
|
+
: undefined;
|
|
1100
|
+
const stopPairWarned = typeof record.stopPairWarned === 'boolean' ? record.stopPairWarned : undefined;
|
|
824
1101
|
if (!payloadHash) {
|
|
825
1102
|
return null;
|
|
826
1103
|
}
|
|
827
1104
|
return {
|
|
828
1105
|
...(flowId ? { flowId } : {}),
|
|
829
1106
|
payloadHash,
|
|
830
|
-
...(repeatCount !== undefined ? { repeatCount } : {})
|
|
1107
|
+
...(repeatCount !== undefined ? { repeatCount } : {}),
|
|
1108
|
+
...(startedAtMs !== undefined ? { startedAtMs } : {}),
|
|
1109
|
+
...(stopPairHash ? { stopPairHash } : {}),
|
|
1110
|
+
...(stopPairRepeatCount !== undefined ? { stopPairRepeatCount } : {}),
|
|
1111
|
+
...(stopPairWarned !== undefined ? { stopPairWarned } : {})
|
|
831
1112
|
};
|
|
832
1113
|
}
|
|
833
1114
|
function hashPayload(payload) {
|
|
@@ -839,6 +1120,71 @@ function hashPayload(payload) {
|
|
|
839
1120
|
return null;
|
|
840
1121
|
}
|
|
841
1122
|
}
|
|
1123
|
+
function hashStopMessageRequestResponsePair(payload, response) {
|
|
1124
|
+
try {
|
|
1125
|
+
const normalizedPayload = sanitizeLoopHashValue(payload);
|
|
1126
|
+
const normalizedResponse = sanitizeLoopHashValue(response ?? {});
|
|
1127
|
+
const stable = stableStringify({ request: normalizedPayload, response: normalizedResponse });
|
|
1128
|
+
return createHash('sha1').update(stable).digest('hex');
|
|
1129
|
+
}
|
|
1130
|
+
catch {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
function sanitizeLoopHashValue(value) {
|
|
1135
|
+
if (value === null || value === undefined) {
|
|
1136
|
+
return value;
|
|
1137
|
+
}
|
|
1138
|
+
if (Array.isArray(value)) {
|
|
1139
|
+
return value.map((entry) => sanitizeLoopHashValue(entry));
|
|
1140
|
+
}
|
|
1141
|
+
if (typeof value !== 'object') {
|
|
1142
|
+
return value;
|
|
1143
|
+
}
|
|
1144
|
+
const record = value;
|
|
1145
|
+
const normalized = {};
|
|
1146
|
+
const volatileKeys = new Set([
|
|
1147
|
+
'id',
|
|
1148
|
+
'created',
|
|
1149
|
+
'created_at',
|
|
1150
|
+
'timestamp',
|
|
1151
|
+
'request_id',
|
|
1152
|
+
'requestId',
|
|
1153
|
+
'trace_id',
|
|
1154
|
+
'response_id',
|
|
1155
|
+
'system_fingerprint'
|
|
1156
|
+
]);
|
|
1157
|
+
for (const key of Object.keys(record)) {
|
|
1158
|
+
if (volatileKeys.has(key)) {
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
normalized[key] = sanitizeLoopHashValue(record[key]);
|
|
1162
|
+
}
|
|
1163
|
+
return normalized;
|
|
1164
|
+
}
|
|
1165
|
+
function appendStopMessageLoopWarning(payload, repeatCountRaw) {
|
|
1166
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
const messages = Array.isArray(payload.messages)
|
|
1170
|
+
? payload.messages
|
|
1171
|
+
: null;
|
|
1172
|
+
if (!messages) {
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
const repeatCount = Number.isFinite(repeatCountRaw)
|
|
1176
|
+
? Math.max(STOP_MESSAGE_LOOP_WARN_THRESHOLD, Math.floor(repeatCountRaw))
|
|
1177
|
+
: STOP_MESSAGE_LOOP_WARN_THRESHOLD;
|
|
1178
|
+
const warningText = [
|
|
1179
|
+
`检测到 stopMessage 请求/响应参数已连续 ${repeatCount} 轮一致。`,
|
|
1180
|
+
'请立即尝试跳出循环(换路径、换验证方法、或直接给结论)。',
|
|
1181
|
+
`若继续达到 ${STOP_MESSAGE_LOOP_FAIL_THRESHOLD} 轮一致,将返回 fetch failed 网络错误并停止自动续跑。`
|
|
1182
|
+
].join('\n');
|
|
1183
|
+
messages.push({
|
|
1184
|
+
role: 'system',
|
|
1185
|
+
content: warningText
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
842
1188
|
function stableStringify(value) {
|
|
843
1189
|
if (value === null || typeof value !== 'object') {
|
|
844
1190
|
return JSON.stringify(value);
|
|
@@ -57,19 +57,19 @@ function buildClockToolSchema() {
|
|
|
57
57
|
type: 'function',
|
|
58
58
|
function: {
|
|
59
59
|
name: 'clock',
|
|
60
|
-
description: 'Time + Alarm for this session. Use get/schedule/list/cancel/clear. Scheduled reminders will be injected into future requests.',
|
|
60
|
+
description: 'Time + Alarm for this session. Use get/schedule/update/list/cancel/clear. Scheduled reminders will be injected into future requests.',
|
|
61
61
|
strict: true,
|
|
62
62
|
parameters: {
|
|
63
63
|
type: 'object',
|
|
64
64
|
properties: {
|
|
65
65
|
action: {
|
|
66
66
|
type: 'string',
|
|
67
|
-
enum: ['get', 'schedule', 'list', 'cancel', 'clear'],
|
|
68
|
-
description: 'Get current time, or schedule/list/cancel/clear session-scoped reminders.'
|
|
67
|
+
enum: ['get', 'schedule', 'update', 'list', 'cancel', 'clear'],
|
|
68
|
+
description: 'Get current time, or schedule/update/list/cancel/clear session-scoped reminders.'
|
|
69
69
|
},
|
|
70
70
|
items: {
|
|
71
71
|
type: 'array',
|
|
72
|
-
description: 'For schedule: list of reminders
|
|
72
|
+
description: 'For schedule/update: list of reminders (update uses items[0]).',
|
|
73
73
|
items: {
|
|
74
74
|
type: 'object',
|
|
75
75
|
properties: {
|
|
@@ -96,7 +96,7 @@ function buildClockToolSchema() {
|
|
|
96
96
|
},
|
|
97
97
|
taskId: {
|
|
98
98
|
type: 'string',
|
|
99
|
-
description: 'For cancel: taskId to
|
|
99
|
+
description: 'For cancel/update: taskId to target.'
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
required: ['action', 'items', 'taskId'],
|
|
@@ -198,4 +198,4 @@ const handler = async (ctx) => {
|
|
|
198
198
|
})
|
|
199
199
|
};
|
|
200
200
|
};
|
|
201
|
-
registerServerToolHandler(FLOW_ID, handler, { trigger: 'auto' });
|
|
201
|
+
registerServerToolHandler(FLOW_ID, handler, { trigger: 'auto', hook: { phase: 'default', priority: 30 } });
|