@j0hanz/cortex-mcp 1.4.0 → 1.6.0
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/engine/config.d.ts +4 -4
- package/dist/engine/config.js +6 -4
- package/dist/engine/context.d.ts +0 -1
- package/dist/engine/context.js +0 -3
- package/dist/engine/events.d.ts +1 -1
- package/dist/engine/events.js +2 -3
- package/dist/engine/heuristics.d.ts +4 -0
- package/dist/engine/heuristics.js +65 -0
- package/dist/engine/reasoner.d.ts +1 -1
- package/dist/engine/reasoner.js +22 -98
- package/dist/engine/session-store.d.ts +7 -1
- package/dist/engine/session-store.js +68 -27
- package/dist/lib/concurrency.d.ts +5 -0
- package/dist/lib/concurrency.js +17 -0
- package/dist/lib/errors.d.ts +23 -7
- package/dist/lib/errors.js +47 -5
- package/dist/lib/formatting.d.ts +13 -0
- package/dist/lib/formatting.js +18 -0
- package/dist/lib/prompt-contracts.d.ts +1 -9
- package/dist/lib/prompt-contracts.js +33 -122
- package/dist/lib/tool-contracts.d.ts +1 -1
- package/dist/lib/tool-contracts.js +85 -90
- package/dist/lib/tool-response.d.ts +4 -0
- package/dist/lib/tool-response.js +3 -0
- package/dist/lib/types.d.ts +17 -0
- package/dist/lib/types.js +8 -1
- package/dist/lib/validators.d.ts +12 -1
- package/dist/lib/validators.js +48 -7
- package/dist/prompts/index.js +136 -47
- package/dist/prompts/templates.d.ts +2 -0
- package/dist/prompts/templates.js +227 -0
- package/dist/resources/index.js +34 -60
- package/dist/resources/instructions.js +55 -64
- package/dist/resources/tool-catalog.js +10 -9
- package/dist/resources/tool-info.js +1 -1
- package/dist/resources/workflows.js +43 -42
- package/dist/schemas/inputs.d.ts +0 -1
- package/dist/schemas/inputs.js +14 -31
- package/dist/schemas/outputs.d.ts +29 -25
- package/dist/schemas/outputs.js +18 -22
- package/dist/server.js +11 -3
- package/dist/tools/reasoning-think.js +142 -158
- package/package.json +1 -1
package/dist/engine/config.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type LevelConfig, type ReasoningLevel } from '../lib/types.js';
|
|
2
2
|
export declare const LEVEL_CONFIGS: {
|
|
3
3
|
readonly basic: {
|
|
4
|
+
readonly tokenBudget: 2048;
|
|
4
5
|
readonly minThoughts: 3;
|
|
5
6
|
readonly maxThoughts: 5;
|
|
6
|
-
readonly tokenBudget: 2048;
|
|
7
7
|
};
|
|
8
8
|
readonly normal: {
|
|
9
|
+
readonly tokenBudget: 8192;
|
|
9
10
|
readonly minThoughts: 6;
|
|
10
11
|
readonly maxThoughts: 10;
|
|
11
|
-
readonly tokenBudget: 8192;
|
|
12
12
|
};
|
|
13
13
|
readonly high: {
|
|
14
|
+
readonly tokenBudget: 32768;
|
|
14
15
|
readonly minThoughts: 15;
|
|
15
16
|
readonly maxThoughts: 25;
|
|
16
|
-
readonly tokenBudget: 32768;
|
|
17
17
|
};
|
|
18
18
|
};
|
|
19
19
|
export declare function getLevelConfig(level: ReasoningLevel): LevelConfig;
|
package/dist/engine/config.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { InvalidThoughtCountError } from '../lib/errors.js';
|
|
2
|
+
import { LEVEL_BOUNDS, } from '../lib/types.js';
|
|
1
3
|
import { getTargetThoughtsError } from '../lib/validators.js';
|
|
2
4
|
export const LEVEL_CONFIGS = {
|
|
3
|
-
basic: {
|
|
4
|
-
normal: {
|
|
5
|
-
high: {
|
|
5
|
+
basic: { ...LEVEL_BOUNDS.basic, tokenBudget: 2048 },
|
|
6
|
+
normal: { ...LEVEL_BOUNDS.normal, tokenBudget: 8192 },
|
|
7
|
+
high: { ...LEVEL_BOUNDS.high, tokenBudget: 32768 },
|
|
6
8
|
};
|
|
7
9
|
export function getLevelConfig(level) {
|
|
8
10
|
return LEVEL_CONFIGS[level];
|
|
@@ -12,7 +14,7 @@ export function assertTargetThoughtsInRange(level, targetThoughts) {
|
|
|
12
14
|
if (!errorMessage) {
|
|
13
15
|
return;
|
|
14
16
|
}
|
|
15
|
-
throw new
|
|
17
|
+
throw new InvalidThoughtCountError(errorMessage);
|
|
16
18
|
}
|
|
17
19
|
export function getLevelDescriptionString() {
|
|
18
20
|
return Object.entries(LEVEL_CONFIGS)
|
package/dist/engine/context.d.ts
CHANGED
package/dist/engine/context.js
CHANGED
package/dist/engine/events.d.ts
CHANGED
package/dist/engine/events.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { getErrorMessage } from '../lib/errors.js';
|
|
3
|
-
const
|
|
4
|
-
const ENGINE_ERROR_PREFIX_WITH_SPACE = `${ENGINE_ERROR_LOG_PREFIX} `;
|
|
3
|
+
const ENGINE_ERROR_PREFIX = '[engine] ';
|
|
5
4
|
export const engineEvents = new EventEmitter({
|
|
6
5
|
captureRejections: true,
|
|
7
6
|
});
|
|
8
7
|
function logEngineError(err) {
|
|
9
|
-
process.stderr.write(`${
|
|
8
|
+
process.stderr.write(`${ENGINE_ERROR_PREFIX}${getErrorMessage(err)}\n`);
|
|
10
9
|
}
|
|
11
10
|
engineEvents.on('error', logEngineError);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { LevelConfig, ReasoningLevel } from '../lib/types.js';
|
|
2
|
+
export declare function countSentences(queryText: string): number;
|
|
3
|
+
export declare function getStructureDensityScore(queryText: string): number;
|
|
4
|
+
export declare function resolveThoughtCount(level: ReasoningLevel, query: string, config: Pick<LevelConfig, 'minThoughts' | 'maxThoughts'>, targetThoughts?: number): number;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { createSegmenter } from '../lib/text.js';
|
|
3
|
+
import { assertTargetThoughtsInRange } from './config.js';
|
|
4
|
+
const NON_WHITESPACE = /\S/u;
|
|
5
|
+
const COMPLEXITY_KEYWORDS = /\b(compare|analy[sz]e|trade[- ]?off|design|plan|critique|evaluate|review|architecture)\b/i;
|
|
6
|
+
let _sentenceSegmenter;
|
|
7
|
+
let _sentenceSegmenterInitialized = false;
|
|
8
|
+
function getSentenceSegmenter() {
|
|
9
|
+
if (!_sentenceSegmenterInitialized) {
|
|
10
|
+
_sentenceSegmenter = createSegmenter('sentence');
|
|
11
|
+
_sentenceSegmenterInitialized = true;
|
|
12
|
+
}
|
|
13
|
+
return _sentenceSegmenter;
|
|
14
|
+
}
|
|
15
|
+
export function countSentences(queryText) {
|
|
16
|
+
const segmenter = getSentenceSegmenter();
|
|
17
|
+
if (!segmenter) {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
let count = 0;
|
|
21
|
+
for (const sentence of segmenter.segment(queryText)) {
|
|
22
|
+
if (NON_WHITESPACE.test(sentence.segment)) {
|
|
23
|
+
count++;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return count;
|
|
27
|
+
}
|
|
28
|
+
export function getStructureDensityScore(queryText) {
|
|
29
|
+
const sentenceCount = countSentences(queryText);
|
|
30
|
+
if (sentenceCount > 1) {
|
|
31
|
+
return (sentenceCount - 1) * 0.08;
|
|
32
|
+
}
|
|
33
|
+
let markerMatches = 0;
|
|
34
|
+
for (let index = 0; index < queryText.length; index++) {
|
|
35
|
+
switch (queryText.charCodeAt(index)) {
|
|
36
|
+
case 63: // ?
|
|
37
|
+
case 58: // :
|
|
38
|
+
case 59: // ;
|
|
39
|
+
case 44: // ,
|
|
40
|
+
case 10: // \n
|
|
41
|
+
markerMatches += 1;
|
|
42
|
+
break;
|
|
43
|
+
default:
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return markerMatches * 0.05;
|
|
48
|
+
}
|
|
49
|
+
export function resolveThoughtCount(level, query, config, targetThoughts) {
|
|
50
|
+
if (targetThoughts !== undefined) {
|
|
51
|
+
assertTargetThoughtsInRange(level, targetThoughts);
|
|
52
|
+
return targetThoughts;
|
|
53
|
+
}
|
|
54
|
+
if (config.minThoughts === config.maxThoughts) {
|
|
55
|
+
return config.minThoughts;
|
|
56
|
+
}
|
|
57
|
+
const queryText = query.trim();
|
|
58
|
+
const span = config.maxThoughts - config.minThoughts;
|
|
59
|
+
const queryByteLength = Buffer.byteLength(queryText, 'utf8');
|
|
60
|
+
const lengthScore = Math.min(1, queryByteLength / 400);
|
|
61
|
+
const structureScore = Math.min(0.4, getStructureDensityScore(queryText));
|
|
62
|
+
const keywordScore = COMPLEXITY_KEYWORDS.test(queryText) ? 0.25 : 0;
|
|
63
|
+
const score = Math.min(1, lengthScore + structureScore + keywordScore);
|
|
64
|
+
return config.minThoughts + Math.round(span * score);
|
|
65
|
+
}
|
|
@@ -13,6 +13,6 @@ interface ReasonOptions {
|
|
|
13
13
|
isConclusion?: boolean;
|
|
14
14
|
rollbackToStep?: number;
|
|
15
15
|
abortSignal?: AbortSignal;
|
|
16
|
-
onProgress?: (progress: number, total: number) => void | Promise<void>;
|
|
16
|
+
onProgress?: (progress: number, total: number, stepSummary?: string) => void | Promise<void>;
|
|
17
17
|
}
|
|
18
18
|
export declare function reason(query: string, level: ReasoningLevel | undefined, options?: ReasonOptions): Promise<Readonly<Session>>;
|
package/dist/engine/reasoner.js
CHANGED
|
@@ -1,27 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { InvalidRunModeArgsError, ReasoningAbortedError, SessionNotFoundError, } from '../lib/errors.js';
|
|
2
|
+
import { parsePositiveIntEnv } from '../lib/validators.js';
|
|
3
|
+
import { getLevelConfig } from './config.js';
|
|
4
4
|
import { runWithContext } from './context.js';
|
|
5
5
|
import { engineEvents } from './events.js';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const DEFAULT_MAX_SESSIONS = 100;
|
|
9
|
-
const DEFAULT_MAX_TOTAL_TOKENS = 500_000;
|
|
10
|
-
const NON_WHITESPACE = /\S/u;
|
|
11
|
-
const COMPLEXITY_KEYWORDS = /\b(compare|analy[sz]e|trade[- ]?off|design|plan|critique|evaluate|review|architecture)\b/i;
|
|
12
|
-
function parsePositiveIntEnv(name, fallback, minimum = 1) {
|
|
13
|
-
const raw = process.env[name];
|
|
14
|
-
if (raw === undefined) {
|
|
15
|
-
return fallback;
|
|
16
|
-
}
|
|
17
|
-
const parsed = Number.parseInt(raw, 10);
|
|
18
|
-
if (!Number.isInteger(parsed) || parsed < minimum) {
|
|
19
|
-
return fallback;
|
|
20
|
-
}
|
|
21
|
-
return parsed;
|
|
22
|
-
}
|
|
6
|
+
import { resolveThoughtCount } from './heuristics.js';
|
|
7
|
+
import { DEFAULT_MAX_SESSIONS, DEFAULT_MAX_TOTAL_TOKENS, DEFAULT_SESSION_TTL_MS, SessionStore, } from './session-store.js';
|
|
23
8
|
const sessionStore = new SessionStore(parsePositiveIntEnv('CORTEX_SESSION_TTL_MS', DEFAULT_SESSION_TTL_MS), parsePositiveIntEnv('CORTEX_MAX_SESSIONS', DEFAULT_MAX_SESSIONS), parsePositiveIntEnv('CORTEX_MAX_TOTAL_TOKENS', DEFAULT_MAX_TOTAL_TOKENS));
|
|
24
|
-
const sentenceSegmenter = createSegmenter('sentence');
|
|
25
9
|
const sessionLocks = new Map();
|
|
26
10
|
export { sessionStore };
|
|
27
11
|
export async function reason(query, level, options) {
|
|
@@ -31,7 +15,7 @@ export async function reason(query, level, options) {
|
|
|
31
15
|
hypothesis !== undefined &&
|
|
32
16
|
evaluation !== undefined);
|
|
33
17
|
if (!hasContent && rollbackToStep === undefined) {
|
|
34
|
-
throw new
|
|
18
|
+
throw new InvalidRunModeArgsError('Either thought (or observation/hypothesis/evaluation) or rollback_to_step is required');
|
|
35
19
|
}
|
|
36
20
|
const session = resolveSession(level, sessionId, query, targetThoughts);
|
|
37
21
|
const config = getLevelConfig(session.level);
|
|
@@ -69,7 +53,7 @@ export async function reason(query, level, options) {
|
|
|
69
53
|
content: addedThought.content,
|
|
70
54
|
});
|
|
71
55
|
const updated = getSessionOrThrow(session.id);
|
|
72
|
-
emitBudgetExhaustedIfNeeded({
|
|
56
|
+
void emitBudgetExhaustedIfNeeded({
|
|
73
57
|
session: updated,
|
|
74
58
|
tokenBudget: config.tokenBudget,
|
|
75
59
|
generatedThoughts: addedThought.index + 1,
|
|
@@ -79,7 +63,7 @@ export async function reason(query, level, options) {
|
|
|
79
63
|
sessionStore.markCompleted(session.id);
|
|
80
64
|
}
|
|
81
65
|
if (onProgress) {
|
|
82
|
-
await onProgress(addedThought.index + 1, totalThoughts);
|
|
66
|
+
await onProgress(addedThought.index + 1, totalThoughts, stepSummary);
|
|
83
67
|
throwIfReasoningAborted(abortSignal);
|
|
84
68
|
}
|
|
85
69
|
return getSessionOrThrow(session.id);
|
|
@@ -98,7 +82,7 @@ async function withSessionLock(sessionId, fn) {
|
|
|
98
82
|
return await fn();
|
|
99
83
|
}
|
|
100
84
|
finally {
|
|
101
|
-
release
|
|
85
|
+
release();
|
|
102
86
|
if (sessionLocks.get(sessionId) === currentTail) {
|
|
103
87
|
sessionLocks.delete(sessionId);
|
|
104
88
|
}
|
|
@@ -107,51 +91,41 @@ async function withSessionLock(sessionId, fn) {
|
|
|
107
91
|
function getSessionOrThrow(sessionId) {
|
|
108
92
|
const session = sessionStore.get(sessionId);
|
|
109
93
|
if (!session) {
|
|
110
|
-
throw new
|
|
94
|
+
throw new SessionNotFoundError(sessionId);
|
|
111
95
|
}
|
|
112
96
|
return session;
|
|
113
97
|
}
|
|
114
|
-
function
|
|
115
|
-
engineEvents.emit('thought:budget-exhausted', data);
|
|
116
|
-
}
|
|
117
|
-
function createBudgetExhaustedPayload(args) {
|
|
98
|
+
function emitBudgetExhaustedIfNeeded(args) {
|
|
118
99
|
const { session, tokenBudget, generatedThoughts, requestedThoughts } = args;
|
|
119
|
-
|
|
100
|
+
if (session.tokensUsed < tokenBudget) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
engineEvents.emit('thought:budget-exhausted', {
|
|
120
104
|
sessionId: session.id,
|
|
121
105
|
tokensUsed: session.tokensUsed,
|
|
122
106
|
tokenBudget,
|
|
123
107
|
generatedThoughts,
|
|
124
108
|
requestedThoughts,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
function emitBudgetExhaustedIfNeeded(args) {
|
|
128
|
-
const { session, tokenBudget } = args;
|
|
129
|
-
if (session.tokensUsed < tokenBudget) {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
emitBudgetExhausted(createBudgetExhaustedPayload(args));
|
|
109
|
+
});
|
|
133
110
|
return true;
|
|
134
111
|
}
|
|
135
|
-
function assertExistingSessionConstraints(existing,
|
|
136
|
-
if (level !== undefined && existing.level !== level) {
|
|
137
|
-
// Warning: ignoring provided level in favor of session level
|
|
138
|
-
}
|
|
112
|
+
function assertExistingSessionConstraints(existing, targetThoughts) {
|
|
139
113
|
if (targetThoughts !== undefined &&
|
|
140
114
|
targetThoughts !== existing.totalThoughts) {
|
|
141
|
-
throw new
|
|
115
|
+
throw new InvalidRunModeArgsError(`Cannot change targetThoughts on an existing session (current: ${String(existing.totalThoughts)}). Omit targetThoughts or pass ${String(existing.totalThoughts)}.`);
|
|
142
116
|
}
|
|
143
117
|
}
|
|
144
118
|
function resolveSession(level, sessionId, query, targetThoughts) {
|
|
145
119
|
if (sessionId) {
|
|
146
120
|
const existing = sessionStore.get(sessionId);
|
|
147
121
|
if (!existing) {
|
|
148
|
-
throw new
|
|
122
|
+
throw new SessionNotFoundError(sessionId);
|
|
149
123
|
}
|
|
150
|
-
assertExistingSessionConstraints(existing,
|
|
124
|
+
assertExistingSessionConstraints(existing, targetThoughts);
|
|
151
125
|
return existing;
|
|
152
126
|
}
|
|
153
127
|
if (level === undefined) {
|
|
154
|
-
throw new
|
|
128
|
+
throw new InvalidRunModeArgsError('level is required for new sessions');
|
|
155
129
|
}
|
|
156
130
|
const config = getLevelConfig(level);
|
|
157
131
|
const totalThoughts = resolveThoughtCount(level, query, config, targetThoughts);
|
|
@@ -162,56 +136,6 @@ function resolveSession(level, sessionId, query, targetThoughts) {
|
|
|
162
136
|
});
|
|
163
137
|
return session;
|
|
164
138
|
}
|
|
165
|
-
function resolveThoughtCount(level, query, config, targetThoughts) {
|
|
166
|
-
if (targetThoughts !== undefined) {
|
|
167
|
-
assertTargetThoughtsInRange(level, targetThoughts);
|
|
168
|
-
return targetThoughts;
|
|
169
|
-
}
|
|
170
|
-
if (config.minThoughts === config.maxThoughts) {
|
|
171
|
-
return config.minThoughts;
|
|
172
|
-
}
|
|
173
|
-
const queryText = query.trim();
|
|
174
|
-
const span = config.maxThoughts - config.minThoughts;
|
|
175
|
-
const queryByteLength = Buffer.byteLength(queryText, 'utf8');
|
|
176
|
-
const lengthScore = Math.min(1, queryByteLength / 400);
|
|
177
|
-
const structureScore = Math.min(0.4, getStructureDensityScore(queryText));
|
|
178
|
-
const keywordScore = COMPLEXITY_KEYWORDS.test(queryText) ? 0.25 : 0;
|
|
179
|
-
const score = Math.min(1, lengthScore + structureScore + keywordScore);
|
|
180
|
-
return config.minThoughts + Math.round(span * score);
|
|
181
|
-
}
|
|
182
|
-
function countSentences(queryText) {
|
|
183
|
-
if (!sentenceSegmenter) {
|
|
184
|
-
return 0;
|
|
185
|
-
}
|
|
186
|
-
let count = 0;
|
|
187
|
-
for (const sentence of sentenceSegmenter.segment(queryText)) {
|
|
188
|
-
if (NON_WHITESPACE.test(sentence.segment)) {
|
|
189
|
-
count++;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return count;
|
|
193
|
-
}
|
|
194
|
-
function getStructureDensityScore(queryText) {
|
|
195
|
-
const sentenceCount = countSentences(queryText);
|
|
196
|
-
if (sentenceCount > 1) {
|
|
197
|
-
return (sentenceCount - 1) * 0.08;
|
|
198
|
-
}
|
|
199
|
-
let markerMatches = 0;
|
|
200
|
-
for (let index = 0; index < queryText.length; index++) {
|
|
201
|
-
switch (queryText.charCodeAt(index)) {
|
|
202
|
-
case 63: // ?
|
|
203
|
-
case 58: // :
|
|
204
|
-
case 59: // ;
|
|
205
|
-
case 44: // ,
|
|
206
|
-
case 10: // \n
|
|
207
|
-
markerMatches += 1;
|
|
208
|
-
break;
|
|
209
|
-
default:
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return markerMatches * 0.05;
|
|
214
|
-
}
|
|
215
139
|
function throwIfReasoningAborted(signal) {
|
|
216
140
|
if (!signal) {
|
|
217
141
|
return;
|
|
@@ -220,6 +144,6 @@ function throwIfReasoningAborted(signal) {
|
|
|
220
144
|
signal.throwIfAborted();
|
|
221
145
|
}
|
|
222
146
|
catch {
|
|
223
|
-
throw new
|
|
147
|
+
throw new ReasoningAbortedError();
|
|
224
148
|
}
|
|
225
149
|
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import type { ReasoningLevel, Session, SessionSummary, Thought } from '../lib/types.js';
|
|
2
|
+
export declare const DEFAULT_SESSION_TTL_MS: number;
|
|
3
|
+
export declare const DEFAULT_MAX_SESSIONS = 100;
|
|
4
|
+
export declare const DEFAULT_MAX_TOTAL_TOKENS = 500000;
|
|
2
5
|
export declare class SessionStore {
|
|
3
6
|
private readonly sessions;
|
|
4
7
|
private readonly sessionOrder;
|
|
5
8
|
private oldestSessionId;
|
|
6
9
|
private newestSessionId;
|
|
7
10
|
private sortedSessionIdsCache;
|
|
8
|
-
private
|
|
11
|
+
private cleanupInterval;
|
|
9
12
|
private readonly ttlMs;
|
|
10
13
|
private readonly maxSessions;
|
|
11
14
|
private readonly maxTotalTokens;
|
|
12
15
|
private totalTokens;
|
|
13
16
|
constructor(ttlMs?: number, maxSessions?: number, maxTotalTokens?: number);
|
|
17
|
+
ensureCleanupTimer(): void;
|
|
18
|
+
dispose(): void;
|
|
14
19
|
create(level: ReasoningLevel, totalThoughts?: number): Readonly<Session>;
|
|
15
20
|
get(id: string): Readonly<Session> | undefined;
|
|
16
21
|
getSummary(id: string): Readonly<SessionSummary> | undefined;
|
|
@@ -40,6 +45,7 @@ export declare class SessionStore {
|
|
|
40
45
|
private deleteSessionInternal;
|
|
41
46
|
private markSessionTouched;
|
|
42
47
|
private getSessionIdsForIteration;
|
|
48
|
+
private collectSessions;
|
|
43
49
|
private snapshotThought;
|
|
44
50
|
private snapshotSession;
|
|
45
51
|
private snapshotSessionSummary;
|
|
@@ -2,9 +2,9 @@ import { Buffer } from 'node:buffer';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { getLevelConfig } from './config.js';
|
|
4
4
|
import { engineEvents } from './events.js';
|
|
5
|
-
const
|
|
6
|
-
const DEFAULT_MAX_SESSIONS = 100;
|
|
7
|
-
const DEFAULT_MAX_TOTAL_TOKENS = 500_000;
|
|
5
|
+
export const DEFAULT_SESSION_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
6
|
+
export const DEFAULT_MAX_SESSIONS = 100;
|
|
7
|
+
export const DEFAULT_MAX_TOTAL_TOKENS = 500_000;
|
|
8
8
|
const TOKEN_ESTIMATE_DIVISOR = 4;
|
|
9
9
|
const MIN_SWEEP_INTERVAL_MS = 10;
|
|
10
10
|
const MAX_SWEEP_INTERVAL_MS = 60_000;
|
|
@@ -12,6 +12,9 @@ function estimateTokens(text) {
|
|
|
12
12
|
const byteLength = Buffer.byteLength(text, 'utf8');
|
|
13
13
|
return Math.max(1, Math.ceil(byteLength / TOKEN_ESTIMATE_DIVISOR));
|
|
14
14
|
}
|
|
15
|
+
function getThoughtTokenCount(thought) {
|
|
16
|
+
return thought.tokenCount ?? estimateTokens(thought.content);
|
|
17
|
+
}
|
|
15
18
|
function resolveSweepInterval(ttlMs) {
|
|
16
19
|
return Math.max(MIN_SWEEP_INTERVAL_MS, Math.min(MAX_SWEEP_INTERVAL_MS, ttlMs));
|
|
17
20
|
}
|
|
@@ -26,16 +29,29 @@ export class SessionStore {
|
|
|
26
29
|
maxSessions;
|
|
27
30
|
maxTotalTokens;
|
|
28
31
|
totalTokens = 0;
|
|
29
|
-
constructor(ttlMs =
|
|
32
|
+
constructor(ttlMs = DEFAULT_SESSION_TTL_MS, maxSessions = DEFAULT_MAX_SESSIONS, maxTotalTokens = DEFAULT_MAX_TOTAL_TOKENS) {
|
|
30
33
|
this.ttlMs = ttlMs;
|
|
31
34
|
this.maxSessions = maxSessions;
|
|
32
35
|
this.maxTotalTokens = maxTotalTokens;
|
|
33
|
-
|
|
36
|
+
this.ensureCleanupTimer();
|
|
37
|
+
}
|
|
38
|
+
ensureCleanupTimer() {
|
|
39
|
+
if (this.cleanupInterval) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const sweepInterval = resolveSweepInterval(this.ttlMs);
|
|
34
43
|
this.cleanupInterval = setInterval(() => {
|
|
35
44
|
this.sweep();
|
|
36
45
|
}, sweepInterval);
|
|
37
46
|
this.cleanupInterval.unref();
|
|
38
47
|
}
|
|
48
|
+
dispose() {
|
|
49
|
+
if (!this.cleanupInterval) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
clearInterval(this.cleanupInterval);
|
|
53
|
+
this.cleanupInterval = undefined;
|
|
54
|
+
}
|
|
39
55
|
create(level, totalThoughts) {
|
|
40
56
|
this.evictIfAtCapacity();
|
|
41
57
|
const config = getLevelConfig(level);
|
|
@@ -67,28 +83,14 @@ export class SessionStore {
|
|
|
67
83
|
return session ? this.snapshotSessionSummary(session) : undefined;
|
|
68
84
|
}
|
|
69
85
|
list() {
|
|
70
|
-
|
|
71
|
-
for (const sessionId of this.getSessionIdsForIteration()) {
|
|
72
|
-
const session = this.sessions.get(sessionId);
|
|
73
|
-
if (session) {
|
|
74
|
-
sessions.push(this.snapshotSession(session));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return sessions;
|
|
86
|
+
return this.collectSessions(this.snapshotSession.bind(this));
|
|
78
87
|
}
|
|
79
88
|
listSessionIds() {
|
|
80
89
|
this.sortedSessionIdsCache ??= this.buildSortedSessionIdsCache();
|
|
81
90
|
return [...this.sortedSessionIdsCache];
|
|
82
91
|
}
|
|
83
92
|
listSummaries() {
|
|
84
|
-
|
|
85
|
-
for (const sessionId of this.getSessionIdsForIteration()) {
|
|
86
|
-
const session = this.sessions.get(sessionId);
|
|
87
|
-
if (session) {
|
|
88
|
-
summaries.push(this.snapshotSessionSummary(session));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return summaries;
|
|
93
|
+
return this.collectSessions(this.snapshotSessionSummary.bind(this));
|
|
92
94
|
}
|
|
93
95
|
getTtlMs() {
|
|
94
96
|
return this.ttlMs;
|
|
@@ -123,6 +125,7 @@ export class SessionStore {
|
|
|
123
125
|
index: session.thoughts.length,
|
|
124
126
|
content,
|
|
125
127
|
revision: 0,
|
|
128
|
+
tokenCount: tokens,
|
|
126
129
|
...(stepSummary !== undefined ? { stepSummary } : {}),
|
|
127
130
|
};
|
|
128
131
|
session.thoughts.push(thought);
|
|
@@ -145,7 +148,7 @@ export class SessionStore {
|
|
|
145
148
|
session.thoughts = session.thoughts.slice(0, toIndex + 1);
|
|
146
149
|
let removedTokens = 0;
|
|
147
150
|
for (const t of removedThoughts) {
|
|
148
|
-
removedTokens +=
|
|
151
|
+
removedTokens += getThoughtTokenCount(t);
|
|
149
152
|
}
|
|
150
153
|
session.tokensUsed -= removedTokens;
|
|
151
154
|
this.totalTokens -= removedTokens;
|
|
@@ -160,7 +163,7 @@ export class SessionStore {
|
|
|
160
163
|
if (!existing) {
|
|
161
164
|
throw new Error(`Thought index ${String(thoughtIndex)} not found in session ${sessionId}`);
|
|
162
165
|
}
|
|
163
|
-
const oldTokens =
|
|
166
|
+
const oldTokens = getThoughtTokenCount(existing);
|
|
164
167
|
const newTokens = estimateTokens(content);
|
|
165
168
|
const delta = newTokens - oldTokens;
|
|
166
169
|
if (delta > 0) {
|
|
@@ -170,12 +173,23 @@ export class SessionStore {
|
|
|
170
173
|
index: thoughtIndex,
|
|
171
174
|
content,
|
|
172
175
|
revision: existing.revision + 1,
|
|
176
|
+
tokenCount: newTokens,
|
|
177
|
+
...(existing.stepSummary !== undefined
|
|
178
|
+
? { stepSummary: existing.stepSummary }
|
|
179
|
+
: {}),
|
|
173
180
|
};
|
|
174
181
|
session.thoughts[thoughtIndex] = revised;
|
|
175
182
|
session.tokensUsed = session.tokensUsed - oldTokens + newTokens;
|
|
176
183
|
this.totalTokens += delta;
|
|
177
184
|
this.markSessionTouched(session);
|
|
178
|
-
|
|
185
|
+
const snapshotted = this.snapshotThought(revised);
|
|
186
|
+
engineEvents.emit('thought:revised', {
|
|
187
|
+
sessionId,
|
|
188
|
+
index: snapshotted.index,
|
|
189
|
+
content: snapshotted.content,
|
|
190
|
+
revision: snapshotted.revision,
|
|
191
|
+
});
|
|
192
|
+
return snapshotted;
|
|
179
193
|
}
|
|
180
194
|
markCompleted(sessionId) {
|
|
181
195
|
this.updateSessionStatus(sessionId, 'completed');
|
|
@@ -344,6 +358,8 @@ export class SessionStore {
|
|
|
344
358
|
}
|
|
345
359
|
markSessionTouched(session) {
|
|
346
360
|
session.updatedAt = Date.now();
|
|
361
|
+
session._cachedSnapshot = undefined;
|
|
362
|
+
session._cachedSummary = undefined;
|
|
347
363
|
this.touchOrder(session.id);
|
|
348
364
|
this.sortedSessionIdsCache = null;
|
|
349
365
|
this.emitSessionResourcesUpdated(session.id);
|
|
@@ -352,8 +368,18 @@ export class SessionStore {
|
|
|
352
368
|
this.sortedSessionIdsCache ??= this.buildSortedSessionIdsCache();
|
|
353
369
|
return this.sortedSessionIdsCache;
|
|
354
370
|
}
|
|
371
|
+
collectSessions(mapSession) {
|
|
372
|
+
const collected = [];
|
|
373
|
+
for (const sessionId of this.getSessionIdsForIteration()) {
|
|
374
|
+
const session = this.sessions.get(sessionId);
|
|
375
|
+
if (session) {
|
|
376
|
+
collected.push(mapSession(session));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return collected;
|
|
380
|
+
}
|
|
355
381
|
snapshotThought(thought) {
|
|
356
|
-
|
|
382
|
+
const t = {
|
|
357
383
|
index: thought.index,
|
|
358
384
|
content: thought.content,
|
|
359
385
|
revision: thought.revision,
|
|
@@ -361,10 +387,15 @@ export class SessionStore {
|
|
|
361
387
|
? { stepSummary: thought.stepSummary }
|
|
362
388
|
: {}),
|
|
363
389
|
};
|
|
390
|
+
Object.freeze(t);
|
|
391
|
+
return t;
|
|
364
392
|
}
|
|
365
393
|
snapshotSession(session) {
|
|
394
|
+
if (session._cachedSnapshot) {
|
|
395
|
+
return session._cachedSnapshot;
|
|
396
|
+
}
|
|
366
397
|
const thoughts = session.thoughts.map((thought) => this.snapshotThought(thought));
|
|
367
|
-
|
|
398
|
+
const snapshot = {
|
|
368
399
|
id: session.id,
|
|
369
400
|
level: session.level,
|
|
370
401
|
status: session.status,
|
|
@@ -375,9 +406,16 @@ export class SessionStore {
|
|
|
375
406
|
createdAt: session.createdAt,
|
|
376
407
|
updatedAt: session.updatedAt,
|
|
377
408
|
};
|
|
409
|
+
Object.freeze(snapshot);
|
|
410
|
+
Object.freeze(snapshot.thoughts);
|
|
411
|
+
session._cachedSnapshot = snapshot;
|
|
412
|
+
return snapshot;
|
|
378
413
|
}
|
|
379
414
|
snapshotSessionSummary(session) {
|
|
380
|
-
|
|
415
|
+
if (session._cachedSummary) {
|
|
416
|
+
return session._cachedSummary;
|
|
417
|
+
}
|
|
418
|
+
const summary = {
|
|
381
419
|
id: session.id,
|
|
382
420
|
level: session.level,
|
|
383
421
|
status: session.status,
|
|
@@ -388,6 +426,9 @@ export class SessionStore {
|
|
|
388
426
|
createdAt: session.createdAt,
|
|
389
427
|
updatedAt: session.updatedAt,
|
|
390
428
|
};
|
|
429
|
+
Object.freeze(summary);
|
|
430
|
+
session._cachedSummary = summary;
|
|
431
|
+
return summary;
|
|
391
432
|
}
|
|
392
433
|
emitSessionsListChanged() {
|
|
393
434
|
engineEvents.emit('resources:changed', { uri: 'reasoning://sessions' });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function createTaskLimiter(maxActiveTasks) {
|
|
2
|
+
let activeTasks = 0;
|
|
3
|
+
return {
|
|
4
|
+
tryAcquire() {
|
|
5
|
+
if (activeTasks >= maxActiveTasks) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
activeTasks += 1;
|
|
9
|
+
return true;
|
|
10
|
+
},
|
|
11
|
+
release() {
|
|
12
|
+
if (activeTasks > 0) {
|
|
13
|
+
activeTasks -= 1;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -4,15 +4,31 @@ interface ErrorResponse {
|
|
|
4
4
|
type: 'text';
|
|
5
5
|
text: string;
|
|
6
6
|
}[];
|
|
7
|
-
structuredContent: {
|
|
8
|
-
ok: false;
|
|
9
|
-
error: {
|
|
10
|
-
code: string;
|
|
11
|
-
message: string;
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
7
|
isError: true;
|
|
15
8
|
}
|
|
9
|
+
export declare class ReasoningError extends Error {
|
|
10
|
+
readonly code: string;
|
|
11
|
+
constructor(code: string, message: string);
|
|
12
|
+
}
|
|
13
|
+
export declare class SessionNotFoundError extends ReasoningError {
|
|
14
|
+
constructor(sessionId: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class InvalidThoughtCountError extends ReasoningError {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
export declare class InsufficientThoughtsError extends ReasoningError {
|
|
20
|
+
constructor(message: string);
|
|
21
|
+
}
|
|
22
|
+
export declare class InvalidRunModeArgsError extends ReasoningError {
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
export declare class ReasoningAbortedError extends ReasoningError {
|
|
26
|
+
constructor(message?: string);
|
|
27
|
+
}
|
|
28
|
+
export declare class ServerBusyError extends ReasoningError {
|
|
29
|
+
constructor(message?: string);
|
|
30
|
+
}
|
|
31
|
+
export declare function isObjectRecord(value: unknown): value is Record<string, unknown>;
|
|
16
32
|
export declare function getErrorMessage(error: unknown): string;
|
|
17
33
|
export declare function createErrorResponse(code: string, message: string): ErrorResponse;
|
|
18
34
|
export {};
|