@jsonstudio/llms 0.6.954 → 0.6.1164
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversion/hub/operation-table/operation-table-runner.d.ts +18 -0
- package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
- package/dist/conversion/hub/ops/operations.d.ts +19 -0
- package/dist/conversion/hub/ops/operations.js +126 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +489 -19
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
- package/dist/conversion/hub/policy/policy-engine.js +41 -9
- package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
- package/dist/conversion/hub/policy/protocol-spec.js +73 -23
- package/dist/conversion/hub/process/chat-process.js +252 -41
- package/dist/conversion/hub/response/provider-response.js +175 -2
- package/dist/conversion/hub/response/response-runtime.js +1 -1
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
- package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -467
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -903
- package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
- package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
- package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
- package/dist/conversion/responses/responses-openai-bridge.js +14 -2
- package/dist/conversion/shared/bridge-message-utils.js +2 -8
- package/dist/conversion/shared/bridge-policies.js +5 -105
- package/dist/conversion/shared/gemini-tool-utils.js +89 -15
- package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
- package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
- package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
- package/dist/conversion/shared/snapshot-hooks.js +166 -3
- package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer.js +345 -9
- package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
- package/dist/conversion/shared/thought-signature-validator.js +170 -0
- package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
- package/dist/conversion/shared/tool-argument-repairer.js +56 -0
- package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
- package/dist/conversion/shared/tool-call-id-manager.js +231 -0
- package/dist/conversion/shared/tool-canonicalizer.js +2 -11
- package/dist/router/virtual-router/bootstrap.js +54 -5
- package/dist/router/virtual-router/engine-selection.js +132 -42
- package/dist/router/virtual-router/engine.d.ts +3 -0
- package/dist/router/virtual-router/engine.js +142 -33
- package/dist/router/virtual-router/health-weighted.d.ts +25 -0
- package/dist/router/virtual-router/health-weighted.js +63 -0
- package/dist/router/virtual-router/load-balancer.d.ts +2 -0
- package/dist/router/virtual-router/load-balancer.js +45 -16
- package/dist/router/virtual-router/routing-instructions.js +17 -1
- package/dist/router/virtual-router/sticky-session-store.js +136 -24
- package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
- package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
- package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
- package/dist/router/virtual-router/types.d.ts +70 -0
- package/dist/servertool/clock/config.d.ts +7 -0
- package/dist/servertool/clock/config.js +27 -0
- package/dist/servertool/clock/daemon.d.ts +3 -0
- package/dist/servertool/clock/daemon.js +79 -0
- package/dist/servertool/clock/io.d.ts +2 -0
- package/dist/servertool/clock/io.js +13 -0
- package/dist/servertool/clock/paths.d.ts +4 -0
- package/dist/servertool/clock/paths.js +25 -0
- package/dist/servertool/clock/session-store.d.ts +3 -0
- package/dist/servertool/clock/session-store.js +56 -0
- package/dist/servertool/clock/state.d.ts +5 -0
- package/dist/servertool/clock/state.js +62 -0
- package/dist/servertool/clock/task-store.d.ts +5 -0
- package/dist/servertool/clock/task-store.js +4 -0
- package/dist/servertool/clock/tasks.d.ts +17 -0
- package/dist/servertool/clock/tasks.js +221 -0
- package/dist/servertool/clock/types.d.ts +36 -0
- package/dist/servertool/clock/types.js +1 -0
- package/dist/servertool/engine.d.ts +2 -0
- package/dist/servertool/engine.js +161 -7
- package/dist/servertool/followup-shadow.d.ts +16 -0
- package/dist/servertool/followup-shadow.js +145 -0
- package/dist/servertool/handlers/apply-patch-guard.js +1 -265
- package/dist/servertool/handlers/clock-auto.d.ts +1 -0
- package/dist/servertool/handlers/clock-auto.js +160 -0
- package/dist/servertool/handlers/clock.d.ts +1 -0
- package/dist/servertool/handlers/clock.js +197 -0
- package/dist/servertool/handlers/exec-command-guard.js +7 -555
- package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
- package/dist/servertool/handlers/followup-request-builder.js +248 -28
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
- package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
- package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
- package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
- package/dist/servertool/handlers/stop-message-auto.js +47 -175
- package/dist/servertool/handlers/vision.d.ts +7 -1
- package/dist/servertool/handlers/vision.js +61 -117
- package/dist/servertool/handlers/web-search.d.ts +7 -1
- package/dist/servertool/handlers/web-search.js +122 -105
- package/dist/servertool/reenter-backend.d.ts +23 -0
- package/dist/servertool/reenter-backend.js +18 -0
- package/dist/servertool/server-side-tools.d.ts +3 -2
- package/dist/servertool/server-side-tools.js +64 -10
- package/dist/servertool/types.d.ts +92 -3
- package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
- package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
- package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
- package/dist/sse/shared/writer.js +24 -7
- package/dist/tools/apply-patch/execution-capturer.js +3 -1
- package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
- package/dist/tools/apply-patch/json/parse-loose.js +139 -0
- package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
- package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
- package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
- package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
- package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
- package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
- package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
- package/dist/tools/apply-patch/structured/coercion.js +82 -0
- package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
- package/dist/tools/apply-patch/validation/shared.js +6 -0
- package/dist/tools/apply-patch/validator.d.ts +2 -2
- package/dist/tools/apply-patch/validator.js +6 -556
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readSessionDirEnv, resolveClockDir } from './paths.js';
|
|
4
|
+
import { cleanExpiredTasks, coerceState, nowMs } from './state.js';
|
|
5
|
+
import { readJsonFile, writeJsonFileAtomic } from './io.js';
|
|
6
|
+
let daemonStarted = false;
|
|
7
|
+
let daemonTimer;
|
|
8
|
+
let daemonConfig;
|
|
9
|
+
export async function startClockDaemonIfNeeded(config) {
|
|
10
|
+
if (daemonStarted) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const sessionDir = readSessionDirEnv();
|
|
14
|
+
if (!sessionDir) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
daemonStarted = true;
|
|
18
|
+
daemonConfig = config;
|
|
19
|
+
const tickOnce = async () => {
|
|
20
|
+
const effective = daemonConfig;
|
|
21
|
+
if (!effective)
|
|
22
|
+
return;
|
|
23
|
+
const base = readSessionDirEnv();
|
|
24
|
+
if (!base)
|
|
25
|
+
return;
|
|
26
|
+
const dir = resolveClockDir(base);
|
|
27
|
+
try {
|
|
28
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
29
|
+
const at = nowMs();
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (!entry.isFile())
|
|
32
|
+
continue;
|
|
33
|
+
if (!entry.name.endsWith('.json'))
|
|
34
|
+
continue;
|
|
35
|
+
const filePath = path.join(dir, entry.name);
|
|
36
|
+
try {
|
|
37
|
+
const raw = await readJsonFile(filePath);
|
|
38
|
+
const sessionId = entry.name.slice(0, -'.json'.length);
|
|
39
|
+
const state = coerceState(raw, sessionId);
|
|
40
|
+
const cleaned = cleanExpiredTasks(state.tasks, effective, at);
|
|
41
|
+
if (!cleaned.length) {
|
|
42
|
+
await fs.rm(filePath, { force: true });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (cleaned.length !== state.tasks.length) {
|
|
46
|
+
const next = {
|
|
47
|
+
...state,
|
|
48
|
+
tasks: cleaned,
|
|
49
|
+
updatedAtMs: at
|
|
50
|
+
};
|
|
51
|
+
await writeJsonFileAtomic(filePath, next);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// best-effort: ignore per-file errors
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// best-effort: ignore global scan errors
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
// Startup scan (best-effort).
|
|
64
|
+
void tickOnce();
|
|
65
|
+
if (config.tickMs > 0) {
|
|
66
|
+
daemonTimer = setInterval(() => {
|
|
67
|
+
void tickOnce();
|
|
68
|
+
}, config.tickMs);
|
|
69
|
+
daemonTimer.unref?.();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export async function stopClockDaemonForTests() {
|
|
73
|
+
if (daemonTimer) {
|
|
74
|
+
clearInterval(daemonTimer);
|
|
75
|
+
daemonTimer = undefined;
|
|
76
|
+
}
|
|
77
|
+
daemonStarted = false;
|
|
78
|
+
daemonConfig = undefined;
|
|
79
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function readJsonFile(filePath) {
|
|
4
|
+
const buf = await fs.readFile(filePath);
|
|
5
|
+
return JSON.parse(buf.toString('utf8'));
|
|
6
|
+
}
|
|
7
|
+
export async function writeJsonFileAtomic(filePath, value) {
|
|
8
|
+
const dir = path.dirname(filePath);
|
|
9
|
+
const tmp = path.join(dir, `.tmp_${path.basename(filePath)}_${process.pid}_${Date.now()}_${Math.random().toString(16).slice(2)}`);
|
|
10
|
+
const payload = JSON.stringify(value, null, 2);
|
|
11
|
+
await fs.writeFile(tmp, payload, 'utf8');
|
|
12
|
+
await fs.rename(tmp, filePath);
|
|
13
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function readSessionDirEnv(): string;
|
|
2
|
+
export declare function resolveClockDir(sessionDir: string): string;
|
|
3
|
+
export declare function resolveClockStateFile(sessionDir: string, sessionId: string): string | null;
|
|
4
|
+
export declare function ensureDir(dir: string): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function readSessionDirEnv() {
|
|
4
|
+
return String(process.env.ROUTECODEX_SESSION_DIR || '').trim();
|
|
5
|
+
}
|
|
6
|
+
function sanitizeSegment(value) {
|
|
7
|
+
return String(value || '')
|
|
8
|
+
.trim()
|
|
9
|
+
.replace(/[^a-zA-Z0-9_.-]/g, '_')
|
|
10
|
+
.replace(/_+/g, '_')
|
|
11
|
+
.replace(/^_+|_+$/g, '');
|
|
12
|
+
}
|
|
13
|
+
export function resolveClockDir(sessionDir) {
|
|
14
|
+
return path.join(sessionDir, 'clock');
|
|
15
|
+
}
|
|
16
|
+
export function resolveClockStateFile(sessionDir, sessionId) {
|
|
17
|
+
const safe = sanitizeSegment(sessionId);
|
|
18
|
+
if (!safe) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return path.join(resolveClockDir(sessionDir), `${safe}.json`);
|
|
22
|
+
}
|
|
23
|
+
export async function ensureDir(dir) {
|
|
24
|
+
await fs.mkdir(dir, { recursive: true });
|
|
25
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ClockConfigSnapshot, ClockSessionState } from './types.js';
|
|
2
|
+
export declare function loadClockSessionState(sessionId: string, config: ClockConfigSnapshot): Promise<ClockSessionState>;
|
|
3
|
+
export declare function clearClockSession(sessionId: string): Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureDir, readSessionDirEnv, resolveClockStateFile } from './paths.js';
|
|
4
|
+
import { buildEmptyState, cleanExpiredTasks, coerceState, nowMs } from './state.js';
|
|
5
|
+
import { readJsonFile, writeJsonFileAtomic } from './io.js';
|
|
6
|
+
export async function loadClockSessionState(sessionId, config) {
|
|
7
|
+
const sessionDir = readSessionDirEnv();
|
|
8
|
+
if (!sessionDir) {
|
|
9
|
+
return buildEmptyState(sessionId);
|
|
10
|
+
}
|
|
11
|
+
const filePath = resolveClockStateFile(sessionDir, sessionId);
|
|
12
|
+
if (!filePath) {
|
|
13
|
+
return buildEmptyState(sessionId);
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const raw = await readJsonFile(filePath);
|
|
17
|
+
const state = coerceState(raw, sessionId);
|
|
18
|
+
const at = nowMs();
|
|
19
|
+
const cleaned = cleanExpiredTasks(state.tasks, config, at);
|
|
20
|
+
if (cleaned.length !== state.tasks.length) {
|
|
21
|
+
const next = { ...state, tasks: cleaned, updatedAtMs: at };
|
|
22
|
+
if (!next.tasks.length) {
|
|
23
|
+
await fs.rm(filePath, { force: true });
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
await ensureDir(path.dirname(filePath));
|
|
27
|
+
await writeJsonFileAtomic(filePath, next);
|
|
28
|
+
}
|
|
29
|
+
return next;
|
|
30
|
+
}
|
|
31
|
+
return state;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const message = error instanceof Error ? error.message : String(error ?? 'unknown');
|
|
35
|
+
if (message.includes('ENOENT')) {
|
|
36
|
+
return buildEmptyState(sessionId);
|
|
37
|
+
}
|
|
38
|
+
return buildEmptyState(sessionId);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function clearClockSession(sessionId) {
|
|
42
|
+
const sessionDir = readSessionDirEnv();
|
|
43
|
+
if (!sessionDir) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const filePath = resolveClockStateFile(sessionDir, sessionId);
|
|
47
|
+
if (!filePath) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
await fs.rm(filePath, { force: true });
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// ignore
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ClockConfigSnapshot, ClockSessionState, ClockTask } from './types.js';
|
|
2
|
+
export declare function nowMs(): number;
|
|
3
|
+
export declare function buildEmptyState(sessionId: string): ClockSessionState;
|
|
4
|
+
export declare function coerceState(raw: unknown, sessionId: string): ClockSessionState;
|
|
5
|
+
export declare function cleanExpiredTasks(tasks: ClockTask[], config: ClockConfigSnapshot, atMs: number): ClockTask[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function nowMs() {
|
|
2
|
+
return Date.now();
|
|
3
|
+
}
|
|
4
|
+
export function buildEmptyState(sessionId) {
|
|
5
|
+
const t = nowMs();
|
|
6
|
+
return { version: 1, sessionId, tasks: [], updatedAtMs: t };
|
|
7
|
+
}
|
|
8
|
+
export function coerceState(raw, sessionId) {
|
|
9
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
10
|
+
return buildEmptyState(sessionId);
|
|
11
|
+
}
|
|
12
|
+
const record = raw;
|
|
13
|
+
const tasksRaw = Array.isArray(record.tasks) ? record.tasks : [];
|
|
14
|
+
const tasks = [];
|
|
15
|
+
for (const entry of tasksRaw) {
|
|
16
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
17
|
+
continue;
|
|
18
|
+
const e = entry;
|
|
19
|
+
const taskId = typeof e.taskId === 'string' ? e.taskId.trim() : '';
|
|
20
|
+
const dueAtMs = typeof e.dueAtMs === 'number' && Number.isFinite(e.dueAtMs) ? Math.floor(e.dueAtMs) : NaN;
|
|
21
|
+
const createdAtMs = typeof e.createdAtMs === 'number' && Number.isFinite(e.createdAtMs) ? Math.floor(e.createdAtMs) : NaN;
|
|
22
|
+
const updatedAtMs = typeof e.updatedAtMs === 'number' && Number.isFinite(e.updatedAtMs) ? Math.floor(e.updatedAtMs) : NaN;
|
|
23
|
+
const task = typeof e.task === 'string' ? e.task.trim() : '';
|
|
24
|
+
if (!taskId || !task || !Number.isFinite(dueAtMs) || !Number.isFinite(createdAtMs) || !Number.isFinite(updatedAtMs)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const tool = typeof e.tool === 'string' && e.tool.trim() ? e.tool.trim() : undefined;
|
|
28
|
+
const args = e.arguments && typeof e.arguments === 'object' && !Array.isArray(e.arguments)
|
|
29
|
+
? e.arguments
|
|
30
|
+
: undefined;
|
|
31
|
+
const deliveredAtMs = typeof e.deliveredAtMs === 'number' && Number.isFinite(e.deliveredAtMs) ? Math.floor(e.deliveredAtMs) : undefined;
|
|
32
|
+
const deliveryCount = typeof e.deliveryCount === 'number' && Number.isFinite(e.deliveryCount) ? Math.max(0, Math.floor(e.deliveryCount)) : 0;
|
|
33
|
+
tasks.push({
|
|
34
|
+
taskId,
|
|
35
|
+
sessionId,
|
|
36
|
+
dueAtMs,
|
|
37
|
+
createdAtMs,
|
|
38
|
+
updatedAtMs,
|
|
39
|
+
task,
|
|
40
|
+
...(tool ? { tool } : {}),
|
|
41
|
+
...(args ? { arguments: args } : {}),
|
|
42
|
+
...(deliveredAtMs !== undefined ? { deliveredAtMs } : {}),
|
|
43
|
+
deliveryCount
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const updatedAtMs = typeof record.updatedAtMs === 'number' && Number.isFinite(record.updatedAtMs) ? Math.floor(record.updatedAtMs) : nowMs();
|
|
47
|
+
return { version: 1, sessionId, tasks, updatedAtMs };
|
|
48
|
+
}
|
|
49
|
+
export function cleanExpiredTasks(tasks, config, atMs) {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const task of tasks) {
|
|
52
|
+
if (!task || typeof task !== 'object')
|
|
53
|
+
continue;
|
|
54
|
+
if (!Number.isFinite(task.dueAtMs))
|
|
55
|
+
continue;
|
|
56
|
+
if (atMs > task.dueAtMs + config.retentionMs) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
out.push(task);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { ClockConfigSnapshot, ClockReservation, ClockScheduleItem, ClockSessionState, ClockTask } from './types.js';
|
|
2
|
+
export { normalizeClockConfig } from './config.js';
|
|
3
|
+
export { startClockDaemonIfNeeded, stopClockDaemonForTests } from './daemon.js';
|
|
4
|
+
export { loadClockSessionState, clearClockSession } from './session-store.js';
|
|
5
|
+
export { cancelClockTask, clearClockTasks, commitClockReservation, findNextUndeliveredDueAtMs, listClockTasks, parseDueAtMs, reserveDueTasksForRequest, scheduleClockTasks, selectDueUndeliveredTasks } from './tasks.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { normalizeClockConfig } from './config.js';
|
|
2
|
+
export { startClockDaemonIfNeeded, stopClockDaemonForTests } from './daemon.js';
|
|
3
|
+
export { loadClockSessionState, clearClockSession } from './session-store.js';
|
|
4
|
+
export { cancelClockTask, clearClockTasks, commitClockReservation, findNextUndeliveredDueAtMs, listClockTasks, parseDueAtMs, reserveDueTasksForRequest, scheduleClockTasks, selectDueUndeliveredTasks } from './tasks.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ClockConfigSnapshot, ClockReservation, ClockScheduleItem, ClockTask } from './types.js';
|
|
2
|
+
export declare function parseDueAtMs(value: unknown): number | null;
|
|
3
|
+
export declare function listClockTasks(sessionId: string, config: ClockConfigSnapshot): Promise<ClockTask[]>;
|
|
4
|
+
export declare function scheduleClockTasks(sessionId: string, items: ClockScheduleItem[], config: ClockConfigSnapshot): Promise<ClockTask[]>;
|
|
5
|
+
export declare function cancelClockTask(sessionId: string, taskId: string, config: ClockConfigSnapshot): Promise<boolean>;
|
|
6
|
+
export declare function clearClockTasks(sessionId: string, config: ClockConfigSnapshot): Promise<number>;
|
|
7
|
+
export declare function selectDueUndeliveredTasks(tasks: ClockTask[], config: ClockConfigSnapshot, atMs: number): ClockTask[];
|
|
8
|
+
export declare function findNextUndeliveredDueAtMs(tasks: ClockTask[], atMs: number): number | null;
|
|
9
|
+
export declare function reserveDueTasksForRequest(args: {
|
|
10
|
+
reservationId: string;
|
|
11
|
+
sessionId: string;
|
|
12
|
+
config: ClockConfigSnapshot;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
reservation: ClockReservation | null;
|
|
15
|
+
injectText?: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function commitClockReservation(reservation: ClockReservation, config: ClockConfigSnapshot): Promise<void>;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { ensureDir, readSessionDirEnv, resolveClockStateFile } from './paths.js';
|
|
5
|
+
import { cleanExpiredTasks, nowMs } from './state.js';
|
|
6
|
+
import { writeJsonFileAtomic } from './io.js';
|
|
7
|
+
import { clearClockSession, loadClockSessionState } from './session-store.js';
|
|
8
|
+
function safeJson(value) {
|
|
9
|
+
try {
|
|
10
|
+
return JSON.stringify(value ?? {});
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return '{}';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function safeQuoted(text) {
|
|
17
|
+
const normalized = String(text ?? '');
|
|
18
|
+
const escaped = normalized.replace(/\"/g, '\\"');
|
|
19
|
+
return `"${escaped}"`;
|
|
20
|
+
}
|
|
21
|
+
function buildTaskId() {
|
|
22
|
+
try {
|
|
23
|
+
return crypto.randomUUID();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return `task_${Math.random().toString(16).slice(2)}_${Date.now()}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function parseDueAtMs(value) {
|
|
30
|
+
if (typeof value !== 'string')
|
|
31
|
+
return null;
|
|
32
|
+
const t = value.trim();
|
|
33
|
+
if (!t)
|
|
34
|
+
return null;
|
|
35
|
+
const ms = Date.parse(t);
|
|
36
|
+
if (!Number.isFinite(ms))
|
|
37
|
+
return null;
|
|
38
|
+
return ms;
|
|
39
|
+
}
|
|
40
|
+
export async function listClockTasks(sessionId, config) {
|
|
41
|
+
const state = await loadClockSessionState(sessionId, config);
|
|
42
|
+
return state.tasks.slice();
|
|
43
|
+
}
|
|
44
|
+
export async function scheduleClockTasks(sessionId, items, config) {
|
|
45
|
+
const sessionDir = readSessionDirEnv();
|
|
46
|
+
if (!sessionDir) {
|
|
47
|
+
throw new Error('clock: missing ROUTECODEX_SESSION_DIR');
|
|
48
|
+
}
|
|
49
|
+
const filePath = resolveClockStateFile(sessionDir, sessionId);
|
|
50
|
+
if (!filePath) {
|
|
51
|
+
throw new Error('clock: invalid sessionId');
|
|
52
|
+
}
|
|
53
|
+
await ensureDir(path.dirname(filePath));
|
|
54
|
+
const at = nowMs();
|
|
55
|
+
const existing = await loadClockSessionState(sessionId, config);
|
|
56
|
+
const cleaned = cleanExpiredTasks(existing.tasks, config, at);
|
|
57
|
+
const scheduled = [];
|
|
58
|
+
for (const item of items) {
|
|
59
|
+
const text = typeof item.task === 'string' ? item.task.trim() : '';
|
|
60
|
+
const dueAtMs = item.dueAtMs;
|
|
61
|
+
if (!text || !Number.isFinite(dueAtMs)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const taskId = buildTaskId();
|
|
65
|
+
scheduled.push({
|
|
66
|
+
taskId,
|
|
67
|
+
sessionId,
|
|
68
|
+
dueAtMs: Math.floor(dueAtMs),
|
|
69
|
+
createdAtMs: at,
|
|
70
|
+
updatedAtMs: at,
|
|
71
|
+
task: text,
|
|
72
|
+
...(item.tool ? { tool: item.tool } : {}),
|
|
73
|
+
...(item.arguments ? { arguments: item.arguments } : {}),
|
|
74
|
+
deliveryCount: 0
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const nextTasks = cleanExpiredTasks([...cleaned, ...scheduled], config, at);
|
|
78
|
+
const next = {
|
|
79
|
+
version: 1,
|
|
80
|
+
sessionId,
|
|
81
|
+
tasks: nextTasks,
|
|
82
|
+
updatedAtMs: at
|
|
83
|
+
};
|
|
84
|
+
if (!next.tasks.length) {
|
|
85
|
+
await fs.rm(filePath, { force: true });
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
await writeJsonFileAtomic(filePath, next);
|
|
89
|
+
}
|
|
90
|
+
return scheduled;
|
|
91
|
+
}
|
|
92
|
+
export async function cancelClockTask(sessionId, taskId, config) {
|
|
93
|
+
const sessionDir = readSessionDirEnv();
|
|
94
|
+
if (!sessionDir) {
|
|
95
|
+
throw new Error('clock: missing ROUTECODEX_SESSION_DIR');
|
|
96
|
+
}
|
|
97
|
+
const filePath = resolveClockStateFile(sessionDir, sessionId);
|
|
98
|
+
if (!filePath) {
|
|
99
|
+
throw new Error('clock: invalid sessionId');
|
|
100
|
+
}
|
|
101
|
+
await ensureDir(path.dirname(filePath));
|
|
102
|
+
const at = nowMs();
|
|
103
|
+
const state = await loadClockSessionState(sessionId, config);
|
|
104
|
+
const cleaned = cleanExpiredTasks(state.tasks, config, at);
|
|
105
|
+
const nextTasks = cleaned.filter((t) => t.taskId !== taskId);
|
|
106
|
+
const removed = nextTasks.length !== cleaned.length;
|
|
107
|
+
const next = { version: 1, sessionId, tasks: nextTasks, updatedAtMs: at };
|
|
108
|
+
if (!next.tasks.length) {
|
|
109
|
+
await fs.rm(filePath, { force: true });
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
await writeJsonFileAtomic(filePath, next);
|
|
113
|
+
}
|
|
114
|
+
return removed;
|
|
115
|
+
}
|
|
116
|
+
export async function clearClockTasks(sessionId, config) {
|
|
117
|
+
const items = await listClockTasks(sessionId, config);
|
|
118
|
+
await clearClockSession(sessionId);
|
|
119
|
+
return items.length;
|
|
120
|
+
}
|
|
121
|
+
export function selectDueUndeliveredTasks(tasks, config, atMs) {
|
|
122
|
+
const due = [];
|
|
123
|
+
for (const task of tasks) {
|
|
124
|
+
if (!task || typeof task !== 'object')
|
|
125
|
+
continue;
|
|
126
|
+
if (task.deliveredAtMs !== undefined)
|
|
127
|
+
continue;
|
|
128
|
+
if (!Number.isFinite(task.dueAtMs))
|
|
129
|
+
continue;
|
|
130
|
+
if (atMs < task.dueAtMs - config.dueWindowMs)
|
|
131
|
+
continue;
|
|
132
|
+
if (atMs > task.dueAtMs + config.retentionMs)
|
|
133
|
+
continue;
|
|
134
|
+
due.push(task);
|
|
135
|
+
}
|
|
136
|
+
due.sort((a, b) => a.dueAtMs - b.dueAtMs);
|
|
137
|
+
return due;
|
|
138
|
+
}
|
|
139
|
+
export function findNextUndeliveredDueAtMs(tasks, atMs) {
|
|
140
|
+
let next = null;
|
|
141
|
+
for (const task of tasks) {
|
|
142
|
+
if (!task || typeof task !== 'object')
|
|
143
|
+
continue;
|
|
144
|
+
if (task.deliveredAtMs !== undefined)
|
|
145
|
+
continue;
|
|
146
|
+
if (!Number.isFinite(task.dueAtMs))
|
|
147
|
+
continue;
|
|
148
|
+
if (task.dueAtMs < atMs) {
|
|
149
|
+
// keep past due tasks as "next" too (will be due immediately)
|
|
150
|
+
}
|
|
151
|
+
if (next === null || task.dueAtMs < next) {
|
|
152
|
+
next = task.dueAtMs;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return next;
|
|
156
|
+
}
|
|
157
|
+
export async function reserveDueTasksForRequest(args) {
|
|
158
|
+
const state = await loadClockSessionState(args.sessionId, args.config);
|
|
159
|
+
const at = nowMs();
|
|
160
|
+
const due = selectDueUndeliveredTasks(state.tasks, args.config, at);
|
|
161
|
+
if (!due.length) {
|
|
162
|
+
return { reservation: null };
|
|
163
|
+
}
|
|
164
|
+
const taskIds = due.map((t) => t.taskId);
|
|
165
|
+
const reservation = {
|
|
166
|
+
reservationId: args.reservationId,
|
|
167
|
+
sessionId: args.sessionId,
|
|
168
|
+
taskIds,
|
|
169
|
+
reservedAtMs: at
|
|
170
|
+
};
|
|
171
|
+
const injectText = due
|
|
172
|
+
.map((t) => {
|
|
173
|
+
const dueAtIso = new Date(t.dueAtMs).toISOString();
|
|
174
|
+
const toolLabel = t.tool ? ` tool=${t.tool}` : '';
|
|
175
|
+
const argsLabel = t.arguments ? ` args=${safeJson(t.arguments)}` : '';
|
|
176
|
+
return `[scheduled task:${safeQuoted(t.task)}${toolLabel}${argsLabel} dueAt=${dueAtIso}]`;
|
|
177
|
+
})
|
|
178
|
+
.join('\n');
|
|
179
|
+
return { reservation, injectText };
|
|
180
|
+
}
|
|
181
|
+
export async function commitClockReservation(reservation, config) {
|
|
182
|
+
const sessionDir = readSessionDirEnv();
|
|
183
|
+
if (!sessionDir) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const filePath = resolveClockStateFile(sessionDir, reservation.sessionId);
|
|
187
|
+
if (!filePath) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const at = nowMs();
|
|
191
|
+
const state = await loadClockSessionState(reservation.sessionId, config);
|
|
192
|
+
const cleaned = cleanExpiredTasks(state.tasks, config, at);
|
|
193
|
+
const reservedSet = new Set(reservation.taskIds);
|
|
194
|
+
let touched = false;
|
|
195
|
+
const nextTasks = cleaned.map((t) => {
|
|
196
|
+
if (!reservedSet.has(t.taskId)) {
|
|
197
|
+
return t;
|
|
198
|
+
}
|
|
199
|
+
if (t.deliveredAtMs !== undefined) {
|
|
200
|
+
return t;
|
|
201
|
+
}
|
|
202
|
+
touched = true;
|
|
203
|
+
return {
|
|
204
|
+
...t,
|
|
205
|
+
deliveredAtMs: at,
|
|
206
|
+
deliveryCount: (typeof t.deliveryCount === 'number' ? t.deliveryCount : 0) + 1,
|
|
207
|
+
updatedAtMs: at
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
if (!touched) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const next = { version: 1, sessionId: reservation.sessionId, tasks: nextTasks, updatedAtMs: at };
|
|
214
|
+
await ensureDir(path.dirname(filePath));
|
|
215
|
+
if (!next.tasks.length) {
|
|
216
|
+
await fs.rm(filePath, { force: true });
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
await writeJsonFileAtomic(filePath, next);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type ClockTask = {
|
|
2
|
+
taskId: string;
|
|
3
|
+
sessionId: string;
|
|
4
|
+
dueAtMs: number;
|
|
5
|
+
createdAtMs: number;
|
|
6
|
+
updatedAtMs: number;
|
|
7
|
+
task: string;
|
|
8
|
+
tool?: string;
|
|
9
|
+
arguments?: Record<string, unknown>;
|
|
10
|
+
deliveredAtMs?: number;
|
|
11
|
+
deliveryCount: number;
|
|
12
|
+
};
|
|
13
|
+
export type ClockSessionState = {
|
|
14
|
+
version: 1;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
tasks: ClockTask[];
|
|
17
|
+
updatedAtMs: number;
|
|
18
|
+
};
|
|
19
|
+
export type ClockReservation = {
|
|
20
|
+
reservationId: string;
|
|
21
|
+
sessionId: string;
|
|
22
|
+
taskIds: string[];
|
|
23
|
+
reservedAtMs: number;
|
|
24
|
+
};
|
|
25
|
+
export type ClockConfigSnapshot = {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
retentionMs: number;
|
|
28
|
+
dueWindowMs: number;
|
|
29
|
+
tickMs: number;
|
|
30
|
+
};
|
|
31
|
+
export type ClockScheduleItem = {
|
|
32
|
+
dueAtMs: number;
|
|
33
|
+
task: string;
|
|
34
|
+
tool?: string;
|
|
35
|
+
arguments?: Record<string, unknown>;
|
|
36
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { AdapterContext } from '../conversion/hub/types/chat-envelope.js';
|
|
2
2
|
import type { JsonObject } from '../conversion/hub/types/json.js';
|
|
3
3
|
import type { ProviderInvoker } from './types.js';
|
|
4
|
+
import type { StageRecorder } from '../conversion/hub/format-adapters/index.js';
|
|
4
5
|
export interface ServerToolOrchestrationOptions {
|
|
5
6
|
chat: JsonObject;
|
|
6
7
|
adapterContext: AdapterContext;
|
|
7
8
|
requestId: string;
|
|
8
9
|
entryEndpoint: string;
|
|
9
10
|
providerProtocol: string;
|
|
11
|
+
stageRecorder?: StageRecorder;
|
|
10
12
|
reenterPipeline?: (options: {
|
|
11
13
|
entryEndpoint: string;
|
|
12
14
|
requestId: string;
|