@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
package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js
CHANGED
|
@@ -19,6 +19,15 @@ export function runRespOutboundStage1ClientRemap(options) {
|
|
|
19
19
|
requestId: options.requestId,
|
|
20
20
|
...(toolsRaw ? { toolsRaw } : {})
|
|
21
21
|
});
|
|
22
|
+
if (options.payload && typeof options.payload === 'object' && !Array.isArray(options.payload)) {
|
|
23
|
+
const source = options.payload;
|
|
24
|
+
const passthrough = ['metadata', 'temperature', 'top_p', 'prompt_cache_key', 'reasoning'];
|
|
25
|
+
for (const key of passthrough) {
|
|
26
|
+
if (clientPayload[key] === undefined && source[key] !== undefined) {
|
|
27
|
+
clientPayload[key] = jsonClone(source[key]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
22
31
|
}
|
|
23
32
|
// Preserve upstream error envelope when present (used by host status mapping + servertool auto flows).
|
|
24
33
|
try {
|
|
@@ -33,6 +33,7 @@ export declare function recordHubPolicyObservation(options: {
|
|
|
33
33
|
policy?: HubPolicyConfig;
|
|
34
34
|
phase?: PolicyObservation['phase'];
|
|
35
35
|
providerProtocol: string;
|
|
36
|
+
compatibilityProfile?: string;
|
|
36
37
|
payload: JsonObject;
|
|
37
38
|
stageRecorder?: StageRecorder;
|
|
38
39
|
requestId?: string;
|
|
@@ -40,6 +41,7 @@ export declare function recordHubPolicyObservation(options: {
|
|
|
40
41
|
export declare function applyHubProviderOutboundPolicy(options: {
|
|
41
42
|
policy?: HubPolicyConfig;
|
|
42
43
|
providerProtocol: string;
|
|
44
|
+
compatibilityProfile?: string;
|
|
43
45
|
payload: JsonObject;
|
|
44
46
|
stageRecorder?: StageRecorder;
|
|
45
47
|
requestId?: string;
|
|
@@ -207,6 +207,10 @@ export function recordHubPolicyObservation(options) {
|
|
|
207
207
|
if (mode !== 'observe' && mode !== 'enforce') {
|
|
208
208
|
return;
|
|
209
209
|
}
|
|
210
|
+
const compatibilityProfile = typeof options.compatibilityProfile === 'string' ? options.compatibilityProfile.trim().toLowerCase() : '';
|
|
211
|
+
if (compatibilityProfile === 'chat:deepseek-web') {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
210
214
|
if (!shouldSample(effectivePolicy?.sampleRate)) {
|
|
211
215
|
return;
|
|
212
216
|
}
|
|
@@ -236,6 +240,10 @@ export function applyHubProviderOutboundPolicy(options) {
|
|
|
236
240
|
if (mode !== 'enforce') {
|
|
237
241
|
return options.payload;
|
|
238
242
|
}
|
|
243
|
+
const compatibilityProfile = typeof options.compatibilityProfile === 'string' ? options.compatibilityProfile.trim().toLowerCase() : '';
|
|
244
|
+
if (compatibilityProfile === 'chat:deepseek-web') {
|
|
245
|
+
return options.payload;
|
|
246
|
+
}
|
|
239
247
|
const result = applyProviderOutboundPolicy(options.providerProtocol, options.payload);
|
|
240
248
|
if (!result.changed) {
|
|
241
249
|
return options.payload;
|
|
@@ -4,13 +4,23 @@ import { readRuntimeMetadata } from '../../shared/runtime-metadata.js';
|
|
|
4
4
|
import { ensureApplyPatchSchema } from '../../shared/tool-mapping.js';
|
|
5
5
|
import { normalizeApplyPatchToolCallsOnRequest } from '../../shared/tool-governor.js';
|
|
6
6
|
import { buildAnthropicToolAliasMap } from '../../shared/anthropic-message-utils.js';
|
|
7
|
-
import { clearClockSession, resolveClockConfig, reserveDueTasksForRequest, startClockDaemonIfNeeded } from '../../../servertool/clock/task-store.js';
|
|
7
|
+
import { clearClockSession, parseDueAtMs, resolveClockConfig, reserveDueTasksForRequest, scheduleClockTasks, startClockDaemonIfNeeded } from '../../../servertool/clock/task-store.js';
|
|
8
8
|
import { logClock } from '../../../servertool/clock/log.js';
|
|
9
|
+
import { logContinueExecution } from '../../../servertool/continue-execution/log.js';
|
|
9
10
|
import { buildTimeTagLine, getClockTimeSnapshot } from '../../../servertool/clock/ntp.js';
|
|
10
11
|
import { clearPendingServerToolInjection, loadPendingServerToolInjection } from '../../../servertool/pending-session.js';
|
|
12
|
+
import { loadRoutingInstructionStateSync } from '../../../router/virtual-router/sticky-session-store.js';
|
|
11
13
|
import { isJsonObject } from '../types/json.js';
|
|
12
14
|
import { applyHubOperations } from '../ops/operations.js';
|
|
13
15
|
const toolGovernanceEngine = new ToolGovernanceEngine();
|
|
16
|
+
const CONTINUE_EXECUTION_SYSTEM_DIRECTIVE_MARKER = '[routecodex:continue_execution_directive]';
|
|
17
|
+
const CONTINUE_EXECUTION_SYSTEM_DIRECTIVE = [
|
|
18
|
+
CONTINUE_EXECUTION_SYSTEM_DIRECTIVE_MARKER,
|
|
19
|
+
'Execution policy (must follow):',
|
|
20
|
+
'- Do not output status-talk placeholders such as \"继续执行中\" / \"I will continue\" before real work.',
|
|
21
|
+
'- Perform at least one real action first (prefer tool calls), then report concise results when needed.',
|
|
22
|
+
'- If you are about to return finish_reason=stop (or protocol-equivalent end_turn), do not end directly; emit a continue_execution tool call first.'
|
|
23
|
+
].join('\n');
|
|
14
24
|
export async function runHubChatProcess(options) {
|
|
15
25
|
const startTime = Date.now();
|
|
16
26
|
try {
|
|
@@ -144,6 +154,9 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
144
154
|
merged = applyHubOperations(merged, buildWebSearchOperations(merged, metadata));
|
|
145
155
|
// Server-side clock tool + scheduled reminders injection (config-driven, best-effort).
|
|
146
156
|
merged = applyHubOperations(merged, buildClockOperations(metadata));
|
|
157
|
+
// Server-side no-op continue tool injection (always-on, lightweight).
|
|
158
|
+
merged = applyHubOperations(merged, buildContinueExecutionOperations(metadata));
|
|
159
|
+
merged = injectContinueExecutionDirectiveIntoUserMessage(merged, metadata);
|
|
147
160
|
merged = await maybeInjectClockRemindersAndApplyDirectives(merged, metadata, context.requestId);
|
|
148
161
|
const { request: sanitized, summary } = toolGovernanceEngine.governRequest(merged, providerProtocol);
|
|
149
162
|
if (summary.applied) {
|
|
@@ -646,7 +659,8 @@ function buildWebSearchOperations(request, metadata) {
|
|
|
646
659
|
function buildClockOperations(metadata) {
|
|
647
660
|
const rt = readRuntimeMetadata(metadata);
|
|
648
661
|
// Do not inject clock tool into internal servertool followup hops.
|
|
649
|
-
|
|
662
|
+
const allowClockFollowupToolInjection = rt?.clockFollowupInjectTool === true;
|
|
663
|
+
if (rt?.serverToolFollowup === true && !allowClockFollowupToolInjection) {
|
|
650
664
|
return [];
|
|
651
665
|
}
|
|
652
666
|
const rawConfig = rt?.clock;
|
|
@@ -662,12 +676,12 @@ function buildClockOperations(metadata) {
|
|
|
662
676
|
properties: {
|
|
663
677
|
action: {
|
|
664
678
|
type: 'string',
|
|
665
|
-
enum: ['get', 'schedule', 'list', 'cancel', 'clear'],
|
|
666
|
-
description: 'Get current time, or schedule/list/cancel/clear session-scoped reminders.'
|
|
679
|
+
enum: ['get', 'schedule', 'update', 'list', 'cancel', 'clear'],
|
|
680
|
+
description: 'Get current time, or schedule/update/list/cancel/clear session-scoped reminders. For any wait/later requirement, schedule immediately.'
|
|
667
681
|
},
|
|
668
682
|
items: {
|
|
669
683
|
type: 'array',
|
|
670
|
-
description: 'For schedule: list of
|
|
684
|
+
description: 'For schedule/update: list of reminder payloads. update uses items[0] as patch source.',
|
|
671
685
|
items: {
|
|
672
686
|
type: 'object',
|
|
673
687
|
properties: {
|
|
@@ -677,7 +691,7 @@ function buildClockOperations(metadata) {
|
|
|
677
691
|
},
|
|
678
692
|
task: {
|
|
679
693
|
type: 'string',
|
|
680
|
-
description: 'Reminder text
|
|
694
|
+
description: 'Reminder text that states the exact action to execute on wake-up (no vague placeholders).'
|
|
681
695
|
},
|
|
682
696
|
tool: {
|
|
683
697
|
type: 'string',
|
|
@@ -696,7 +710,7 @@ function buildClockOperations(metadata) {
|
|
|
696
710
|
},
|
|
697
711
|
taskId: {
|
|
698
712
|
type: 'string',
|
|
699
|
-
description: 'For cancel: taskId
|
|
713
|
+
description: 'For cancel/update: target taskId.'
|
|
700
714
|
}
|
|
701
715
|
},
|
|
702
716
|
// For strict tool schemas (OpenAI Responses), required must include every key in properties.
|
|
@@ -708,7 +722,7 @@ function buildClockOperations(metadata) {
|
|
|
708
722
|
type: 'function',
|
|
709
723
|
function: {
|
|
710
724
|
name: 'clock',
|
|
711
|
-
description: 'Time + Alarm for this session. Use get/schedule/list/cancel/clear. Scheduled reminders
|
|
725
|
+
description: 'Time + Alarm for this session. MUST use clock.schedule whenever waiting/delay is required. Never promise to wait without scheduling. Use get/schedule/update/list/cancel/clear. Scheduled reminders are injected into future requests.',
|
|
712
726
|
parameters,
|
|
713
727
|
strict: true
|
|
714
728
|
}
|
|
@@ -721,6 +735,175 @@ function buildClockOperations(metadata) {
|
|
|
721
735
|
{ op: 'append_tool_if_missing', toolName: 'clock', tool: clockTool }
|
|
722
736
|
];
|
|
723
737
|
}
|
|
738
|
+
function hasActiveStopMessageStateForContinueExecution(metadata) {
|
|
739
|
+
const rt = readRuntimeMetadata(metadata);
|
|
740
|
+
if (isStopMessageStateActive(rt?.stopMessageState)) {
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
return hasPersistedActiveStopMessageState(metadata);
|
|
744
|
+
}
|
|
745
|
+
function hasPersistedActiveStopMessageState(metadata) {
|
|
746
|
+
const sessionScope = resolveStopMessageSessionScope(metadata);
|
|
747
|
+
if (!sessionScope) {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
try {
|
|
751
|
+
const persisted = loadRoutingInstructionStateSync(sessionScope);
|
|
752
|
+
return isStopMessageStateActive(persisted);
|
|
753
|
+
}
|
|
754
|
+
catch {
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function resolveStopMessageSessionScope(metadata) {
|
|
759
|
+
const sessionId = readString(metadata.sessionId);
|
|
760
|
+
if (sessionId) {
|
|
761
|
+
return 'session:' + sessionId;
|
|
762
|
+
}
|
|
763
|
+
const conversationId = readString(metadata.conversationId);
|
|
764
|
+
if (conversationId) {
|
|
765
|
+
return 'conversation:' + conversationId;
|
|
766
|
+
}
|
|
767
|
+
return undefined;
|
|
768
|
+
}
|
|
769
|
+
function isStopMessageStateActive(raw) {
|
|
770
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
const record = raw;
|
|
774
|
+
const text = typeof record.stopMessageText === 'string' ? record.stopMessageText.trim() : '';
|
|
775
|
+
const maxRepeats = typeof record.stopMessageMaxRepeats === 'number' && Number.isFinite(record.stopMessageMaxRepeats)
|
|
776
|
+
? Math.max(1, Math.floor(record.stopMessageMaxRepeats))
|
|
777
|
+
: 0;
|
|
778
|
+
const stageMode = typeof record.stopMessageStageMode === 'string' ? record.stopMessageStageMode.trim().toLowerCase() : '';
|
|
779
|
+
if (stageMode === 'off') {
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
if (text && maxRepeats > 0) {
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
return stageMode === 'on' || stageMode === 'auto';
|
|
786
|
+
}
|
|
787
|
+
function buildContinueExecutionOperations(metadata) {
|
|
788
|
+
const rt = readRuntimeMetadata(metadata);
|
|
789
|
+
if (rt?.serverToolFollowup === true) {
|
|
790
|
+
return [];
|
|
791
|
+
}
|
|
792
|
+
const sessionId = readString(metadata.sessionId);
|
|
793
|
+
const hasSessionId = Boolean(sessionId && sessionId.trim());
|
|
794
|
+
if (hasActiveStopMessageStateForContinueExecution(metadata)) {
|
|
795
|
+
logContinueExecution('skip_schema_due_stopmessage', { hasSessionId });
|
|
796
|
+
return [];
|
|
797
|
+
}
|
|
798
|
+
logContinueExecution('inject_schema', { hasSessionId });
|
|
799
|
+
const parameters = {
|
|
800
|
+
type: 'object',
|
|
801
|
+
properties: {},
|
|
802
|
+
required: [],
|
|
803
|
+
additionalProperties: false
|
|
804
|
+
};
|
|
805
|
+
const continueTool = {
|
|
806
|
+
type: 'function',
|
|
807
|
+
function: {
|
|
808
|
+
name: 'continue_execution',
|
|
809
|
+
description: 'No-op control tool. Use this before status summaries or before ending the turn. If you are about to send finish_reason=stop (or protocol-equivalent end_turn), do not end directly; emit a continue_execution tool call instead. Keep it lightweight. The server will auto-followup so execution continues, avoiding interruption and improving execution efficiency.',
|
|
810
|
+
parameters,
|
|
811
|
+
strict: true
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
return [
|
|
815
|
+
{
|
|
816
|
+
op: 'set_request_metadata_fields',
|
|
817
|
+
fields: { continueExecutionEnabled: true }
|
|
818
|
+
},
|
|
819
|
+
{ op: 'append_tool_if_missing', toolName: 'continue_execution', tool: continueTool }
|
|
820
|
+
];
|
|
821
|
+
}
|
|
822
|
+
function injectContinueExecutionDirectiveIntoUserMessage(request, metadata) {
|
|
823
|
+
const rt = readRuntimeMetadata(metadata);
|
|
824
|
+
if (rt?.serverToolFollowup === true) {
|
|
825
|
+
return request;
|
|
826
|
+
}
|
|
827
|
+
if (hasActiveStopMessageStateForContinueExecution(metadata)) {
|
|
828
|
+
const sessionId = readString(metadata.sessionId);
|
|
829
|
+
const hasSessionId = Boolean(sessionId && sessionId.trim());
|
|
830
|
+
logContinueExecution('skip_user_directive_due_stopmessage', { hasSessionId });
|
|
831
|
+
return request;
|
|
832
|
+
}
|
|
833
|
+
const messages = Array.isArray(request.messages) ? request.messages : [];
|
|
834
|
+
if (messages.length === 0) {
|
|
835
|
+
return request;
|
|
836
|
+
}
|
|
837
|
+
const hasDirective = messages.some((message) => {
|
|
838
|
+
if (!message || typeof message !== 'object' || message.role !== 'user') {
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
return messageContentContainsDirectiveMarker(message.content);
|
|
842
|
+
});
|
|
843
|
+
if (hasDirective) {
|
|
844
|
+
return request;
|
|
845
|
+
}
|
|
846
|
+
const lastUserIdx = findLastUserMessageIndex(messages);
|
|
847
|
+
if (lastUserIdx < 0) {
|
|
848
|
+
return request;
|
|
849
|
+
}
|
|
850
|
+
const nextMessages = messages.slice();
|
|
851
|
+
const target = nextMessages[lastUserIdx];
|
|
852
|
+
const nextContent = appendDirectiveToUserContent(target.content, CONTINUE_EXECUTION_SYSTEM_DIRECTIVE);
|
|
853
|
+
nextMessages[lastUserIdx] = {
|
|
854
|
+
...target,
|
|
855
|
+
content: nextContent
|
|
856
|
+
};
|
|
857
|
+
const sessionId = readString(metadata.sessionId);
|
|
858
|
+
const hasSessionId = Boolean(sessionId && sessionId.trim());
|
|
859
|
+
logContinueExecution('inject_user_directive', { hasSessionId });
|
|
860
|
+
return {
|
|
861
|
+
...request,
|
|
862
|
+
messages: nextMessages
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
function messageContentContainsDirectiveMarker(content) {
|
|
866
|
+
if (typeof content === 'string') {
|
|
867
|
+
return content.includes(CONTINUE_EXECUTION_SYSTEM_DIRECTIVE_MARKER);
|
|
868
|
+
}
|
|
869
|
+
if (!Array.isArray(content)) {
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
for (const part of content) {
|
|
873
|
+
if (!part || typeof part !== 'object') {
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
const text = typeof part.text === 'string' ? String(part.text) : '';
|
|
877
|
+
if (text.includes(CONTINUE_EXECUTION_SYSTEM_DIRECTIVE_MARKER)) {
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
function appendDirectiveToUserContent(content, directive) {
|
|
884
|
+
if (typeof content === 'string') {
|
|
885
|
+
const base = content.trimEnd();
|
|
886
|
+
return base.length ? `${base}\n\n${directive}` : directive;
|
|
887
|
+
}
|
|
888
|
+
if (!Array.isArray(content)) {
|
|
889
|
+
return directive;
|
|
890
|
+
}
|
|
891
|
+
const next = content.slice();
|
|
892
|
+
for (let idx = next.length - 1; idx >= 0; idx -= 1) {
|
|
893
|
+
const part = next[idx];
|
|
894
|
+
if (!part || typeof part !== 'object' || typeof part.text !== 'string') {
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
const base = String(part.text).trimEnd();
|
|
898
|
+
next[idx] = {
|
|
899
|
+
...part,
|
|
900
|
+
text: base.length ? `${base}\n\n${directive}` : directive
|
|
901
|
+
};
|
|
902
|
+
return next;
|
|
903
|
+
}
|
|
904
|
+
next.push({ type: 'input_text', text: directive });
|
|
905
|
+
return next;
|
|
906
|
+
}
|
|
724
907
|
function resolveSessionIdForClock(metadata, request) {
|
|
725
908
|
const candidate = readString(metadata.sessionId) ?? readString(request.metadata?.sessionId);
|
|
726
909
|
return candidate && candidate.trim() ? candidate.trim() : null;
|
|
@@ -736,6 +919,219 @@ function stripClockClearDirectiveFromText(text) {
|
|
|
736
919
|
const next = replaced.replace(/\n{3,}/g, '\n\n').trim();
|
|
737
920
|
return { hadClear: true, next };
|
|
738
921
|
}
|
|
922
|
+
function unquoteMarkerToken(value) {
|
|
923
|
+
const trimmed = String(value || '').trim();
|
|
924
|
+
if (!trimmed) {
|
|
925
|
+
return '';
|
|
926
|
+
}
|
|
927
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
928
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
929
|
+
return trimmed.slice(1, -1).trim();
|
|
930
|
+
}
|
|
931
|
+
return trimmed;
|
|
932
|
+
}
|
|
933
|
+
function parseMarkerRecurrenceKind(raw) {
|
|
934
|
+
const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
935
|
+
if (!value) {
|
|
936
|
+
return undefined;
|
|
937
|
+
}
|
|
938
|
+
if (value === 'daily' || value === 'day') {
|
|
939
|
+
return 'daily';
|
|
940
|
+
}
|
|
941
|
+
if (value === 'weekly' || value === 'week') {
|
|
942
|
+
return 'weekly';
|
|
943
|
+
}
|
|
944
|
+
if (value === 'interval' || value === 'every_minutes' || value === 'every-minutes' || value === 'everyminutes') {
|
|
945
|
+
return 'interval';
|
|
946
|
+
}
|
|
947
|
+
return undefined;
|
|
948
|
+
}
|
|
949
|
+
function parseMarkerRecurrence(record) {
|
|
950
|
+
const recurrenceRaw = record.recurrence ?? record.repeat ?? record.cycle;
|
|
951
|
+
if (recurrenceRaw === undefined || recurrenceRaw === null || recurrenceRaw === false) {
|
|
952
|
+
return undefined;
|
|
953
|
+
}
|
|
954
|
+
let kind;
|
|
955
|
+
let everyMinutesRaw;
|
|
956
|
+
let maxRunsRaw;
|
|
957
|
+
if (typeof recurrenceRaw === 'string') {
|
|
958
|
+
kind = parseMarkerRecurrenceKind(recurrenceRaw);
|
|
959
|
+
everyMinutesRaw = record.everyMinutes;
|
|
960
|
+
maxRunsRaw = record.maxRuns;
|
|
961
|
+
}
|
|
962
|
+
else if (recurrenceRaw && typeof recurrenceRaw === 'object' && !Array.isArray(recurrenceRaw)) {
|
|
963
|
+
const recurrenceRecord = recurrenceRaw;
|
|
964
|
+
kind = parseMarkerRecurrenceKind(recurrenceRecord.kind ?? recurrenceRecord.type ?? recurrenceRecord.mode ?? recurrenceRecord.every);
|
|
965
|
+
everyMinutesRaw = recurrenceRecord.everyMinutes ?? recurrenceRecord.minutes ?? record.everyMinutes;
|
|
966
|
+
maxRunsRaw = recurrenceRecord.maxRuns ?? record.maxRuns;
|
|
967
|
+
}
|
|
968
|
+
if (!kind) {
|
|
969
|
+
return undefined;
|
|
970
|
+
}
|
|
971
|
+
const maxRuns = Number(maxRunsRaw);
|
|
972
|
+
if (!Number.isFinite(maxRuns) || Math.floor(maxRuns) <= 0) {
|
|
973
|
+
return undefined;
|
|
974
|
+
}
|
|
975
|
+
if (kind === 'interval') {
|
|
976
|
+
const everyMinutes = Number(everyMinutesRaw);
|
|
977
|
+
if (!Number.isFinite(everyMinutes) || Math.floor(everyMinutes) <= 0) {
|
|
978
|
+
return undefined;
|
|
979
|
+
}
|
|
980
|
+
return { kind: 'interval', maxRuns: Math.floor(maxRuns), everyMinutes: Math.floor(everyMinutes) };
|
|
981
|
+
}
|
|
982
|
+
return { kind, maxRuns: Math.floor(maxRuns) };
|
|
983
|
+
}
|
|
984
|
+
function parseClockScheduleDirectivePayload(payload) {
|
|
985
|
+
const raw = String(payload || '').trim();
|
|
986
|
+
if (!raw || /^clear$/i.test(raw)) {
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
const parseFromRecord = (record) => {
|
|
990
|
+
const timeRaw = typeof record.time === 'string' ? record.time :
|
|
991
|
+
typeof record.dueAt === 'string' ? record.dueAt :
|
|
992
|
+
typeof record.due_at === 'string' ? record.due_at :
|
|
993
|
+
'';
|
|
994
|
+
const messageRaw = typeof record.message === 'string' ? record.message :
|
|
995
|
+
typeof record.task === 'string' ? record.task :
|
|
996
|
+
'';
|
|
997
|
+
const dueAt = String(timeRaw || '').trim();
|
|
998
|
+
const task = String(messageRaw || '').trim();
|
|
999
|
+
const dueAtMs = parseDueAtMs(dueAt);
|
|
1000
|
+
if (!dueAtMs || !task) {
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
const hasRecurrenceRaw = Object.prototype.hasOwnProperty.call(record, 'recurrence')
|
|
1004
|
+
|| Object.prototype.hasOwnProperty.call(record, 'repeat')
|
|
1005
|
+
|| Object.prototype.hasOwnProperty.call(record, 'cycle');
|
|
1006
|
+
const recurrence = parseMarkerRecurrence(record);
|
|
1007
|
+
if (hasRecurrenceRaw && !recurrence) {
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
return {
|
|
1011
|
+
dueAtMs,
|
|
1012
|
+
dueAt: new Date(dueAtMs).toISOString(),
|
|
1013
|
+
task,
|
|
1014
|
+
...(recurrence ? { recurrence } : {})
|
|
1015
|
+
};
|
|
1016
|
+
};
|
|
1017
|
+
try {
|
|
1018
|
+
const parsed = JSON.parse(raw);
|
|
1019
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
1020
|
+
const fromJson = parseFromRecord(parsed);
|
|
1021
|
+
if (fromJson) {
|
|
1022
|
+
return fromJson;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
catch {
|
|
1027
|
+
// fall through to loose parser
|
|
1028
|
+
}
|
|
1029
|
+
const loose = /^\{\s*time\s*:\s*([^,}]+)\s*,\s*message\s*:\s*([^}]+)\s*\}$/i.exec(raw);
|
|
1030
|
+
if (!loose) {
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
const dueAt = unquoteMarkerToken(loose[1]);
|
|
1034
|
+
const task = unquoteMarkerToken(loose[2]);
|
|
1035
|
+
const dueAtMs = parseDueAtMs(dueAt);
|
|
1036
|
+
if (!dueAtMs || !task) {
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
dueAtMs,
|
|
1041
|
+
dueAt: new Date(dueAtMs).toISOString(),
|
|
1042
|
+
task
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
function extractClockScheduleDirectivesFromText(text) {
|
|
1046
|
+
const pattern = /<\*\*\s*clock\s*:\s*([\s\S]*?)\s*\*\*>/gi;
|
|
1047
|
+
const directives = [];
|
|
1048
|
+
const next = String(text || '').replace(pattern, (full, payload) => {
|
|
1049
|
+
const parsed = parseClockScheduleDirectivePayload(String(payload || ''));
|
|
1050
|
+
if (!parsed) {
|
|
1051
|
+
return full;
|
|
1052
|
+
}
|
|
1053
|
+
directives.push(parsed);
|
|
1054
|
+
return '';
|
|
1055
|
+
});
|
|
1056
|
+
return {
|
|
1057
|
+
directives,
|
|
1058
|
+
next: next.replace(/\n{3,}/g, '\n\n').trim()
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function extractClockScheduleDirectivesFromContent(content) {
|
|
1062
|
+
if (typeof content === 'string') {
|
|
1063
|
+
const result = extractClockScheduleDirectivesFromText(content);
|
|
1064
|
+
return { directives: result.directives, next: result.next };
|
|
1065
|
+
}
|
|
1066
|
+
if (Array.isArray(content)) {
|
|
1067
|
+
const directives = [];
|
|
1068
|
+
const next = content.map((part) => {
|
|
1069
|
+
if (typeof part === 'string') {
|
|
1070
|
+
const result = extractClockScheduleDirectivesFromText(part);
|
|
1071
|
+
if (result.directives.length) {
|
|
1072
|
+
directives.push(...result.directives);
|
|
1073
|
+
}
|
|
1074
|
+
return result.next;
|
|
1075
|
+
}
|
|
1076
|
+
if (part && typeof part === 'object' && !Array.isArray(part)) {
|
|
1077
|
+
const block = part;
|
|
1078
|
+
const text = typeof block.text === 'string' ? block.text : undefined;
|
|
1079
|
+
if (!text) {
|
|
1080
|
+
return part;
|
|
1081
|
+
}
|
|
1082
|
+
const result = extractClockScheduleDirectivesFromText(text);
|
|
1083
|
+
if (result.directives.length) {
|
|
1084
|
+
directives.push(...result.directives);
|
|
1085
|
+
}
|
|
1086
|
+
return { ...block, text: result.next };
|
|
1087
|
+
}
|
|
1088
|
+
return part;
|
|
1089
|
+
});
|
|
1090
|
+
return { directives, next };
|
|
1091
|
+
}
|
|
1092
|
+
return { directives: [], next: content };
|
|
1093
|
+
}
|
|
1094
|
+
let clockMarkerCallSeq = 0;
|
|
1095
|
+
function buildClockMarkerCallId(requestId, index) {
|
|
1096
|
+
clockMarkerCallSeq += 1;
|
|
1097
|
+
const token = String(requestId || 'req').replace(/[^a-zA-Z0-9_-]+/g, '_') || 'req';
|
|
1098
|
+
return `call_clock_marker_${token}_${index + 1}_${clockMarkerCallSeq}`;
|
|
1099
|
+
}
|
|
1100
|
+
function buildClockMarkerScheduleMessages(requestId, markerIndex, marker, payload) {
|
|
1101
|
+
const callId = buildClockMarkerCallId(requestId, markerIndex);
|
|
1102
|
+
const args = {
|
|
1103
|
+
action: 'schedule',
|
|
1104
|
+
items: [
|
|
1105
|
+
{
|
|
1106
|
+
dueAt: marker.dueAt,
|
|
1107
|
+
task: marker.task,
|
|
1108
|
+
...(marker.recurrence ? { recurrence: marker.recurrence } : {})
|
|
1109
|
+
}
|
|
1110
|
+
]
|
|
1111
|
+
};
|
|
1112
|
+
return [
|
|
1113
|
+
{
|
|
1114
|
+
role: 'assistant',
|
|
1115
|
+
content: null,
|
|
1116
|
+
tool_calls: [
|
|
1117
|
+
{
|
|
1118
|
+
id: callId,
|
|
1119
|
+
type: 'function',
|
|
1120
|
+
function: {
|
|
1121
|
+
name: 'clock',
|
|
1122
|
+
arguments: JSON.stringify(args)
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
]
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
role: 'tool',
|
|
1129
|
+
tool_call_id: callId,
|
|
1130
|
+
name: 'clock',
|
|
1131
|
+
content: JSON.stringify(payload)
|
|
1132
|
+
}
|
|
1133
|
+
];
|
|
1134
|
+
}
|
|
739
1135
|
function stripClockClearDirectiveFromContent(content) {
|
|
740
1136
|
if (typeof content === 'string') {
|
|
741
1137
|
const { hadClear, next } = stripClockClearDirectiveFromText(content);
|
|
@@ -781,7 +1177,8 @@ function findLastUserMessageIndex(messages) {
|
|
|
781
1177
|
async function maybeInjectClockRemindersAndApplyDirectives(request, metadata, requestId) {
|
|
782
1178
|
const rt = readRuntimeMetadata(metadata);
|
|
783
1179
|
// Do not inject reminders or apply clock directives during internal servertool followup hops.
|
|
784
|
-
|
|
1180
|
+
const allowClockFollowupReminderInjection = rt?.clockFollowupInjectReminders === true;
|
|
1181
|
+
if (rt?.serverToolFollowup === true && !allowClockFollowupReminderInjection) {
|
|
785
1182
|
return request;
|
|
786
1183
|
}
|
|
787
1184
|
const rawConfig = rt?.clock;
|
|
@@ -800,14 +1197,17 @@ async function maybeInjectClockRemindersAndApplyDirectives(request, metadata, re
|
|
|
800
1197
|
const lastUserIdx = findLastUserMessageIndex(messages);
|
|
801
1198
|
// 1) Apply <**clock:clear**> directive (latest user message only).
|
|
802
1199
|
let hadClear = false;
|
|
1200
|
+
let clockScheduleDirectives = [];
|
|
803
1201
|
let baseMessages = messages;
|
|
804
1202
|
if (lastUserIdx >= 0) {
|
|
805
1203
|
const lastUser = messages[lastUserIdx];
|
|
806
1204
|
const stripped = stripClockClearDirectiveFromContent(lastUser.content);
|
|
807
1205
|
hadClear = stripped.hadClear;
|
|
808
|
-
|
|
1206
|
+
const extracted = extractClockScheduleDirectivesFromContent(stripped.next);
|
|
1207
|
+
clockScheduleDirectives = extracted.directives;
|
|
1208
|
+
if (hadClear || clockScheduleDirectives.length > 0) {
|
|
809
1209
|
baseMessages = messages.slice();
|
|
810
|
-
baseMessages[lastUserIdx] = { ...lastUser, content:
|
|
1210
|
+
baseMessages[lastUserIdx] = { ...lastUser, content: extracted.next };
|
|
811
1211
|
}
|
|
812
1212
|
}
|
|
813
1213
|
if (hadClear) {
|
|
@@ -822,7 +1222,54 @@ async function maybeInjectClockRemindersAndApplyDirectives(request, metadata, re
|
|
|
822
1222
|
}
|
|
823
1223
|
// Continue: still inject per-request time tag (but skip due reminders).
|
|
824
1224
|
}
|
|
825
|
-
// 2)
|
|
1225
|
+
// 2) Apply private schedule directives: <**clock:{time,message}**>
|
|
1226
|
+
// Convert to actual clock scheduling and append synthetic tool_call/tool_result messages
|
|
1227
|
+
// so downstream model sees canonical tool semantics.
|
|
1228
|
+
let markerToolMessages = [];
|
|
1229
|
+
if (clockScheduleDirectives.length > 0) {
|
|
1230
|
+
for (let index = 0; index < clockScheduleDirectives.length; index += 1) {
|
|
1231
|
+
const marker = clockScheduleDirectives[index];
|
|
1232
|
+
const itemBase = {
|
|
1233
|
+
dueAtMs: marker.dueAtMs,
|
|
1234
|
+
task: marker.task,
|
|
1235
|
+
...(marker.recurrence ? { recurrence: marker.recurrence } : {})
|
|
1236
|
+
};
|
|
1237
|
+
const now = Date.now();
|
|
1238
|
+
const guardedItem = marker.dueAtMs <= now + clockConfig.dueWindowMs
|
|
1239
|
+
? { ...itemBase, notBeforeRequestId: requestId }
|
|
1240
|
+
: itemBase;
|
|
1241
|
+
if (!sessionId) {
|
|
1242
|
+
markerToolMessages = markerToolMessages.concat(buildClockMarkerScheduleMessages(requestId, index, marker, {
|
|
1243
|
+
ok: false,
|
|
1244
|
+
action: 'schedule',
|
|
1245
|
+
message: 'clock requires sessionId (x-session-id header or metadata.sessionId).'
|
|
1246
|
+
}));
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
try {
|
|
1250
|
+
const scheduled = await scheduleClockTasks(sessionId, [guardedItem], clockConfig);
|
|
1251
|
+
markerToolMessages = markerToolMessages.concat(buildClockMarkerScheduleMessages(requestId, index, marker, {
|
|
1252
|
+
ok: true,
|
|
1253
|
+
action: 'schedule',
|
|
1254
|
+
scheduled: scheduled.map((entry) => ({
|
|
1255
|
+
taskId: entry.taskId,
|
|
1256
|
+
dueAt: new Date(entry.dueAtMs).toISOString(),
|
|
1257
|
+
task: entry.task,
|
|
1258
|
+
deliveryCount: entry.deliveryCount
|
|
1259
|
+
}))
|
|
1260
|
+
}));
|
|
1261
|
+
logClock('schedule', { sessionId, count: scheduled.length, source: 'marker' });
|
|
1262
|
+
}
|
|
1263
|
+
catch (error) {
|
|
1264
|
+
markerToolMessages = markerToolMessages.concat(buildClockMarkerScheduleMessages(requestId, index, marker, {
|
|
1265
|
+
ok: false,
|
|
1266
|
+
action: 'schedule',
|
|
1267
|
+
message: `clock.schedule failed: ${error instanceof Error ? error.message : String(error ?? 'unknown')}`
|
|
1268
|
+
}));
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
// 3) Inject due reminders as a user message + attach reservation for response-side commit.
|
|
826
1273
|
let reservation = null;
|
|
827
1274
|
let dueInjectText = '';
|
|
828
1275
|
if (!hadClear && sessionId) {
|
|
@@ -850,17 +1297,17 @@ async function maybeInjectClockRemindersAndApplyDirectives(request, metadata, re
|
|
|
850
1297
|
content: [
|
|
851
1298
|
'[Clock Reminder]: scheduled tasks are due.',
|
|
852
1299
|
dueInjectText,
|
|
853
|
-
'You may call tools to complete these tasks. If the tool list is incomplete, standard tools have been injected.'
|
|
1300
|
+
'You may call tools to complete these tasks. If the tool list is incomplete, standard tools have been injected. MANDATORY: if waiting is needed, use the clock tool to schedule wake-up (clock.schedule) now; do not only promise to wait.'
|
|
854
1301
|
].join('\n')
|
|
855
1302
|
};
|
|
856
1303
|
})();
|
|
857
|
-
//
|
|
1304
|
+
// 4) When we have due tasks, ensure a standard tool set is present (best-effort).
|
|
858
1305
|
let nextRequest = request;
|
|
859
1306
|
if (dueUserMessage) {
|
|
860
1307
|
const ensureToolsOps = buildClockStandardToolsOperations();
|
|
861
1308
|
nextRequest = applyHubOperations(nextRequest, ensureToolsOps);
|
|
862
1309
|
}
|
|
863
|
-
//
|
|
1310
|
+
// 5) Per-request time injection (user time tag or paired clock.get tool result).
|
|
864
1311
|
let snapshot = null;
|
|
865
1312
|
try {
|
|
866
1313
|
snapshot = await getClockTimeSnapshot();
|
|
@@ -882,7 +1329,10 @@ async function maybeInjectClockRemindersAndApplyDirectives(request, metadata, re
|
|
|
882
1329
|
__clockReservation: reservation
|
|
883
1330
|
}
|
|
884
1331
|
: baseMetadata;
|
|
885
|
-
const
|
|
1332
|
+
const messagesWithMarkers = markerToolMessages.length > 0
|
|
1333
|
+
? [...baseMessages, ...markerToolMessages]
|
|
1334
|
+
: baseMessages.slice();
|
|
1335
|
+
const messagesWithDue = dueUserMessage ? [...messagesWithMarkers, dueUserMessage] : messagesWithMarkers;
|
|
886
1336
|
// Always inject time via user-role content to keep the tag visible without adding
|
|
887
1337
|
// extra tool-call semantics that may distract the model.
|
|
888
1338
|
//
|