@j0hanz/cortex-mcp 1.3.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 +5 -5
- package/dist/engine/config.js +14 -5
- package/dist/engine/context.d.ts +0 -2
- package/dist/engine/context.js +0 -4
- package/dist/engine/events.d.ts +1 -2
- package/dist/engine/events.js +2 -4
- package/dist/engine/heuristics.d.ts +4 -0
- package/dist/engine/heuristics.js +65 -0
- package/dist/engine/reasoner.d.ts +7 -2
- package/dist/engine/reasoner.js +41 -103
- package/dist/engine/session-store.d.ts +6 -2
- package/dist/engine/session-store.js +78 -27
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/lib/concurrency.d.ts +5 -0
- package/dist/lib/concurrency.js +17 -0
- package/dist/lib/errors.d.ts +23 -8
- package/dist/lib/errors.js +47 -6
- package/dist/lib/formatting.d.ts +13 -1
- package/dist/lib/formatting.js +18 -1
- package/dist/lib/prompt-contracts.d.ts +6 -0
- package/dist/lib/prompt-contracts.js +35 -0
- package/dist/lib/text.d.ts +0 -1
- package/dist/lib/text.js +0 -1
- package/dist/lib/tool-contracts.d.ts +15 -0
- package/dist/lib/tool-contracts.js +93 -0
- package/dist/lib/tool-response.d.ts +4 -1
- package/dist/lib/tool-response.js +3 -1
- package/dist/lib/types.d.ts +18 -1
- package/dist/lib/types.js +8 -2
- package/dist/lib/validators.d.ts +7 -2
- package/dist/lib/validators.js +30 -8
- package/dist/prompts/index.d.ts +0 -1
- package/dist/prompts/index.js +171 -74
- package/dist/prompts/templates.d.ts +2 -0
- package/dist/prompts/templates.js +106 -0
- package/dist/resources/index.d.ts +0 -1
- package/dist/resources/index.js +40 -63
- package/dist/resources/instructions.d.ts +1 -0
- package/dist/resources/instructions.js +103 -0
- package/dist/resources/tool-catalog.d.ts +1 -0
- package/dist/resources/tool-catalog.js +20 -0
- package/dist/resources/tool-info.d.ts +2 -0
- package/dist/resources/tool-info.js +35 -0
- package/dist/resources/workflows.d.ts +1 -0
- package/dist/resources/workflows.js +61 -0
- package/dist/schemas/inputs.d.ts +7 -2
- package/dist/schemas/inputs.js +39 -4
- package/dist/schemas/outputs.d.ts +32 -26
- package/dist/schemas/outputs.js +17 -14
- package/dist/server.d.ts +0 -1
- package/dist/server.js +1 -4
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/tools/reasoning-think.d.ts +0 -1
- package/dist/tools/reasoning-think.js +211 -118
- package/package.json +9 -10
- package/dist/engine/config.d.ts.map +0 -1
- package/dist/engine/config.js.map +0 -1
- package/dist/engine/context.d.ts.map +0 -1
- package/dist/engine/context.js.map +0 -1
- package/dist/engine/events.d.ts.map +0 -1
- package/dist/engine/events.js.map +0 -1
- package/dist/engine/reasoner.d.ts.map +0 -1
- package/dist/engine/reasoner.js.map +0 -1
- package/dist/engine/session-store.d.ts.map +0 -1
- package/dist/engine/session-store.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/instructions.md +0 -148
- package/dist/lib/errors.d.ts.map +0 -1
- package/dist/lib/errors.js.map +0 -1
- package/dist/lib/formatting.d.ts.map +0 -1
- package/dist/lib/formatting.js.map +0 -1
- package/dist/lib/instructions.d.ts +0 -5
- package/dist/lib/instructions.d.ts.map +0 -1
- package/dist/lib/instructions.js +0 -30
- package/dist/lib/instructions.js.map +0 -1
- package/dist/lib/text.d.ts.map +0 -1
- package/dist/lib/text.js.map +0 -1
- package/dist/lib/tool-response.d.ts.map +0 -1
- package/dist/lib/tool-response.js.map +0 -1
- package/dist/lib/types.d.ts.map +0 -1
- package/dist/lib/types.js.map +0 -1
- package/dist/lib/validators.d.ts.map +0 -1
- package/dist/lib/validators.js.map +0 -1
- package/dist/prompts/index.d.ts.map +0 -1
- package/dist/prompts/index.js.map +0 -1
- package/dist/resources/index.d.ts.map +0 -1
- package/dist/resources/index.js.map +0 -1
- package/dist/schemas/inputs.d.ts.map +0 -1
- package/dist/schemas/inputs.js.map +0 -1
- package/dist/schemas/outputs.d.ts.map +0 -1
- package/dist/schemas/outputs.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/reasoning-think.d.ts.map +0 -1
- package/dist/tools/reasoning-think.js.map +0 -1
package/dist/engine/config.d.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
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;
|
|
20
20
|
export declare function assertTargetThoughtsInRange(level: ReasoningLevel, targetThoughts: number): void;
|
|
21
|
-
|
|
21
|
+
export declare function getLevelDescriptionString(): string;
|
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,6 +14,13 @@ export function assertTargetThoughtsInRange(level, targetThoughts) {
|
|
|
12
14
|
if (!errorMessage) {
|
|
13
15
|
return;
|
|
14
16
|
}
|
|
15
|
-
throw new
|
|
17
|
+
throw new InvalidThoughtCountError(errorMessage);
|
|
18
|
+
}
|
|
19
|
+
export function getLevelDescriptionString() {
|
|
20
|
+
return Object.entries(LEVEL_CONFIGS)
|
|
21
|
+
.map(([level, config]) => {
|
|
22
|
+
const budgetK = Math.round(config.tokenBudget / 1024);
|
|
23
|
+
return `${level} (${config.minThoughts}–${config.maxThoughts} steps, ${budgetK}K budget)`;
|
|
24
|
+
})
|
|
25
|
+
.join(', ');
|
|
16
26
|
}
|
|
17
|
-
//# sourceMappingURL=config.js.map
|
package/dist/engine/context.d.ts
CHANGED
package/dist/engine/context.js
CHANGED
package/dist/engine/events.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import type { ReasoningLevel } from '../lib/types.js';
|
|
3
|
-
interface ThoughtBudgetExhaustedPayload {
|
|
3
|
+
export interface ThoughtBudgetExhaustedPayload {
|
|
4
4
|
sessionId: string;
|
|
5
5
|
tokensUsed: number;
|
|
6
6
|
tokenBudget: number;
|
|
@@ -51,4 +51,3 @@ interface TypedEmitter<T> extends Omit<EventEmitter, 'on' | 'off' | 'emit'> {
|
|
|
51
51
|
}
|
|
52
52
|
export declare const engineEvents: TypedEmitter<EngineEvents>;
|
|
53
53
|
export {};
|
|
54
|
-
//# sourceMappingURL=events.d.ts.map
|
package/dist/engine/events.js
CHANGED
|
@@ -1,12 +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);
|
|
12
|
-
//# sourceMappingURL=events.js.map
|
|
@@ -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
|
+
}
|
|
@@ -5,9 +5,14 @@ export { sessionStore };
|
|
|
5
5
|
interface ReasonOptions {
|
|
6
6
|
sessionId?: string;
|
|
7
7
|
targetThoughts?: number;
|
|
8
|
-
thought
|
|
8
|
+
thought?: string;
|
|
9
|
+
observation?: string;
|
|
10
|
+
hypothesis?: string;
|
|
11
|
+
evaluation?: string;
|
|
12
|
+
stepSummary?: string;
|
|
13
|
+
isConclusion?: boolean;
|
|
14
|
+
rollbackToStep?: number;
|
|
9
15
|
abortSignal?: AbortSignal;
|
|
10
16
|
onProgress?: (progress: number, total: number) => void | Promise<void>;
|
|
11
17
|
}
|
|
12
18
|
export declare function reason(query: string, level: ReasoningLevel | undefined, options?: ReasonOptions): Promise<Readonly<Session>>;
|
|
13
|
-
//# sourceMappingURL=reasoner.d.ts.map
|
package/dist/engine/reasoner.js
CHANGED
|
@@ -1,40 +1,39 @@
|
|
|
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) {
|
|
28
|
-
|
|
29
|
-
|
|
12
|
+
const { sessionId, targetThoughts, thought, observation, hypothesis, evaluation, stepSummary, isConclusion, rollbackToStep, abortSignal, onProgress, } = options ?? {};
|
|
13
|
+
const hasContent = thought !== undefined ||
|
|
14
|
+
(observation !== undefined &&
|
|
15
|
+
hypothesis !== undefined &&
|
|
16
|
+
evaluation !== undefined);
|
|
17
|
+
if (!hasContent && rollbackToStep === undefined) {
|
|
18
|
+
throw new InvalidRunModeArgsError('Either thought (or observation/hypothesis/evaluation) or rollback_to_step is required');
|
|
30
19
|
}
|
|
31
|
-
const { sessionId, targetThoughts, thought, abortSignal, onProgress } = options;
|
|
32
20
|
const session = resolveSession(level, sessionId, query, targetThoughts);
|
|
33
21
|
const config = getLevelConfig(session.level);
|
|
34
22
|
const { totalThoughts } = session;
|
|
35
23
|
return runWithContext({ sessionId: session.id, ...(abortSignal ? { abortSignal } : {}) }, () => withSessionLock(session.id, async () => {
|
|
36
24
|
throwIfReasoningAborted(abortSignal);
|
|
25
|
+
if (rollbackToStep !== undefined) {
|
|
26
|
+
sessionStore.rollback(session.id, rollbackToStep);
|
|
27
|
+
}
|
|
37
28
|
const current = getSessionOrThrow(session.id);
|
|
29
|
+
let content = thought;
|
|
30
|
+
if (!content && observation) {
|
|
31
|
+
content = `**Observation:** ${observation}\n\n**Hypothesis:** ${hypothesis ?? ''}\n\n**Evaluation:** ${evaluation ?? ''}`;
|
|
32
|
+
}
|
|
33
|
+
if (!content) {
|
|
34
|
+
// Only rollback occurred
|
|
35
|
+
return current;
|
|
36
|
+
}
|
|
38
37
|
if (emitBudgetExhaustedIfNeeded({
|
|
39
38
|
session: current,
|
|
40
39
|
tokenBudget: config.tokenBudget,
|
|
@@ -44,23 +43,23 @@ export async function reason(query, level, options) {
|
|
|
44
43
|
return current;
|
|
45
44
|
}
|
|
46
45
|
const nextIndex = current.thoughts.length;
|
|
47
|
-
if (nextIndex >= totalThoughts) {
|
|
46
|
+
if (nextIndex >= totalThoughts && !isConclusion) {
|
|
48
47
|
return current;
|
|
49
48
|
}
|
|
50
|
-
const addedThought = sessionStore.addThought(session.id,
|
|
49
|
+
const addedThought = sessionStore.addThought(session.id, content, stepSummary);
|
|
51
50
|
engineEvents.emit('thought:added', {
|
|
52
51
|
sessionId: session.id,
|
|
53
52
|
index: addedThought.index,
|
|
54
53
|
content: addedThought.content,
|
|
55
54
|
});
|
|
56
55
|
const updated = getSessionOrThrow(session.id);
|
|
57
|
-
emitBudgetExhaustedIfNeeded({
|
|
56
|
+
void emitBudgetExhaustedIfNeeded({
|
|
58
57
|
session: updated,
|
|
59
58
|
tokenBudget: config.tokenBudget,
|
|
60
59
|
generatedThoughts: addedThought.index + 1,
|
|
61
60
|
requestedThoughts: totalThoughts,
|
|
62
61
|
});
|
|
63
|
-
if (updated.thoughts.length >= totalThoughts) {
|
|
62
|
+
if (isConclusion || updated.thoughts.length >= totalThoughts) {
|
|
64
63
|
sessionStore.markCompleted(session.id);
|
|
65
64
|
}
|
|
66
65
|
if (onProgress) {
|
|
@@ -83,7 +82,7 @@ async function withSessionLock(sessionId, fn) {
|
|
|
83
82
|
return await fn();
|
|
84
83
|
}
|
|
85
84
|
finally {
|
|
86
|
-
release
|
|
85
|
+
release();
|
|
87
86
|
if (sessionLocks.get(sessionId) === currentTail) {
|
|
88
87
|
sessionLocks.delete(sessionId);
|
|
89
88
|
}
|
|
@@ -92,51 +91,41 @@ async function withSessionLock(sessionId, fn) {
|
|
|
92
91
|
function getSessionOrThrow(sessionId) {
|
|
93
92
|
const session = sessionStore.get(sessionId);
|
|
94
93
|
if (!session) {
|
|
95
|
-
throw new
|
|
94
|
+
throw new SessionNotFoundError(sessionId);
|
|
96
95
|
}
|
|
97
96
|
return session;
|
|
98
97
|
}
|
|
99
|
-
function
|
|
100
|
-
engineEvents.emit('thought:budget-exhausted', data);
|
|
101
|
-
}
|
|
102
|
-
function createBudgetExhaustedPayload(args) {
|
|
98
|
+
function emitBudgetExhaustedIfNeeded(args) {
|
|
103
99
|
const { session, tokenBudget, generatedThoughts, requestedThoughts } = args;
|
|
104
|
-
|
|
100
|
+
if (session.tokensUsed < tokenBudget) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
engineEvents.emit('thought:budget-exhausted', {
|
|
105
104
|
sessionId: session.id,
|
|
106
105
|
tokensUsed: session.tokensUsed,
|
|
107
106
|
tokenBudget,
|
|
108
107
|
generatedThoughts,
|
|
109
108
|
requestedThoughts,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
function emitBudgetExhaustedIfNeeded(args) {
|
|
113
|
-
const { session, tokenBudget } = args;
|
|
114
|
-
if (session.tokensUsed < tokenBudget) {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
emitBudgetExhausted(createBudgetExhaustedPayload(args));
|
|
109
|
+
});
|
|
118
110
|
return true;
|
|
119
111
|
}
|
|
120
|
-
function assertExistingSessionConstraints(existing,
|
|
121
|
-
if (level !== undefined && existing.level !== level) {
|
|
122
|
-
// Warning: ignoring provided level in favor of session level
|
|
123
|
-
}
|
|
112
|
+
function assertExistingSessionConstraints(existing, targetThoughts) {
|
|
124
113
|
if (targetThoughts !== undefined &&
|
|
125
114
|
targetThoughts !== existing.totalThoughts) {
|
|
126
|
-
throw new
|
|
115
|
+
throw new InvalidRunModeArgsError(`Cannot change targetThoughts on an existing session (current: ${String(existing.totalThoughts)}). Omit targetThoughts or pass ${String(existing.totalThoughts)}.`);
|
|
127
116
|
}
|
|
128
117
|
}
|
|
129
118
|
function resolveSession(level, sessionId, query, targetThoughts) {
|
|
130
119
|
if (sessionId) {
|
|
131
120
|
const existing = sessionStore.get(sessionId);
|
|
132
121
|
if (!existing) {
|
|
133
|
-
throw new
|
|
122
|
+
throw new SessionNotFoundError(sessionId);
|
|
134
123
|
}
|
|
135
|
-
assertExistingSessionConstraints(existing,
|
|
124
|
+
assertExistingSessionConstraints(existing, targetThoughts);
|
|
136
125
|
return existing;
|
|
137
126
|
}
|
|
138
127
|
if (level === undefined) {
|
|
139
|
-
throw new
|
|
128
|
+
throw new InvalidRunModeArgsError('level is required for new sessions');
|
|
140
129
|
}
|
|
141
130
|
const config = getLevelConfig(level);
|
|
142
131
|
const totalThoughts = resolveThoughtCount(level, query, config, targetThoughts);
|
|
@@ -147,56 +136,6 @@ function resolveSession(level, sessionId, query, targetThoughts) {
|
|
|
147
136
|
});
|
|
148
137
|
return session;
|
|
149
138
|
}
|
|
150
|
-
function resolveThoughtCount(level, query, config, targetThoughts) {
|
|
151
|
-
if (targetThoughts !== undefined) {
|
|
152
|
-
assertTargetThoughtsInRange(level, targetThoughts);
|
|
153
|
-
return targetThoughts;
|
|
154
|
-
}
|
|
155
|
-
if (config.minThoughts === config.maxThoughts) {
|
|
156
|
-
return config.minThoughts;
|
|
157
|
-
}
|
|
158
|
-
const queryText = query.trim();
|
|
159
|
-
const span = config.maxThoughts - config.minThoughts;
|
|
160
|
-
const queryByteLength = Buffer.byteLength(queryText, 'utf8');
|
|
161
|
-
const lengthScore = Math.min(1, queryByteLength / 400);
|
|
162
|
-
const structureScore = Math.min(0.4, getStructureDensityScore(queryText));
|
|
163
|
-
const keywordScore = COMPLEXITY_KEYWORDS.test(queryText) ? 0.25 : 0;
|
|
164
|
-
const score = Math.min(1, lengthScore + structureScore + keywordScore);
|
|
165
|
-
return config.minThoughts + Math.round(span * score);
|
|
166
|
-
}
|
|
167
|
-
function countSentences(queryText) {
|
|
168
|
-
if (!sentenceSegmenter) {
|
|
169
|
-
return 0;
|
|
170
|
-
}
|
|
171
|
-
let count = 0;
|
|
172
|
-
for (const sentence of sentenceSegmenter.segment(queryText)) {
|
|
173
|
-
if (NON_WHITESPACE.test(sentence.segment)) {
|
|
174
|
-
count++;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return count;
|
|
178
|
-
}
|
|
179
|
-
function getStructureDensityScore(queryText) {
|
|
180
|
-
const sentenceCount = countSentences(queryText);
|
|
181
|
-
if (sentenceCount > 1) {
|
|
182
|
-
return (sentenceCount - 1) * 0.08;
|
|
183
|
-
}
|
|
184
|
-
let markerMatches = 0;
|
|
185
|
-
for (let index = 0; index < queryText.length; index++) {
|
|
186
|
-
switch (queryText.charCodeAt(index)) {
|
|
187
|
-
case 63: // ?
|
|
188
|
-
case 58: // :
|
|
189
|
-
case 59: // ;
|
|
190
|
-
case 44: // ,
|
|
191
|
-
case 10: // \n
|
|
192
|
-
markerMatches += 1;
|
|
193
|
-
break;
|
|
194
|
-
default:
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return markerMatches * 0.05;
|
|
199
|
-
}
|
|
200
139
|
function throwIfReasoningAborted(signal) {
|
|
201
140
|
if (!signal) {
|
|
202
141
|
return;
|
|
@@ -205,7 +144,6 @@ function throwIfReasoningAborted(signal) {
|
|
|
205
144
|
signal.throwIfAborted();
|
|
206
145
|
}
|
|
207
146
|
catch {
|
|
208
|
-
throw new
|
|
147
|
+
throw new ReasoningAbortedError();
|
|
209
148
|
}
|
|
210
149
|
}
|
|
211
|
-
//# sourceMappingURL=reasoner.js.map
|
|
@@ -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;
|
|
@@ -21,7 +24,8 @@ export declare class SessionStore {
|
|
|
21
24
|
getExpiresAt(sessionId: string): number | undefined;
|
|
22
25
|
getTotalTokensUsed(): number;
|
|
23
26
|
delete(id: string): boolean;
|
|
24
|
-
addThought(sessionId: string, content: string): Thought;
|
|
27
|
+
addThought(sessionId: string, content: string, stepSummary?: string): Thought;
|
|
28
|
+
rollback(sessionId: string, toIndex: number): void;
|
|
25
29
|
reviseThought(sessionId: string, thoughtIndex: number, content: string): Thought;
|
|
26
30
|
markCompleted(sessionId: string): void;
|
|
27
31
|
markCancelled(sessionId: string): void;
|
|
@@ -39,6 +43,7 @@ export declare class SessionStore {
|
|
|
39
43
|
private deleteSessionInternal;
|
|
40
44
|
private markSessionTouched;
|
|
41
45
|
private getSessionIdsForIteration;
|
|
46
|
+
private collectSessions;
|
|
42
47
|
private snapshotThought;
|
|
43
48
|
private snapshotSession;
|
|
44
49
|
private snapshotSessionSummary;
|
|
@@ -48,4 +53,3 @@ export declare class SessionStore {
|
|
|
48
53
|
private emitSessionEvicted;
|
|
49
54
|
private emitSessionResourcesUpdated;
|
|
50
55
|
}
|
|
51
|
-
//# sourceMappingURL=session-store.d.ts.map
|
|
@@ -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;
|
|
@@ -112,7 +101,7 @@ export class SessionStore {
|
|
|
112
101
|
this.emitSessionsCollectionUpdated();
|
|
113
102
|
return true;
|
|
114
103
|
}
|
|
115
|
-
addThought(sessionId, content) {
|
|
104
|
+
addThought(sessionId, content, stepSummary) {
|
|
116
105
|
const session = this.sessions.get(sessionId);
|
|
117
106
|
if (!session) {
|
|
118
107
|
throw new Error(`Session not found: ${sessionId}`);
|
|
@@ -123,6 +112,8 @@ export class SessionStore {
|
|
|
123
112
|
index: session.thoughts.length,
|
|
124
113
|
content,
|
|
125
114
|
revision: 0,
|
|
115
|
+
tokenCount: tokens,
|
|
116
|
+
...(stepSummary !== undefined ? { stepSummary } : {}),
|
|
126
117
|
};
|
|
127
118
|
session.thoughts.push(thought);
|
|
128
119
|
session.tokensUsed += tokens;
|
|
@@ -130,6 +121,26 @@ export class SessionStore {
|
|
|
130
121
|
this.markSessionTouched(session);
|
|
131
122
|
return this.snapshotThought(thought);
|
|
132
123
|
}
|
|
124
|
+
rollback(sessionId, toIndex) {
|
|
125
|
+
const session = this.sessions.get(sessionId);
|
|
126
|
+
if (!session) {
|
|
127
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
128
|
+
}
|
|
129
|
+
// If toIndex is out of bounds or implies no change, return.
|
|
130
|
+
// We keep thoughts up to and including toIndex.
|
|
131
|
+
if (toIndex < 0 || toIndex >= session.thoughts.length - 1) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const removedThoughts = session.thoughts.slice(toIndex + 1);
|
|
135
|
+
session.thoughts = session.thoughts.slice(0, toIndex + 1);
|
|
136
|
+
let removedTokens = 0;
|
|
137
|
+
for (const t of removedThoughts) {
|
|
138
|
+
removedTokens += getThoughtTokenCount(t);
|
|
139
|
+
}
|
|
140
|
+
session.tokensUsed -= removedTokens;
|
|
141
|
+
this.totalTokens -= removedTokens;
|
|
142
|
+
this.markSessionTouched(session);
|
|
143
|
+
}
|
|
133
144
|
reviseThought(sessionId, thoughtIndex, content) {
|
|
134
145
|
const session = this.sessions.get(sessionId);
|
|
135
146
|
if (!session) {
|
|
@@ -139,7 +150,7 @@ export class SessionStore {
|
|
|
139
150
|
if (!existing) {
|
|
140
151
|
throw new Error(`Thought index ${String(thoughtIndex)} not found in session ${sessionId}`);
|
|
141
152
|
}
|
|
142
|
-
const oldTokens =
|
|
153
|
+
const oldTokens = getThoughtTokenCount(existing);
|
|
143
154
|
const newTokens = estimateTokens(content);
|
|
144
155
|
const delta = newTokens - oldTokens;
|
|
145
156
|
if (delta > 0) {
|
|
@@ -149,12 +160,23 @@ export class SessionStore {
|
|
|
149
160
|
index: thoughtIndex,
|
|
150
161
|
content,
|
|
151
162
|
revision: existing.revision + 1,
|
|
163
|
+
tokenCount: newTokens,
|
|
164
|
+
...(existing.stepSummary !== undefined
|
|
165
|
+
? { stepSummary: existing.stepSummary }
|
|
166
|
+
: {}),
|
|
152
167
|
};
|
|
153
168
|
session.thoughts[thoughtIndex] = revised;
|
|
154
169
|
session.tokensUsed = session.tokensUsed - oldTokens + newTokens;
|
|
155
170
|
this.totalTokens += delta;
|
|
156
171
|
this.markSessionTouched(session);
|
|
157
|
-
|
|
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;
|
|
158
180
|
}
|
|
159
181
|
markCompleted(sessionId) {
|
|
160
182
|
this.updateSessionStatus(sessionId, 'completed');
|
|
@@ -323,6 +345,8 @@ export class SessionStore {
|
|
|
323
345
|
}
|
|
324
346
|
markSessionTouched(session) {
|
|
325
347
|
session.updatedAt = Date.now();
|
|
348
|
+
session._cachedSnapshot = undefined;
|
|
349
|
+
session._cachedSummary = undefined;
|
|
326
350
|
this.touchOrder(session.id);
|
|
327
351
|
this.sortedSessionIdsCache = null;
|
|
328
352
|
this.emitSessionResourcesUpdated(session.id);
|
|
@@ -331,16 +355,34 @@ export class SessionStore {
|
|
|
331
355
|
this.sortedSessionIdsCache ??= this.buildSortedSessionIdsCache();
|
|
332
356
|
return this.sortedSessionIdsCache;
|
|
333
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
|
+
}
|
|
334
368
|
snapshotThought(thought) {
|
|
335
|
-
|
|
369
|
+
const t = {
|
|
336
370
|
index: thought.index,
|
|
337
371
|
content: thought.content,
|
|
338
372
|
revision: thought.revision,
|
|
373
|
+
...(thought.stepSummary !== undefined
|
|
374
|
+
? { stepSummary: thought.stepSummary }
|
|
375
|
+
: {}),
|
|
339
376
|
};
|
|
377
|
+
Object.freeze(t);
|
|
378
|
+
return t;
|
|
340
379
|
}
|
|
341
380
|
snapshotSession(session) {
|
|
381
|
+
if (session._cachedSnapshot) {
|
|
382
|
+
return session._cachedSnapshot;
|
|
383
|
+
}
|
|
342
384
|
const thoughts = session.thoughts.map((thought) => this.snapshotThought(thought));
|
|
343
|
-
|
|
385
|
+
const snapshot = {
|
|
344
386
|
id: session.id,
|
|
345
387
|
level: session.level,
|
|
346
388
|
status: session.status,
|
|
@@ -351,9 +393,16 @@ export class SessionStore {
|
|
|
351
393
|
createdAt: session.createdAt,
|
|
352
394
|
updatedAt: session.updatedAt,
|
|
353
395
|
};
|
|
396
|
+
Object.freeze(snapshot);
|
|
397
|
+
Object.freeze(snapshot.thoughts);
|
|
398
|
+
session._cachedSnapshot = snapshot;
|
|
399
|
+
return snapshot;
|
|
354
400
|
}
|
|
355
401
|
snapshotSessionSummary(session) {
|
|
356
|
-
|
|
402
|
+
if (session._cachedSummary) {
|
|
403
|
+
return session._cachedSummary;
|
|
404
|
+
}
|
|
405
|
+
const summary = {
|
|
357
406
|
id: session.id,
|
|
358
407
|
level: session.level,
|
|
359
408
|
status: session.status,
|
|
@@ -364,6 +413,9 @@ export class SessionStore {
|
|
|
364
413
|
createdAt: session.createdAt,
|
|
365
414
|
updatedAt: session.updatedAt,
|
|
366
415
|
};
|
|
416
|
+
Object.freeze(summary);
|
|
417
|
+
session._cachedSummary = summary;
|
|
418
|
+
return summary;
|
|
367
419
|
}
|
|
368
420
|
emitSessionsListChanged() {
|
|
369
421
|
engineEvents.emit('resources:changed', { uri: 'reasoning://sessions' });
|
|
@@ -389,4 +441,3 @@ export class SessionStore {
|
|
|
389
441
|
this.emitSessionsResourceUpdated();
|
|
390
442
|
}
|
|
391
443
|
}
|
|
392
|
-
//# sourceMappingURL=session-store.js.map
|
package/dist/index.d.ts
CHANGED