snow-ai 0.3.22 → 0.3.23
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/api/gemini.d.ts +5 -1
- package/dist/api/gemini.js +30 -5
- package/dist/api/responses.js +18 -3
- package/dist/hooks/useConversation.js +18 -12
- package/dist/hooks/useHistoryNavigation.js +14 -7
- package/dist/hooks/useInputBuffer.js +4 -3
- package/dist/ui/components/ChatInput.js +5 -4
- package/dist/ui/components/SessionListPanel.js +12 -8
- package/dist/ui/components/SessionListScreen.js +2 -1
- package/dist/ui/components/ToolResultPreview.js +33 -6
- package/dist/ui/pages/ChatScreen.js +20 -12
- package/dist/ui/pages/ConfigScreen.js +163 -14
- package/dist/utils/apiConfig.d.ts +10 -0
- package/dist/utils/sessionConverter.js +16 -11
- package/package.json +1 -1
package/dist/api/gemini.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface GeminiOptions {
|
|
|
7
7
|
includeBuiltinSystemPrompt?: boolean;
|
|
8
8
|
}
|
|
9
9
|
export interface GeminiStreamChunk {
|
|
10
|
-
type: 'content' | 'tool_calls' | 'tool_call_delta' | 'done' | 'usage';
|
|
10
|
+
type: 'content' | 'tool_calls' | 'tool_call_delta' | 'done' | 'usage' | 'reasoning_started' | 'reasoning_delta';
|
|
11
11
|
content?: string;
|
|
12
12
|
tool_calls?: Array<{
|
|
13
13
|
id: string;
|
|
@@ -19,6 +19,10 @@ export interface GeminiStreamChunk {
|
|
|
19
19
|
}>;
|
|
20
20
|
delta?: string;
|
|
21
21
|
usage?: UsageInfo;
|
|
22
|
+
thinking?: {
|
|
23
|
+
type: 'thinking';
|
|
24
|
+
thinking: string;
|
|
25
|
+
};
|
|
22
26
|
}
|
|
23
27
|
export declare function resetGeminiClient(): void;
|
|
24
28
|
/**
|
package/dist/api/gemini.js
CHANGED
|
@@ -17,6 +17,7 @@ function getGeminiConfig() {
|
|
|
17
17
|
? config.baseUrl
|
|
18
18
|
: 'https://generativelanguage.googleapis.com/v1beta',
|
|
19
19
|
customHeaders,
|
|
20
|
+
geminiThinking: config.geminiThinking,
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
return geminiConfig;
|
|
@@ -230,10 +231,16 @@ export async function* createStreamingGeminiCompletion(options, abortSignal, onR
|
|
|
230
231
|
systemInstruction: systemInstruction
|
|
231
232
|
? { parts: [{ text: systemInstruction }] }
|
|
232
233
|
: undefined,
|
|
233
|
-
generationConfig: {
|
|
234
|
-
temperature: options.temperature ?? 0.7,
|
|
235
|
-
},
|
|
236
234
|
};
|
|
235
|
+
// Add thinking configuration if enabled
|
|
236
|
+
// Only include generationConfig when thinking is enabled
|
|
237
|
+
if (config.geminiThinking?.enabled) {
|
|
238
|
+
requestBody.generationConfig = {
|
|
239
|
+
thinkingConfig: {
|
|
240
|
+
thinkingBudget: config.geminiThinking.budget,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
237
244
|
// Add tools if provided
|
|
238
245
|
const geminiTools = convertToolsToGemini(options.tools);
|
|
239
246
|
if (geminiTools) {
|
|
@@ -263,6 +270,7 @@ export async function* createStreamingGeminiCompletion(options, abortSignal, onR
|
|
|
263
270
|
throw new Error('No response body from Gemini API');
|
|
264
271
|
}
|
|
265
272
|
let contentBuffer = '';
|
|
273
|
+
let thinkingTextBuffer = ''; // Accumulate thinking text content
|
|
266
274
|
let toolCallsBuffer = [];
|
|
267
275
|
let hasToolCalls = false;
|
|
268
276
|
let toolCallIndex = 0;
|
|
@@ -310,8 +318,17 @@ export async function* createStreamingGeminiCompletion(options, abortSignal, onR
|
|
|
310
318
|
const candidate = chunk.candidates[0];
|
|
311
319
|
if (candidate.content && candidate.content.parts) {
|
|
312
320
|
for (const part of candidate.content.parts) {
|
|
313
|
-
// Process
|
|
314
|
-
|
|
321
|
+
// Process thought content (Gemini thinking)
|
|
322
|
+
// When part.thought === true, the text field contains thinking content
|
|
323
|
+
if (part.thought === true && part.text) {
|
|
324
|
+
thinkingTextBuffer += part.text;
|
|
325
|
+
yield {
|
|
326
|
+
type: 'reasoning_delta',
|
|
327
|
+
delta: part.text,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
// Process regular text content (when thought is not true)
|
|
331
|
+
else if (part.text) {
|
|
315
332
|
contentBuffer += part.text;
|
|
316
333
|
yield {
|
|
317
334
|
type: 'content',
|
|
@@ -374,9 +391,17 @@ export async function* createStreamingGeminiCompletion(options, abortSignal, onR
|
|
|
374
391
|
usage: usageData,
|
|
375
392
|
};
|
|
376
393
|
}
|
|
394
|
+
// Return complete thinking block if thinking content exists
|
|
395
|
+
const thinkingBlock = thinkingTextBuffer
|
|
396
|
+
? {
|
|
397
|
+
type: 'thinking',
|
|
398
|
+
thinking: thinkingTextBuffer,
|
|
399
|
+
}
|
|
400
|
+
: undefined;
|
|
377
401
|
// Signal completion
|
|
378
402
|
yield {
|
|
379
403
|
type: 'done',
|
|
404
|
+
thinking: thinkingBlock,
|
|
380
405
|
};
|
|
381
406
|
}, {
|
|
382
407
|
abortSignal,
|
package/dist/api/responses.js
CHANGED
|
@@ -79,6 +79,19 @@ function getOpenAIConfig() {
|
|
|
79
79
|
}
|
|
80
80
|
return openaiConfig;
|
|
81
81
|
}
|
|
82
|
+
function getResponsesReasoningConfig() {
|
|
83
|
+
const config = getOpenAiConfig();
|
|
84
|
+
const reasoningConfig = config.responsesReasoning;
|
|
85
|
+
// 如果 reasoning 未启用,返回 null
|
|
86
|
+
if (!reasoningConfig?.enabled) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
// 返回配置,summary 永远默认为 'auto'
|
|
90
|
+
return {
|
|
91
|
+
effort: reasoningConfig.effort || 'high',
|
|
92
|
+
summary: 'auto',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
82
95
|
export function resetOpenAIClient() {
|
|
83
96
|
openaiConfig = null;
|
|
84
97
|
}
|
|
@@ -237,6 +250,8 @@ export async function* createStreamingResponse(options, abortSignal, onRetry) {
|
|
|
237
250
|
const config = getOpenAIConfig();
|
|
238
251
|
// 提取系统提示词和转换后的消息
|
|
239
252
|
const { input: requestInput, systemInstructions } = convertToResponseInput(options.messages, options.includeBuiltinSystemPrompt !== false);
|
|
253
|
+
// 获取配置的 reasoning 设置
|
|
254
|
+
const configuredReasoning = getResponsesReasoningConfig();
|
|
240
255
|
// 使用重试包装生成器
|
|
241
256
|
yield* withRetryGenerator(async function* () {
|
|
242
257
|
const requestPayload = {
|
|
@@ -246,9 +261,9 @@ export async function* createStreamingResponse(options, abortSignal, onRetry) {
|
|
|
246
261
|
tools: convertToolsForResponses(options.tools),
|
|
247
262
|
tool_choice: options.tool_choice,
|
|
248
263
|
parallel_tool_calls: false,
|
|
249
|
-
//
|
|
250
|
-
...(
|
|
251
|
-
reasoning:
|
|
264
|
+
// 只有当 reasoning 启用时才添加 reasoning 字段
|
|
265
|
+
...(configuredReasoning && {
|
|
266
|
+
reasoning: configuredReasoning,
|
|
252
267
|
}),
|
|
253
268
|
store: false,
|
|
254
269
|
stream: true,
|
|
@@ -124,6 +124,7 @@ export async function handleConversationWithTools(options) {
|
|
|
124
124
|
let receivedToolCalls;
|
|
125
125
|
let receivedReasoning;
|
|
126
126
|
let receivedThinking; // Accumulate thinking content from all platforms
|
|
127
|
+
let hasStartedReasoning = false; // Track if reasoning has started (for Gemini thinking)
|
|
127
128
|
// Stream AI response - choose API based on config
|
|
128
129
|
let toolCallAccumulator = ''; // Accumulate tool call deltas for token counting
|
|
129
130
|
let reasoningAccumulator = ''; // Accumulate reasoning summary deltas for token counting (Responses API only)
|
|
@@ -194,6 +195,23 @@ export async function handleConversationWithTools(options) {
|
|
|
194
195
|
// Reasoning started (Responses API only) - set reasoning state
|
|
195
196
|
setIsReasoning?.(true);
|
|
196
197
|
}
|
|
198
|
+
else if (chunk.type === 'reasoning_delta' && chunk.delta) {
|
|
199
|
+
// Handle reasoning delta from Gemini thinking
|
|
200
|
+
// When reasoning_delta is received, set reasoning state if not already set
|
|
201
|
+
if (!hasStartedReasoning) {
|
|
202
|
+
setIsReasoning?.(true);
|
|
203
|
+
hasStartedReasoning = true;
|
|
204
|
+
}
|
|
205
|
+
// Note: reasoning content is NOT sent back to AI, only counted for display
|
|
206
|
+
reasoningAccumulator += chunk.delta;
|
|
207
|
+
try {
|
|
208
|
+
const tokens = encoder.encode(streamedContent + toolCallAccumulator + reasoningAccumulator);
|
|
209
|
+
setStreamTokenCount(tokens.length);
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
// Ignore encoding errors
|
|
213
|
+
}
|
|
214
|
+
}
|
|
197
215
|
else if (chunk.type === 'content' && chunk.content) {
|
|
198
216
|
// Accumulate content and update token count
|
|
199
217
|
// When content starts, reasoning is done
|
|
@@ -220,18 +238,6 @@ export async function handleConversationWithTools(options) {
|
|
|
220
238
|
// Ignore encoding errors
|
|
221
239
|
}
|
|
222
240
|
}
|
|
223
|
-
else if (chunk.type === 'reasoning_delta' && chunk.delta) {
|
|
224
|
-
// Accumulate reasoning summary deltas for token counting (Responses API only)
|
|
225
|
-
// Note: reasoning content is NOT sent back to AI, only counted for display
|
|
226
|
-
reasoningAccumulator += chunk.delta;
|
|
227
|
-
try {
|
|
228
|
-
const tokens = encoder.encode(streamedContent + toolCallAccumulator + reasoningAccumulator);
|
|
229
|
-
setStreamTokenCount(tokens.length);
|
|
230
|
-
}
|
|
231
|
-
catch (e) {
|
|
232
|
-
// Ignore encoding errors
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
241
|
else if (chunk.type === 'tool_calls' && chunk.tool_calls) {
|
|
236
242
|
receivedToolCalls = chunk.tool_calls;
|
|
237
243
|
}
|
|
@@ -34,11 +34,18 @@ export function useHistoryNavigation(buffer, triggerUpdate, chatHistory, onHisto
|
|
|
34
34
|
.map((msg, index) => ({ ...msg, originalIndex: index }))
|
|
35
35
|
.filter(msg => msg.role === 'user' && msg.content.trim());
|
|
36
36
|
// Keep original order (oldest first, newest last) and map with display numbers
|
|
37
|
-
return userMessages.map((msg, index) =>
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
return userMessages.map((msg, index) => {
|
|
38
|
+
// Remove all newlines, control characters and extra whitespace to ensure single line display
|
|
39
|
+
const cleanContent = msg.content
|
|
40
|
+
.replace(/[\r\n\t\v\f\u0000-\u001F\u007F-\u009F]+/g, ' ')
|
|
41
|
+
.replace(/\s+/g, ' ')
|
|
42
|
+
.trim();
|
|
43
|
+
return {
|
|
44
|
+
label: `${index + 1}. ${cleanContent.slice(0, 50)}${cleanContent.length > 50 ? '...' : ''}`,
|
|
45
|
+
value: msg.originalIndex.toString(),
|
|
46
|
+
infoText: msg.content,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
42
49
|
}, [chatHistory]);
|
|
43
50
|
// Handle history selection
|
|
44
51
|
const handleHistorySelect = useCallback((value) => {
|
|
@@ -71,7 +78,7 @@ export function useHistoryNavigation(buffer, triggerUpdate, chatHistory, onHisto
|
|
|
71
78
|
triggerUpdate();
|
|
72
79
|
}
|
|
73
80
|
return true;
|
|
74
|
-
}, [currentHistoryIndex
|
|
81
|
+
}, [currentHistoryIndex]); // 移除 buffer 避免循环依赖
|
|
75
82
|
// Terminal-style history navigation: navigate down (newer)
|
|
76
83
|
const navigateHistoryDown = useCallback(() => {
|
|
77
84
|
if (currentHistoryIndex === -1)
|
|
@@ -93,7 +100,7 @@ export function useHistoryNavigation(buffer, triggerUpdate, chatHistory, onHisto
|
|
|
93
100
|
}
|
|
94
101
|
triggerUpdate();
|
|
95
102
|
return true;
|
|
96
|
-
}, [currentHistoryIndex
|
|
103
|
+
}, [currentHistoryIndex]); // 移除 buffer 避免循环依赖
|
|
97
104
|
// Reset history navigation state
|
|
98
105
|
const resetHistoryNavigation = useCallback(() => {
|
|
99
106
|
setCurrentHistoryIndex(-1);
|
|
@@ -8,13 +8,14 @@ export function useInputBuffer(viewport) {
|
|
|
8
8
|
const now = Date.now();
|
|
9
9
|
lastUpdateTime.current = now;
|
|
10
10
|
forceUpdate({});
|
|
11
|
-
}, []);
|
|
11
|
+
}, []); // 空依赖项确保函数稳定
|
|
12
12
|
const [buffer] = useState(() => new TextBuffer(viewport, triggerUpdate));
|
|
13
13
|
// Update buffer viewport when viewport changes
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
buffer.updateViewport(viewport);
|
|
16
|
-
triggerUpdate
|
|
17
|
-
|
|
16
|
+
// 直接调用 forceUpdate 而不是 triggerUpdate,避免依赖问题
|
|
17
|
+
forceUpdate({});
|
|
18
|
+
}, [viewport.width, viewport.height]); // 移除 buffer 和 triggerUpdate 避免循环依赖
|
|
18
19
|
// Cleanup buffer on unmount
|
|
19
20
|
useEffect(() => {
|
|
20
21
|
return () => {
|
|
@@ -193,7 +193,7 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
|
|
|
193
193
|
const percentage = calculateContextPercentage(contextUsage);
|
|
194
194
|
onContextPercentageChange(percentage);
|
|
195
195
|
}
|
|
196
|
-
}, [contextUsage
|
|
196
|
+
}, [contextUsage]); // 移除 onContextPercentageChange 避免循环依赖
|
|
197
197
|
// Render cursor based on focus state
|
|
198
198
|
const renderCursor = useCallback((char) => {
|
|
199
199
|
if (hasFocus) {
|
|
@@ -223,7 +223,7 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
|
|
|
223
223
|
renderCursor(' '),
|
|
224
224
|
React.createElement(Text, { color: disabled ? 'darkGray' : 'gray', dimColor: true }, disabled ? 'Waiting for response...' : placeholder)));
|
|
225
225
|
}
|
|
226
|
-
}, [buffer, disabled, placeholder, renderCursor
|
|
226
|
+
}, [buffer, disabled, placeholder, renderCursor]); // 移除 buffer.text 避免循环依赖,buffer 变化时会自然触发重渲染
|
|
227
227
|
return (React.createElement(Box, { flexDirection: "column", paddingX: 1, width: terminalWidth },
|
|
228
228
|
showHistoryMenu && (React.createElement(Box, { flexDirection: "column", marginBottom: 1, width: terminalWidth - 2 },
|
|
229
229
|
React.createElement(Box, { flexDirection: "column" }, (() => {
|
|
@@ -248,8 +248,9 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
|
|
|
248
248
|
" more above...")) : (React.createElement(Text, null, " "))),
|
|
249
249
|
visibleMessages.map((message, displayIndex) => {
|
|
250
250
|
const actualIndex = startIndex + displayIndex;
|
|
251
|
-
//
|
|
251
|
+
// Ensure single line by removing all newlines and control characters
|
|
252
252
|
const singleLineLabel = message.label
|
|
253
|
+
.replace(/[\r\n\t\v\f\u0000-\u001F\u007F-\u009F]+/g, ' ')
|
|
253
254
|
.replace(/\s+/g, ' ')
|
|
254
255
|
.trim();
|
|
255
256
|
// Calculate available width for the message
|
|
@@ -261,7 +262,7 @@ export default function ChatInput({ onSubmit, onCommand, placeholder = 'Type you
|
|
|
261
262
|
return (React.createElement(Box, { key: message.value, height: 1 },
|
|
262
263
|
React.createElement(Text, { color: actualIndex === historySelectedIndex
|
|
263
264
|
? 'green'
|
|
264
|
-
: 'white', bold: true },
|
|
265
|
+
: 'white', bold: true, wrap: "truncate" },
|
|
265
266
|
actualIndex === historySelectedIndex ? '❯ ' : ' ',
|
|
266
267
|
truncatedLabel)));
|
|
267
268
|
}),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
|
-
import { sessionManager } from '../../utils/sessionManager.js';
|
|
3
|
+
import { sessionManager, } from '../../utils/sessionManager.js';
|
|
4
4
|
export default function SessionListPanel({ onSelectSession, onClose }) {
|
|
5
5
|
const [sessions, setSessions] = useState([]);
|
|
6
6
|
const [loading, setLoading] = useState(true);
|
|
@@ -140,32 +140,36 @@ export default function SessionListPanel({ onSelectSession, onClose }) {
|
|
|
140
140
|
sessions.length,
|
|
141
141
|
")",
|
|
142
142
|
currentSession && ` • ${currentSession.messageCount} msgs`,
|
|
143
|
-
markedSessions.size > 0 && React.createElement(Text, { color: "yellow" },
|
|
143
|
+
markedSessions.size > 0 && (React.createElement(Text, { color: "yellow" },
|
|
144
144
|
" \u2022 ",
|
|
145
145
|
markedSessions.size,
|
|
146
|
-
" marked")),
|
|
146
|
+
" marked"))),
|
|
147
147
|
React.createElement(Text, { color: "gray", dimColor: true }, "\u2191\u2193 navigate \u2022 Space mark \u2022 D delete \u2022 Enter select \u2022 ESC close")),
|
|
148
148
|
hasPrevious && (React.createElement(Text, { color: "gray", dimColor: true },
|
|
149
|
-
|
|
149
|
+
' ',
|
|
150
|
+
"\u2191 ",
|
|
150
151
|
scrollOffset,
|
|
151
152
|
" more above")),
|
|
152
153
|
visibleSessions.map((session, index) => {
|
|
153
154
|
const actualIndex = scrollOffset + index;
|
|
154
155
|
const isSelected = actualIndex === selectedIndex;
|
|
155
156
|
const isMarked = markedSessions.has(session.id);
|
|
156
|
-
|
|
157
|
+
// Remove newlines and other whitespace characters from title
|
|
158
|
+
const cleanTitle = (session.title || 'Untitled').replace(/[\r\n\t]+/g, ' ');
|
|
157
159
|
const timeStr = formatDate(session.updatedAt);
|
|
158
|
-
const truncatedLabel =
|
|
160
|
+
const truncatedLabel = cleanTitle.length > 50 ? cleanTitle.slice(0, 47) + '...' : cleanTitle;
|
|
159
161
|
return (React.createElement(Box, { key: session.id },
|
|
160
162
|
React.createElement(Text, { color: isMarked ? 'green' : 'gray' }, isMarked ? '✔ ' : ' '),
|
|
161
163
|
React.createElement(Text, { color: isSelected ? 'green' : 'gray' }, isSelected ? '❯ ' : ' '),
|
|
162
164
|
React.createElement(Text, { color: isSelected ? 'cyan' : isMarked ? 'green' : 'white' }, truncatedLabel),
|
|
163
165
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
164
|
-
|
|
166
|
+
' ',
|
|
167
|
+
"\u2022 ",
|
|
165
168
|
timeStr)));
|
|
166
169
|
}),
|
|
167
170
|
hasMore && (React.createElement(Text, { color: "gray", dimColor: true },
|
|
168
|
-
|
|
171
|
+
' ',
|
|
172
|
+
"\u2193 ",
|
|
169
173
|
sessions.length - scrollOffset - VISIBLE_ITEMS,
|
|
170
174
|
" more below"))));
|
|
171
175
|
}
|
|
@@ -58,7 +58,8 @@ export default function SessionListScreen({ onBack, onSelectSession }) {
|
|
|
58
58
|
const maxLabelWidth = Math.max(30, terminalWidth - reservedSpace);
|
|
59
59
|
return sessions.map(session => {
|
|
60
60
|
const timeString = formatDate(session.updatedAt);
|
|
61
|
-
|
|
61
|
+
// Remove newlines and other whitespace characters from title
|
|
62
|
+
const title = (session.title || 'Untitled').replace(/[\r\n\t]+/g, ' ');
|
|
62
63
|
// Format: "Title • 5 msgs • 2h ago"
|
|
63
64
|
const messageInfo = `${session.messageCount} msg${session.messageCount !== 1 ? 's' : ''}`;
|
|
64
65
|
const fullLabel = `${title} • ${messageInfo} • ${timeString}`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
+
import TodoTree from './TodoTree.js';
|
|
3
4
|
/**
|
|
4
5
|
* Display a compact preview of tool execution results
|
|
5
6
|
* Shows a tree-like structure with limited content
|
|
@@ -36,6 +37,9 @@ export default function ToolResultPreview({ toolName, result, maxLines = 5, }) {
|
|
|
36
37
|
else if (toolName.startsWith('ace-')) {
|
|
37
38
|
return renderACEPreview(toolName, data, maxLines);
|
|
38
39
|
}
|
|
40
|
+
else if (toolName.startsWith('todo-')) {
|
|
41
|
+
return renderTodoPreview(toolName, data, maxLines);
|
|
42
|
+
}
|
|
39
43
|
else {
|
|
40
44
|
// Generic preview for unknown tools
|
|
41
45
|
return renderGenericPreview(data, maxLines);
|
|
@@ -56,7 +60,7 @@ function renderSubAgentPreview(data, _maxLines) {
|
|
|
56
60
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
57
61
|
"\u2514\u2500 Sub-agent completed (",
|
|
58
62
|
lines.length,
|
|
59
|
-
|
|
63
|
+
' ',
|
|
60
64
|
lines.length === 1 ? 'line' : 'lines',
|
|
61
65
|
" output)")));
|
|
62
66
|
}
|
|
@@ -141,7 +145,7 @@ function renderACEPreview(toolName, data, maxLines) {
|
|
|
141
145
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
142
146
|
"\u2514\u2500 Found ",
|
|
143
147
|
symbols.length,
|
|
144
|
-
|
|
148
|
+
' ',
|
|
145
149
|
symbols.length === 1 ? 'symbol' : 'symbols')));
|
|
146
150
|
}
|
|
147
151
|
// Handle ace-find-references results
|
|
@@ -156,7 +160,7 @@ function renderACEPreview(toolName, data, maxLines) {
|
|
|
156
160
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
157
161
|
"\u2514\u2500 Found ",
|
|
158
162
|
references.length,
|
|
159
|
-
|
|
163
|
+
' ',
|
|
160
164
|
references.length === 1 ? 'reference' : 'references')));
|
|
161
165
|
}
|
|
162
166
|
// Handle ace-find-definition result
|
|
@@ -188,7 +192,7 @@ function renderACEPreview(toolName, data, maxLines) {
|
|
|
188
192
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
189
193
|
"\u2514\u2500 Found ",
|
|
190
194
|
symbols.length,
|
|
191
|
-
|
|
195
|
+
' ',
|
|
192
196
|
symbols.length === 1 ? 'symbol' : 'symbols',
|
|
193
197
|
" in file")));
|
|
194
198
|
}
|
|
@@ -204,12 +208,12 @@ function renderACEPreview(toolName, data, maxLines) {
|
|
|
204
208
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
205
209
|
"\u251C\u2500 ",
|
|
206
210
|
data.symbols?.length || 0,
|
|
207
|
-
|
|
211
|
+
' ',
|
|
208
212
|
(data.symbols?.length || 0) === 1 ? 'symbol' : 'symbols'),
|
|
209
213
|
React.createElement(Text, { color: "gray", dimColor: true },
|
|
210
214
|
"\u2514\u2500 ",
|
|
211
215
|
data.references?.length || 0,
|
|
212
|
-
|
|
216
|
+
' ',
|
|
213
217
|
(data.references?.length || 0) === 1 ? 'reference' : 'references')));
|
|
214
218
|
}
|
|
215
219
|
// Generic ACE tool preview
|
|
@@ -278,3 +282,26 @@ function renderGenericPreview(data, maxLines) {
|
|
|
278
282
|
valueStr));
|
|
279
283
|
})));
|
|
280
284
|
}
|
|
285
|
+
function renderTodoPreview(_toolName, data, _maxLines) {
|
|
286
|
+
// Handle todo-create, todo-get, todo-update, todo-add, todo-delete
|
|
287
|
+
// Debug: Check if data is actually the stringified result that needs parsing again
|
|
288
|
+
// Some tools might return the result wrapped in content[0].text
|
|
289
|
+
let todoData = data;
|
|
290
|
+
// If data has content array (MCP format), extract the text
|
|
291
|
+
if (data.content && Array.isArray(data.content) && data.content[0]?.text) {
|
|
292
|
+
try {
|
|
293
|
+
todoData = JSON.parse(data.content[0].text);
|
|
294
|
+
}
|
|
295
|
+
catch (e) {
|
|
296
|
+
// If parsing fails, just use original data
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (!todoData.todos) {
|
|
300
|
+
return (React.createElement(Box, { marginLeft: 2 },
|
|
301
|
+
React.createElement(Text, { color: "gray", dimColor: true },
|
|
302
|
+
"\u2514\u2500 ",
|
|
303
|
+
todoData.message || 'No TODO list')));
|
|
304
|
+
}
|
|
305
|
+
// Use the TodoTree component to display the TODO list
|
|
306
|
+
return React.createElement(TodoTree, { todos: todoData.todos });
|
|
307
|
+
}
|
|
@@ -157,7 +157,7 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
157
157
|
return () => {
|
|
158
158
|
clearTimeout(handler);
|
|
159
159
|
};
|
|
160
|
-
}, [terminalWidth
|
|
160
|
+
}, [terminalWidth]); // stdout 对象可能在每次渲染时变化,移除以避免循环
|
|
161
161
|
// Reload messages from session when remountKey changes (to restore sub-agent messages)
|
|
162
162
|
useEffect(() => {
|
|
163
163
|
if (remountKey === 0)
|
|
@@ -361,18 +361,26 @@ export default function ChatScreen({ skipWelcome }) {
|
|
|
361
361
|
return;
|
|
362
362
|
}
|
|
363
363
|
}
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
let sessionUserMessageCount = 0;
|
|
364
|
+
// Special case: if rolling back to index 0 (first message), always delete entire session
|
|
365
|
+
// This handles the case where user interrupts the first conversation
|
|
367
366
|
let sessionTruncateIndex = currentSession.messages.length;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
367
|
+
if (selectedIndex === 0) {
|
|
368
|
+
// Rolling back to the very first message means deleting entire session
|
|
369
|
+
sessionTruncateIndex = 0;
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
// Find the corresponding user message in session to delete
|
|
373
|
+
// We start from the end and count backwards
|
|
374
|
+
let sessionUserMessageCount = 0;
|
|
375
|
+
for (let i = currentSession.messages.length - 1; i >= 0; i--) {
|
|
376
|
+
const msg = currentSession.messages[i];
|
|
377
|
+
if (msg && msg.role === 'user') {
|
|
378
|
+
sessionUserMessageCount++;
|
|
379
|
+
if (sessionUserMessageCount === uiUserMessagesToDelete) {
|
|
380
|
+
// We want to delete from this user message onwards
|
|
381
|
+
sessionTruncateIndex = i;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
376
384
|
}
|
|
377
385
|
}
|
|
378
386
|
}
|
|
@@ -51,6 +51,10 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
51
51
|
const [anthropicBeta, setAnthropicBeta] = useState(false);
|
|
52
52
|
const [thinkingEnabled, setThinkingEnabled] = useState(false);
|
|
53
53
|
const [thinkingBudgetTokens, setThinkingBudgetTokens] = useState(10000);
|
|
54
|
+
const [geminiThinkingEnabled, setGeminiThinkingEnabled] = useState(false);
|
|
55
|
+
const [geminiThinkingBudget, setGeminiThinkingBudget] = useState(1024);
|
|
56
|
+
const [responsesReasoningEnabled, setResponsesReasoningEnabled] = useState(false);
|
|
57
|
+
const [responsesReasoningEffort, setResponsesReasoningEffort] = useState('high');
|
|
54
58
|
// Model settings
|
|
55
59
|
const [advancedModel, setAdvancedModel] = useState('');
|
|
56
60
|
const [basicModel, setBasicModel] = useState('');
|
|
@@ -101,7 +105,17 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
101
105
|
'thinkingEnabled',
|
|
102
106
|
'thinkingBudgetTokens',
|
|
103
107
|
]
|
|
104
|
-
:
|
|
108
|
+
: requestMethod === 'gemini'
|
|
109
|
+
? [
|
|
110
|
+
'geminiThinkingEnabled',
|
|
111
|
+
'geminiThinkingBudget',
|
|
112
|
+
]
|
|
113
|
+
: requestMethod === 'responses'
|
|
114
|
+
? [
|
|
115
|
+
'responsesReasoningEnabled',
|
|
116
|
+
'responsesReasoningEffort',
|
|
117
|
+
]
|
|
118
|
+
: []),
|
|
105
119
|
'advancedModel',
|
|
106
120
|
'basicModel',
|
|
107
121
|
'compactModelName',
|
|
@@ -126,6 +140,20 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
126
140
|
currentField === 'thinkingBudgetTokens')) {
|
|
127
141
|
setCurrentField('advancedModel');
|
|
128
142
|
}
|
|
143
|
+
// If requestMethod is not 'gemini' and currentField is on Gemini-specific fields,
|
|
144
|
+
// move to the next available field
|
|
145
|
+
if (requestMethod !== 'gemini' &&
|
|
146
|
+
(currentField === 'geminiThinkingEnabled' ||
|
|
147
|
+
currentField === 'geminiThinkingBudget')) {
|
|
148
|
+
setCurrentField('advancedModel');
|
|
149
|
+
}
|
|
150
|
+
// If requestMethod is not 'responses' and currentField is on Responses-specific fields,
|
|
151
|
+
// move to the next available field
|
|
152
|
+
if (requestMethod !== 'responses' &&
|
|
153
|
+
(currentField === 'responsesReasoningEnabled' ||
|
|
154
|
+
currentField === 'responsesReasoningEffort')) {
|
|
155
|
+
setCurrentField('advancedModel');
|
|
156
|
+
}
|
|
129
157
|
}, [requestMethod, currentField]);
|
|
130
158
|
const loadProfilesAndConfig = () => {
|
|
131
159
|
// Load profiles
|
|
@@ -139,6 +167,10 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
139
167
|
setAnthropicBeta(config.anthropicBeta || false);
|
|
140
168
|
setThinkingEnabled(config.thinking?.type === 'enabled' || false);
|
|
141
169
|
setThinkingBudgetTokens(config.thinking?.budget_tokens || 10000);
|
|
170
|
+
setGeminiThinkingEnabled(config.geminiThinking?.enabled || false);
|
|
171
|
+
setGeminiThinkingBudget(config.geminiThinking?.budget || 1024);
|
|
172
|
+
setResponsesReasoningEnabled(config.responsesReasoning?.enabled || false);
|
|
173
|
+
setResponsesReasoningEffort(config.responsesReasoning?.effort || 'high');
|
|
142
174
|
setAdvancedModel(config.advancedModel || '');
|
|
143
175
|
setBasicModel(config.basicModel || '');
|
|
144
176
|
setMaxContextTokens(config.maxContextTokens || 4000);
|
|
@@ -197,6 +229,10 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
197
229
|
return maxTokens.toString();
|
|
198
230
|
if (currentField === 'thinkingBudgetTokens')
|
|
199
231
|
return thinkingBudgetTokens.toString();
|
|
232
|
+
if (currentField === 'geminiThinkingBudget')
|
|
233
|
+
return geminiThinkingBudget.toString();
|
|
234
|
+
if (currentField === 'responsesReasoningEffort')
|
|
235
|
+
return responsesReasoningEffort;
|
|
200
236
|
if (currentField === 'compactModelName')
|
|
201
237
|
return compactModelName;
|
|
202
238
|
return '';
|
|
@@ -306,6 +342,26 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
306
342
|
// Explicitly set to undefined to clear it when disabled
|
|
307
343
|
config.thinking = undefined;
|
|
308
344
|
}
|
|
345
|
+
// Save Gemini thinking configuration
|
|
346
|
+
if (geminiThinkingEnabled) {
|
|
347
|
+
config.geminiThinking = {
|
|
348
|
+
enabled: true,
|
|
349
|
+
budget: geminiThinkingBudget,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
config.geminiThinking = undefined;
|
|
354
|
+
}
|
|
355
|
+
// Save Responses reasoning configuration
|
|
356
|
+
if (responsesReasoningEnabled) {
|
|
357
|
+
config.responsesReasoning = {
|
|
358
|
+
enabled: true,
|
|
359
|
+
effort: responsesReasoningEffort,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
config.responsesReasoning = undefined;
|
|
364
|
+
}
|
|
309
365
|
// Only save compactModel if modelName is provided (uses same baseUrl/apiKey)
|
|
310
366
|
if (compactModelName) {
|
|
311
367
|
config.compactModel = {
|
|
@@ -325,6 +381,12 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
325
381
|
thinking: thinkingEnabled
|
|
326
382
|
? { type: 'enabled', budget_tokens: thinkingBudgetTokens }
|
|
327
383
|
: undefined,
|
|
384
|
+
geminiThinking: geminiThinkingEnabled
|
|
385
|
+
? { enabled: true, budget: geminiThinkingBudget }
|
|
386
|
+
: undefined,
|
|
387
|
+
responsesReasoning: responsesReasoningEnabled
|
|
388
|
+
? { enabled: true, effort: responsesReasoningEffort }
|
|
389
|
+
: undefined,
|
|
328
390
|
advancedModel,
|
|
329
391
|
basicModel,
|
|
330
392
|
maxContextTokens,
|
|
@@ -415,6 +477,51 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
415
477
|
thinkingBudgetTokens))),
|
|
416
478
|
!isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
417
479
|
React.createElement(Text, { color: "gray" }, thinkingBudgetTokens)))));
|
|
480
|
+
case 'geminiThinkingEnabled':
|
|
481
|
+
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
482
|
+
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
483
|
+
isActive ? '❯ ' : ' ',
|
|
484
|
+
"Gemini Thinking Enabled:"),
|
|
485
|
+
React.createElement(Box, { marginLeft: 3 },
|
|
486
|
+
React.createElement(Text, { color: "gray" },
|
|
487
|
+
geminiThinkingEnabled ? '☒ Enabled' : '☐ Disabled',
|
|
488
|
+
" (Press Enter to toggle)"))));
|
|
489
|
+
case 'geminiThinkingBudget':
|
|
490
|
+
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
491
|
+
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
492
|
+
isActive ? '❯ ' : ' ',
|
|
493
|
+
"Gemini Thinking Budget:"),
|
|
494
|
+
isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
495
|
+
React.createElement(Text, { color: "cyan" },
|
|
496
|
+
"Enter value: ",
|
|
497
|
+
geminiThinkingBudget))),
|
|
498
|
+
!isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
499
|
+
React.createElement(Text, { color: "gray" }, geminiThinkingBudget)))));
|
|
500
|
+
case 'responsesReasoningEnabled':
|
|
501
|
+
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
502
|
+
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
503
|
+
isActive ? '❯ ' : ' ',
|
|
504
|
+
"Responses Reasoning Enabled:"),
|
|
505
|
+
React.createElement(Box, { marginLeft: 3 },
|
|
506
|
+
React.createElement(Text, { color: "gray" },
|
|
507
|
+
responsesReasoningEnabled ? '☒ Enabled' : '☐ Disabled',
|
|
508
|
+
" (Press Enter to toggle)"))));
|
|
509
|
+
case 'responsesReasoningEffort':
|
|
510
|
+
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
511
|
+
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
512
|
+
isActive ? '❯ ' : ' ',
|
|
513
|
+
"Responses Reasoning Effort:"),
|
|
514
|
+
!isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
515
|
+
React.createElement(Text, { color: "gray" }, responsesReasoningEffort.toUpperCase()))),
|
|
516
|
+
isCurrentlyEditing && (React.createElement(Box, { marginLeft: 3 },
|
|
517
|
+
React.createElement(Select, { options: [
|
|
518
|
+
{ label: 'Low', value: 'low' },
|
|
519
|
+
{ label: 'Medium', value: 'medium' },
|
|
520
|
+
{ label: 'High', value: 'high' },
|
|
521
|
+
], defaultValue: responsesReasoningEffort, onChange: value => {
|
|
522
|
+
setResponsesReasoningEffort(value);
|
|
523
|
+
setIsEditing(false);
|
|
524
|
+
} })))));
|
|
418
525
|
case 'advancedModel':
|
|
419
526
|
return (React.createElement(Box, { key: field, flexDirection: "column" },
|
|
420
527
|
React.createElement(Text, { color: isActive ? 'green' : 'white' },
|
|
@@ -561,7 +668,8 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
561
668
|
currentField === 'requestMethod' ||
|
|
562
669
|
currentField === 'advancedModel' ||
|
|
563
670
|
currentField === 'basicModel' ||
|
|
564
|
-
currentField === 'compactModelName'
|
|
671
|
+
currentField === 'compactModelName' ||
|
|
672
|
+
currentField === 'responsesReasoningEffort') &&
|
|
565
673
|
key.escape) {
|
|
566
674
|
setIsEditing(false);
|
|
567
675
|
setSearchTerm('');
|
|
@@ -581,13 +689,16 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
581
689
|
// Handle numeric input for token fields
|
|
582
690
|
if (currentField === 'maxContextTokens' ||
|
|
583
691
|
currentField === 'maxTokens' ||
|
|
584
|
-
currentField === 'thinkingBudgetTokens'
|
|
692
|
+
currentField === 'thinkingBudgetTokens' ||
|
|
693
|
+
currentField === 'geminiThinkingBudget') {
|
|
585
694
|
if (input && input.match(/[0-9]/)) {
|
|
586
695
|
const currentValue = currentField === 'maxContextTokens'
|
|
587
696
|
? maxContextTokens
|
|
588
697
|
: currentField === 'maxTokens'
|
|
589
698
|
? maxTokens
|
|
590
|
-
: thinkingBudgetTokens
|
|
699
|
+
: currentField === 'thinkingBudgetTokens'
|
|
700
|
+
? thinkingBudgetTokens
|
|
701
|
+
: geminiThinkingBudget;
|
|
591
702
|
const newValue = parseInt(currentValue.toString() + input, 10);
|
|
592
703
|
if (!isNaN(newValue)) {
|
|
593
704
|
if (currentField === 'maxContextTokens') {
|
|
@@ -596,9 +707,12 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
596
707
|
else if (currentField === 'maxTokens') {
|
|
597
708
|
setMaxTokens(newValue);
|
|
598
709
|
}
|
|
599
|
-
else {
|
|
710
|
+
else if (currentField === 'thinkingBudgetTokens') {
|
|
600
711
|
setThinkingBudgetTokens(newValue);
|
|
601
712
|
}
|
|
713
|
+
else {
|
|
714
|
+
setGeminiThinkingBudget(newValue);
|
|
715
|
+
}
|
|
602
716
|
}
|
|
603
717
|
}
|
|
604
718
|
else if (key.backspace || key.delete) {
|
|
@@ -606,7 +720,9 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
606
720
|
? maxContextTokens
|
|
607
721
|
: currentField === 'maxTokens'
|
|
608
722
|
? maxTokens
|
|
609
|
-
: thinkingBudgetTokens
|
|
723
|
+
: currentField === 'thinkingBudgetTokens'
|
|
724
|
+
? thinkingBudgetTokens
|
|
725
|
+
: geminiThinkingBudget;
|
|
610
726
|
const currentStr = currentValue.toString();
|
|
611
727
|
const newStr = currentStr.slice(0, -1);
|
|
612
728
|
const newValue = parseInt(newStr, 10);
|
|
@@ -616,21 +732,28 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
616
732
|
else if (currentField === 'maxTokens') {
|
|
617
733
|
setMaxTokens(!isNaN(newValue) ? newValue : 0);
|
|
618
734
|
}
|
|
619
|
-
else {
|
|
735
|
+
else if (currentField === 'thinkingBudgetTokens') {
|
|
620
736
|
setThinkingBudgetTokens(!isNaN(newValue) ? newValue : 0);
|
|
621
737
|
}
|
|
738
|
+
else {
|
|
739
|
+
setGeminiThinkingBudget(!isNaN(newValue) ? newValue : 0);
|
|
740
|
+
}
|
|
622
741
|
}
|
|
623
742
|
else if (key.return) {
|
|
624
743
|
const minValue = currentField === 'maxContextTokens'
|
|
625
744
|
? 4000
|
|
626
745
|
: currentField === 'maxTokens'
|
|
627
746
|
? 100
|
|
628
|
-
:
|
|
747
|
+
: currentField === 'thinkingBudgetTokens'
|
|
748
|
+
? 1000
|
|
749
|
+
: 1;
|
|
629
750
|
const currentValue = currentField === 'maxContextTokens'
|
|
630
751
|
? maxContextTokens
|
|
631
752
|
: currentField === 'maxTokens'
|
|
632
753
|
? maxTokens
|
|
633
|
-
: thinkingBudgetTokens
|
|
754
|
+
: currentField === 'thinkingBudgetTokens'
|
|
755
|
+
? thinkingBudgetTokens
|
|
756
|
+
: geminiThinkingBudget;
|
|
634
757
|
const finalValue = currentValue < minValue ? minValue : currentValue;
|
|
635
758
|
if (currentField === 'maxContextTokens') {
|
|
636
759
|
setMaxContextTokens(finalValue);
|
|
@@ -638,9 +761,12 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
638
761
|
else if (currentField === 'maxTokens') {
|
|
639
762
|
setMaxTokens(finalValue);
|
|
640
763
|
}
|
|
641
|
-
else {
|
|
764
|
+
else if (currentField === 'thinkingBudgetTokens') {
|
|
642
765
|
setThinkingBudgetTokens(finalValue);
|
|
643
766
|
}
|
|
767
|
+
else {
|
|
768
|
+
setGeminiThinkingBudget(finalValue);
|
|
769
|
+
}
|
|
644
770
|
setIsEditing(false);
|
|
645
771
|
}
|
|
646
772
|
return;
|
|
@@ -676,9 +802,19 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
676
802
|
else if (currentField === 'thinkingEnabled') {
|
|
677
803
|
setThinkingEnabled(!thinkingEnabled);
|
|
678
804
|
}
|
|
805
|
+
else if (currentField === 'geminiThinkingEnabled') {
|
|
806
|
+
setGeminiThinkingEnabled(!geminiThinkingEnabled);
|
|
807
|
+
}
|
|
808
|
+
else if (currentField === 'responsesReasoningEnabled') {
|
|
809
|
+
setResponsesReasoningEnabled(!responsesReasoningEnabled);
|
|
810
|
+
}
|
|
679
811
|
else if (currentField === 'maxContextTokens' ||
|
|
680
812
|
currentField === 'maxTokens' ||
|
|
681
|
-
currentField === 'thinkingBudgetTokens'
|
|
813
|
+
currentField === 'thinkingBudgetTokens' ||
|
|
814
|
+
currentField === 'geminiThinkingBudget') {
|
|
815
|
+
setIsEditing(true);
|
|
816
|
+
}
|
|
817
|
+
else if (currentField === 'responsesReasoningEffort') {
|
|
682
818
|
setIsEditing(true);
|
|
683
819
|
}
|
|
684
820
|
else if (currentField === 'advancedModel' ||
|
|
@@ -819,7 +955,8 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
819
955
|
currentField === 'requestMethod' ||
|
|
820
956
|
currentField === 'advancedModel' ||
|
|
821
957
|
currentField === 'basicModel' ||
|
|
822
|
-
currentField === 'compactModelName'
|
|
958
|
+
currentField === 'compactModelName' ||
|
|
959
|
+
currentField === 'responsesReasoningEffort') ? (React.createElement(Box, { flexDirection: "column" },
|
|
823
960
|
React.createElement(Text, { color: "green" },
|
|
824
961
|
"\u276F ",
|
|
825
962
|
currentField === 'profile' && 'Profile',
|
|
@@ -827,6 +964,7 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
827
964
|
currentField === 'advancedModel' && 'Advanced Model',
|
|
828
965
|
currentField === 'basicModel' && 'Basic Model',
|
|
829
966
|
currentField === 'compactModelName' && 'Compact Model',
|
|
967
|
+
currentField === 'responsesReasoningEffort' && 'Responses Reasoning Effort',
|
|
830
968
|
":"),
|
|
831
969
|
React.createElement(Box, { marginLeft: 3, marginTop: 1 },
|
|
832
970
|
currentField === 'profile' && (React.createElement(Box, { flexDirection: "column" },
|
|
@@ -857,13 +995,23 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
857
995
|
searchTerm && React.createElement(Text, { color: "cyan" },
|
|
858
996
|
"Filter: ",
|
|
859
997
|
searchTerm),
|
|
860
|
-
React.createElement(Select, { options: getCurrentOptions(), defaultValue: getCurrentValue(), onChange: handleModelChange })))
|
|
998
|
+
React.createElement(Select, { options: getCurrentOptions(), defaultValue: getCurrentValue(), onChange: handleModelChange }))),
|
|
999
|
+
currentField === 'responsesReasoningEffort' && (React.createElement(Select, { options: [
|
|
1000
|
+
{ label: 'Low', value: 'low' },
|
|
1001
|
+
{ label: 'Medium', value: 'medium' },
|
|
1002
|
+
{ label: 'High', value: 'high' },
|
|
1003
|
+
], defaultValue: responsesReasoningEffort, onChange: value => {
|
|
1004
|
+
setResponsesReasoningEffort(value);
|
|
1005
|
+
setIsEditing(false);
|
|
1006
|
+
} }))),
|
|
861
1007
|
React.createElement(Box, { marginTop: 1 },
|
|
862
1008
|
React.createElement(Alert, { variant: "info" },
|
|
863
1009
|
(currentField === 'advancedModel' ||
|
|
864
1010
|
currentField === 'basicModel' ||
|
|
865
1011
|
currentField === 'compactModelName') &&
|
|
866
1012
|
'Type to filter, ↑↓ to select, Enter to confirm, Esc to cancel',
|
|
1013
|
+
currentField === 'responsesReasoningEffort' &&
|
|
1014
|
+
'↑↓ to select, Enter to confirm, Esc to cancel',
|
|
867
1015
|
currentField === 'profile' &&
|
|
868
1016
|
'↑↓ to select profile, N to create new, D to delete, Enter to confirm, Esc to cancel',
|
|
869
1017
|
currentField === 'requestMethod' &&
|
|
@@ -894,7 +1042,8 @@ export default function ConfigScreen({ onBack, onSave, inlineMode = false, }) {
|
|
|
894
1042
|
currentField === 'requestMethod' ||
|
|
895
1043
|
currentField === 'advancedModel' ||
|
|
896
1044
|
currentField === 'basicModel' ||
|
|
897
|
-
currentField === 'compactModelName'
|
|
1045
|
+
currentField === 'compactModelName' ||
|
|
1046
|
+
currentField === 'responsesReasoningEffort')) && (React.createElement(Box, { flexDirection: "column", marginTop: 1 }, isEditing ? (React.createElement(Alert, { variant: "info" },
|
|
898
1047
|
"Editing mode:",
|
|
899
1048
|
' ',
|
|
900
1049
|
currentField === 'maxContextTokens' ||
|
|
@@ -6,6 +6,14 @@ export interface ThinkingConfig {
|
|
|
6
6
|
type: 'enabled';
|
|
7
7
|
budget_tokens: number;
|
|
8
8
|
}
|
|
9
|
+
export interface GeminiThinkingConfig {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
budget: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ResponsesReasoningConfig {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
effort: 'low' | 'medium' | 'high';
|
|
16
|
+
}
|
|
9
17
|
export interface ApiConfig {
|
|
10
18
|
baseUrl: string;
|
|
11
19
|
apiKey: string;
|
|
@@ -17,6 +25,8 @@ export interface ApiConfig {
|
|
|
17
25
|
compactModel?: CompactModelConfig;
|
|
18
26
|
anthropicBeta?: boolean;
|
|
19
27
|
thinking?: ThinkingConfig;
|
|
28
|
+
geminiThinking?: GeminiThinkingConfig;
|
|
29
|
+
responsesReasoning?: ResponsesReasoningConfig;
|
|
20
30
|
}
|
|
21
31
|
export interface MCPServer {
|
|
22
32
|
url?: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatToolCallMessage } from './messageFormatter.js';
|
|
2
|
+
import { isToolNeedTwoStepDisplay } from './toolDisplayConfig.js';
|
|
2
3
|
/**
|
|
3
4
|
* Convert API format session messages to UI format messages
|
|
4
5
|
* Process messages in order to maintain correct sequence
|
|
@@ -114,17 +115,21 @@ export function convertSessionMessagesToUI(sessionMessages) {
|
|
|
114
115
|
catch (e) {
|
|
115
116
|
toolArgs = {};
|
|
116
117
|
}
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
118
|
+
// Only add "in progress" message for tools that need two-step display
|
|
119
|
+
const needTwoSteps = isToolNeedTwoStepDisplay(toolCall.function.name);
|
|
120
|
+
if (needTwoSteps) {
|
|
121
|
+
// Add tool call message (in progress)
|
|
122
|
+
uiMessages.push({
|
|
123
|
+
role: 'assistant',
|
|
124
|
+
content: `⚡ ${toolDisplay.toolName}`,
|
|
125
|
+
streaming: false,
|
|
126
|
+
toolCall: {
|
|
127
|
+
name: toolCall.function.name,
|
|
128
|
+
arguments: toolArgs,
|
|
129
|
+
},
|
|
130
|
+
toolDisplay,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
128
133
|
processedToolCalls.add(toolCall.id);
|
|
129
134
|
}
|
|
130
135
|
continue;
|