@j0hanz/cortex-mcp 1.4.0 → 1.5.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.js +21 -97
- package/dist/engine/session-store.d.ts +4 -0
- package/dist/engine/session-store.js +54 -26
- 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 +91 -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 +7 -1
- package/dist/lib/validators.js +30 -7
- package/dist/prompts/index.js +132 -47
- package/dist/prompts/templates.d.ts +2 -0
- package/dist/prompts/templates.js +106 -0
- package/dist/resources/index.js +14 -60
- package/dist/resources/instructions.js +10 -2
- package/dist/resources/workflows.js +4 -3
- package/dist/schemas/inputs.js +2 -2
- package/dist/schemas/outputs.d.ts +29 -25
- package/dist/schemas/outputs.js +13 -13
- package/dist/server.js +1 -3
- package/dist/tools/reasoning-think.js +106 -149
- 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
|
+
}
|
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,
|
|
@@ -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,4 +1,7 @@
|
|
|
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;
|
|
@@ -40,6 +43,7 @@ export declare class SessionStore {
|
|
|
40
43
|
private deleteSessionInternal;
|
|
41
44
|
private markSessionTouched;
|
|
42
45
|
private getSessionIdsForIteration;
|
|
46
|
+
private collectSessions;
|
|
43
47
|
private snapshotThought;
|
|
44
48
|
private snapshotSession;
|
|
45
49
|
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,7 +29,7 @@ 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;
|
|
@@ -67,28 +70,14 @@ export class SessionStore {
|
|
|
67
70
|
return session ? this.snapshotSessionSummary(session) : undefined;
|
|
68
71
|
}
|
|
69
72
|
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;
|
|
73
|
+
return this.collectSessions(this.snapshotSession.bind(this));
|
|
78
74
|
}
|
|
79
75
|
listSessionIds() {
|
|
80
76
|
this.sortedSessionIdsCache ??= this.buildSortedSessionIdsCache();
|
|
81
77
|
return [...this.sortedSessionIdsCache];
|
|
82
78
|
}
|
|
83
79
|
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;
|
|
80
|
+
return this.collectSessions(this.snapshotSessionSummary.bind(this));
|
|
92
81
|
}
|
|
93
82
|
getTtlMs() {
|
|
94
83
|
return this.ttlMs;
|
|
@@ -123,6 +112,7 @@ export class SessionStore {
|
|
|
123
112
|
index: session.thoughts.length,
|
|
124
113
|
content,
|
|
125
114
|
revision: 0,
|
|
115
|
+
tokenCount: tokens,
|
|
126
116
|
...(stepSummary !== undefined ? { stepSummary } : {}),
|
|
127
117
|
};
|
|
128
118
|
session.thoughts.push(thought);
|
|
@@ -145,7 +135,7 @@ export class SessionStore {
|
|
|
145
135
|
session.thoughts = session.thoughts.slice(0, toIndex + 1);
|
|
146
136
|
let removedTokens = 0;
|
|
147
137
|
for (const t of removedThoughts) {
|
|
148
|
-
removedTokens +=
|
|
138
|
+
removedTokens += getThoughtTokenCount(t);
|
|
149
139
|
}
|
|
150
140
|
session.tokensUsed -= removedTokens;
|
|
151
141
|
this.totalTokens -= removedTokens;
|
|
@@ -160,7 +150,7 @@ export class SessionStore {
|
|
|
160
150
|
if (!existing) {
|
|
161
151
|
throw new Error(`Thought index ${String(thoughtIndex)} not found in session ${sessionId}`);
|
|
162
152
|
}
|
|
163
|
-
const oldTokens =
|
|
153
|
+
const oldTokens = getThoughtTokenCount(existing);
|
|
164
154
|
const newTokens = estimateTokens(content);
|
|
165
155
|
const delta = newTokens - oldTokens;
|
|
166
156
|
if (delta > 0) {
|
|
@@ -170,12 +160,23 @@ export class SessionStore {
|
|
|
170
160
|
index: thoughtIndex,
|
|
171
161
|
content,
|
|
172
162
|
revision: existing.revision + 1,
|
|
163
|
+
tokenCount: newTokens,
|
|
164
|
+
...(existing.stepSummary !== undefined
|
|
165
|
+
? { stepSummary: existing.stepSummary }
|
|
166
|
+
: {}),
|
|
173
167
|
};
|
|
174
168
|
session.thoughts[thoughtIndex] = revised;
|
|
175
169
|
session.tokensUsed = session.tokensUsed - oldTokens + newTokens;
|
|
176
170
|
this.totalTokens += delta;
|
|
177
171
|
this.markSessionTouched(session);
|
|
178
|
-
|
|
172
|
+
const snapshotted = this.snapshotThought(revised);
|
|
173
|
+
engineEvents.emit('thought:revised', {
|
|
174
|
+
sessionId,
|
|
175
|
+
index: snapshotted.index,
|
|
176
|
+
content: snapshotted.content,
|
|
177
|
+
revision: snapshotted.revision,
|
|
178
|
+
});
|
|
179
|
+
return snapshotted;
|
|
179
180
|
}
|
|
180
181
|
markCompleted(sessionId) {
|
|
181
182
|
this.updateSessionStatus(sessionId, 'completed');
|
|
@@ -344,6 +345,8 @@ export class SessionStore {
|
|
|
344
345
|
}
|
|
345
346
|
markSessionTouched(session) {
|
|
346
347
|
session.updatedAt = Date.now();
|
|
348
|
+
session._cachedSnapshot = undefined;
|
|
349
|
+
session._cachedSummary = undefined;
|
|
347
350
|
this.touchOrder(session.id);
|
|
348
351
|
this.sortedSessionIdsCache = null;
|
|
349
352
|
this.emitSessionResourcesUpdated(session.id);
|
|
@@ -352,8 +355,18 @@ export class SessionStore {
|
|
|
352
355
|
this.sortedSessionIdsCache ??= this.buildSortedSessionIdsCache();
|
|
353
356
|
return this.sortedSessionIdsCache;
|
|
354
357
|
}
|
|
358
|
+
collectSessions(mapSession) {
|
|
359
|
+
const collected = [];
|
|
360
|
+
for (const sessionId of this.getSessionIdsForIteration()) {
|
|
361
|
+
const session = this.sessions.get(sessionId);
|
|
362
|
+
if (session) {
|
|
363
|
+
collected.push(mapSession(session));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return collected;
|
|
367
|
+
}
|
|
355
368
|
snapshotThought(thought) {
|
|
356
|
-
|
|
369
|
+
const t = {
|
|
357
370
|
index: thought.index,
|
|
358
371
|
content: thought.content,
|
|
359
372
|
revision: thought.revision,
|
|
@@ -361,10 +374,15 @@ export class SessionStore {
|
|
|
361
374
|
? { stepSummary: thought.stepSummary }
|
|
362
375
|
: {}),
|
|
363
376
|
};
|
|
377
|
+
Object.freeze(t);
|
|
378
|
+
return t;
|
|
364
379
|
}
|
|
365
380
|
snapshotSession(session) {
|
|
381
|
+
if (session._cachedSnapshot) {
|
|
382
|
+
return session._cachedSnapshot;
|
|
383
|
+
}
|
|
366
384
|
const thoughts = session.thoughts.map((thought) => this.snapshotThought(thought));
|
|
367
|
-
|
|
385
|
+
const snapshot = {
|
|
368
386
|
id: session.id,
|
|
369
387
|
level: session.level,
|
|
370
388
|
status: session.status,
|
|
@@ -375,9 +393,16 @@ export class SessionStore {
|
|
|
375
393
|
createdAt: session.createdAt,
|
|
376
394
|
updatedAt: session.updatedAt,
|
|
377
395
|
};
|
|
396
|
+
Object.freeze(snapshot);
|
|
397
|
+
Object.freeze(snapshot.thoughts);
|
|
398
|
+
session._cachedSnapshot = snapshot;
|
|
399
|
+
return snapshot;
|
|
378
400
|
}
|
|
379
401
|
snapshotSessionSummary(session) {
|
|
380
|
-
|
|
402
|
+
if (session._cachedSummary) {
|
|
403
|
+
return session._cachedSummary;
|
|
404
|
+
}
|
|
405
|
+
const summary = {
|
|
381
406
|
id: session.id,
|
|
382
407
|
level: session.level,
|
|
383
408
|
status: session.status,
|
|
@@ -388,6 +413,9 @@ export class SessionStore {
|
|
|
388
413
|
createdAt: session.createdAt,
|
|
389
414
|
updatedAt: session.updatedAt,
|
|
390
415
|
};
|
|
416
|
+
Object.freeze(summary);
|
|
417
|
+
session._cachedSummary = summary;
|
|
418
|
+
return summary;
|
|
391
419
|
}
|
|
392
420
|
emitSessionsListChanged() {
|
|
393
421
|
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 {};
|