orquesta-cli 0.2.47 → 0.2.48
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/core/compact/context-tracker.js +1 -1
- package/dist/core/file-cache.d.ts +5 -0
- package/dist/core/file-cache.js +27 -0
- package/dist/core/llm/llm-client.d.ts +1 -0
- package/dist/core/llm/llm-client.js +35 -14
- package/dist/orchestration/plan-executor.d.ts +2 -0
- package/dist/orchestration/plan-executor.js +12 -3
- package/dist/tools/llm/simple/file-tools.js +5 -1
- package/dist/tools/llm/simple/simple-tool-executor.js +17 -0
- package/package.json +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function getCachedFile(path: string): string | null;
|
|
2
|
+
export declare function setCachedFile(path: string, content: string): void;
|
|
3
|
+
export declare function invalidateCache(path: string): void;
|
|
4
|
+
export declare function clearFileCache(): void;
|
|
5
|
+
//# sourceMappingURL=file-cache.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
const cache = new Map();
|
|
3
|
+
export function getCachedFile(path) {
|
|
4
|
+
const entry = cache.get(path);
|
|
5
|
+
if (!entry)
|
|
6
|
+
return null;
|
|
7
|
+
try {
|
|
8
|
+
const stat = fs.statSync(path);
|
|
9
|
+
if (stat.mtimeMs === entry.mtime)
|
|
10
|
+
return entry.content;
|
|
11
|
+
cache.delete(path);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
cache.delete(path);
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
export function setCachedFile(path, content) {
|
|
19
|
+
try {
|
|
20
|
+
const stat = fs.statSync(path);
|
|
21
|
+
cache.set(path, { content, mtime: stat.mtimeMs });
|
|
22
|
+
}
|
|
23
|
+
catch { }
|
|
24
|
+
}
|
|
25
|
+
export function invalidateCache(path) { cache.delete(path); }
|
|
26
|
+
export function clearFileCache() { cache.clear(); }
|
|
27
|
+
//# sourceMappingURL=file-cache.js.map
|
|
@@ -100,6 +100,20 @@ function captureBatutaHeaders(headers) {
|
|
|
100
100
|
setLastBatutaRoute({ tier, routedTo, routedFrom });
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
+
export async function fetchWithRetry(url, options, maxRetries = 3) {
|
|
104
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
105
|
+
const res = await fetch(url, options);
|
|
106
|
+
if (res.ok || attempt === maxRetries)
|
|
107
|
+
return res;
|
|
108
|
+
if (res.status === 429 || res.status >= 500) {
|
|
109
|
+
const delay = Math.min(1000 * 2 ** attempt, 30000);
|
|
110
|
+
await new Promise(r => setTimeout(r, delay));
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
return res;
|
|
114
|
+
}
|
|
115
|
+
throw new Error('Unreachable');
|
|
116
|
+
}
|
|
103
117
|
export class LLMClient {
|
|
104
118
|
axiosInstance;
|
|
105
119
|
baseUrl;
|
|
@@ -138,29 +152,33 @@ export class LLMClient {
|
|
|
138
152
|
});
|
|
139
153
|
}
|
|
140
154
|
preprocessMessages(messages, modelId) {
|
|
155
|
+
const isGptOss = /^gpt-oss-(120b|20b)$/i.test(modelId);
|
|
141
156
|
return messages.map((msg) => {
|
|
142
|
-
|
|
143
|
-
const multimodal = processedMsg.multimodal;
|
|
157
|
+
const multimodal = msg.multimodal;
|
|
144
158
|
if (multimodal && Array.isArray(multimodal)) {
|
|
159
|
+
const processedMsg = { ...msg };
|
|
145
160
|
processedMsg.content = multimodal;
|
|
146
161
|
delete processedMsg.multimodal;
|
|
147
162
|
return processedMsg;
|
|
148
163
|
}
|
|
149
164
|
if (msg.role !== 'assistant') {
|
|
150
|
-
return
|
|
165
|
+
return msg;
|
|
166
|
+
}
|
|
167
|
+
const msgAny = msg;
|
|
168
|
+
const needsReasoningFix = msgAny.reasoning_content && (!msg.content || msg.content.trim() === '');
|
|
169
|
+
const needsHarmonyFix = isGptOss && msg.tool_calls && msg.tool_calls.length > 0 && (!msg.content || msg.content.trim() === '');
|
|
170
|
+
const needsNullFix = msg.content === undefined || msg.content === null;
|
|
171
|
+
if (!needsReasoningFix && !needsHarmonyFix && !needsNullFix) {
|
|
172
|
+
return msg;
|
|
151
173
|
}
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
174
|
+
const processedMsg = { ...msg };
|
|
175
|
+
if (needsReasoningFix) {
|
|
154
176
|
processedMsg.content = msgAny.reasoning_content;
|
|
155
177
|
delete processedMsg.reasoning_content;
|
|
156
178
|
}
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const toolNames = msg.tool_calls.map(tc => tc.function.name).join(', ');
|
|
161
|
-
processedMsg.content = msgAny.reasoning || `Calling tools: ${toolNames}`;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
179
|
+
if (needsHarmonyFix) {
|
|
180
|
+
const toolNames = msg.tool_calls.map(tc => tc.function.name).join(', ');
|
|
181
|
+
processedMsg.content = msgAny.reasoning || `Calling tools: ${toolNames}`;
|
|
164
182
|
}
|
|
165
183
|
if (processedMsg.content === undefined || processedMsg.content === null) {
|
|
166
184
|
processedMsg.content = '';
|
|
@@ -185,12 +203,14 @@ export class LLMClient {
|
|
|
185
203
|
const processedMessages = options.messages ?
|
|
186
204
|
this.preprocessMessages(options.messages, modelId) : [];
|
|
187
205
|
logger.vars({ name: 'modelId', value: modelId }, { name: 'originalMessages', value: options.messages?.length || 0 }, { name: 'processedMessages', value: processedMessages.length }, { name: 'temperature', value: options.temperature ?? 0 });
|
|
206
|
+
const isClaudeModel = /claude|sonnet|opus|haiku/i.test(modelId);
|
|
188
207
|
const requestBody = {
|
|
189
208
|
model: modelId,
|
|
190
209
|
messages: processedMessages,
|
|
191
210
|
temperature: options.temperature ?? 0,
|
|
192
211
|
max_tokens: options.max_tokens,
|
|
193
212
|
stream: false,
|
|
213
|
+
...(isClaudeModel && { thinking: { type: 'enabled', budget_tokens: 10000 } }),
|
|
194
214
|
...(options.tools && {
|
|
195
215
|
tools: options.tools,
|
|
196
216
|
parallel_tool_calls: false,
|
|
@@ -451,12 +471,14 @@ export class LLMClient {
|
|
|
451
471
|
const modelId = options.model || this.model;
|
|
452
472
|
const processedMessages = options.messages ?
|
|
453
473
|
this.preprocessMessages(options.messages, modelId) : [];
|
|
474
|
+
const isClaudeModel = /claude|sonnet|opus|haiku/i.test(modelId);
|
|
454
475
|
const requestBody = {
|
|
455
476
|
model: modelId,
|
|
456
477
|
messages: processedMessages,
|
|
457
478
|
temperature: options.temperature ?? 0,
|
|
458
479
|
max_tokens: options.max_tokens,
|
|
459
480
|
stream: true,
|
|
481
|
+
...(isClaudeModel && { thinking: { type: 'enabled', budget_tokens: 10000 } }),
|
|
460
482
|
...(options.tools && {
|
|
461
483
|
tools: options.tools,
|
|
462
484
|
...(options.tool_choice && { tool_choice: options.tool_choice }),
|
|
@@ -597,6 +619,7 @@ export class LLMClient {
|
|
|
597
619
|
let contextLengthRecoveryAttempted = false;
|
|
598
620
|
let finalResponseFailures = 0;
|
|
599
621
|
const MAX_FINAL_RESPONSE_FAILURES = 3;
|
|
622
|
+
const { executeFileTool, requestToolApproval, emitAssistantResponse } = await import('../../tools/llm/simple/file-tools.js');
|
|
600
623
|
const recentToolSignatures = [];
|
|
601
624
|
const recentNormalizedSignatures = [];
|
|
602
625
|
const LOOP_WINDOW = 5;
|
|
@@ -736,7 +759,6 @@ export class LLMClient {
|
|
|
736
759
|
});
|
|
737
760
|
continue;
|
|
738
761
|
}
|
|
739
|
-
const { executeFileTool, requestToolApproval } = await import('../../tools/llm/simple/file-tools.js');
|
|
740
762
|
const approvalResult = await requestToolApproval(toolName, toolArgs);
|
|
741
763
|
if (approvalResult && typeof approvalResult === 'object' && approvalResult.reject) {
|
|
742
764
|
logger.flow(`Tool rejected by user: ${toolName}`);
|
|
@@ -791,7 +813,6 @@ export class LLMClient {
|
|
|
791
813
|
if (finalResponseFailures >= MAX_FINAL_RESPONSE_FAILURES) {
|
|
792
814
|
logger.warn('Max final_response failures exceeded - forcing completion');
|
|
793
815
|
const fallbackMessage = toolArgs['message'] || 'Task completed with incomplete TODOs.';
|
|
794
|
-
const { emitAssistantResponse } = await import('../../tools/llm/simple/file-tools.js');
|
|
795
816
|
emitAssistantResponse(fallbackMessage);
|
|
796
817
|
return {
|
|
797
818
|
message: { role: 'assistant', content: fallbackMessage },
|
|
@@ -5,7 +5,9 @@ import type { StateCallbacks } from './types.js';
|
|
|
5
5
|
export declare function setAppendedSystemPrompt(text: string): void;
|
|
6
6
|
export declare class PlanExecutor {
|
|
7
7
|
private currentLLMClient;
|
|
8
|
+
private cachedSystemPrompt;
|
|
8
9
|
constructor();
|
|
10
|
+
private getSystemPrompt;
|
|
9
11
|
executePlanMode(userMessage: string, llmClient: LLMClient, messages: Message[], isInterruptedRef: {
|
|
10
12
|
current: boolean;
|
|
11
13
|
}, callbacks: StateCallbacks): Promise<void>;
|
|
@@ -40,8 +40,15 @@ function buildSystemPrompt() {
|
|
|
40
40
|
}
|
|
41
41
|
export class PlanExecutor {
|
|
42
42
|
currentLLMClient = null;
|
|
43
|
+
cachedSystemPrompt = null;
|
|
43
44
|
constructor() {
|
|
44
45
|
}
|
|
46
|
+
getSystemPrompt() {
|
|
47
|
+
if (!this.cachedSystemPrompt) {
|
|
48
|
+
this.cachedSystemPrompt = buildSystemPrompt();
|
|
49
|
+
}
|
|
50
|
+
return this.cachedSystemPrompt;
|
|
51
|
+
}
|
|
45
52
|
async executePlanMode(userMessage, llmClient, messages, isInterruptedRef, callbacks) {
|
|
46
53
|
const planningStartTime = Date.now();
|
|
47
54
|
const streamLogger = getStreamLogger();
|
|
@@ -163,7 +170,7 @@ export class PlanExecutor {
|
|
|
163
170
|
const hasSystemMessage = currentMessages.some(m => m.role === 'system');
|
|
164
171
|
if (!hasSystemMessage) {
|
|
165
172
|
currentMessages = [
|
|
166
|
-
{ role: 'system', content:
|
|
173
|
+
{ role: 'system', content: this.getSystemPrompt() },
|
|
167
174
|
...currentMessages
|
|
168
175
|
];
|
|
169
176
|
}
|
|
@@ -191,7 +198,7 @@ export class PlanExecutor {
|
|
|
191
198
|
});
|
|
192
199
|
if (useParallel && sessionId) {
|
|
193
200
|
logger.flow('Dispatching parallel orchestrator', { todoCount: currentTodos.length });
|
|
194
|
-
const baseSystem = currentMessages.find(m => m.role === 'system')?.content ||
|
|
201
|
+
const baseSystem = currentMessages.find(m => m.role === 'system')?.content || this.getSystemPrompt();
|
|
195
202
|
const graphResult = await runParallelGraph({
|
|
196
203
|
llmClient,
|
|
197
204
|
todos: currentTodos,
|
|
@@ -285,6 +292,7 @@ export class PlanExecutor {
|
|
|
285
292
|
clearFinalResponseCallbacks();
|
|
286
293
|
clearDocsSearchLLMClientGetter();
|
|
287
294
|
this.currentLLMClient = null;
|
|
295
|
+
this.cachedSystemPrompt = null;
|
|
288
296
|
}
|
|
289
297
|
}
|
|
290
298
|
async resumeTodoExecution(userMessage, llmClient, messages, todos, isInterruptedRef, callbacks) {
|
|
@@ -311,7 +319,7 @@ export class PlanExecutor {
|
|
|
311
319
|
const hasSystemMessage = currentMessages.some(m => m.role === 'system');
|
|
312
320
|
if (!hasSystemMessage) {
|
|
313
321
|
currentMessages = [
|
|
314
|
-
{ role: 'system', content:
|
|
322
|
+
{ role: 'system', content: this.getSystemPrompt() },
|
|
315
323
|
...currentMessages
|
|
316
324
|
];
|
|
317
325
|
}
|
|
@@ -355,6 +363,7 @@ export class PlanExecutor {
|
|
|
355
363
|
clearFinalResponseCallbacks();
|
|
356
364
|
clearDocsSearchLLMClientGetter();
|
|
357
365
|
this.currentLLMClient = null;
|
|
366
|
+
this.cachedSystemPrompt = null;
|
|
358
367
|
}
|
|
359
368
|
}
|
|
360
369
|
async executeAutoMode(userMessage, llmClient, messages, _todos, isInterruptedRef, callbacks) {
|
|
@@ -2,6 +2,7 @@ import * as fs from 'fs/promises';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { logger } from '../../../utils/logger.js';
|
|
4
4
|
import { shouldIgnore } from '../../../core/ignore-filter.js';
|
|
5
|
+
import { getCachedFile, setCachedFile, invalidateCache } from '../../../core/file-cache.js';
|
|
5
6
|
const EXCLUDED_DIRS = new Set([
|
|
6
7
|
'node_modules',
|
|
7
8
|
'.git',
|
|
@@ -71,7 +72,8 @@ async function _executeReadFile(args) {
|
|
|
71
72
|
error: `File too large to read (${(stats.size / 1024 / 1024).toFixed(2)}MB). Maximum: ${MAX_FILE_SIZE / 1024 / 1024}MB`,
|
|
72
73
|
};
|
|
73
74
|
}
|
|
74
|
-
const content = await fs.readFile(resolvedPath, 'utf-8');
|
|
75
|
+
const content = getCachedFile(resolvedPath) ?? await fs.readFile(resolvedPath, 'utf-8');
|
|
76
|
+
setCachedFile(resolvedPath, content);
|
|
75
77
|
const allLines = content.split('\n');
|
|
76
78
|
const totalLines = allLines.length;
|
|
77
79
|
const startIdx = offset - 1;
|
|
@@ -192,6 +194,7 @@ async function _executeCreateFile(args) {
|
|
|
192
194
|
const dir = path.dirname(resolvedPath);
|
|
193
195
|
await fs.mkdir(dir, { recursive: true });
|
|
194
196
|
await fs.writeFile(resolvedPath, content, 'utf-8');
|
|
197
|
+
invalidateCache(resolvedPath);
|
|
195
198
|
const lines = content.split('\n').length;
|
|
196
199
|
logger.toolSuccess('create_file', args, { file: displayPath, lines }, 0);
|
|
197
200
|
return {
|
|
@@ -354,6 +357,7 @@ async function _executeEditFile(args) {
|
|
|
354
357
|
};
|
|
355
358
|
}
|
|
356
359
|
await fs.writeFile(resolvedPath, newContent, 'utf-8');
|
|
360
|
+
invalidateCache(resolvedPath);
|
|
357
361
|
const oldLinesArr = oldString.split('\n');
|
|
358
362
|
const newLinesArr = newString.split('\n');
|
|
359
363
|
const replacements = replaceAll ? occurrences : 1;
|
|
@@ -106,6 +106,20 @@ export function emitReasoning(content, isStreaming = false) {
|
|
|
106
106
|
reasoningCallback(content, isStreaming);
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
function truncateToolResult(toolName, result) {
|
|
110
|
+
const totalChars = result.length;
|
|
111
|
+
if (toolName === 'read_file') {
|
|
112
|
+
const totalLines = result.split('\n').length;
|
|
113
|
+
return result.slice(0, 20000) + `\n\n[...truncated. File has ${totalLines} total lines. Use offset parameter to read more.]`;
|
|
114
|
+
}
|
|
115
|
+
if (toolName === 'bash') {
|
|
116
|
+
return result.slice(0, 10000) + `\n\n[...truncated middle. Total output: ${totalChars} chars]\n\n` + result.slice(-5000);
|
|
117
|
+
}
|
|
118
|
+
if (toolName === 'search_content' || toolName === 'find_files') {
|
|
119
|
+
return result.slice(0, 15000) + `\n\n[...truncated. Total output: ${totalChars} chars]`;
|
|
120
|
+
}
|
|
121
|
+
return result.slice(0, 20000) + `\n\n[...truncated. Total output: ${totalChars} chars]`;
|
|
122
|
+
}
|
|
109
123
|
export async function executeSimpleTool(toolName, args) {
|
|
110
124
|
const startTime = Date.now();
|
|
111
125
|
const logger = getStreamLogger();
|
|
@@ -141,6 +155,9 @@ export async function executeSimpleTool(toolName, args) {
|
|
|
141
155
|
error: result.success ? undefined : result.error,
|
|
142
156
|
});
|
|
143
157
|
}
|
|
158
|
+
if (result.success && result.result && result.result.length > 30000) {
|
|
159
|
+
return { ...result, result: truncateToolResult(toolName, result.result) };
|
|
160
|
+
}
|
|
144
161
|
return result;
|
|
145
162
|
}
|
|
146
163
|
export const executeFileTool = executeSimpleTool;
|