@qduc/term2 0.1.2 → 0.1.4
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/agent.d.ts.map +1 -1
- package/dist/agent.js +5 -0
- package/dist/agent.js.map +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +3 -1
- package/dist/app.js.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/components/ApprovalPrompt.d.ts.map +1 -1
- package/dist/components/ApprovalPrompt.js +20 -0
- package/dist/components/ApprovalPrompt.js.map +1 -1
- package/dist/components/ChatMessage.js +1 -1
- package/dist/components/ChatMessage.js.map +1 -1
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +3 -8
- package/dist/components/InputBox.js.map +1 -1
- package/dist/components/MarkdownRenderer.js +1 -1
- package/dist/components/MarkdownRenderer.js.map +1 -1
- package/dist/components/ModelSelectionMenu.error-tabs.test.d.ts +2 -0
- package/dist/components/ModelSelectionMenu.error-tabs.test.d.ts.map +1 -0
- package/dist/components/ModelSelectionMenu.error-tabs.test.js +18 -0
- package/dist/components/ModelSelectionMenu.error-tabs.test.js.map +1 -0
- package/dist/components/SettingsSelectionMenu.js +1 -1
- package/dist/components/SettingsSelectionMenu.js.map +1 -1
- package/dist/components/SlashCommandMenu.js +2 -2
- package/dist/components/SlashCommandMenu.js.map +1 -1
- package/dist/hooks/use-conversation.d.ts.map +1 -1
- package/dist/hooks/use-conversation.js +53 -508
- package/dist/hooks/use-conversation.js.map +1 -1
- package/dist/hooks/use-model-selection.d.ts.map +1 -1
- package/dist/hooks/use-model-selection.js +7 -3
- package/dist/hooks/use-model-selection.js.map +1 -1
- package/dist/hooks/use-settings-completion.d.ts.map +1 -1
- package/dist/hooks/use-settings-completion.js +5 -0
- package/dist/hooks/use-settings-completion.js.map +1 -1
- package/dist/hooks/use-settings-completion.test.js +6 -0
- package/dist/hooks/use-settings-completion.test.js.map +1 -1
- package/dist/hooks/use-settings-value-completion.d.ts.map +1 -1
- package/dist/hooks/use-settings-value-completion.js +1 -0
- package/dist/hooks/use-settings-value-completion.js.map +1 -1
- package/dist/lib/editor-impl.test.d.ts +2 -0
- package/dist/lib/editor-impl.test.d.ts.map +1 -0
- package/dist/lib/editor-impl.test.js +188 -0
- package/dist/lib/editor-impl.test.js.map +1 -0
- package/dist/lib/openai-agent-client.d.ts.map +1 -1
- package/dist/lib/openai-agent-client.js +35 -15
- package/dist/lib/openai-agent-client.js.map +1 -1
- package/dist/lib/openai-agent-client.public-methods.test.d.ts +2 -0
- package/dist/lib/openai-agent-client.public-methods.test.d.ts.map +1 -0
- package/dist/lib/openai-agent-client.public-methods.test.js +188 -0
- package/dist/lib/openai-agent-client.public-methods.test.js.map +1 -0
- package/dist/lib/tool-invoke.test.js +1 -1
- package/dist/lib/tool-invoke.test.js.map +1 -1
- package/dist/providers/github-copilot/converters.d.ts +45 -0
- package/dist/providers/github-copilot/converters.d.ts.map +1 -0
- package/dist/providers/github-copilot/converters.js +118 -0
- package/dist/providers/github-copilot/converters.js.map +1 -0
- package/dist/providers/github-copilot/converters.test.d.ts +2 -0
- package/dist/providers/github-copilot/converters.test.d.ts.map +1 -0
- package/dist/providers/github-copilot/converters.test.js +162 -0
- package/dist/providers/github-copilot/converters.test.js.map +1 -0
- package/dist/providers/github-copilot/github-copilot.provider.d.ts +2 -0
- package/dist/providers/github-copilot/github-copilot.provider.d.ts.map +1 -0
- package/dist/providers/github-copilot/github-copilot.provider.js +75 -0
- package/dist/providers/github-copilot/github-copilot.provider.js.map +1 -0
- package/dist/providers/github-copilot/github-copilot.provider.test.d.ts +2 -0
- package/dist/providers/github-copilot/github-copilot.provider.test.d.ts.map +1 -0
- package/dist/providers/github-copilot/github-copilot.provider.test.js +26 -0
- package/dist/providers/github-copilot/github-copilot.provider.test.js.map +1 -0
- package/dist/providers/github-copilot/index.d.ts +4 -0
- package/dist/providers/github-copilot/index.d.ts.map +1 -0
- package/dist/providers/github-copilot/index.js +4 -0
- package/dist/providers/github-copilot/index.js.map +1 -0
- package/dist/providers/github-copilot/model-direct.d.ts +34 -0
- package/dist/providers/github-copilot/model-direct.d.ts.map +1 -0
- package/dist/providers/github-copilot/model-direct.js +443 -0
- package/dist/providers/github-copilot/model-direct.js.map +1 -0
- package/dist/providers/github-copilot/model.d.ts +24 -0
- package/dist/providers/github-copilot/model.d.ts.map +1 -0
- package/dist/providers/github-copilot/model.delta.test.d.ts +2 -0
- package/dist/providers/github-copilot/model.delta.test.d.ts.map +1 -0
- package/dist/providers/github-copilot/model.delta.test.js +15 -0
- package/dist/providers/github-copilot/model.delta.test.js.map +1 -0
- package/dist/providers/github-copilot/model.js +581 -0
- package/dist/providers/github-copilot/model.js.map +1 -0
- package/dist/providers/github-copilot/provider.d.ts +20 -0
- package/dist/providers/github-copilot/provider.d.ts.map +1 -0
- package/dist/providers/github-copilot/provider.js +30 -0
- package/dist/providers/github-copilot/provider.js.map +1 -0
- package/dist/providers/github-copilot/provider.test.d.ts +2 -0
- package/dist/providers/github-copilot/provider.test.d.ts.map +1 -0
- package/dist/providers/github-copilot/provider.test.js +52 -0
- package/dist/providers/github-copilot/provider.test.js.map +1 -0
- package/dist/providers/github-copilot/utils.d.ts +20 -0
- package/dist/providers/github-copilot/utils.d.ts.map +1 -0
- package/dist/providers/github-copilot/utils.js +142 -0
- package/dist/providers/github-copilot/utils.js.map +1 -0
- package/dist/providers/github-copilot/utils.test.d.ts +2 -0
- package/dist/providers/github-copilot/utils.test.d.ts.map +1 -0
- package/dist/providers/github-copilot/utils.test.js +21 -0
- package/dist/providers/github-copilot/utils.test.js.map +1 -0
- package/dist/providers/openai-compatible/model.d.ts.map +1 -1
- package/dist/providers/openai-compatible/model.js +13 -2
- package/dist/providers/openai-compatible/model.js.map +1 -1
- package/dist/providers/openai-compatible/reasoning-content.test.js +2 -2
- package/dist/providers/openai-compatible/reasoning-content.test.js.map +1 -1
- package/dist/providers/openrouter/converters.d.ts.map +1 -1
- package/dist/providers/openrouter/converters.js +64 -46
- package/dist/providers/openrouter/converters.js.map +1 -1
- package/dist/providers/openrouter/converters.test.js +13 -12
- package/dist/providers/openrouter/converters.test.js.map +1 -1
- package/dist/providers/openrouter/merge-messages.test.d.ts +2 -0
- package/dist/providers/openrouter/merge-messages.test.d.ts.map +1 -0
- package/dist/providers/openrouter/merge-messages.test.js +83 -0
- package/dist/providers/openrouter/merge-messages.test.js.map +1 -0
- package/dist/providers/openrouter/reasoning-content.test.js +2 -2
- package/dist/providers/openrouter/reasoning-content.test.js.map +1 -1
- package/dist/providers/openrouter.test.js +30 -21
- package/dist/providers/openrouter.test.js.map +1 -1
- package/dist/reproduce_issue.test.d.ts +2 -0
- package/dist/reproduce_issue.test.d.ts.map +1 -0
- package/dist/reproduce_issue.test.js +31 -0
- package/dist/reproduce_issue.test.js.map +1 -0
- package/dist/services/conversation-store.test.js +12 -11
- package/dist/services/conversation-store.test.js.map +1 -1
- package/dist/services/settings-service.d.ts +12 -3
- package/dist/services/settings-service.d.ts.map +1 -1
- package/dist/services/settings-service.js +26 -0
- package/dist/services/settings-service.js.map +1 -1
- package/dist/tools/ask-mentor.d.ts +1 -1
- package/dist/tools/ask-mentor.d.ts.map +1 -1
- package/dist/tools/ask-mentor.js +1 -0
- package/dist/tools/ask-mentor.js.map +1 -1
- package/dist/tools/edit-healing.d.ts +24 -0
- package/dist/tools/edit-healing.d.ts.map +1 -0
- package/dist/tools/edit-healing.js +230 -0
- package/dist/tools/edit-healing.js.map +1 -0
- package/dist/tools/edit-healing.test.d.ts +2 -0
- package/dist/tools/edit-healing.test.d.ts.map +1 -0
- package/dist/tools/edit-healing.test.js +34 -0
- package/dist/tools/edit-healing.test.js.map +1 -0
- package/dist/tools/find-files.d.ts +2 -2
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +2 -0
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/grep.d.ts +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +1 -0
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +10 -9
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/read-file.test.js +24 -19
- package/dist/tools/read-file.test.js.map +1 -1
- package/dist/tools/search-replace.d.ts +3 -1
- package/dist/tools/search-replace.d.ts.map +1 -1
- package/dist/tools/search-replace.js +331 -19
- package/dist/tools/search-replace.js.map +1 -1
- package/dist/tools/search-replace.test.js +241 -2
- package/dist/tools/search-replace.test.js.map +1 -1
- package/dist/tools/search.d.ts +4 -4
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +4 -0
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/shell.d.ts +2 -2
- package/dist/tools/shell.d.ts.map +1 -1
- package/dist/tools/shell.js +2 -0
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/types.d.ts +2 -2
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/web-fetch.d.ts +20 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +300 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/tools/web-fetch.test.d.ts +2 -0
- package/dist/tools/web-fetch.test.d.ts.map +1 -0
- package/dist/tools/web-fetch.test.js +94 -0
- package/dist/tools/web-fetch.test.js.map +1 -0
- package/dist/utils/command-safety/index.d.ts +2 -2
- package/dist/utils/command-safety/index.d.ts.map +1 -1
- package/dist/utils/command-safety/index.js +7 -6
- package/dist/utils/command-safety/index.js.map +1 -1
- package/dist/utils/command-safety.find.test.js +19 -21
- package/dist/utils/command-safety.find.test.js.map +1 -1
- package/dist/utils/conversation-event-handler.d.ts +63 -0
- package/dist/utils/conversation-event-handler.d.ts.map +1 -0
- package/dist/utils/conversation-event-handler.js +132 -0
- package/dist/utils/conversation-event-handler.js.map +1 -0
- package/dist/utils/conversation-event-handler.test.d.ts +2 -0
- package/dist/utils/conversation-event-handler.test.d.ts.map +1 -0
- package/dist/utils/conversation-event-handler.test.js +281 -0
- package/dist/utils/conversation-event-handler.test.js.map +1 -0
- package/dist/utils/conversation-utils.d.ts +41 -0
- package/dist/utils/conversation-utils.d.ts.map +1 -0
- package/dist/utils/conversation-utils.js +109 -0
- package/dist/utils/conversation-utils.js.map +1 -0
- package/dist/utils/conversation-utils.test.d.ts +2 -0
- package/dist/utils/conversation-utils.test.d.ts.map +1 -0
- package/dist/utils/conversation-utils.test.js +190 -0
- package/dist/utils/conversation-utils.test.js.map +1 -0
- package/dist/utils/ink-render-options.d.ts +9 -0
- package/dist/utils/ink-render-options.d.ts.map +1 -0
- package/dist/utils/ink-render-options.js +8 -0
- package/dist/utils/ink-render-options.js.map +1 -0
- package/dist/utils/message-utils.d.ts +17 -0
- package/dist/utils/message-utils.d.ts.map +1 -0
- package/dist/utils/message-utils.js +52 -0
- package/dist/utils/message-utils.js.map +1 -0
- package/dist/utils/message-utils.test.d.ts +2 -0
- package/dist/utils/message-utils.test.d.ts.map +1 -0
- package/dist/utils/message-utils.test.js +48 -0
- package/dist/utils/message-utils.test.js.map +1 -0
- package/dist/utils/settings-command.d.ts.map +1 -1
- package/dist/utils/settings-command.js +13 -4
- package/dist/utils/settings-command.js.map +1 -1
- package/package.json +10 -4
|
@@ -2,6 +2,8 @@ import { useCallback, useRef, useState } from 'react';
|
|
|
2
2
|
import { isAbortLikeError } from '../utils/error-helpers.js';
|
|
3
3
|
import { createStreamingUpdateCoordinator } from '../utils/streaming-updater.js';
|
|
4
4
|
import { appendMessagesCapped } from '../utils/message-buffer.js';
|
|
5
|
+
import { createStreamingState, enhanceApiKeyError, isMaxTurnsError, } from '../utils/conversation-utils.js';
|
|
6
|
+
import { createConversationEventHandler } from '../utils/conversation-event-handler.js';
|
|
5
7
|
const LIVE_RESPONSE_THROTTLE_MS = 150;
|
|
6
8
|
const REASONING_RESPONSE_THROTTLE_MS = 200;
|
|
7
9
|
const MAX_MESSAGE_COUNT = 300;
|
|
@@ -180,16 +182,12 @@ export const useConversation = ({ conversationService, loggingService, }) => {
|
|
|
180
182
|
text: '',
|
|
181
183
|
});
|
|
182
184
|
const liveResponseUpdater = createLiveResponseUpdater(liveMessageId);
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
let accumulatedReasoningText = '';
|
|
186
|
-
let flushedReasoningLength = 0; // Track how much reasoning has been flushed
|
|
187
|
-
let textWasFlushed = false;
|
|
188
|
-
let currentReasoningMessageId = null; // Track current reasoning message ID
|
|
185
|
+
// Create streaming state object for this message send
|
|
186
|
+
const streamingState = createStreamingState();
|
|
189
187
|
const reasoningUpdater = createStreamingUpdateCoordinator((newReasoningText) => {
|
|
190
188
|
setMessages(prev => {
|
|
191
|
-
if (currentReasoningMessageId !== null) {
|
|
192
|
-
const index = prev.findIndex(msg => msg.id === currentReasoningMessageId);
|
|
189
|
+
if (streamingState.currentReasoningMessageId !== null) {
|
|
190
|
+
const index = prev.findIndex(msg => msg.id === streamingState.currentReasoningMessageId);
|
|
193
191
|
if (index === -1)
|
|
194
192
|
return prev;
|
|
195
193
|
const current = prev[index];
|
|
@@ -201,7 +199,7 @@ export const useConversation = ({ conversationService, loggingService, }) => {
|
|
|
201
199
|
return trimMessages(next);
|
|
202
200
|
}
|
|
203
201
|
const newId = Date.now();
|
|
204
|
-
currentReasoningMessageId = newId;
|
|
202
|
+
streamingState.currentReasoningMessageId = newId;
|
|
205
203
|
return trimMessages([
|
|
206
204
|
...prev,
|
|
207
205
|
{
|
|
@@ -212,175 +210,21 @@ export const useConversation = ({ conversationService, loggingService, }) => {
|
|
|
212
210
|
]);
|
|
213
211
|
});
|
|
214
212
|
}, REASONING_RESPONSE_THROTTLE_MS);
|
|
215
|
-
// Create event
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
case 'reasoning_delta': {
|
|
226
|
-
// logDeduplicated('reasoning_delta');
|
|
227
|
-
const fullReasoningText = event.fullText ?? '';
|
|
228
|
-
// Only show reasoning text after what was already flushed
|
|
229
|
-
const newReasoningText = fullReasoningText.slice(flushedReasoningLength);
|
|
230
|
-
accumulatedReasoningText = newReasoningText;
|
|
231
|
-
if (!newReasoningText.trim())
|
|
232
|
-
return;
|
|
233
|
-
reasoningUpdater.push(newReasoningText);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
case 'tool_started': {
|
|
237
|
-
// Flush reasoning state
|
|
238
|
-
if (accumulatedReasoningText.trim()) {
|
|
239
|
-
reasoningUpdater.flush();
|
|
240
|
-
flushedReasoningLength += accumulatedReasoningText.length;
|
|
241
|
-
accumulatedReasoningText = '';
|
|
242
|
-
currentReasoningMessageId = null;
|
|
243
|
-
}
|
|
244
|
-
// Flush any accumulated text before showing the tool call
|
|
245
|
-
if (accumulatedText.trim()) {
|
|
246
|
-
const textMessage = {
|
|
247
|
-
id: Date.now() + 1,
|
|
248
|
-
sender: 'bot',
|
|
249
|
-
text: accumulatedText,
|
|
250
|
-
};
|
|
251
|
-
appendMessages([textMessage]);
|
|
252
|
-
accumulatedText = '';
|
|
253
|
-
textWasFlushed = true;
|
|
254
|
-
liveResponseUpdater.cancel();
|
|
255
|
-
setLiveResponse(null);
|
|
256
|
-
}
|
|
257
|
-
// Emit a "pending" command message when tool starts running
|
|
258
|
-
// This provides immediate UI feedback before output is available
|
|
259
|
-
const { toolCallId, toolName, arguments: rawArgs } = event;
|
|
260
|
-
// tool_started.arguments may be either an object or a JSON string
|
|
261
|
-
// depending on provider. Normalize so we can render params.
|
|
262
|
-
const args = (() => {
|
|
263
|
-
if (typeof rawArgs !== 'string') {
|
|
264
|
-
return rawArgs;
|
|
265
|
-
}
|
|
266
|
-
const trimmed = rawArgs.trim();
|
|
267
|
-
if (!trimmed) {
|
|
268
|
-
return rawArgs;
|
|
269
|
-
}
|
|
270
|
-
try {
|
|
271
|
-
return JSON.parse(trimmed);
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
return rawArgs;
|
|
275
|
-
}
|
|
276
|
-
})();
|
|
277
|
-
// Create a command string from the tool info
|
|
278
|
-
const command = (() => {
|
|
279
|
-
if (toolName === 'shell') {
|
|
280
|
-
const cmd = args?.command ?? args?.commands;
|
|
281
|
-
if (typeof cmd === 'string' && cmd.trim()) {
|
|
282
|
-
return cmd;
|
|
283
|
-
}
|
|
284
|
-
if (Array.isArray(cmd) && cmd.length > 0) {
|
|
285
|
-
return cmd.join('\n');
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (toolName === 'grep' && args?.pattern) {
|
|
289
|
-
return `grep "${args.pattern}" ${args.path ?? '.'}`;
|
|
290
|
-
}
|
|
291
|
-
if (toolName === 'search_replace') {
|
|
292
|
-
return `search_replace "${args.search_content ?? ''}" → "${args.replace_content ?? ''}" ${args.path ?? ''}`;
|
|
293
|
-
}
|
|
294
|
-
if (toolName === 'apply_patch') {
|
|
295
|
-
return `apply_patch ${args?.type ?? 'unknown'} ${args?.path ?? ''}`;
|
|
296
|
-
}
|
|
297
|
-
if (toolName === 'ask_mentor') {
|
|
298
|
-
return `ask_mentor: ${args?.question ?? ''}`;
|
|
299
|
-
}
|
|
300
|
-
return `${toolName ?? 'unknown_tool'}`;
|
|
301
|
-
})();
|
|
302
|
-
const pendingMessage = {
|
|
303
|
-
id: toolCallId ?? String(Date.now()),
|
|
304
|
-
sender: 'command',
|
|
305
|
-
status: 'running',
|
|
306
|
-
command,
|
|
307
|
-
output: '',
|
|
308
|
-
callId: toolCallId,
|
|
309
|
-
toolName,
|
|
310
|
-
toolArgs: args,
|
|
311
|
-
};
|
|
312
|
-
appendMessages([pendingMessage]);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
case 'command_message': {
|
|
316
|
-
// logDeduplicated('command_message');
|
|
317
|
-
const cmdMsg = event.message;
|
|
318
|
-
const annotated = annotateCommandMessage(cmdMsg);
|
|
319
|
-
// Before adding command message, flush reasoning and text separately
|
|
320
|
-
// This preserves the order: reasoning -> command -> response text
|
|
321
|
-
const messagesToAdd = [];
|
|
322
|
-
if (accumulatedReasoningText.trim()) {
|
|
323
|
-
reasoningUpdater.flush();
|
|
324
|
-
// Reasoning is already in messages via stream updates.
|
|
325
|
-
// We just need to track what we've "flushed" (sealed) so next reasoning chunks start fresh.
|
|
326
|
-
flushedReasoningLength +=
|
|
327
|
-
accumulatedReasoningText.length;
|
|
328
|
-
accumulatedReasoningText = '';
|
|
329
|
-
currentReasoningMessageId = null; // Reset for potential post-command reasoning
|
|
330
|
-
}
|
|
331
|
-
if (accumulatedText.trim()) {
|
|
332
|
-
const textMessage = {
|
|
333
|
-
id: Date.now() + 1,
|
|
334
|
-
sender: 'bot',
|
|
335
|
-
text: accumulatedText,
|
|
336
|
-
};
|
|
337
|
-
messagesToAdd.push(textMessage);
|
|
338
|
-
accumulatedText = '';
|
|
339
|
-
textWasFlushed = true;
|
|
340
|
-
}
|
|
341
|
-
if (messagesToAdd.length > 0) {
|
|
342
|
-
appendMessages(messagesToAdd);
|
|
343
|
-
// Clear live response since we've committed the text
|
|
344
|
-
liveResponseUpdater.cancel();
|
|
345
|
-
setLiveResponse(null);
|
|
346
|
-
}
|
|
347
|
-
// Replace pending message with completed one, or add new if not found
|
|
348
|
-
setMessages(prev => {
|
|
349
|
-
// Try to find the pending message by callId
|
|
350
|
-
const pendingIndex = annotated.callId
|
|
351
|
-
? prev.findIndex(msg => msg.sender === 'command' &&
|
|
352
|
-
msg.callId === annotated.callId &&
|
|
353
|
-
msg.status === 'running')
|
|
354
|
-
: -1;
|
|
355
|
-
if (pendingIndex !== -1) {
|
|
356
|
-
// Replace the pending message with the completed one
|
|
357
|
-
const next = [...prev];
|
|
358
|
-
next[pendingIndex] = annotated;
|
|
359
|
-
return trimMessages(next);
|
|
360
|
-
}
|
|
361
|
-
// If no pending message found, append the completed one
|
|
362
|
-
return trimMessages([...prev, annotated]);
|
|
363
|
-
});
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
case 'retry': {
|
|
367
|
-
const systemMessage = {
|
|
368
|
-
id: Date.now(),
|
|
369
|
-
sender: 'system',
|
|
370
|
-
text: `Tool hallucination detected (${event.toolName}). Retrying... (Attempt ${event.attempt}/${event.maxRetries})`,
|
|
371
|
-
};
|
|
372
|
-
setMessages(prev => [...prev, systemMessage]);
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
default:
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
};
|
|
213
|
+
// Create event handler using extracted factory
|
|
214
|
+
const applyConversationEvent = createConversationEventHandler({
|
|
215
|
+
liveResponseUpdater,
|
|
216
|
+
reasoningUpdater,
|
|
217
|
+
appendMessages,
|
|
218
|
+
setMessages,
|
|
219
|
+
setLiveResponse,
|
|
220
|
+
trimMessages,
|
|
221
|
+
annotateCommandMessage,
|
|
222
|
+
}, streamingState);
|
|
379
223
|
try {
|
|
380
224
|
const result = await conversationService.sendMessage(value, {
|
|
381
225
|
onEvent: applyConversationEvent,
|
|
382
226
|
});
|
|
383
|
-
applyServiceResult(result, accumulatedText, accumulatedReasoningText, textWasFlushed);
|
|
227
|
+
applyServiceResult(result, streamingState.accumulatedText, streamingState.accumulatedReasoningText, streamingState.textWasFlushed);
|
|
384
228
|
}
|
|
385
229
|
catch (error) {
|
|
386
230
|
loggingService.error('Error in sendUserMessage', {
|
|
@@ -393,19 +237,9 @@ export const useConversation = ({ conversationService, loggingService, }) => {
|
|
|
393
237
|
// The finally block will handle cleanup
|
|
394
238
|
return;
|
|
395
239
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (errorMessage
|
|
399
|
-
(errorMessage.includes('401') &&
|
|
400
|
-
errorMessage.toLowerCase().includes('unauthorized'))) {
|
|
401
|
-
errorMessage =
|
|
402
|
-
'OpenAI API key is not configured or invalid. Please set the OPENAI_API_KEY environment variable. ' +
|
|
403
|
-
'Get your API key from: https://platform.openai.com/api-keys';
|
|
404
|
-
}
|
|
405
|
-
// Check if this is a max turns exceeded error
|
|
406
|
-
const isMaxTurnsError = errorMessage.includes('Max turns') &&
|
|
407
|
-
errorMessage.includes('exceeded');
|
|
408
|
-
if (isMaxTurnsError) {
|
|
240
|
+
const rawErrorMessage = error instanceof Error ? error.message : String(error);
|
|
241
|
+
const errorMessage = enhanceApiKeyError(rawErrorMessage);
|
|
242
|
+
if (isMaxTurnsError(errorMessage)) {
|
|
409
243
|
// Create an approval prompt for max turns continuation
|
|
410
244
|
setPendingApproval({
|
|
411
245
|
agentName: 'System',
|
|
@@ -470,16 +304,12 @@ export const useConversation = ({ conversationService, loggingService, }) => {
|
|
|
470
304
|
text: '',
|
|
471
305
|
});
|
|
472
306
|
const liveResponseUpdater = createLiveResponseUpdater(liveMessageId);
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
let accumulatedReasoningText = '';
|
|
476
|
-
let flushedReasoningLength = 0;
|
|
477
|
-
let textWasFlushed = false;
|
|
478
|
-
let currentReasoningMessageId = null;
|
|
307
|
+
// Create streaming state object for max turns continuation
|
|
308
|
+
const streamingState = createStreamingState();
|
|
479
309
|
const reasoningUpdater = createStreamingUpdateCoordinator((newReasoningText) => {
|
|
480
310
|
setMessages(prev => {
|
|
481
|
-
if (currentReasoningMessageId !== null) {
|
|
482
|
-
const index = prev.findIndex(msg => msg.id === currentReasoningMessageId);
|
|
311
|
+
if (streamingState.currentReasoningMessageId !== null) {
|
|
312
|
+
const index = prev.findIndex(msg => msg.id === streamingState.currentReasoningMessageId);
|
|
483
313
|
if (index === -1)
|
|
484
314
|
return prev;
|
|
485
315
|
const current = prev[index];
|
|
@@ -494,7 +324,7 @@ export const useConversation = ({ conversationService, loggingService, }) => {
|
|
|
494
324
|
return trimMessages(next);
|
|
495
325
|
}
|
|
496
326
|
const newId = Date.now();
|
|
497
|
-
currentReasoningMessageId = newId;
|
|
327
|
+
streamingState.currentReasoningMessageId = newId;
|
|
498
328
|
return trimMessages([
|
|
499
329
|
...prev,
|
|
500
330
|
{
|
|
@@ -505,162 +335,23 @@ export const useConversation = ({ conversationService, loggingService, }) => {
|
|
|
505
335
|
]);
|
|
506
336
|
});
|
|
507
337
|
}, REASONING_RESPONSE_THROTTLE_MS);
|
|
508
|
-
//
|
|
509
|
-
const applyConversationEvent = (
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
// logDeduplicated('reasoning_delta');
|
|
519
|
-
const fullReasoningText = event.fullText ?? '';
|
|
520
|
-
const newReasoningText = fullReasoningText.slice(flushedReasoningLength);
|
|
521
|
-
accumulatedReasoningText = newReasoningText;
|
|
522
|
-
if (!newReasoningText.trim())
|
|
523
|
-
return;
|
|
524
|
-
reasoningUpdater.push(newReasoningText);
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
case 'tool_started': {
|
|
528
|
-
// Flush reasoning state
|
|
529
|
-
if (accumulatedReasoningText.trim()) {
|
|
530
|
-
reasoningUpdater.flush();
|
|
531
|
-
flushedReasoningLength += accumulatedReasoningText.length;
|
|
532
|
-
accumulatedReasoningText = '';
|
|
533
|
-
currentReasoningMessageId = null;
|
|
534
|
-
}
|
|
535
|
-
// Flush any accumulated text before showing the tool call
|
|
536
|
-
if (accumulatedText.trim()) {
|
|
537
|
-
const textMessage = {
|
|
538
|
-
id: Date.now() + 1,
|
|
539
|
-
sender: 'bot',
|
|
540
|
-
text: accumulatedText,
|
|
541
|
-
};
|
|
542
|
-
appendMessages([textMessage]);
|
|
543
|
-
accumulatedText = '';
|
|
544
|
-
textWasFlushed = true;
|
|
545
|
-
liveResponseUpdater.cancel();
|
|
546
|
-
setLiveResponse(null);
|
|
547
|
-
}
|
|
548
|
-
const { toolCallId, toolName, arguments: rawArgs } = event;
|
|
549
|
-
const args = (() => {
|
|
550
|
-
if (typeof rawArgs !== 'string') {
|
|
551
|
-
return rawArgs;
|
|
552
|
-
}
|
|
553
|
-
const trimmed = rawArgs.trim();
|
|
554
|
-
if (!trimmed) {
|
|
555
|
-
return rawArgs;
|
|
556
|
-
}
|
|
557
|
-
try {
|
|
558
|
-
return JSON.parse(trimmed);
|
|
559
|
-
}
|
|
560
|
-
catch {
|
|
561
|
-
return rawArgs;
|
|
562
|
-
}
|
|
563
|
-
})();
|
|
564
|
-
const command = (() => {
|
|
565
|
-
if (toolName === 'shell') {
|
|
566
|
-
const cmd = args?.command ?? args?.commands;
|
|
567
|
-
if (typeof cmd === 'string' && cmd.trim()) {
|
|
568
|
-
return cmd;
|
|
569
|
-
}
|
|
570
|
-
if (Array.isArray(cmd) && cmd.length > 0) {
|
|
571
|
-
return cmd.join('\n');
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
if (toolName === 'grep' && args?.pattern) {
|
|
575
|
-
return `grep "${args.pattern}" ${args.path ?? '.'}`;
|
|
576
|
-
}
|
|
577
|
-
if (toolName === 'search_replace') {
|
|
578
|
-
return `search_replace "${args.search_content ?? ''}" → "${args.replace_content ?? ''}" ${args.path ?? ''}`;
|
|
579
|
-
}
|
|
580
|
-
if (toolName === 'apply_patch') {
|
|
581
|
-
return `apply_patch ${args?.type ?? 'unknown'} ${args?.path ?? ''}`;
|
|
582
|
-
}
|
|
583
|
-
if (toolName === 'ask_mentor') {
|
|
584
|
-
return `ask_mentor: ${args?.question ?? ''}`;
|
|
585
|
-
}
|
|
586
|
-
return `${toolName ?? 'unknown_tool'}`;
|
|
587
|
-
})();
|
|
588
|
-
const pendingMessage = {
|
|
589
|
-
id: toolCallId ?? String(Date.now()),
|
|
590
|
-
sender: 'command',
|
|
591
|
-
status: 'running',
|
|
592
|
-
command,
|
|
593
|
-
output: '',
|
|
594
|
-
callId: toolCallId,
|
|
595
|
-
toolName,
|
|
596
|
-
toolArgs: args,
|
|
597
|
-
};
|
|
598
|
-
appendMessages([pendingMessage]);
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
case 'command_message': {
|
|
602
|
-
// logDeduplicated('command_message');
|
|
603
|
-
const cmdMsg = event.message;
|
|
604
|
-
const annotated = annotateCommandMessage(cmdMsg);
|
|
605
|
-
const messagesToAdd = [];
|
|
606
|
-
if (accumulatedReasoningText.trim()) {
|
|
607
|
-
reasoningUpdater.flush();
|
|
608
|
-
flushedReasoningLength +=
|
|
609
|
-
accumulatedReasoningText.length;
|
|
610
|
-
accumulatedReasoningText = '';
|
|
611
|
-
currentReasoningMessageId = null;
|
|
612
|
-
}
|
|
613
|
-
if (accumulatedText.trim()) {
|
|
614
|
-
const textMessage = {
|
|
615
|
-
id: Date.now() + 1,
|
|
616
|
-
sender: 'bot',
|
|
617
|
-
text: accumulatedText,
|
|
618
|
-
};
|
|
619
|
-
messagesToAdd.push(textMessage);
|
|
620
|
-
accumulatedText = '';
|
|
621
|
-
textWasFlushed = true;
|
|
622
|
-
}
|
|
623
|
-
if (messagesToAdd.length > 0) {
|
|
624
|
-
appendMessages(messagesToAdd);
|
|
625
|
-
liveResponseUpdater.cancel();
|
|
626
|
-
setLiveResponse(null);
|
|
627
|
-
}
|
|
628
|
-
// Replace pending message with completed one, or add new if not found
|
|
629
|
-
setMessages(prev => {
|
|
630
|
-
const pendingIndex = annotated.callId
|
|
631
|
-
? prev.findIndex(msg => msg.sender === 'command' &&
|
|
632
|
-
msg.callId === annotated.callId &&
|
|
633
|
-
msg.status === 'running')
|
|
634
|
-
: -1;
|
|
635
|
-
if (pendingIndex !== -1) {
|
|
636
|
-
const next = [...prev];
|
|
637
|
-
next[pendingIndex] = annotated;
|
|
638
|
-
return trimMessages(next);
|
|
639
|
-
}
|
|
640
|
-
return trimMessages([...prev, annotated]);
|
|
641
|
-
});
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
case 'retry': {
|
|
645
|
-
const systemMessage = {
|
|
646
|
-
id: Date.now(),
|
|
647
|
-
sender: 'system',
|
|
648
|
-
text: `Tool hallucination detected (${event.toolName}). Retrying... (Attempt ${event.attempt}/${event.maxRetries})`,
|
|
649
|
-
};
|
|
650
|
-
setMessages(prev => [...prev, systemMessage]);
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
default:
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
};
|
|
338
|
+
// Create event handler using extracted factory
|
|
339
|
+
const applyConversationEvent = createConversationEventHandler({
|
|
340
|
+
liveResponseUpdater,
|
|
341
|
+
reasoningUpdater,
|
|
342
|
+
appendMessages,
|
|
343
|
+
setMessages,
|
|
344
|
+
setLiveResponse,
|
|
345
|
+
trimMessages,
|
|
346
|
+
annotateCommandMessage,
|
|
347
|
+
}, streamingState);
|
|
657
348
|
try {
|
|
658
349
|
// Send a continuation message to resume work
|
|
659
350
|
const continuationMessage = 'Please continue with your previous task.';
|
|
660
351
|
const result = await conversationService.sendMessage(continuationMessage, {
|
|
661
352
|
onEvent: applyConversationEvent,
|
|
662
353
|
});
|
|
663
|
-
applyServiceResult(result, accumulatedText, accumulatedReasoningText, textWasFlushed);
|
|
354
|
+
applyServiceResult(result, streamingState.accumulatedText, streamingState.accumulatedReasoningText, streamingState.textWasFlushed);
|
|
664
355
|
}
|
|
665
356
|
catch (error) {
|
|
666
357
|
loggingService.error('Error in continuation after max turns', {
|
|
@@ -704,16 +395,12 @@ export const useConversation = ({ conversationService, loggingService, }) => {
|
|
|
704
395
|
text: '',
|
|
705
396
|
});
|
|
706
397
|
const liveResponseUpdater = createLiveResponseUpdater(liveMessageId);
|
|
707
|
-
//
|
|
708
|
-
|
|
709
|
-
let accumulatedReasoningText = '';
|
|
710
|
-
let flushedReasoningLength = 0; // Track how much reasoning has been flushed
|
|
711
|
-
let textWasFlushed = false;
|
|
712
|
-
let currentReasoningMessageId = null; // Track current reasoning message ID
|
|
398
|
+
// Create streaming state object for this approval decision
|
|
399
|
+
const streamingState = createStreamingState();
|
|
713
400
|
const reasoningUpdater = createStreamingUpdateCoordinator((newReasoningText) => {
|
|
714
401
|
setMessages(prev => {
|
|
715
|
-
if (currentReasoningMessageId !== null) {
|
|
716
|
-
const index = prev.findIndex(msg => msg.id === currentReasoningMessageId);
|
|
402
|
+
if (streamingState.currentReasoningMessageId !== null) {
|
|
403
|
+
const index = prev.findIndex(msg => msg.id === streamingState.currentReasoningMessageId);
|
|
717
404
|
if (index === -1)
|
|
718
405
|
return prev;
|
|
719
406
|
const current = prev[index];
|
|
@@ -725,7 +412,7 @@ export const useConversation = ({ conversationService, loggingService, }) => {
|
|
|
725
412
|
return trimMessages(next);
|
|
726
413
|
}
|
|
727
414
|
const newId = Date.now();
|
|
728
|
-
currentReasoningMessageId = newId;
|
|
415
|
+
streamingState.currentReasoningMessageId = newId;
|
|
729
416
|
return trimMessages([
|
|
730
417
|
...prev,
|
|
731
418
|
{
|
|
@@ -736,163 +423,21 @@ export const useConversation = ({ conversationService, loggingService, }) => {
|
|
|
736
423
|
]);
|
|
737
424
|
});
|
|
738
425
|
}, REASONING_RESPONSE_THROTTLE_MS);
|
|
739
|
-
// Create event
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
case 'reasoning_delta': {
|
|
750
|
-
// logDeduplicated('reasoning_delta');
|
|
751
|
-
const fullReasoningText = event.fullText ?? '';
|
|
752
|
-
const newReasoningText = fullReasoningText.slice(flushedReasoningLength);
|
|
753
|
-
accumulatedReasoningText = newReasoningText;
|
|
754
|
-
if (!newReasoningText.trim())
|
|
755
|
-
return;
|
|
756
|
-
reasoningUpdater.push(newReasoningText);
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
case 'tool_started': {
|
|
760
|
-
// Flush reasoning state
|
|
761
|
-
if (accumulatedReasoningText.trim()) {
|
|
762
|
-
reasoningUpdater.flush();
|
|
763
|
-
flushedReasoningLength += accumulatedReasoningText.length;
|
|
764
|
-
accumulatedReasoningText = '';
|
|
765
|
-
currentReasoningMessageId = null;
|
|
766
|
-
}
|
|
767
|
-
// Flush any accumulated text before showing the tool call
|
|
768
|
-
if (accumulatedText.trim()) {
|
|
769
|
-
const textMessage = {
|
|
770
|
-
id: Date.now() + 1,
|
|
771
|
-
sender: 'bot',
|
|
772
|
-
text: accumulatedText,
|
|
773
|
-
};
|
|
774
|
-
appendMessages([textMessage]);
|
|
775
|
-
accumulatedText = '';
|
|
776
|
-
textWasFlushed = true;
|
|
777
|
-
liveResponseUpdater.cancel();
|
|
778
|
-
setLiveResponse(null);
|
|
779
|
-
}
|
|
780
|
-
const { toolCallId, toolName, arguments: rawArgs } = event;
|
|
781
|
-
// tool_started.arguments may be either an object or a JSON string
|
|
782
|
-
// depending on provider. Normalize so we can render params.
|
|
783
|
-
const args = (() => {
|
|
784
|
-
if (typeof rawArgs !== 'string') {
|
|
785
|
-
return rawArgs;
|
|
786
|
-
}
|
|
787
|
-
const trimmed = rawArgs.trim();
|
|
788
|
-
if (!trimmed) {
|
|
789
|
-
return rawArgs;
|
|
790
|
-
}
|
|
791
|
-
try {
|
|
792
|
-
return JSON.parse(trimmed);
|
|
793
|
-
}
|
|
794
|
-
catch {
|
|
795
|
-
return rawArgs;
|
|
796
|
-
}
|
|
797
|
-
})();
|
|
798
|
-
const command = (() => {
|
|
799
|
-
if (toolName === 'shell') {
|
|
800
|
-
const cmd = args?.command ?? args?.commands;
|
|
801
|
-
if (typeof cmd === 'string' && cmd.trim()) {
|
|
802
|
-
return cmd;
|
|
803
|
-
}
|
|
804
|
-
if (Array.isArray(cmd) && cmd.length > 0) {
|
|
805
|
-
return cmd.join('\n');
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
if (toolName === 'grep' && args?.pattern) {
|
|
809
|
-
return `grep "${args.pattern}" ${args.path ?? '.'}`;
|
|
810
|
-
}
|
|
811
|
-
if (toolName === 'search_replace') {
|
|
812
|
-
return `search_replace "${args.search_content ?? ''}" → "${args.replace_content ?? ''}" ${args.path ?? ''}`;
|
|
813
|
-
}
|
|
814
|
-
if (toolName === 'apply_patch') {
|
|
815
|
-
return `apply_patch ${args?.type ?? 'unknown'} ${args?.path ?? ''}`;
|
|
816
|
-
}
|
|
817
|
-
if (toolName === 'ask_mentor') {
|
|
818
|
-
return `ask_mentor: ${args?.question ?? ''}`;
|
|
819
|
-
}
|
|
820
|
-
return `${toolName ?? 'unknown_tool'}`;
|
|
821
|
-
})();
|
|
822
|
-
const pendingMessage = {
|
|
823
|
-
id: toolCallId ?? String(Date.now()),
|
|
824
|
-
sender: 'command',
|
|
825
|
-
status: 'running',
|
|
826
|
-
command,
|
|
827
|
-
output: '',
|
|
828
|
-
callId: toolCallId,
|
|
829
|
-
toolName,
|
|
830
|
-
toolArgs: args,
|
|
831
|
-
};
|
|
832
|
-
appendMessages([pendingMessage]);
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
case 'command_message': {
|
|
836
|
-
// logDeduplicated('command_message');
|
|
837
|
-
const cmdMsg = event.message;
|
|
838
|
-
const annotated = annotateCommandMessage(cmdMsg);
|
|
839
|
-
const messagesToAdd = [];
|
|
840
|
-
if (accumulatedReasoningText.trim()) {
|
|
841
|
-
reasoningUpdater.flush();
|
|
842
|
-
flushedReasoningLength +=
|
|
843
|
-
accumulatedReasoningText.length;
|
|
844
|
-
accumulatedReasoningText = '';
|
|
845
|
-
currentReasoningMessageId = null;
|
|
846
|
-
}
|
|
847
|
-
if (accumulatedText.trim()) {
|
|
848
|
-
const textMessage = {
|
|
849
|
-
id: Date.now() + 1,
|
|
850
|
-
sender: 'bot',
|
|
851
|
-
text: accumulatedText,
|
|
852
|
-
};
|
|
853
|
-
messagesToAdd.push(textMessage);
|
|
854
|
-
accumulatedText = '';
|
|
855
|
-
textWasFlushed = true;
|
|
856
|
-
}
|
|
857
|
-
if (messagesToAdd.length > 0) {
|
|
858
|
-
appendMessages(messagesToAdd);
|
|
859
|
-
liveResponseUpdater.cancel();
|
|
860
|
-
setLiveResponse(null);
|
|
861
|
-
}
|
|
862
|
-
// Replace pending message with completed one, or add new if not found
|
|
863
|
-
setMessages(prev => {
|
|
864
|
-
const pendingIndex = annotated.callId
|
|
865
|
-
? prev.findIndex(msg => msg.sender === 'command' &&
|
|
866
|
-
msg.callId === annotated.callId &&
|
|
867
|
-
msg.status === 'running')
|
|
868
|
-
: -1;
|
|
869
|
-
if (pendingIndex !== -1) {
|
|
870
|
-
const next = [...prev];
|
|
871
|
-
next[pendingIndex] = annotated;
|
|
872
|
-
return trimMessages(next);
|
|
873
|
-
}
|
|
874
|
-
return trimMessages([...prev, annotated]);
|
|
875
|
-
});
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
case 'retry': {
|
|
879
|
-
const systemMessage = {
|
|
880
|
-
id: Date.now(),
|
|
881
|
-
sender: 'system',
|
|
882
|
-
text: `Tool hallucination detected (${event.toolName}). Retrying... (Attempt ${event.attempt}/${event.maxRetries})`,
|
|
883
|
-
};
|
|
884
|
-
setMessages(prev => [...prev, systemMessage]);
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
default:
|
|
888
|
-
return;
|
|
889
|
-
}
|
|
890
|
-
};
|
|
426
|
+
// Create event handler using extracted factory
|
|
427
|
+
const applyConversationEvent = createConversationEventHandler({
|
|
428
|
+
liveResponseUpdater,
|
|
429
|
+
reasoningUpdater,
|
|
430
|
+
appendMessages,
|
|
431
|
+
setMessages,
|
|
432
|
+
setLiveResponse,
|
|
433
|
+
trimMessages,
|
|
434
|
+
annotateCommandMessage,
|
|
435
|
+
}, streamingState);
|
|
891
436
|
try {
|
|
892
437
|
const result = await conversationService.handleApprovalDecision(answer, rejectionReason, {
|
|
893
438
|
onEvent: applyConversationEvent,
|
|
894
439
|
});
|
|
895
|
-
applyServiceResult(result, accumulatedText, accumulatedReasoningText, textWasFlushed);
|
|
440
|
+
applyServiceResult(result, streamingState.accumulatedText, streamingState.accumulatedReasoningText, streamingState.textWasFlushed);
|
|
896
441
|
}
|
|
897
442
|
catch (error) {
|
|
898
443
|
loggingService.error('Error in handleApprovalDecision', {
|