bingocode 1.1.131 → 1.1.133
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.
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
+
"Read(//c/Users/qi.lin/.claude/**)",
|
|
4
5
|
"Bash(dir \"F:\\\\Leanchy\\\\VirtuosAgent\\\\BingoCode\\\\src\\\\server\\\\proxy\")",
|
|
5
|
-
"Bash(
|
|
6
|
-
"Bash(
|
|
7
|
-
"Bash(xargs grep:*)"
|
|
6
|
+
"Bash(ls src/**/)",
|
|
7
|
+
"Bash(cat config/*.json)"
|
|
8
8
|
]
|
|
9
9
|
}
|
|
10
10
|
}
|
package/package.json
CHANGED
|
@@ -78,34 +78,46 @@ const TOP_H_COMPACT = Number(process.env.CLI_TOP_H_COMPACT || 3);
|
|
|
78
78
|
// Bottom bar height
|
|
79
79
|
const BOTTOM_H = Number(process.env.CLI_BOTTOM_H || 3);
|
|
80
80
|
|
|
81
|
+
const LANG_OPTIONS = [
|
|
82
|
+
{ label: 'English', value: 'en' as const },
|
|
83
|
+
{ label: '中文', value: 'zh' as const },
|
|
84
|
+
{ label: '日本語', value: 'ja' as const },
|
|
85
|
+
];
|
|
86
|
+
|
|
81
87
|
const i18nMap = {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
about: 'Bingo CLI - Version Info & About',
|
|
92
|
-
aboutContent: [
|
|
93
|
-
'Bingo is an AI assistant terminal client.',
|
|
94
|
-
'1. API Config: Press "P" or select "API Config" to set up your keys.',
|
|
95
|
-
'2. Model Slots: Configure specific models in the Provider panel.',
|
|
96
|
-
'3. Background Service: Bingo runs a local server to manage sessions.',
|
|
97
|
-
'4. Start Chat: Run `bingocode` or `claude` in any terminal to start.',
|
|
98
|
-
].join('\n'),
|
|
99
|
-
aboutFooter: 'Author: leanchy (leanchy07@outlook.com) · github.com/leanchy/bingo-claude-code-offline-installer',
|
|
100
|
-
mark: '→ Mark Session',
|
|
101
|
-
unmark: '→ Unmark Session',
|
|
102
|
-
tipsSimple: 'L Lang | ESC Back | ←→ Menu | ↩ Enter | ? Help',
|
|
103
|
-
noData: 'No data',
|
|
104
|
-
emptyHistory: 'Nothing here yet. Start a new session?',
|
|
105
|
-
deleting: 'Delete this session? (Irreversible)',
|
|
106
|
-
historyHint: 'Enter to open · j next · k first · q back',
|
|
107
|
-
helpTitle: 'Shortcuts',
|
|
88
|
+
zh: {
|
|
89
|
+
menu: {
|
|
90
|
+
newSession: '新建会话',
|
|
91
|
+
history: '会话历史',
|
|
92
|
+
provider: 'API 配置',
|
|
93
|
+
settings: '设置',
|
|
94
|
+
about: '关于',
|
|
95
|
+
exit: '退出',
|
|
108
96
|
},
|
|
97
|
+
about: 'Bingo CLI 终端 - 版本信息与关于',
|
|
98
|
+
aboutContent: [
|
|
99
|
+
'Bingo 是一款 AI 助手终端客户端。',
|
|
100
|
+
'1. API 配置:按 "P" 或选择「API 配置」来设置你的密钥。',
|
|
101
|
+
'2. 模型槽:在 Provider 面板中配置各模型。',
|
|
102
|
+
'3. 后台服务:Bingo 会运行一个本地服务器来管理会话。',
|
|
103
|
+
'4. 开始聊天:在任意终端中运行 `bingocode` 或 `claude`。',
|
|
104
|
+
].join('\n'),
|
|
105
|
+
aboutFooter: '作者: leanchy (leanchy07@outlook.com) · github.com/leanchy/bingo-claude-code-offline-installer',
|
|
106
|
+
mark: '→ 标记会话',
|
|
107
|
+
unmark: '→ 取消标记',
|
|
108
|
+
tipsSimple: 'L 语言 | ESC 返回 | ←→ 菜单 | ↩ 确认 | ? 帮助',
|
|
109
|
+
noData: '暂无数据',
|
|
110
|
+
emptyHistory: '还没有会话,要新建一个吗?',
|
|
111
|
+
deleting: '确定删除此会话?(不可恢复)',
|
|
112
|
+
historyHint: '↩ 打开 · j 下一页 · k 首页 · q 返回',
|
|
113
|
+
helpTitle: '快捷键',
|
|
114
|
+
// Settings page
|
|
115
|
+
settingsTitle: '设置',
|
|
116
|
+
langLabel: '语言',
|
|
117
|
+
langPickerTitle: '选择语言',
|
|
118
|
+
settingsHint: '↑/k ↓/j 滚动 · ↩ 编辑 · ESC 返回',
|
|
119
|
+
langOptions: LANG_OPTIONS,
|
|
120
|
+
},
|
|
109
121
|
en: {
|
|
110
122
|
menu: {
|
|
111
123
|
newSession: 'New Session',
|
|
@@ -132,7 +144,46 @@ const i18nMap = {
|
|
|
132
144
|
deleting: 'Delete this session? (Irreversible)',
|
|
133
145
|
historyHint: 'Enter to open · j next · k first · q back',
|
|
134
146
|
helpTitle: 'Shortcuts',
|
|
135
|
-
|
|
147
|
+
// Settings page
|
|
148
|
+
settingsTitle: 'Settings',
|
|
149
|
+
langLabel: 'Language',
|
|
150
|
+
langPickerTitle: 'Select Language',
|
|
151
|
+
settingsHint: '↑/k ↓/j scroll · ↩ edit · ESC back',
|
|
152
|
+
langOptions: LANG_OPTIONS,
|
|
153
|
+
},
|
|
154
|
+
ja: {
|
|
155
|
+
menu: {
|
|
156
|
+
newSession: '新規セッション',
|
|
157
|
+
history: 'セッション履歴',
|
|
158
|
+
provider: 'API設定',
|
|
159
|
+
settings: '設定',
|
|
160
|
+
about: 'について',
|
|
161
|
+
exit: '終了',
|
|
162
|
+
},
|
|
163
|
+
about: 'Bingo CLI ターミナル - バージョン情報',
|
|
164
|
+
aboutContent: [
|
|
165
|
+
'BingoはAIアシスタントのターミナルクライアントです。',
|
|
166
|
+
'1. API設定: "P"キーまたは「API設定」を選択してキーを設定。',
|
|
167
|
+
'2. モデルスロット: Providerパネルで各モデルを設定。',
|
|
168
|
+
'3. バックグラウンドサービス: セッション管理用ローカルサーバーを起動。',
|
|
169
|
+
'4. チャット開始: 任意のターミナルで `bingocode` または `claude` を実行。',
|
|
170
|
+
].join('\n'),
|
|
171
|
+
aboutFooter: '作者: leanchy (leanchy07@outlook.com) · github.com/leanchy/bingo-claude-code-offline-installer',
|
|
172
|
+
mark: '→ セッションをマーク',
|
|
173
|
+
unmark: '→ マークを解除',
|
|
174
|
+
tipsSimple: 'L 言語 | ESC 戻る | ←→ メニュー | ↩ 決定 | ? ヘルプ',
|
|
175
|
+
noData: 'データなし',
|
|
176
|
+
emptyHistory: 'まだセッションがありません。新規作成しますか?',
|
|
177
|
+
deleting: 'このセッションを削除しますか?(元に戻せません)',
|
|
178
|
+
historyHint: '↩ 開く · j 次へ · k 最初へ · q 戻る',
|
|
179
|
+
helpTitle: 'ショートカット',
|
|
180
|
+
// Settings page
|
|
181
|
+
settingsTitle: '設定',
|
|
182
|
+
langLabel: '言語',
|
|
183
|
+
langPickerTitle: '言語を選択',
|
|
184
|
+
settingsHint: '↑/k ↓/j スクロール · ↩ 編集 · ESC 戻る',
|
|
185
|
+
langOptions: LANG_OPTIONS,
|
|
186
|
+
},
|
|
136
187
|
};
|
|
137
188
|
|
|
138
189
|
const menuKeys = [
|
|
@@ -243,7 +294,7 @@ export const CliMenuManager: React.FC = () => {
|
|
|
243
294
|
if (configReady) {
|
|
244
295
|
try {
|
|
245
296
|
const cfg = getGlobalConfig();
|
|
246
|
-
if (cfg.language && (cfg.language === 'en' || cfg.language === 'zh')) {
|
|
297
|
+
if (cfg.language && (cfg.language === 'en' || cfg.language === 'zh' || cfg.language === 'ja')) {
|
|
247
298
|
setLang(cfg.language as Lang);
|
|
248
299
|
}
|
|
249
300
|
} catch (e) {
|
|
@@ -294,6 +345,8 @@ export const CliMenuManager: React.FC = () => {
|
|
|
294
345
|
const [settingData, setSettingData] = useState<any>(null);
|
|
295
346
|
const [loadingSetting, setLoadingSetting] = useState(false);
|
|
296
347
|
const [setErr, setSetErr] = useState<string | null>(null);
|
|
348
|
+
const [settingsStage, setSettingsStage] = useState<'list' | 'langPicker'>('list');
|
|
349
|
+
const [settingsCursor, setSettingsCursor] = useState(0);
|
|
297
350
|
|
|
298
351
|
// Top toolbar state
|
|
299
352
|
const [animEnabled, setAnimEnabled] = useState(true);
|
|
@@ -373,6 +426,8 @@ export const CliMenuManager: React.FC = () => {
|
|
|
373
426
|
}
|
|
374
427
|
if (page !== 'settings') {
|
|
375
428
|
setSettingsOffset(0);
|
|
429
|
+
setSettingsStage('list');
|
|
430
|
+
setSettingsCursor(0);
|
|
376
431
|
}
|
|
377
432
|
// Close help overlay
|
|
378
433
|
setShowHelp(false);
|
|
@@ -515,9 +570,10 @@ export const CliMenuManager: React.FC = () => {
|
|
|
515
570
|
|
|
516
571
|
// Keyboard interactions
|
|
517
572
|
useInput((input, key) => {
|
|
518
|
-
// Language toggle
|
|
573
|
+
// Language toggle (en → zh → ja → en)
|
|
519
574
|
if (input === 'l' || input === 'L') {
|
|
520
|
-
const
|
|
575
|
+
const langOrder: Lang[] = ['en', 'zh', 'ja'];
|
|
576
|
+
const nextLang = langOrder[(langOrder.indexOf(lang) + 1) % langOrder.length];
|
|
521
577
|
setLang(nextLang);
|
|
522
578
|
try {
|
|
523
579
|
const cfg = getGlobalConfig();
|
|
@@ -563,6 +619,10 @@ export const CliMenuManager: React.FC = () => {
|
|
|
563
619
|
if (key.escape) {
|
|
564
620
|
if (showHelp) { setShowHelp(false); return; }
|
|
565
621
|
if (page === 'provider') return; // Handled internally
|
|
622
|
+
// Settings: langPicker → back to list; list → back to main menu
|
|
623
|
+
if (page === 'settings') {
|
|
624
|
+
if (settingsStage === 'langPicker') { setSettingsStage('list'); return; }
|
|
625
|
+
}
|
|
566
626
|
setPage(null);
|
|
567
627
|
setHistoryMenuStage('list');
|
|
568
628
|
setSelectedHistory(null);
|
|
@@ -670,18 +730,30 @@ export const CliMenuManager: React.FC = () => {
|
|
|
670
730
|
}
|
|
671
731
|
}
|
|
672
732
|
|
|
673
|
-
// Settings
|
|
674
|
-
if (!showHelp && page === 'settings'
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
733
|
+
// Settings interactions
|
|
734
|
+
if (!showHelp && page === 'settings') {
|
|
735
|
+
if (settingsStage === 'list') {
|
|
736
|
+
// +1 for the fixed Language row prepended before settingData entries
|
|
737
|
+
const totalRows = 1 + (settingData && typeof settingData === 'object' ? Object.keys(settingData).length : 0);
|
|
738
|
+
const visible = Math.max(1, MID_H - 2);
|
|
739
|
+
if (key.downArrow || input === 'j') {
|
|
740
|
+
setSettingsCursor(c => Math.min(totalRows - 1, c + 1));
|
|
741
|
+
setSettingsOffset(o => Math.min(Math.max(0, totalRows - visible), o + 1));
|
|
742
|
+
}
|
|
743
|
+
if (key.upArrow || input === 'k') {
|
|
744
|
+
setSettingsCursor(c => Math.max(0, c - 1));
|
|
745
|
+
setSettingsOffset(o => Math.max(0, o - 1));
|
|
746
|
+
}
|
|
747
|
+
if (key.return) {
|
|
748
|
+
// Row 0 is the interactive Language row
|
|
749
|
+
if (settingsCursor === 0) {
|
|
750
|
+
setSettingsStage('langPicker');
|
|
751
|
+
}
|
|
752
|
+
}
|
|
682
753
|
}
|
|
754
|
+
// langPicker stage: ESC handled above; selection via SelectInput onSelect
|
|
683
755
|
}
|
|
684
|
-
}, [menuItems, page, historyMenuStage, historyList, historyHasMore, navIndex, sessionMessages, settingData, MID_H, MSGS_PAGE_SIZE, showHelp, theme]);
|
|
756
|
+
}, [menuItems, page, historyMenuStage, historyList, historyHasMore, navIndex, sessionMessages, settingData, MID_H, MSGS_PAGE_SIZE, showHelp, theme, settingsStage, settingsCursor]);
|
|
685
757
|
|
|
686
758
|
function cleanText(text: string): string {
|
|
687
759
|
return String(text ?? '').replace(/[\n\r]+/g, ' ').replace(/\u001b\[[0-9;]*m/g, '').trim();
|
|
@@ -915,7 +987,7 @@ export const CliMenuManager: React.FC = () => {
|
|
|
915
987
|
<Text color="cyan">R</Text><Text> Quick Resume</Text>
|
|
916
988
|
<Text color="cyan">P</Text><Text> Open Provider Config</Text>
|
|
917
989
|
<Text color="cyan">G</Text><Text> Toggle Theme (light/dark/highContrast)</Text>
|
|
918
|
-
<Text color="cyan">L</Text><Text> Toggle Language (en
|
|
990
|
+
<Text color="cyan">L</Text><Text> Toggle Language (en → zh → ja)</Text>
|
|
919
991
|
<Text color="cyan">O</Text><Text> Toggle Top Animation</Text>
|
|
920
992
|
<Text color="cyan">T</Text><Text> Toggle Top Tips</Text>
|
|
921
993
|
<Text color="cyan">?</Text><Text> Toggle Help</Text>
|
|
@@ -1130,18 +1202,84 @@ export const CliMenuManager: React.FC = () => {
|
|
|
1130
1202
|
if (page === 'settings') {
|
|
1131
1203
|
if (loadingSetting) return <StateDisplay type="loading" message="Loading settings..." />;
|
|
1132
1204
|
if (setErr) return <StateDisplay type="error" message={setErr} />;
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1205
|
+
|
|
1206
|
+
const tS = i18nMap[lang];
|
|
1207
|
+
const currentLangLabel = LANG_OPTIONS.find(o => o.value === lang)?.label ?? lang;
|
|
1208
|
+
|
|
1209
|
+
// --- langPicker sub-menu ---
|
|
1210
|
+
if (settingsStage === 'langPicker') {
|
|
1211
|
+
return (
|
|
1212
|
+
<Box width={VIEW_W} height={MID_H} flexDirection="column">
|
|
1213
|
+
<Box paddingX={1} marginBottom={1}>
|
|
1214
|
+
<Text color="magenta" bold>{tS.langPickerTitle}</Text>
|
|
1215
|
+
</Box>
|
|
1216
|
+
<Box paddingX={2} flexGrow={1} flexDirection="column">
|
|
1217
|
+
<SelectInput
|
|
1218
|
+
items={tS.langOptions}
|
|
1219
|
+
initialIndex={tS.langOptions.findIndex(o => o.value === lang)}
|
|
1220
|
+
onSelect={(item: { label: string; value: Lang }) => {
|
|
1221
|
+
setLang(item.value);
|
|
1222
|
+
try {
|
|
1223
|
+
const cfg = getGlobalConfig();
|
|
1224
|
+
cfg.language = item.value;
|
|
1225
|
+
saveGlobalConfig(cfg);
|
|
1226
|
+
} catch {}
|
|
1227
|
+
setSettingsStage('list');
|
|
1228
|
+
}}
|
|
1229
|
+
/>
|
|
1230
|
+
</Box>
|
|
1231
|
+
<Box paddingX={1}>
|
|
1232
|
+
<Hint>↩ confirm · ESC back</Hint>
|
|
1233
|
+
</Box>
|
|
1234
|
+
</Box>
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// --- settings list ---
|
|
1239
|
+
type SettingRow = { key: string; label: string; value: string; interactive: boolean };
|
|
1240
|
+
const fixedRows: SettingRow[] = [
|
|
1241
|
+
{ key: '__lang', label: tS.langLabel, value: currentLangLabel, interactive: true },
|
|
1242
|
+
];
|
|
1243
|
+
const dataEntries = settingData && typeof settingData === 'object' ? Object.entries(settingData) : [];
|
|
1244
|
+
const dataRows: SettingRow[] = dataEntries.map(([k, v]) => ({
|
|
1245
|
+
key: k,
|
|
1246
|
+
label: k,
|
|
1247
|
+
value: typeof v === 'object' ? JSON.stringify(v) : String(v),
|
|
1248
|
+
interactive: false,
|
|
1249
|
+
}));
|
|
1250
|
+
const allRows: SettingRow[] = [...fixedRows, ...dataRows];
|
|
1251
|
+
const visible = Math.max(1, MID_H - 2);
|
|
1252
|
+
const start = Math.min(settingsOffset, Math.max(0, allRows.length - visible));
|
|
1253
|
+
const sliced = allRows.slice(start, start + visible);
|
|
1254
|
+
|
|
1138
1255
|
return (
|
|
1139
1256
|
<Box width={VIEW_W} height={MID_H} flexDirection="column">
|
|
1140
|
-
<
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1257
|
+
<Box flexDirection="row" position="relative" flexGrow={1}>
|
|
1258
|
+
<Box flexDirection="column" flexGrow={1} paddingX={1} overflow="hidden">
|
|
1259
|
+
{sliced.map((row, idx) => {
|
|
1260
|
+
const absIdx = start + idx;
|
|
1261
|
+
const isCursor = absIdx === settingsCursor;
|
|
1262
|
+
const prefix = isCursor ? '>' : ' ';
|
|
1263
|
+
const labelColor = isCursor ? 'cyan' : (row.interactive ? 'white' : 'gray');
|
|
1264
|
+
const valueColor = row.interactive ? 'green' : undefined;
|
|
1265
|
+
return (
|
|
1266
|
+
<Box key={row.key} height={1}>
|
|
1267
|
+
<Text color={labelColor}>
|
|
1268
|
+
{prefix} {row.label}:{' '}
|
|
1269
|
+
<Text color={valueColor ?? (isCursor ? 'white' : 'gray')}>
|
|
1270
|
+
{row.value}
|
|
1271
|
+
{row.interactive ? ' ↩' : ''}
|
|
1272
|
+
</Text>
|
|
1273
|
+
</Text>
|
|
1274
|
+
</Box>
|
|
1275
|
+
);
|
|
1276
|
+
})}
|
|
1277
|
+
</Box>
|
|
1278
|
+
<ScrollBar total={allRows.length} offset={start} height={visible - 1} />
|
|
1279
|
+
</Box>
|
|
1280
|
+
<Box paddingX={1}>
|
|
1281
|
+
<Hint>{tS.settingsHint} · {start + 1}-{Math.min(start + visible, allRows.length)}/{allRows.length}</Hint>
|
|
1282
|
+
</Box>
|
|
1145
1283
|
</Box>
|
|
1146
1284
|
);
|
|
1147
1285
|
}
|
|
@@ -298,20 +298,40 @@ function extractReasoning(delta: DeltaEx): { thinking: string; signature: string
|
|
|
298
298
|
return null
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
-
// ─── Main chunk processing ─────────────────────────────────
|
|
302
|
-
|
|
303
301
|
/**
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
* Pass 2 — text content (if present)
|
|
307
|
-
* Pass 3 — tool calls (if present; mutually exclusive with text/thinking)
|
|
308
|
-
*
|
|
309
|
-
* This avoids the single-return priority chain that caused spurious
|
|
310
|
-
* close/open cycles when providers (Gemini via OpenRouter, DeepSeek, Qwen3, …)
|
|
311
|
-
* send reasoning_content and content in the same chunk or in alternating chunks,
|
|
312
|
-
* which previously produced multiple text content_block_start events and
|
|
313
|
-
* duplicate rendering in Claude Code's Ink terminal UI.
|
|
302
|
+
* Determine what block type this chunk carries and whether it's a new block.
|
|
303
|
+
* Priority (matches LiteLLM): tool_calls > text > reasoning > ignore
|
|
314
304
|
*/
|
|
305
|
+
function detectBlockTransition(
|
|
306
|
+
delta: DeltaEx,
|
|
307
|
+
state: StreamState,
|
|
308
|
+
): { type: ContentBlockType; isNew: boolean } | null {
|
|
309
|
+
// Priority 1: Tool calls
|
|
310
|
+
if (delta.tool_calls && delta.tool_calls.length > 0) {
|
|
311
|
+
const tc = delta.tool_calls[0]
|
|
312
|
+
// A tool call with function.name signals a NEW tool block
|
|
313
|
+
const isNew = state.currentBlockType !== 'tool_use' || !!(tc.function?.name)
|
|
314
|
+
return { type: 'tool_use', isNew }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Priority 2: Text content
|
|
318
|
+
if (delta.content != null && delta.content !== '') {
|
|
319
|
+
const isNew = state.currentBlockType !== 'text' || !state.blockStartSent
|
|
320
|
+
return { type: 'text', isNew }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Priority 3: Reasoning/thinking
|
|
324
|
+
const reasoning = extractReasoning(delta)
|
|
325
|
+
if (reasoning) {
|
|
326
|
+
const isNew = state.currentBlockType !== 'thinking' || !state.blockStartSent
|
|
327
|
+
return { type: 'thinking', isNew }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return null
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ─── Main chunk processing ─────────────────────────────────
|
|
334
|
+
|
|
315
335
|
function processChunk(chunk: OpenAIChatStreamChunk, state: StreamState): void {
|
|
316
336
|
const choice = chunk.choices?.[0]
|
|
317
337
|
|
|
@@ -330,33 +350,31 @@ function processChunk(chunk: OpenAIChatStreamChunk, state: StreamState): void {
|
|
|
330
350
|
|
|
331
351
|
const delta = choice.delta as DeltaEx
|
|
332
352
|
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
// If currently in a text block, close it before opening thinking
|
|
345
|
-
if (state.currentBlockType === 'text' && state.blockStartSent && !state.blockStopSent) {
|
|
353
|
+
// Detect what this chunk carries
|
|
354
|
+
const transition = detectBlockTransition(delta, state)
|
|
355
|
+
|
|
356
|
+
if (transition) {
|
|
357
|
+
// Handle block transition: close previous block if type changed
|
|
358
|
+
if (transition.isNew && state.blockStartSent && !state.blockStopSent) {
|
|
359
|
+
if (transition.type !== 'tool_use') {
|
|
360
|
+
// For text/thinking, close the current block
|
|
361
|
+
closeCurrentBlock(state)
|
|
362
|
+
} else if (state.currentBlockType !== 'tool_use') {
|
|
363
|
+
// Switching TO tool_use from text/thinking: close current
|
|
346
364
|
closeCurrentBlock(state)
|
|
347
365
|
}
|
|
348
|
-
handleThinking(delta, state)
|
|
349
366
|
}
|
|
350
367
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
368
|
+
switch (transition.type) {
|
|
369
|
+
case 'thinking':
|
|
370
|
+
handleThinking(delta, state)
|
|
371
|
+
break
|
|
372
|
+
case 'text':
|
|
373
|
+
handleText(delta, state)
|
|
374
|
+
break
|
|
375
|
+
case 'tool_use':
|
|
376
|
+
handleToolCalls(delta, state)
|
|
377
|
+
break
|
|
360
378
|
}
|
|
361
379
|
}
|
|
362
380
|
|
|
@@ -45,9 +45,13 @@ export function anthropicToOpenaiChat(body: AnthropicRequest): OpenAIChatRequest
|
|
|
45
45
|
|
|
46
46
|
// max_tokens — cap to avoid upstream 400 errors from Claude's high defaults (e.g. 64k).
|
|
47
47
|
// DeepSeek: tools/thinking fail above 8192. Other providers: 32768 covers most upstreams.
|
|
48
|
+
// GPT models (gpt-*): use max_completion_tokens instead of max_tokens (required by newer GPT models).
|
|
48
49
|
if (body.max_tokens !== undefined) {
|
|
49
|
-
|
|
50
|
+
const modelLower = body.model.toLowerCase()
|
|
51
|
+
if (modelLower.includes('deepseek')) {
|
|
50
52
|
result.max_tokens = Math.min(body.max_tokens, 8192)
|
|
53
|
+
} else if (modelLower.startsWith('gpt-') || modelLower.startsWith('o1') || modelLower.startsWith('o3') || modelLower.startsWith('o4')) {
|
|
54
|
+
result.max_completion_tokens = body.max_tokens
|
|
51
55
|
} else {
|
|
52
56
|
result.max_tokens = Math.min(body.max_tokens, 32768)
|
|
53
57
|
}
|
package/src/utils/config.ts
CHANGED
|
@@ -242,7 +242,7 @@ export type GlobalConfig = {
|
|
|
242
242
|
hasUsedBackgroundTask?: boolean // Whether the user has backgrounded a task (Ctrl+B)
|
|
243
243
|
queuedCommandUpHintCount?: number // Counter for how many times the user has seen the queued command up hint
|
|
244
244
|
diffTool?: DiffTool // Which tool to use for displaying diffs (terminal or vscode)
|
|
245
|
-
language: 'en' | 'zh' // User's preferred language for CLI menus
|
|
245
|
+
language: 'en' | 'zh' | 'ja' // User's preferred language for CLI menus
|
|
246
246
|
|
|
247
247
|
// Terminal setup state tracking
|
|
248
248
|
iterm2SetupInProgress?: boolean
|