@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.
Files changed (39) 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.js +21 -97
  10. package/dist/engine/session-store.d.ts +4 -0
  11. package/dist/engine/session-store.js +54 -26
  12. package/dist/lib/concurrency.d.ts +5 -0
  13. package/dist/lib/concurrency.js +17 -0
  14. package/dist/lib/errors.d.ts +23 -7
  15. package/dist/lib/errors.js +47 -5
  16. package/dist/lib/formatting.d.ts +13 -0
  17. package/dist/lib/formatting.js +18 -0
  18. package/dist/lib/prompt-contracts.d.ts +1 -9
  19. package/dist/lib/prompt-contracts.js +33 -122
  20. package/dist/lib/tool-contracts.d.ts +1 -1
  21. package/dist/lib/tool-contracts.js +91 -90
  22. package/dist/lib/tool-response.d.ts +4 -0
  23. package/dist/lib/tool-response.js +3 -0
  24. package/dist/lib/types.d.ts +17 -0
  25. package/dist/lib/types.js +8 -1
  26. package/dist/lib/validators.d.ts +7 -1
  27. package/dist/lib/validators.js +30 -7
  28. package/dist/prompts/index.js +132 -47
  29. package/dist/prompts/templates.d.ts +2 -0
  30. package/dist/prompts/templates.js +106 -0
  31. package/dist/resources/index.js +14 -60
  32. package/dist/resources/instructions.js +10 -2
  33. package/dist/resources/workflows.js +4 -3
  34. package/dist/schemas/inputs.js +2 -2
  35. package/dist/schemas/outputs.d.ts +29 -25
  36. package/dist/schemas/outputs.js +13 -13
  37. package/dist/server.js +1 -3
  38. package/dist/tools/reasoning-think.js +106 -149
  39. 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
+ }
@@ -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,
@@ -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,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 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,7 +29,7 @@ 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;
@@ -67,28 +70,14 @@ export class SessionStore {
67
70
  return session ? this.snapshotSessionSummary(session) : undefined;
68
71
  }
69
72
  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;
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
- 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;
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 += estimateTokens(t.content);
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 = estimateTokens(existing.content);
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
- return this.snapshotThought(revised);
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
- return {
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
- return {
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
- return {
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,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 {};