@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.
Files changed (43) hide show
  1. package/dist/engine/config.d.ts +4 -4
  2. package/dist/engine/config.js +6 -4
  3. package/dist/engine/context.d.ts +0 -1
  4. package/dist/engine/context.js +0 -3
  5. package/dist/engine/events.d.ts +1 -1
  6. package/dist/engine/events.js +2 -3
  7. package/dist/engine/heuristics.d.ts +4 -0
  8. package/dist/engine/heuristics.js +65 -0
  9. package/dist/engine/reasoner.d.ts +1 -1
  10. package/dist/engine/reasoner.js +22 -98
  11. package/dist/engine/session-store.d.ts +7 -1
  12. package/dist/engine/session-store.js +68 -27
  13. package/dist/lib/concurrency.d.ts +5 -0
  14. package/dist/lib/concurrency.js +17 -0
  15. package/dist/lib/errors.d.ts +23 -7
  16. package/dist/lib/errors.js +47 -5
  17. package/dist/lib/formatting.d.ts +13 -0
  18. package/dist/lib/formatting.js +18 -0
  19. package/dist/lib/prompt-contracts.d.ts +1 -9
  20. package/dist/lib/prompt-contracts.js +33 -122
  21. package/dist/lib/tool-contracts.d.ts +1 -1
  22. package/dist/lib/tool-contracts.js +85 -90
  23. package/dist/lib/tool-response.d.ts +4 -0
  24. package/dist/lib/tool-response.js +3 -0
  25. package/dist/lib/types.d.ts +17 -0
  26. package/dist/lib/types.js +8 -1
  27. package/dist/lib/validators.d.ts +12 -1
  28. package/dist/lib/validators.js +48 -7
  29. package/dist/prompts/index.js +136 -47
  30. package/dist/prompts/templates.d.ts +2 -0
  31. package/dist/prompts/templates.js +227 -0
  32. package/dist/resources/index.js +34 -60
  33. package/dist/resources/instructions.js +55 -64
  34. package/dist/resources/tool-catalog.js +10 -9
  35. package/dist/resources/tool-info.js +1 -1
  36. package/dist/resources/workflows.js +43 -42
  37. package/dist/schemas/inputs.d.ts +0 -1
  38. package/dist/schemas/inputs.js +14 -31
  39. package/dist/schemas/outputs.d.ts +29 -25
  40. package/dist/schemas/outputs.js +18 -22
  41. package/dist/server.js +11 -3
  42. package/dist/tools/reasoning-think.js +142 -158
  43. package/package.json +1 -1
@@ -1,19 +1,19 @@
1
- import type { LevelConfig, ReasoningLevel } from '../lib/types.js';
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;
@@ -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: { minThoughts: 3, maxThoughts: 5, tokenBudget: 2048 },
4
- normal: { minThoughts: 6, maxThoughts: 10, tokenBudget: 8192 },
5
- high: { minThoughts: 15, maxThoughts: 25, tokenBudget: 32768 },
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 Error(errorMessage);
17
+ throw new InvalidThoughtCountError(errorMessage);
16
18
  }
17
19
  export function getLevelDescriptionString() {
18
20
  return Object.entries(LEVEL_CONFIGS)
@@ -3,4 +3,3 @@ export interface EngineContext {
3
3
  readonly abortSignal?: AbortSignal;
4
4
  }
5
5
  export declare function runWithContext<T>(ctx: EngineContext, fn: () => T): T;
6
- export declare function getContext(): EngineContext | undefined;
@@ -3,6 +3,3 @@ const storage = new AsyncLocalStorage();
3
3
  export function runWithContext(ctx, fn) {
4
4
  return storage.run(ctx, fn);
5
5
  }
6
- export function getContext() {
7
- return storage.getStore();
8
- }
@@ -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;
@@ -1,11 +1,10 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import { getErrorMessage } from '../lib/errors.js';
3
- const ENGINE_ERROR_LOG_PREFIX = '[engine]';
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(`${ENGINE_ERROR_PREFIX_WITH_SPACE}${getErrorMessage(err)}\n`);
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>>;
@@ -1,27 +1,11 @@
1
- import { Buffer } from 'node:buffer';
2
- import { createSegmenter } from '../lib/text.js';
3
- import { assertTargetThoughtsInRange, getLevelConfig } from './config.js';
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 { SessionStore } from './session-store.js';
7
- const DEFAULT_SESSION_TTL_MS = 30 * 60 * 1000;
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 Error('Either thought (or observation/hypothesis/evaluation) or rollback_to_step is required');
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 Error(`Session not found: ${sessionId}`);
94
+ throw new SessionNotFoundError(sessionId);
111
95
  }
112
96
  return session;
113
97
  }
114
- function emitBudgetExhausted(data) {
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
- return {
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, level, targetThoughts) {
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 Error(`Cannot change targetThoughts on an existing session (current: ${String(existing.totalThoughts)}). Omit targetThoughts or pass ${String(existing.totalThoughts)}.`);
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 Error(`Session not found: ${sessionId}`);
122
+ throw new SessionNotFoundError(sessionId);
149
123
  }
150
- assertExistingSessionConstraints(existing, level, targetThoughts);
124
+ assertExistingSessionConstraints(existing, targetThoughts);
151
125
  return existing;
152
126
  }
153
127
  if (level === undefined) {
154
- throw new Error('level is required for new sessions');
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 Error('Reasoning aborted');
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 readonly cleanupInterval;
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 DEFAULT_TTL_MS = 30 * 60 * 1000; // 30 minutes
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 = DEFAULT_TTL_MS, maxSessions = DEFAULT_MAX_SESSIONS, maxTotalTokens = DEFAULT_MAX_TOTAL_TOKENS) {
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
- const sweepInterval = resolveSweepInterval(ttlMs);
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
- const sessions = [];
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
- const summaries = [];
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 += estimateTokens(t.content);
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 = estimateTokens(existing.content);
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
- return this.snapshotThought(revised);
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
- return {
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
- return {
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
- return {
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,5 @@
1
+ export interface TaskLimiter {
2
+ tryAcquire: () => boolean;
3
+ release: () => void;
4
+ }
5
+ export declare function createTaskLimiter(maxActiveTasks: number): TaskLimiter;
@@ -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
+ }
@@ -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 {};