Compare commits

...

11 Commits

Author SHA1 Message Date
claude-code-best
f2b751f659 chore: 2.6.5 2026-05-22 21:05:06 +08:00
claude-code-best
d4a601475f fix: 修复 BriefTool 循环依赖导致 isBriefEnabled 未定义
将模块顶层 require() 改为懒加载函数 getBriefToolModule(),
延迟到实际调用时才加载模块,避免循环依赖时模块尚未完成初始化。
2026-05-22 21:04:17 +08:00
claude-code-best
897c186f28 docs: effort 级别描述去掉模型名限制 2026-05-22 20:11:12 +08:00
claude-code-best
03598d3f84 refactor: 移除 resolveAppliedEffort 中的 max/xhigh 降级分支 2026-05-22 20:09:53 +08:00
claude-code-best
7b52054ff5 feat: 解除 max/xhigh effort 级别的模型白名单限制 2026-05-22 20:09:10 +08:00
claude-code-best
66c892521b chore: 2.6.0 2026-05-21 16:38:25 +08:00
claude-code-best
dab04af7c9 perf: Vite 构建启用 code splitting,Bun RSS 从 966MB 降至 35MB
Bun/JSC 全量解析单文件大 JS 的 bytecode 和 JIT,17MB 产物导致
RSS 暴涨至 ~1GB(Node/V8 懒解析仅需 ~220MB)。启用代码分割后
Bun 按需加载 chunk,--version RSS 35MB,完整加载 ~500MB。

改动:
- vite.config.ts: 移除 codeSplitting:false,添加 chunkFileNames
- post-build.ts: 遍历 dist/ + dist/chunks/ 所有文件做 Bun patch
- 新建 distRoot.ts 共享工具函数,统一路径定位逻辑
- ripgrep.ts/computerUse/setup.ts/claudeInChrome/setup.ts/updateCCB.ts:
  用 distRoot 替换内联 import.meta.url 路径推算
2026-05-21 16:36:27 +08:00
claude-code-best
5b5fbb2f47 chore: 2.5.0 2026-05-20 10:47:52 +08:00
claude-code-best
9bfa868e61 chore: 复原原始的 package.json 2026-05-20 10:14:40 +08:00
claude-code-best
f6dcf63902 Revert "chore: 切换到 bun publish,修复 husky 路径问题,调整 diff 折叠距离,导出 VoiceContext"
This reverts commit c80a6d062b.
2026-05-20 10:11:21 +08:00
claude-code-best
5957e26d9b Revert "chore: 修复 publish 问题"
This reverts commit 58c3feb56a.
2026-05-20 10:11:09 +08:00
17 changed files with 234 additions and 106 deletions

View File

@@ -24,6 +24,11 @@ jobs:
with:
ref: ${{ github.event.inputs.version || github.ref }}
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6, 2026-04-25
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2, 2026-04-25
with:
@@ -38,9 +43,9 @@ jobs:
run: bun test
- name: Publish to npm
run: bun publish -p --access public
run: npm publish --provenance --access public
env:
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Generate changelog
id: changelog

View File

@@ -78,8 +78,9 @@ bun run docs:dev
- **Runtime**: Bun (not Node.js). All imports, builds, and execution use Bun APIs.
- **Build**: `build.ts` 执行 `Bun.build()` with `splitting: true`,入口 `src/entrypoints/cli.tsx`,输出 `dist/cli.js` + chunk files。Build 默认启用 19 个 feature见下方 Feature Flag 段)。构建后自动替换 `import.meta.require` 为 Node.js 兼容版本(产物 bun/node 都可运行)。构建时会将 `vendor/audio-capture/``src/utils/vendor/ripgrep/` 复制到 `dist/vendor/` 下。
- **Build (Vite)**: `vite.config.ts` + `scripts/post-build.ts`chunk 输出到 `dist/chunks/`。post-build 同样复制 vendor 文件到 `dist/vendor/`
- **Vendor 路径解析**: 构建后 chunk 文件位于 `dist/``dist/chunks/`vendor 二进制在 `dist/vendor/``src/utils/ripgrep.ts``packages/audio-capture-napi/src/index.ts` 均通过 `import.meta.url` 路径中 `lastIndexOf('dist')` 定位 dist 根目录,再拼接 `vendor/` 路径,确保不同构建产物层级下路径一致
- **Build (Vite)**: `vite.config.ts` + `scripts/post-build.ts`代码分割模式,chunk 输出到 `dist/chunks/`。post-build 遍历 `dist/``dist/chunks/` 下所有 `.js` 文件做 `globalThis.Bun` 解构 patch复制 vendor 文件到 `dist/vendor/`
- **Vendor 路径解析**: 构建后 chunk 文件位于 `dist/``dist/chunks/`vendor 二进制在 `dist/vendor/``src/utils/distRoot.ts` 提供共享的 `distRoot` 函数,通过 `import.meta.url` 路径中 `lastIndexOf('dist')``lastIndexOf('src')` 定位根目录。`ripgrep.ts``computerUse/setup.ts``claudeInChrome/setup.ts``updateCCB.ts` 均使用 `distRoot` 而非内联 `import.meta.url` 路径推算。`packages/audio-capture-napi/src/index.ts` 有独立的 `lastIndexOf('dist')` 逻辑,功能等价
- **为什么 Vite 必须代码分割**: Bun/JSC 会全量解析单个大 JS 文件的 bytecode 和 JIT单文件 17MB 产物导致 RSS 暴涨至 ~1GBNode/V8 懒解析仅需 ~220MB。代码分割为 600+ 小 chunk 后 Bun 按需加载,`--version` RSS 从 966MB 降至 35MB完整加载从 1GB+ 降至 ~500MB。
- **Dev mode**: `scripts/dev.ts` 通过 Bun `-d` flag 注入 `MACRO.*` defines运行 `src/entrypoints/cli.tsx`。默认启用全部 feature。
- **Module system**: ESM (`"type": "module"`), TSX with `react-jsx` transform.
- **Monorepo**: Bun workspaces — 17 个 workspace packages + 若干辅助目录 in `packages/` resolved via `workspace:*`

View File

@@ -0,0 +1,54 @@
# 内存占用 1G 调研报告
> 诊断 session `a3593062` RSS 达 1.09 GB定位 Bun 运行时内存膨胀根因
## 数据收集
- **诊断数据**: RSS 1,118 MBV8 heap 84 MB原生内存缺口 1,034 MB92%
- **构建方式**: `bun run build:vite` → Vite/Rollup 单文件构建,产物 17MB `dist/cli.js`
- **Vite 配置**: `codeSplitting: false``vite.config.ts:97`),所有代码内联为单文件
- **Node.js 对比**: 相同 17MB 产物Node.js RSS 仅 223 MB`--version`/ 340 MB完整加载
## 探索与验证
### 已确认
| 问题 | 位置 | 说明 |
|------|------|------|
| **根因: Vite 单文件构建 + Bun 解析大文件内存效率低** | `vite.config.ts:97` | `codeSplitting: false` 产出 17MB 单文件Bun/JSC 解析时 RSS 暴涨至 966MB |
| Node.js 对同等 17MB 文件仅需 223MB | 实测 | V8 对大文件解析的内存效率远优于 JSC |
| Bun.build 代码分割可解决问题 | 实测 | `bun run build`(代码分割 → 627 chunkBun RSS 仅 30MB`--version`/ 318MB完整加载 |
### 已否认
- 不是 feature flags 数量问题 — 全部 35 features 开启时,代码分割构建内存正常
- 不是内存泄漏 — `detachedContexts: 0``activeHandles: 0`
- 不是原生 addon 问题 — vendor 文件仅 2.7MB
- 不是 TypeScript 源码体量问题 — `bun run dev`(直接加载 TS完整路径仅 345MB
## 结论
**根因是 Vite 构建配置 `codeSplitting: false`,产出 17MB 单文件Bun/JSC 解析单文件大 JS 时内存效率极差966MB vs Node 的 223MB**
实测对比矩阵:
| 构建方式 | 产物结构 | Bun RSS | Node RSS | Bun/Node |
|----------|----------|---------|----------|----------|
| `build:vite` | 17MB 单文件 | **966 MB** | 223 MB | 4.3x |
| `build:vite` pipe mode | 同上 | **1,088 MB** | 340 MB | 3.2x |
| `build` (Bun) | 627 chunk | 30 MB | 42 MB | 0.7x |
| `build` (Bun) pipe mode | 同上 | 318 MB | 253 MB | 1.3x |
| `bun run dev` TS 源码 | 动态加载 | 42 MB | — | — |
| `bun run dev` pipe mode | 动态加载 | 345 MB | — | — |
核心差异:
- **Node/V8** 解析 17MB 文件只需 223MB — V8 的懒解析lazy parsing只编译入口需要的部分
- **Bun/JSC** 解析 17MB 文件需要 966MB — JSC 对单文件做全量编译bytecode + JIT 占用大量原生内存
- 代码分割后627 个小 chunkBun 按需加载,内存回到正常水平
## 建议
1. **开启 Vite 代码分割** — 在 `vite.config.ts` 中启用 `codeSplitting: true` 或使用 Rollup 的 `manualChunks` 配置。这是最直接的修复
2. **或切换到 Bun.build**`bun run build` 已默认启用代码分割(`splitting: true`Bun RSS 仅 30-318MB
3. **如果必须单文件** — 考虑用 Node.js 运行 Vite 产物(`node dist/cli-node.js`),代价是失去 Bun 特有 API
4. **验证 `codeSplitting: false` 的存在理由** — 注释说"all dynamic imports inlined",可能是为了简化部署。评估是否真的需要单文件

View File

@@ -1,6 +1,6 @@
{
"name": "claude-code-best",
"version": "2.4.5",
"version": "2.6.5",
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
"type": "module",
"author": "claude-code-best <claude-code-best@proton.me>",
@@ -53,7 +53,7 @@
"format": "biome format --write .",
"check": "biome check .",
"check:fix": "biome check --fix .",
"prepare": "bunx husky",
"prepare": "husky",
"test": "bun test",
"test:production": "bun run scripts/production-test.ts",
"test:production:offline": "bun run scripts/production-test.ts --offline",

View File

@@ -9,28 +9,52 @@
import { readdir, readFile, writeFile, cp } from 'node:fs/promises'
import { chmodSync } from 'node:fs'
import { join } from 'node:path'
import { execSync } from 'node:child_process'
const outdir = 'dist'
async function postBuild() {
// Step 1: Patch globalThis.Bun destructuring in the single bundled file
const cliPath = join(outdir, 'cli.js')
// Step 1: Patch globalThis.Bun destructuring in ALL output files
const BUN_DESTRUCTURE = /var \{([^}]+)\} = globalThis\.Bun;?/g
const BUN_DESTRUCTURE_SAFE =
'var {$1} = typeof globalThis.Bun !== "undefined" ? globalThis.Bun : {};'
let bunPatched = 0
{
const content = await readFile(cliPath, 'utf-8')
const files = await readdir(outdir)
const jsFiles = files.filter(f => f.endsWith('.js'))
for (const file of jsFiles) {
const filePath = join(outdir, file)
const content = await readFile(filePath, 'utf-8')
BUN_DESTRUCTURE.lastIndex = 0
if (BUN_DESTRUCTURE.test(content)) {
await writeFile(
cliPath,
filePath,
content.replace(BUN_DESTRUCTURE, BUN_DESTRUCTURE_SAFE),
)
bunPatched++
}
}
// Also patch chunk files in dist/chunks/
const chunksDir = join(outdir, 'chunks')
let chunkFiles: string[] = []
try {
chunkFiles = (await readdir(chunksDir)).filter(f => f.endsWith('.js'))
} catch {
// No chunks directory — single-file build fallback
}
for (const file of chunkFiles) {
const filePath = join(chunksDir, file)
const content = await readFile(filePath, 'utf-8')
BUN_DESTRUCTURE.lastIndex = 0
if (BUN_DESTRUCTURE.test(content)) {
await writeFile(
filePath,
content.replace(BUN_DESTRUCTURE, BUN_DESTRUCTURE_SAFE),
)
bunPatched++
}
}
// Step 2: Copy native addon files
@@ -55,7 +79,7 @@ async function postBuild() {
chmodSync(cliNode, 0o755)
console.log(
`Post-build complete: patched ${bunPatched} Bun destructure, generated entry points`,
`Post-build complete: patched ${bunPatched} Bun destructure across ${jsFiles.length + chunkFiles.length} files, generated entry points`,
)
}

View File

@@ -9,9 +9,9 @@ import chalk from 'chalk'
import { execSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
import { logForDebugging } from '../utils/debug.js'
import { distRoot } from '../utils/distRoot.js'
import { execFileNoThrowWithCwd } from '../utils/execFileNoThrow.js'
import { gracefulShutdown } from '../utils/gracefulShutdown.js'
import { writeToStdout } from '../utils/process.js'
@@ -19,12 +19,9 @@ import { writeToStdout } from '../utils/process.js'
const PACKAGE_NAME = 'claude-code-best'
function getCurrentVersion(): string {
// Read version from the nearest package.json (walks up from this file)
// Read version from the nearest package.json (walks up from dist root)
try {
const __dirname = dirname(fileURLToPath(import.meta.url))
// In dev: src/cli/updateCCB.ts → ../../package.json
// In build: dist/chunks/xxx.js → ../../package.json (may not exist)
const pkgPath = join(__dirname, '..', '..', 'package.json')
const pkgPath = join(distRoot, '..', 'package.json')
if (existsSync(pkgPath)) {
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
if (pkg.version) return pkg.version

View File

@@ -155,7 +155,7 @@ export async function call(onDone: LocalJSXCommandOnDone, _context: unknown, arg
if (COMMON_HELP_ARGS.includes(args)) {
onDone(
'Usage: /effort [low|medium|high|xhigh|max|auto]\n\nEffort levels:\n- low: Quick, straightforward implementation\n- medium: Balanced approach with standard testing\n- high: Comprehensive implementation with extensive testing\n- xhigh: Extra high reasoning for supported models, including ChatGPT Codex models\n- max: Maximum capability with deepest reasoning where supported (Opus 4.6/4.7, DeepSeek V4 Pro); maps to xhigh for ChatGPT Codex models\n- auto: Use the default effort level for your model',
'Usage: /effort [low|medium|high|xhigh|max|auto]\n\nEffort levels:\n- low: Quick, straightforward implementation\n- medium: Balanced approach with standard testing\n- high: Comprehensive implementation with extensive testing\n- xhigh: Extended reasoning beyond high, short of max; including ChatGPT Codex models\n- max: Maximum capability with deepest reasoning; maps to xhigh for ChatGPT Codex models\n- auto: Use the default effort level for your model',
);
return;
}

View File

@@ -798,9 +798,7 @@ const MessagesImpl = ({
// Collapse diffs for messages beyond the latest N messages.
// verbose (ctrl+o) overrides and always shows full diffs.
// 0 was too aggressive — tool results are never the last message (assistant
// text follows), so diffs were always collapsed. 3 keeps recent edits visible.
const DIFF_COLLAPSE_DISTANCE = 3;
const DIFF_COLLAPSE_DISTANCE = 0;
const shouldCollapseDiffs = renderableMessages.length - 1 - index > DIFF_COLLAPSE_DISTANCE;
const k = messageKey(msg);

View File

@@ -82,10 +82,11 @@ const BRIEF_PROACTIVE_SECTION: string | null =
require('@claude-code-best/builtin-tools/tools/BriefTool/prompt.js') as typeof import('@claude-code-best/builtin-tools/tools/BriefTool/prompt.js')
).BRIEF_PROACTIVE_SECTION
: null
const briefToolModule =
feature('KAIROS') || feature('KAIROS_BRIEF')
function getBriefToolModule() {
return feature('KAIROS') || feature('KAIROS_BRIEF')
? (require('@claude-code-best/builtin-tools/tools/BriefTool/BriefTool.js') as typeof import('@claude-code-best/builtin-tools/tools/BriefTool/BriefTool.js'))
: null
}
const DISCOVER_SKILLS_TOOL_NAME: string | null = feature(
'EXPERIMENTAL_SKILL_SEARCH',
)
@@ -800,7 +801,7 @@ function getBriefSection(): string | null {
// Whenever the tool is available, the model is told to use it. The
// /brief toggle and --brief flag now only control the isBriefOnly
// display filter — they no longer gate model-facing behavior.
if (!briefToolModule?.isBriefEnabled()) return null
if (!getBriefToolModule()?.isBriefEnabled()) return null
// When proactive is active, getProactiveSection() already appends the
// section inline. Skip here to avoid duplicating it in the system prompt.
if (
@@ -864,5 +865,5 @@ Do not narrate each step, list every file you read, or explain routine actions.
The user context may include a \`terminalFocus\` field indicating whether the user's terminal is focused or unfocused. Use this to calibrate how autonomous you are:
- **Unfocused**: The user is away. Lean heavily into autonomous action — make decisions, explore, commit, push. Only pause for genuinely irreversible or high-risk actions.
- **Focused**: The user is watching. Be more collaborative — surface choices, ask before committing to large changes, and keep your output concise so it's easy to follow in real time.${BRIEF_PROACTIVE_SECTION && briefToolModule?.isBriefEnabled() ? `\n\n${BRIEF_PROACTIVE_SECTION}` : ''}`
- **Focused**: The user is watching. Be more collaborative — surface choices, ask before committing to large changes, and keep your output concise so it's easy to follow in real time.${BRIEF_PROACTIVE_SECTION && getBriefToolModule()?.isBriefEnabled() ? `\n\n${BRIEF_PROACTIVE_SECTION}` : ''}`
}

View File

@@ -19,7 +19,7 @@ const DEFAULT_STATE: VoiceState = {
type VoiceStore = Store<VoiceState>;
export const VoiceContext = createContext<VoiceStore | null>(null);
const VoiceContext = createContext<VoiceStore | null>(null);
type Props = {
children: React.ReactNode;

View File

@@ -224,6 +224,22 @@ describe('getEffortLevelDescription', () => {
const desc = getEffortLevelDescription('max')
expect(desc).toContain('Maximum')
})
test('max description does not contain model names', () => {
const desc = getEffortLevelDescription('max')
expect(desc).not.toContain('Opus')
expect(desc).not.toContain('DeepSeek')
})
test("returns description for 'xhigh'", () => {
const desc = getEffortLevelDescription('xhigh')
expect(desc).toContain('Extended reasoning')
})
test('xhigh description does not contain model names', () => {
const desc = getEffortLevelDescription('xhigh')
expect(desc).not.toContain('Opus')
})
})
// ─── resolvePickerEffortPersistence ────────────────────────────────────
@@ -274,3 +290,61 @@ describe('resolvePickerEffortPersistence', () => {
expect(result).toBeUndefined()
})
})
// ─── modelSupportsMaxEffort ────────────────────────────────────────────
describe('modelSupportsMaxEffort', () => {
test('returns true for opus-4-7', async () => {
const { modelSupportsMaxEffort } = await import('src/utils/effort.js')
expect(modelSupportsMaxEffort('claude-opus-4-7-20250918')).toBe(true)
})
test('returns true for opus-4-6', async () => {
const { modelSupportsMaxEffort } = await import('src/utils/effort.js')
expect(modelSupportsMaxEffort('claude-opus-4-6-20250514')).toBe(true)
})
test('returns true for sonnet models', async () => {
const { modelSupportsMaxEffort } = await import('src/utils/effort.js')
expect(modelSupportsMaxEffort('claude-sonnet-4-6-20250514')).toBe(true)
})
test('returns true for haiku models', async () => {
const { modelSupportsMaxEffort } = await import('src/utils/effort.js')
expect(modelSupportsMaxEffort('claude-haiku-4-5-20251001')).toBe(true)
})
test('returns true for deepseek models', async () => {
const { modelSupportsMaxEffort } = await import('src/utils/effort.js')
expect(modelSupportsMaxEffort('deepseek-v4-pro')).toBe(true)
})
test('returns true for unknown models', async () => {
const { modelSupportsMaxEffort } = await import('src/utils/effort.js')
expect(modelSupportsMaxEffort('some-random-model')).toBe(true)
})
})
// ─── modelSupportsXhighEffort ──────────────────────────────────────────
describe('modelSupportsXhighEffort', () => {
test('returns true for opus-4-7', async () => {
const { modelSupportsXhighEffort } = await import('src/utils/effort.js')
expect(modelSupportsXhighEffort('claude-opus-4-7-20250918')).toBe(true)
})
test('returns true for sonnet models', async () => {
const { modelSupportsXhighEffort } = await import('src/utils/effort.js')
expect(modelSupportsXhighEffort('claude-sonnet-4-6-20250514')).toBe(true)
})
test('returns true for haiku models', async () => {
const { modelSupportsXhighEffort } = await import('src/utils/effort.js')
expect(modelSupportsXhighEffort('claude-haiku-4-5-20251001')).toBe(true)
})
test('returns true for unknown models', async () => {
const { modelSupportsXhighEffort } = await import('src/utils/effort.js')
expect(modelSupportsXhighEffort('some-random-model')).toBe(true)
})
})

View File

@@ -2,7 +2,6 @@ import { BROWSER_TOOLS } from '@ant/claude-for-chrome-mcp'
import { chmod, mkdir, readFile, writeFile } from 'fs/promises'
import { homedir } from 'os'
import { join } from 'path'
import { fileURLToPath } from 'url'
import {
getIsInteractive,
getIsNonInteractiveSession,
@@ -11,6 +10,7 @@ import {
import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js'
import type { ScopedMcpServerConfig } from '../../services/mcp/types.js'
import { isInBundledMode } from '../bundledMode.js'
import { distRoot } from '../distRoot.js'
import { getGlobalConfig, saveGlobalConfig } from '../config.js'
import { logForDebugging } from '../debug.js'
import {
@@ -135,9 +135,7 @@ export function setupClaudeInChrome(): {
systemPrompt: getChromeSystemPrompt(),
}
} else {
const __filename = fileURLToPath(import.meta.url)
const __dirname = join(__filename, '..')
const cliPath = join(__dirname, 'cli.js')
const cliPath = join(distRoot, 'cli.js')
void createWrapperScript(
`"${process.execPath}" "${cliPath}" --chrome-native-host`,

View File

@@ -1,10 +1,10 @@
import { buildComputerUseTools } from '@ant/computer-use-mcp'
import { join } from 'path'
import { fileURLToPath } from 'url'
import { buildMcpToolName } from '../../services/mcp/mcpStringUtils.js'
import type { ScopedMcpServerConfig } from '../../services/mcp/types.js'
import { isInBundledMode } from '../bundledMode.js'
import { distRoot } from '../distRoot.js'
import { CLI_CU_CAPABILITIES, COMPUTER_USE_MCP_SERVER_NAME } from './common.js'
import { getChicagoCoordinateMode } from './gates.js'
@@ -34,10 +34,7 @@ export function setupComputerUseMCP(): {
// type 'stdio' to hit the right branch. Mirrors Chrome's setup.
const args = isInBundledMode()
? ['--computer-use-mcp']
: [
join(fileURLToPath(import.meta.url), '..', 'cli.js'),
'--computer-use-mcp',
]
: [join(distRoot, 'cli.js'), '--computer-use-mcp']
return {
mcpConfig: {

29
src/utils/distRoot.ts Normal file
View File

@@ -0,0 +1,29 @@
import { fileURLToPath } from 'url'
import * as path from 'path'
/**
* Resolve the dist root directory from the current module's location.
*
* Works across all build layouts:
* - Single-file: dist/cli.js → dist/
* - Code-split: dist/chunks/chunk-xxx.js → dist/
* - Dev mode: src/utils/distRoot.ts → <project_root>/
*/
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const distRoot = (() => {
const parts = __dirname.split(path.sep)
const distIdx = parts.lastIndexOf('dist')
if (distIdx !== -1) {
return parts.slice(0, distIdx + 1).join(path.sep)
}
// Dev mode: from src/utils/ → project root
const srcIdx = parts.lastIndexOf('src')
if (srcIdx !== -1) {
return parts.slice(0, srcIdx).join(path.sep)
}
return __dirname
})()
export { distRoot }

View File

@@ -67,51 +67,22 @@ export function modelSupportsEffort(model: string): boolean {
return getAPIProvider() === 'firstParty'
}
// @[MODEL LAUNCH]: Add the new model to the allowlist if it supports 'max' effort.
// Per API docs, 'max' is Opus 4.6/4.7 only for public models — other models return an error.
// However, DeepSeek V4 Pro also supports max effort when using Anthropic-compatible API.
export function modelSupportsMaxEffort(model: string): boolean {
const supported3P = get3PModelCapabilityOverride(model, 'max_effort')
// Effort max/xhigh restrictions removed — all models that support effort
// can now use these levels. API errors are the user's responsibility.
export function modelSupportsMaxEffort(_model: string): boolean {
const supported3P = get3PModelCapabilityOverride(_model, 'max_effort')
if (supported3P !== undefined) {
return supported3P
}
// Support DeepSeek V4 Pro specifically (Anthropic-compatible API)
if (model.toLowerCase().includes('deepseek-v4-pro')) {
return true
}
if (
model.toLowerCase().includes('opus-4-7') ||
model.toLowerCase().includes('opus-4-6')
) {
return true
}
if (process.env.USER_TYPE === 'ant' && resolveAntModel(model)) {
return true
}
return false
return true
}
// @[MODEL LAUNCH]: Add the new model to the allowlist if it supports 'xhigh' effort.
// 'xhigh' was introduced with Opus 4.7 as a level between 'high' and 'max'.
export function modelSupportsXhighEffort(model: string): boolean {
const supported3P = get3PModelCapabilityOverride(model, 'xhigh_effort')
export function modelSupportsXhighEffort(_model: string): boolean {
const supported3P = get3PModelCapabilityOverride(_model, 'xhigh_effort')
if (supported3P !== undefined) {
return supported3P
}
if (
getAPIProvider() === 'openai' &&
isChatGPTAuthMode() &&
isChatGPTCodexReasoningModel(model)
) {
return true
}
if (model.toLowerCase().includes('opus-4-7')) {
return true
}
if (process.env.USER_TYPE === 'ant' && resolveAntModel(model)) {
return true
}
return false
return true
}
export function isEffortLevel(value: string): value is EffortLevel {
@@ -214,10 +185,6 @@ export function resolveAppliedEffort(
}
const resolved =
envOverride ?? appStateEffortValue ?? getDefaultEffortForModel(model)
// API rejects 'xhigh' on pre-Opus-4.7 models — downgrade to 'high'.
if (resolved === 'xhigh' && !modelSupportsXhighEffort(model)) {
return 'high'
}
// OpenAI Responses uses xhigh as its highest public reasoning effort.
// Keep /effort max usable as a familiar alias in ChatGPT subscription mode.
if (
@@ -228,10 +195,6 @@ export function resolveAppliedEffort(
) {
return 'xhigh'
}
// API rejects 'max' on non-Opus-4.6 models — downgrade to 'high'.
if (resolved === 'max' && !modelSupportsMaxEffort(model)) {
return 'high'
}
return resolved
}
@@ -299,9 +262,9 @@ export function getEffortLevelDescription(level: EffortLevel): string {
case 'high':
return 'Comprehensive implementation with extensive testing and documentation'
case 'xhigh':
return 'Extended reasoning beyond high, short of max (Opus 4.7 only)'
return 'Extended reasoning beyond high, short of max'
case 'max':
return 'Maximum capability with deepest reasoning (Opus 4.6/4.7/DeepSeek V4 Pro)'
return 'Maximum capability with deepest reasoning'
}
}

View File

@@ -4,9 +4,9 @@ import memoize from 'lodash-es/memoize.js'
import { homedir } from 'os'
import * as path from 'path'
import { logEvent } from 'src/services/analytics/index.js'
import { fileURLToPath } from 'url'
import { isInBundledMode } from './bundledMode.js'
import { logForDebugging } from './debug.js'
import { distRoot } from './distRoot.js'
import { isEnvDefinedFalsy } from './envUtils.js'
import { execFileNoThrow } from './execFileNoThrow.js'
import { findExecutable } from './findExecutable.js'
@@ -14,25 +14,9 @@ import { logError } from './log.js'
import { getPlatform } from './platform.js'
import { countCharInString } from './stringUtils.js'
const __filename = fileURLToPath(import.meta.url)
// we use node:path.join instead of node:url.resolve because the former doesn't encode spaces
// In dev mode: __filename = <root>/src/utils/ripgrep.ts → __dirname = <root>/src/utils/
// In built mode (bun): __filename = <root>/dist/chunk-xxx.js → need <root>/dist/
// In built mode (vite): __filename = <root>/dist/chunks/chunk-xxx.js → need <root>/dist/
// Both built modes: the dist root is at <root>/dist/ where dist/vendor/ripgrep/ lives.
const __dirname = (() => {
const dir = path.dirname(__filename)
// Test mode: from src/utils/ → project root
if (process.env.NODE_ENV === 'test') return path.resolve(dir, '../../../')
// Check if we're inside a dist directory at any depth
// (dist/ or dist/chunks/) — vendor lives at <dist-root>/vendor/ripgrep/
const parts = dir.split(path.sep)
const distIdx = parts.lastIndexOf('dist')
if (distIdx !== -1) {
return parts.slice(0, distIdx + 1).join(path.sep)
}
// Dev mode: from src/utils/ → src/utils/
return dir
if (process.env.NODE_ENV === 'test') return path.resolve(distRoot)
return distRoot
})()
type RipgrepConfig = {

View File

@@ -93,9 +93,12 @@ export default defineConfig({
output: {
format: 'es',
// Single-file build: no code splitting, all dynamic imports inlined
codeSplitting: false,
// Code splitting: Bun/JSC parses the entire single-file bundle eagerly,
// consuming ~1 GB RSS for a 17 MB output (vs ~220 MB on Node/V8 which
// lazy-parses). Splitting into chunks allows Bun to load modules on demand,
// bringing RSS down to ~300 MB.
entryFileNames: 'cli.js',
chunkFileNames: 'chunks/[name]-[hash].js',
},
plugins: [