@csmedeiros/codemax 1.0.3 → 1.0.7
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/README.md +4 -20
- package/launcher.js +28 -0
- package/package.json +15 -121
- package/LICENSE +0 -21
- package/dist/commands/cursorRules.d.ts +0 -15
- package/dist/commands/cursorRules.js +0 -118
- package/dist/commands/slashCommands.d.ts +0 -36
- package/dist/commands/slashCommands.js +0 -236
- package/dist/configuration/configManager.d.ts +0 -32
- package/dist/configuration/configManager.js +0 -71
- package/dist/configuration/modelContextWindows.d.ts +0 -3
- package/dist/configuration/modelContextWindows.js +0 -13
- package/dist/conversation/agentGraph.d.ts +0 -203
- package/dist/conversation/agentGraph.js +0 -433
- package/dist/conversation/agentTurn.d.ts +0 -40
- package/dist/conversation/agentTurn.js +0 -252
- package/dist/conversation/chatHistory.d.ts +0 -24
- package/dist/conversation/chatHistory.js +0 -251
- package/dist/conversation/compactionUtils.d.ts +0 -17
- package/dist/conversation/compactionUtils.js +0 -57
- package/dist/conversation/prompts/planPrompt.d.ts +0 -1
- package/dist/conversation/prompts/planPrompt.js +0 -12
- package/dist/conversation/prompts/systemPrompt.d.ts +0 -29
- package/dist/conversation/prompts/systemPrompt.js +0 -149
- package/dist/entry/cli.d.ts +0 -2
- package/dist/entry/cli.js +0 -24
- package/dist/observability/langfuseTracing.d.ts +0 -5
- package/dist/observability/langfuseTracing.js +0 -76
- package/dist/shared/types.d.ts +0 -25
- package/dist/shared/types.js +0 -1
- package/dist/terminal/app.d.ts +0 -2
- package/dist/terminal/app.js +0 -1236
- package/dist/terminal/components.d.ts +0 -18
- package/dist/terminal/components.js +0 -43
- package/dist/terminal/markdown.d.ts +0 -4
- package/dist/terminal/markdown.js +0 -47
- package/dist/terminal/screens/compactionSettings.d.ts +0 -6
- package/dist/terminal/screens/compactionSettings.js +0 -66
- package/dist/terminal/screens/modelSettings.d.ts +0 -10
- package/dist/terminal/screens/modelSettings.js +0 -76
- package/dist/terminal/textField.d.ts +0 -7
- package/dist/terminal/textField.js +0 -136
- package/dist/terminal/theme.d.ts +0 -23
- package/dist/terminal/theme.js +0 -23
- package/dist/tooling/mcpConfig.d.ts +0 -40
- package/dist/tooling/mcpConfig.js +0 -49
- package/dist/tooling/planControlChannel.d.ts +0 -2
- package/dist/tooling/planControlChannel.js +0 -21
- package/dist/tooling/toolConfig.d.ts +0 -42
- package/dist/tooling/toolConfig.js +0 -138
- package/dist/tooling/toolUiCallback.d.ts +0 -21
- package/dist/tooling/toolUiCallback.js +0 -268
- package/dist/tooling/tools.d.ts +0 -216
- package/dist/tooling/tools.js +0 -614
|
@@ -1,433 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LangChain agent graph (StateGraph + tools).
|
|
3
|
-
*/
|
|
4
|
-
import { END, MessagesZodState, START, StateGraph, interrupt, } from '@langchain/langgraph';
|
|
5
|
-
import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
|
|
6
|
-
import Database from 'better-sqlite3';
|
|
7
|
-
import os from 'os';
|
|
8
|
-
import fs from 'fs';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { HumanMessage, RemoveMessage, SystemMessage, ToolMessage, } from '@langchain/core/messages';
|
|
11
|
-
import { z } from 'zod';
|
|
12
|
-
import { getConfig, updateConfig } from '../configuration/configManager.js';
|
|
13
|
-
import { TOOLS, PLAN_TOOLS } from '../tooling/tools.js';
|
|
14
|
-
import { SYSTEM_PROMPT, COMPACT_SUMMARY_PROMPT, buildCodemaxTurnDatetimeUserContent, } from './prompts/systemPrompt.js';
|
|
15
|
-
import { PLAN_MODE_PROMPT } from './prompts/planPrompt.js';
|
|
16
|
-
import { loadProjectSkills } from '../commands/slashCommands.js';
|
|
17
|
-
import { loadWorkspaceRules, matchRules } from '../commands/cursorRules.js';
|
|
18
|
-
import { ChatOpenAI } from '@langchain/openai';
|
|
19
|
-
import { getContextWindow } from '../configuration/modelContextWindows.js';
|
|
20
|
-
import { estimateTokens, shouldCompactMessages, computeKeepStart, } from './compactionUtils.js';
|
|
21
|
-
export { estimateTokens, shouldCompactMessages, computeKeepStart };
|
|
22
|
-
export const COMPACTION_SUMMARY_SENTINEL = '[COMPACTION_SUMMARY]';
|
|
23
|
-
let model;
|
|
24
|
-
export function initModel() {
|
|
25
|
-
const config = getConfig();
|
|
26
|
-
if (!config.apiKey) {
|
|
27
|
-
process.stderr.write('\x07'); // Alert beep
|
|
28
|
-
console.error('\n[CodeMax] FATAL: API Key is not set in ~/.codemax/config.json or environment.\n');
|
|
29
|
-
throw new Error('API_KEY missing');
|
|
30
|
-
}
|
|
31
|
-
model = new ChatOpenAI({
|
|
32
|
-
model: config.modelName,
|
|
33
|
-
apiKey: config.apiKey,
|
|
34
|
-
temperature: 0.3,
|
|
35
|
-
maxTokens: 16384,
|
|
36
|
-
streaming: true,
|
|
37
|
-
maxRetries: 0,
|
|
38
|
-
configuration: {
|
|
39
|
-
baseURL: config.baseURL || undefined,
|
|
40
|
-
},
|
|
41
|
-
modelKwargs: {
|
|
42
|
-
chat_template_kwargs: { enable_thinking: true, clear_thinking: false },
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
initModel();
|
|
47
|
-
export function updateModelConfig(cfg) {
|
|
48
|
-
updateConfig(cfg);
|
|
49
|
-
initModel();
|
|
50
|
-
}
|
|
51
|
-
export const ToolExecutionMode = z.enum(['auto', 'ask', 'edits', 'plan']);
|
|
52
|
-
/** Same shape as `MessagesAnnotation` + extra channels — required for `toolsCondition` / `ToolNode`. */
|
|
53
|
-
const StateDefinition = MessagesZodState.extend({
|
|
54
|
-
files: z.record(z.string(), z.string()).default({}),
|
|
55
|
-
toolExecutionMode: ToolExecutionMode.default('ask'),
|
|
56
|
-
todos: z
|
|
57
|
-
.array(z.object({
|
|
58
|
-
id: z.string(),
|
|
59
|
-
task: z.string(),
|
|
60
|
-
status: z.enum(['pending', 'in_progress', 'completed']),
|
|
61
|
-
}))
|
|
62
|
-
.default([]),
|
|
63
|
-
activeSkill: z.string().optional(),
|
|
64
|
-
forceCompact: z.boolean().default(false),
|
|
65
|
-
});
|
|
66
|
-
async function callModel(state, config) {
|
|
67
|
-
const datetimeMsg = new HumanMessage(buildCodemaxTurnDatetimeUserContent());
|
|
68
|
-
const executionCwd = process.env['CODEMAX_SHELL_CWD'] ?? process.cwd();
|
|
69
|
-
const skills = loadProjectSkills(executionCwd);
|
|
70
|
-
const injected = [];
|
|
71
|
-
if (skills.length > 0) {
|
|
72
|
-
const descriptionsText = 'These are the available skills and its descriptions. You MUST read skills when the use case matches with the description.\n\n' +
|
|
73
|
-
skills.map(s => `${s.name}:\n\n${s.description}`).join('\n\n\n');
|
|
74
|
-
injected.push(new HumanMessage(descriptionsText));
|
|
75
|
-
}
|
|
76
|
-
if (state.activeSkill) {
|
|
77
|
-
injected.push(new HumanMessage(state.activeSkill));
|
|
78
|
-
}
|
|
79
|
-
const workspaceRules = loadWorkspaceRules(executionCwd);
|
|
80
|
-
const matchedRules = matchRules(workspaceRules, state.messages);
|
|
81
|
-
if (workspaceRules.length > 0) {
|
|
82
|
-
process.stderr.write(`[CodeMax:rules] loaded=${workspaceRules.length} matched=${matchedRules.length}` +
|
|
83
|
-
(matchedRules.length > 0
|
|
84
|
-
? ` files=[${matchedRules.map(r => r.filename).join(', ')}]`
|
|
85
|
-
: '') +
|
|
86
|
-
'\n');
|
|
87
|
-
}
|
|
88
|
-
for (const rule of matchedRules) {
|
|
89
|
-
injected.push(new HumanMessage(`[Rule: ${rule.description}]\n\n${rule.content}`));
|
|
90
|
-
}
|
|
91
|
-
if (state.toolExecutionMode === 'plan') {
|
|
92
|
-
injected.unshift(new HumanMessage(PLAN_MODE_PROMPT));
|
|
93
|
-
}
|
|
94
|
-
const messages = [
|
|
95
|
-
new SystemMessage(SYSTEM_PROMPT),
|
|
96
|
-
...injected,
|
|
97
|
-
datetimeMsg,
|
|
98
|
-
...state.messages,
|
|
99
|
-
];
|
|
100
|
-
const toolList = state.toolExecutionMode === 'plan' ? PLAN_TOOLS : TOOLS;
|
|
101
|
-
const activeLlm = model.bindTools(toolList);
|
|
102
|
-
const response = await activeLlm.invoke(messages, config);
|
|
103
|
-
return { messages: [response], activeSkill: undefined };
|
|
104
|
-
}
|
|
105
|
-
async function toolNode(state, config) {
|
|
106
|
-
const lastMessage = state.messages[state.messages.length - 1];
|
|
107
|
-
const toolCalls = lastMessage?.tool_calls ?? [];
|
|
108
|
-
const results = [];
|
|
109
|
-
let currentTodos = [...(state.todos || [])];
|
|
110
|
-
for (const toolCall of toolCalls) {
|
|
111
|
-
const { name } = toolCall;
|
|
112
|
-
const mode = state.toolExecutionMode;
|
|
113
|
-
if (state.toolExecutionMode === 'plan' &&
|
|
114
|
-
(name === 'writeFileTool' || name === 'editFileTool')) {
|
|
115
|
-
results.push(new ToolMessage({
|
|
116
|
-
tool_call_id: toolCall.id,
|
|
117
|
-
content: 'writeFileTool and editFileTool are disabled in Plan Mode. Use writePlanTool to output your plan.',
|
|
118
|
-
}));
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
let shouldInterrupt = false;
|
|
122
|
-
if (name === 'readFileTool') {
|
|
123
|
-
shouldInterrupt = false; // Read-only is always safe
|
|
124
|
-
}
|
|
125
|
-
else if (name === 'todoTool') {
|
|
126
|
-
shouldInterrupt = false; // Todo updates are safe
|
|
127
|
-
}
|
|
128
|
-
else if (mode === 'ask') {
|
|
129
|
-
shouldInterrupt = true;
|
|
130
|
-
}
|
|
131
|
-
else if (mode === 'edits') {
|
|
132
|
-
if (name === 'writeFileTool' || name === 'editFileTool') {
|
|
133
|
-
shouldInterrupt = false; // Auto-accept edits
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
shouldInterrupt = true; // Ask for shellTool and others
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
else if (mode === 'auto') {
|
|
140
|
-
shouldInterrupt = false; // Auto-accept everything
|
|
141
|
-
}
|
|
142
|
-
if (shouldInterrupt) {
|
|
143
|
-
const response = interrupt({
|
|
144
|
-
type: 'tool_approval',
|
|
145
|
-
toolCall,
|
|
146
|
-
});
|
|
147
|
-
if (response.action === 'approve_always') {
|
|
148
|
-
state.toolExecutionMode = 'auto';
|
|
149
|
-
}
|
|
150
|
-
else if (response.action !== 'approve') {
|
|
151
|
-
results.push(new ToolMessage({
|
|
152
|
-
tool_call_id: toolCall.id,
|
|
153
|
-
content: 'Tool execution denied by user.',
|
|
154
|
-
}));
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const activeToolList = state.toolExecutionMode === 'plan' ? PLAN_TOOLS : TOOLS;
|
|
159
|
-
const tool = activeToolList.find(t => t.name === name);
|
|
160
|
-
if (!tool) {
|
|
161
|
-
results.push(new ToolMessage({
|
|
162
|
-
tool_call_id: toolCall.id,
|
|
163
|
-
content: `Tool ${name} not found.`,
|
|
164
|
-
}));
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
let output;
|
|
168
|
-
try {
|
|
169
|
-
if (name === 'todoTool') {
|
|
170
|
-
const { action, id, task, status } = toolCall.args;
|
|
171
|
-
if (action === 'add') {
|
|
172
|
-
currentTodos.push({
|
|
173
|
-
id: Math.random().toString(36).substring(7),
|
|
174
|
-
task: task,
|
|
175
|
-
status: status || 'pending',
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
else if (action === 'update') {
|
|
179
|
-
currentTodos = currentTodos.map(t => t.id === id
|
|
180
|
-
? { ...t, task: task || t.task, status: status || t.status }
|
|
181
|
-
: t);
|
|
182
|
-
}
|
|
183
|
-
else if (action === 'remove') {
|
|
184
|
-
currentTodos = currentTodos.filter(t => t.id !== id);
|
|
185
|
-
}
|
|
186
|
-
output = new ToolMessage({
|
|
187
|
-
tool_call_id: toolCall.id,
|
|
188
|
-
content: 'Todo list updated.',
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
output = await tool.invoke(toolCall, config);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
output = new ToolMessage({
|
|
197
|
-
tool_call_id: toolCall.id,
|
|
198
|
-
content: `Error: ${error?.message || String(error)}`,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
results.push(output);
|
|
202
|
-
}
|
|
203
|
-
return {
|
|
204
|
-
messages: results,
|
|
205
|
-
toolExecutionMode: state.toolExecutionMode,
|
|
206
|
-
todos: currentTodos,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
const memoryDir = path.join(os.homedir(), '.codemax', 'memory');
|
|
210
|
-
fs.mkdirSync(memoryDir, { recursive: true });
|
|
211
|
-
const memoryPath = path.join(memoryDir, 'MEMORY.sqlite');
|
|
212
|
-
export const db = new Database(memoryPath);
|
|
213
|
-
export { model };
|
|
214
|
-
const checkpointer = new SqliteSaver(db);
|
|
215
|
-
function shouldCompact(state) {
|
|
216
|
-
if (state.forceCompact)
|
|
217
|
-
return true;
|
|
218
|
-
const cfg = getConfig();
|
|
219
|
-
const window = getContextWindow(cfg.modelName);
|
|
220
|
-
return shouldCompactMessages(state.messages, {
|
|
221
|
-
threshold: cfg.compactionThreshold,
|
|
222
|
-
contextWindow: window,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
let fullMessagesInitialized = false;
|
|
226
|
-
function ensureFullMessagesTable() {
|
|
227
|
-
if (fullMessagesInitialized)
|
|
228
|
-
return;
|
|
229
|
-
db.exec(`
|
|
230
|
-
CREATE TABLE IF NOT EXISTS chat_messages_full (
|
|
231
|
-
thread_id TEXT PRIMARY KEY,
|
|
232
|
-
messages_json TEXT NOT NULL,
|
|
233
|
-
updated_at INTEGER NOT NULL
|
|
234
|
-
)
|
|
235
|
-
`);
|
|
236
|
-
fullMessagesInitialized = true;
|
|
237
|
-
}
|
|
238
|
-
function readStoredFullMessages(threadId) {
|
|
239
|
-
ensureFullMessagesTable();
|
|
240
|
-
try {
|
|
241
|
-
const row = db
|
|
242
|
-
.prepare(`SELECT messages_json FROM chat_messages_full WHERE thread_id = ?`)
|
|
243
|
-
.get(threadId);
|
|
244
|
-
if (!row)
|
|
245
|
-
return null;
|
|
246
|
-
return JSON.parse(row.messages_json);
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
return null;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
function writeStoredFullMessages(threadId, raw) {
|
|
253
|
-
ensureFullMessagesTable();
|
|
254
|
-
try {
|
|
255
|
-
db.prepare(`INSERT INTO chat_messages_full (thread_id, messages_json, updated_at) VALUES (?, ?, ?)
|
|
256
|
-
ON CONFLICT(thread_id) DO UPDATE SET messages_json = excluded.messages_json, updated_at = excluded.updated_at`).run(threadId, JSON.stringify(raw), Date.now());
|
|
257
|
-
}
|
|
258
|
-
catch {
|
|
259
|
-
// ignore
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
function serializeMessageForStorage(m) {
|
|
263
|
-
const type = m._getType();
|
|
264
|
-
const base = { id: m.id, type, content: m.content };
|
|
265
|
-
if (type === 'ai') {
|
|
266
|
-
const ai = m;
|
|
267
|
-
if (ai.tool_calls)
|
|
268
|
-
base.tool_calls = ai.tool_calls;
|
|
269
|
-
}
|
|
270
|
-
if (type === 'tool') {
|
|
271
|
-
const tm = m;
|
|
272
|
-
base.tool_call_id = tm.tool_call_id;
|
|
273
|
-
base.name = tm.name;
|
|
274
|
-
}
|
|
275
|
-
return base;
|
|
276
|
-
}
|
|
277
|
-
function persistFullHistorySnapshot(threadId, currentMessages) {
|
|
278
|
-
const serialized = currentMessages
|
|
279
|
-
.filter(m => {
|
|
280
|
-
const c = m.content;
|
|
281
|
-
const text = typeof c === 'string'
|
|
282
|
-
? c
|
|
283
|
-
: Array.isArray(c)
|
|
284
|
-
? c
|
|
285
|
-
.map((b) => (typeof b === 'string' ? b : b?.text ?? ''))
|
|
286
|
-
.join('')
|
|
287
|
-
: '';
|
|
288
|
-
return !text.startsWith(COMPACTION_SUMMARY_SENTINEL);
|
|
289
|
-
})
|
|
290
|
-
.map(serializeMessageForStorage);
|
|
291
|
-
const stored = readStoredFullMessages(threadId) ?? [];
|
|
292
|
-
const seenIds = new Set();
|
|
293
|
-
const merged = [];
|
|
294
|
-
for (const m of stored) {
|
|
295
|
-
const id = m?.id;
|
|
296
|
-
if (id && seenIds.has(id))
|
|
297
|
-
continue;
|
|
298
|
-
if (id)
|
|
299
|
-
seenIds.add(id);
|
|
300
|
-
merged.push(m);
|
|
301
|
-
}
|
|
302
|
-
for (const m of serialized) {
|
|
303
|
-
const id = m?.id;
|
|
304
|
-
if (id && seenIds.has(id))
|
|
305
|
-
continue;
|
|
306
|
-
if (id)
|
|
307
|
-
seenIds.add(id);
|
|
308
|
-
merged.push(m);
|
|
309
|
-
}
|
|
310
|
-
writeStoredFullMessages(threadId, merged);
|
|
311
|
-
}
|
|
312
|
-
async function compactMessages(state, config) {
|
|
313
|
-
const cfg = getConfig();
|
|
314
|
-
const window = getContextWindow(cfg.modelName);
|
|
315
|
-
const tokensToKeep = Math.max(1, Math.floor(0.15 * window));
|
|
316
|
-
const msgs = state.messages;
|
|
317
|
-
if (msgs.length === 0)
|
|
318
|
-
return { forceCompact: false };
|
|
319
|
-
let keepStart = computeKeepStart(msgs, tokensToKeep);
|
|
320
|
-
if (keepStart >= msgs.length)
|
|
321
|
-
keepStart = msgs.length - 1;
|
|
322
|
-
if (keepStart <= 0)
|
|
323
|
-
return { forceCompact: false };
|
|
324
|
-
const olderMessages = msgs.slice(0, keepStart);
|
|
325
|
-
const threadId = config?.configurable?.thread_id;
|
|
326
|
-
if (threadId) {
|
|
327
|
-
try {
|
|
328
|
-
persistFullHistorySnapshot(threadId, msgs);
|
|
329
|
-
}
|
|
330
|
-
catch {
|
|
331
|
-
// non-fatal
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
let summaryText = '';
|
|
335
|
-
try {
|
|
336
|
-
const summarizer = new ChatOpenAI({
|
|
337
|
-
model: cfg.modelName,
|
|
338
|
-
apiKey: cfg.apiKey,
|
|
339
|
-
temperature: 0.3,
|
|
340
|
-
maxTokens: 8000,
|
|
341
|
-
streaming: false,
|
|
342
|
-
maxRetries: 0,
|
|
343
|
-
configuration: { baseURL: cfg.baseURL || undefined },
|
|
344
|
-
});
|
|
345
|
-
const response = await summarizer.invoke([
|
|
346
|
-
new SystemMessage(COMPACT_SUMMARY_PROMPT),
|
|
347
|
-
...olderMessages,
|
|
348
|
-
new HumanMessage('Produce the detailed compaction summary now as instructed.'),
|
|
349
|
-
], config);
|
|
350
|
-
const c = response.content;
|
|
351
|
-
summaryText =
|
|
352
|
-
typeof c === 'string'
|
|
353
|
-
? c
|
|
354
|
-
: Array.isArray(c)
|
|
355
|
-
? c
|
|
356
|
-
.map((b) => (typeof b === 'string' ? b : b?.text ?? ''))
|
|
357
|
-
.join('')
|
|
358
|
-
: '';
|
|
359
|
-
}
|
|
360
|
-
catch (e) {
|
|
361
|
-
process.stderr.write(`[CodeMax:compact] summarization failed: ${e?.message || e}\n`);
|
|
362
|
-
return { forceCompact: false };
|
|
363
|
-
}
|
|
364
|
-
if (!summaryText.trim())
|
|
365
|
-
return { forceCompact: false };
|
|
366
|
-
const removeOps = [];
|
|
367
|
-
for (const m of olderMessages) {
|
|
368
|
-
if (m.id)
|
|
369
|
-
removeOps.push(new RemoveMessage({ id: m.id }));
|
|
370
|
-
}
|
|
371
|
-
const summaryMsg = new SystemMessage(`${COMPACTION_SUMMARY_SENTINEL}\n\n${summaryText.trim()}`);
|
|
372
|
-
return {
|
|
373
|
-
messages: [...removeOps, summaryMsg],
|
|
374
|
-
forceCompact: false,
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
function routeFromCallModel(state) {
|
|
378
|
-
if (state.forceCompact)
|
|
379
|
-
return 'compactMessages';
|
|
380
|
-
const last = state.messages[state.messages.length - 1];
|
|
381
|
-
const hasToolCalls = last && Array.isArray(last.tool_calls) && last.tool_calls.length > 0;
|
|
382
|
-
if (hasToolCalls)
|
|
383
|
-
return 'tools';
|
|
384
|
-
if (shouldCompact(state))
|
|
385
|
-
return 'compactMessages';
|
|
386
|
-
return END;
|
|
387
|
-
}
|
|
388
|
-
function routeFromTools(state) {
|
|
389
|
-
if (state.forceCompact || shouldCompact(state))
|
|
390
|
-
return 'compactMessages';
|
|
391
|
-
return 'callModel';
|
|
392
|
-
}
|
|
393
|
-
export const agent = new StateGraph(StateDefinition)
|
|
394
|
-
.addNode('callModel', callModel, {
|
|
395
|
-
retryPolicy: {
|
|
396
|
-
maxAttempts: 3,
|
|
397
|
-
initialInterval: 3000,
|
|
398
|
-
backoffFactor: 2.0,
|
|
399
|
-
retryOn: (error) => {
|
|
400
|
-
const isRateLimit = error?.code === 'MODEL_RATE_LIMIT' ||
|
|
401
|
-
error?.status === 429 ||
|
|
402
|
-
error?.message?.includes('429') ||
|
|
403
|
-
error?.name === 'RateLimitError';
|
|
404
|
-
const isAbort = error?.name === 'AbortError';
|
|
405
|
-
if (!isRateLimit && !isAbort) {
|
|
406
|
-
console.error('[CodeMax] Critical model error (skipping retry):', error);
|
|
407
|
-
}
|
|
408
|
-
return isRateLimit;
|
|
409
|
-
},
|
|
410
|
-
},
|
|
411
|
-
})
|
|
412
|
-
.addNode('tools', toolNode)
|
|
413
|
-
.addNode('compactMessages', compactMessages)
|
|
414
|
-
.addEdge(START, 'callModel')
|
|
415
|
-
.addConditionalEdges('callModel', routeFromCallModel, [
|
|
416
|
-
'tools',
|
|
417
|
-
'compactMessages',
|
|
418
|
-
END,
|
|
419
|
-
])
|
|
420
|
-
.addConditionalEdges('tools', routeFromTools, [
|
|
421
|
-
'compactMessages',
|
|
422
|
-
'callModel',
|
|
423
|
-
])
|
|
424
|
-
.addEdge('compactMessages', 'callModel')
|
|
425
|
-
.compile({ checkpointer });
|
|
426
|
-
export async function triggerCompaction(threadId) {
|
|
427
|
-
const config = { configurable: { thread_id: threadId } };
|
|
428
|
-
await agent.updateState(config, { forceCompact: true });
|
|
429
|
-
await agent.invoke(null, {
|
|
430
|
-
...config,
|
|
431
|
-
recursionLimit: getConfig().recursionLimit,
|
|
432
|
-
});
|
|
433
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { ToolExecutionMode } from './agentGraph.js';
|
|
2
|
-
export type AgentTurnEvent = {
|
|
3
|
-
type: 'on_tool_start';
|
|
4
|
-
toolName?: string;
|
|
5
|
-
input?: string;
|
|
6
|
-
} | {
|
|
7
|
-
type: 'on_tool_end';
|
|
8
|
-
toolName?: string;
|
|
9
|
-
output?: string;
|
|
10
|
-
} | {
|
|
11
|
-
type: 'on_tool_error';
|
|
12
|
-
error?: string;
|
|
13
|
-
} | {
|
|
14
|
-
type: 'on_chat_start';
|
|
15
|
-
} | {
|
|
16
|
-
type: 'on_chat_delta';
|
|
17
|
-
text: string;
|
|
18
|
-
} | {
|
|
19
|
-
type: 'on_chat_end';
|
|
20
|
-
} | {
|
|
21
|
-
type: 'on_interrupt';
|
|
22
|
-
payload: any;
|
|
23
|
-
} | {
|
|
24
|
-
type: 'status';
|
|
25
|
-
text: string;
|
|
26
|
-
} | {
|
|
27
|
-
type: 'on_state_update';
|
|
28
|
-
todos: any[];
|
|
29
|
-
};
|
|
30
|
-
export type AgentTurnOptions = {
|
|
31
|
-
prompt?: string;
|
|
32
|
-
threadId: string;
|
|
33
|
-
sessionId: string;
|
|
34
|
-
tracingEnabled: boolean;
|
|
35
|
-
signal?: AbortSignal;
|
|
36
|
-
toolExecutionMode?: ToolExecutionMode;
|
|
37
|
-
resumeResponse?: any;
|
|
38
|
-
activeSkill?: string;
|
|
39
|
-
};
|
|
40
|
-
export declare function streamAgentTurn(options: AgentTurnOptions): AsyncGenerator<AgentTurnEvent>;
|