bingocode 1.1.130 → 1.1.131

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,20 +1,10 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(node:*)",
5
- "WebSearch",
6
- "Bash(curl -s \"https://docs.anthropic.com/en/api/messages\" --max-time 30 -A \"Mozilla/5.0\")",
7
- "Bash(curl -s \"https://docs.anthropic.com/en/api/messages-streaming\" --max-time 30 -A \"Mozilla/5.0\")",
8
- "Bash(curl -sv \"https://docs.anthropic.com/en/api/messages\" --max-time 30)",
9
- "Bash(curl -sv \"https://raw.githubusercontent.com/anthropics/anthropic-sdk-python/main/api.md\" --max-time 30)",
10
- "Bash(curl -s \"https://raw.githubusercontent.com/anthropics/anthropic-sdk-python/refs/heads/main/README.md\" --max-time 30)",
11
- "Bash(findstr /i goal)",
12
- "Bash(bun run:*)",
13
- "Bash(bunx tsc:*)",
14
- "Bash(node_modules/.bin/tsc --noEmit --skipLibCheck)",
15
- "WebFetch(domain:www.npmjs.com)",
16
- "Bash(npm info:*)",
17
- "Bash(npm pack:*)"
4
+ "Bash(dir \"F:\\\\Leanchy\\\\VirtuosAgent\\\\BingoCode\\\\src\\\\server\\\\proxy\")",
5
+ "Bash(dir \"F:\\\\Leanchy\\\\VirtuosAgent\\\\BingoCode\\\\src\\\\server\")",
6
+ "Bash(grep -rn \"stream\\\\|proxy\\\\|SSE\\\\|duplicate\\\\|repeat\\\\|render\" F:LeanchyVirtuosAgentBingoCodesrc --include=*.ts -l)",
7
+ "Bash(xargs grep:*)"
18
8
  ]
19
9
  }
20
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bingocode",
3
- "version": "1.1.130",
3
+ "version": "1.1.131",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude": "bin/claude-win.cjs",
@@ -1,98 +1,98 @@
1
- //@C:M ID=M.UI.TopToolbar;K=M;V=1.3;P=top toolbar;D=CLI;M=cli;S=ui
2
- import React, { memo, useMemo } from 'react';
3
- import { Box, Text } from 'ink';
4
- import { Chip } from './CliMenuUi.tsx';
5
- import { useTheme } from '../components/design-system/ThemeProvider.js';
6
- import { getGlobalConfig } from '../utils/config.ts';
7
- import type { ClawdPose } from '../components/LogoV2/Clawd.tsx';
8
- import { Clawd } from '../components/LogoV2/Clawd.tsx';
9
- import { AnimatedClawd } from '../components/LogoV2/AnimatedClawd.tsx';
10
-
11
- type Props = {
12
- ready: boolean;
13
- page: string | null;
14
- animEnabled: boolean;
15
- tipsEnabled: boolean;
16
- ip?: string;
17
- };
18
-
19
- //@C:F ID=F.UI.TopToolbar;K=F;V=1.4;P=toolbar;D=CLI;M=cli;S=ui;In=Props;Out=JSX.Element
20
- export const TopToolbar: React.FC<Props> = memo(({ ready, page, animEnabled, tipsEnabled, ip }) => {
21
- const [theme] = useTheme();
22
-
23
- const { version } = useMemo(() => {
24
- try {
25
- const cfg = getGlobalConfig();
26
- return { version: (cfg as any)?.version ?? '' };
27
- } catch {
28
- return { version: '' };
29
- }
30
- }, []);
31
-
32
- const themeLabel = String(theme || (ready ? (getGlobalConfig()?.theme ?? 'system') : '…'));
33
- const uiChipValue = `Anim ${animEnabled ? 'On' : 'Off'} · Tips ${tipsEnabled ? 'On' : 'Off'}`;
34
- const uiTone = (String(theme) === 'dark') ? 'accent' : (String(theme) === 'highContrast' ? 'warning' : 'info');
35
-
36
- const clawdPose: ClawdPose = useMemo(() => {
37
- if (!ready) return 'default';
38
- if (page === null) return animEnabled ? 'arms-up' : 'default';
39
- return tipsEnabled ? 'look-left' : 'look-right';
40
- }, [ready, page, animEnabled, tipsEnabled]);
41
-
42
- // ── Compact mode (page !== null): single line, no logo ──────────────
43
- if (page !== null) {
44
- return (
45
- <Box flexDirection="row" alignItems="center">
46
- <Text bold>Bingo Code</Text>
47
- <Box marginLeft={1}>
48
- <Chip label="Theme" value={themeLabel} tone="accent" />
49
- </Box>
50
- <Chip label="UI" value={uiChipValue} tone={uiTone as any} />
51
- {ip ? (
52
- <Text color="green" dimColor> · IP: {ip}</Text>
53
- ) : (
54
- <Text color="yellow" dimColor> · {ready ? 'Server ready' : 'Starting…'}</Text>
55
- )}
56
- </Box>
57
- );
58
- }
59
-
60
- // ── Home mode (page === null): Clawd left + 3-row right column ───────
61
- return (
62
- <Box flexDirection="row" alignItems="flex-start">
63
- {/* Left: Clawd sprite */}
64
- <Box marginRight={2}>
65
- {animEnabled ? <AnimatedClawd /> : <Clawd pose={clawdPose} />}
66
- </Box>
67
-
68
- {/* Right: 3 rows */}
69
- <Box flexDirection="column">
70
- {/* Row 1: brand + version + chips */}
71
- <Box flexDirection="row" alignItems="center">
72
- <Text bold>Welcome to Bingo Code</Text>
73
- {version ? <Text dimColor> v{version}</Text> : null}
74
- <Box marginLeft={1}>
75
- <Chip label="Theme" value={themeLabel} tone="accent" />
76
- </Box>
77
- <Chip label="UI" value={uiChipValue} tone={uiTone as any} />
78
- </Box>
79
-
80
- {/* Row 2: IP / server status */}
81
- <Box>
82
- {ip ? (
83
- <Text color="green" dimColor>IP: {ip}</Text>
84
- ) : (
85
- <Text color="yellow" dimColor>{ready ? 'Server ready' : 'Starting server…'}</Text>
86
- )}
87
- </Box>
88
-
89
- {/* Row 3: keyboard shortcuts */}
90
- <Box>
91
- <Text dimColor>N New · R Resume · P Provider · G Theme · ? Help</Text>
92
- </Box>
93
- </Box>
94
- </Box>
95
- );
96
- });
97
-
98
- export default TopToolbar;
1
+ //@C:M ID=M.UI.TopToolbar;K=M;V=1.3;P=top toolbar;D=CLI;M=cli;S=ui
2
+ import React, { memo, useMemo } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { Chip } from './CliMenuUi.tsx';
5
+ import { useTheme } from '../components/design-system/ThemeProvider.js';
6
+ import { getGlobalConfig } from '../utils/config.ts';
7
+ import type { ClawdPose } from '../components/LogoV2/Clawd.tsx';
8
+ import { Clawd } from '../components/LogoV2/Clawd.tsx';
9
+ import { AnimatedClawd } from '../components/LogoV2/AnimatedClawd.tsx';
10
+
11
+ type Props = {
12
+ ready: boolean;
13
+ page: string | null;
14
+ animEnabled: boolean;
15
+ tipsEnabled: boolean;
16
+ ip?: string;
17
+ };
18
+
19
+ //@C:F ID=F.UI.TopToolbar;K=F;V=1.4;P=toolbar;D=CLI;M=cli;S=ui;In=Props;Out=JSX.Element
20
+ export const TopToolbar: React.FC<Props> = memo(({ ready, page, animEnabled, tipsEnabled, ip }) => {
21
+ const [theme] = useTheme();
22
+
23
+ const { version } = useMemo(() => {
24
+ try {
25
+ const cfg = getGlobalConfig();
26
+ return { version: (cfg as any)?.version ?? '' };
27
+ } catch {
28
+ return { version: '' };
29
+ }
30
+ }, []);
31
+
32
+ const themeLabel = String(theme || (ready ? (getGlobalConfig()?.theme ?? 'system') : '…'));
33
+ const uiChipValue = `Anim ${animEnabled ? 'On' : 'Off'} · Tips ${tipsEnabled ? 'On' : 'Off'}`;
34
+ const uiTone = (String(theme) === 'dark') ? 'accent' : (String(theme) === 'highContrast' ? 'warning' : 'info');
35
+
36
+ const clawdPose: ClawdPose = useMemo(() => {
37
+ if (!ready) return 'default';
38
+ if (page === null) return animEnabled ? 'arms-up' : 'default';
39
+ return tipsEnabled ? 'look-left' : 'look-right';
40
+ }, [ready, page, animEnabled, tipsEnabled]);
41
+
42
+ // ── Compact mode (page !== null): single line, no logo ──────────────
43
+ if (page !== null) {
44
+ return (
45
+ <Box flexDirection="row" alignItems="center">
46
+ <Text bold>Bingo Code</Text>
47
+ <Box marginLeft={1}>
48
+ <Chip label="Theme" value={themeLabel} tone="accent" />
49
+ </Box>
50
+ <Chip label="UI" value={uiChipValue} tone={uiTone as any} />
51
+ {ip ? (
52
+ <Text color="green" dimColor> · IP: {ip}</Text>
53
+ ) : (
54
+ <Text color="yellow" dimColor> · {ready ? 'Server ready' : 'Starting…'}</Text>
55
+ )}
56
+ </Box>
57
+ );
58
+ }
59
+
60
+ // ── Home mode (page === null): Clawd left + 3-row right column ───────
61
+ return (
62
+ <Box flexDirection="row" alignItems="flex-start">
63
+ {/* Left: Clawd sprite */}
64
+ <Box marginRight={2}>
65
+ {animEnabled ? <AnimatedClawd /> : <Clawd pose={clawdPose} />}
66
+ </Box>
67
+
68
+ {/* Right: 3 rows */}
69
+ <Box flexDirection="column">
70
+ {/* Row 1: brand + version + chips */}
71
+ <Box flexDirection="row" alignItems="center">
72
+ <Text bold>Welcome to Bingo Code</Text>
73
+ {version ? <Text dimColor> v{version}</Text> : null}
74
+ <Box marginLeft={1}>
75
+ <Chip label="Theme" value={themeLabel} tone="accent" />
76
+ </Box>
77
+ <Chip label="UI" value={uiChipValue} tone={uiTone as any} />
78
+ </Box>
79
+
80
+ {/* Row 2: IP / server status */}
81
+ <Box>
82
+ {ip ? (
83
+ <Text color="green" dimColor>IP: {ip}</Text>
84
+ ) : (
85
+ <Text color="yellow" dimColor>{ready ? 'Server ready' : 'Starting server…'}</Text>
86
+ )}
87
+ </Box>
88
+
89
+ {/* Row 3: keyboard shortcuts */}
90
+ <Box>
91
+ <Text dimColor>N New · R Resume · P Provider · G Theme · ? Help</Text>
92
+ </Box>
93
+ </Box>
94
+ </Box>
95
+ );
96
+ });
97
+
98
+ export default TopToolbar;
@@ -298,40 +298,20 @@ function extractReasoning(delta: DeltaEx): { thinking: string; signature: string
298
298
  return null
299
299
  }
300
300
 
301
- /**
302
- * Determine what block type this chunk carries and whether it's a new block.
303
- * Priority (matches LiteLLM): tool_calls > text > reasoning > ignore
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
301
  // ─── Main chunk processing ─────────────────────────────────
334
302
 
303
+ /**
304
+ * Process a single SSE chunk using dual-pass logic:
305
+ * Pass 1 — reasoning/thinking (if present)
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.
314
+ */
335
315
  function processChunk(chunk: OpenAIChatStreamChunk, state: StreamState): void {
336
316
  const choice = chunk.choices?.[0]
337
317
 
@@ -350,31 +330,33 @@ function processChunk(chunk: OpenAIChatStreamChunk, state: StreamState): void {
350
330
 
351
331
  const delta = choice.delta as DeltaEx
352
332
 
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
333
+ // Tool calls are mutually exclusive with text/thinking — handle separately
334
+ if (delta.tool_calls && delta.tool_calls.length > 0) {
335
+ // Close any open text/thinking block before entering tool_use
336
+ if (state.currentBlockType !== 'tool_use' && state.blockStartSent && !state.blockStopSent) {
337
+ closeCurrentBlock(state)
338
+ }
339
+ handleToolCalls(delta, state)
340
+ } else {
341
+ // Pass 1: reasoning/thinking
342
+ const reasoning = extractReasoning(delta)
343
+ if (reasoning) {
344
+ // If currently in a text block, close it before opening thinking
345
+ if (state.currentBlockType === 'text' && state.blockStartSent && !state.blockStopSent) {
364
346
  closeCurrentBlock(state)
365
347
  }
348
+ handleThinking(delta, state)
366
349
  }
367
350
 
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
351
+ // Pass 2: text content
352
+ // After thinking is handled, resume/open text block independently.
353
+ // This is the key fix: text is NOT skipped when reasoning was also present.
354
+ if (delta.content != null && delta.content !== '') {
355
+ // If currently in a thinking block, close it before opening text
356
+ if (state.currentBlockType === 'thinking' && state.blockStartSent && !state.blockStopSent) {
357
+ closeCurrentBlock(state)
358
+ }
359
+ handleText(delta, state)
378
360
  }
379
361
  }
380
362