chatccc 0.2.255 → 0.2.256

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/README.md CHANGED
@@ -373,6 +373,7 @@ Codex 的默认模型和推理强度可继续由 `~/.codex/config.toml` 管理
373
373
  | `ccc.DEEPSEEK_API_KEY` / `ccc.DEEPSEEK_BASE_URL` | CCC Agent 的 API Key 和服务地址;**不限于 DeepSeek**——可填任意 OpenAI 兼容端点(OpenAI、Kimi、通义、智谱、Ollama 本地等) |
374
374
  | `ccc.model` | CCC Agent 默认模型 |
375
375
  | `ccc.compactionTimeoutMs` | CCC Agent 上下文压缩单轮超时(毫秒),默认 300000(5 分钟);压缩超时会让整轮对话失败,建议保持默认或调大 |
376
+ | `ccc.contextWindow` | CCC Agent 模型上下文窗口(token),默认 1048576(1M,DeepSeek V4 Pro/Flash 原生规格);压缩阈值自动 = 窗口 × 80%;超过模型/服务端实际上限会被 API 拒绝,可在 Web UI 下拉选择或自定义(单位 k) |
376
377
 
377
378
  > **权限控制**:普通消息以 `bypassPermissions` 模式运行,跳过 Agent 操作确认。使用 `/plan` 或 `/ask` 前缀时,ChatCCC 自动切换为只读模式:Claude SDK 仅放行 Read + stop-stuck-loop 网络请求,Codex 使用 `--sandbox read-only`,Cursor 使用 `--mode plan/ask`。请只在可信环境中使用。
378
379
 
@@ -53,7 +53,8 @@
53
53
  "alternativeModel": "",
54
54
  "effort": "",
55
55
  "provider": "",
56
- "compactionTimeoutMs": 300000
56
+ "compactionTimeoutMs": 300000,
57
+ "contextWindow": 1048576
57
58
  },
58
59
  "claude": {
59
60
  "enabled": false,
@@ -77,6 +77,7 @@ $env:DEEPCCC_STREAMING="true"
77
77
  "model": "deepseek-v4-pro",
78
78
  "effort": "",
79
79
  "streaming": true,
80
+ "contextWindow": 1048576,
80
81
  "rawStreamLogs": {
81
82
  "enabled": true,
82
83
  "maxBytesPerTurn": 1048576,
@@ -90,11 +91,17 @@ $env:DEEPCCC_STREAMING="true"
90
91
  `DEEPCCC_PROVIDER` 或命令行 `--provider` 覆盖:
91
92
 
92
93
  - `openai` 使用 OpenAI-compatible Chat Completions 协议,兼容 DeepSeek、OpenAI、LiteLLM、vLLM 等服务。
93
- - `anthropic` 使用 Anthropic Messages 协议。配置中的 `baseURL` 可以继续填写同一个服务基址;DeepCCC 会为不带 `/v1` 的地址自动补上 `/v1`。`effort` 是 OpenAI/DeepSeek 扩展参数,在 Anthropic 模式下不会发送。
94
+ - `anthropic` 使用 Anthropic Messages 协议。配置中的 `baseURL` **完全按填写值使用,不自动补 `/v1`**,请填写到完整版本化地址,例如 DeepSeek 官方 Anthropic 端点为 `https://api.deepseek.com/anthropic/v1`(官方 Anthropic SDK 使用的 `https://api.deepseek.com/anthropic` 基址会拼接为 `.../anthropic/v1/messages`)。`effort` 是 OpenAI/DeepSeek 扩展参数,在 Anthropic 模式下不会发送。
94
95
 
95
96
  `streaming` 控制主对话是否使用流式请求,默认 `true`;也可以通过
96
97
  `DEEPCCC_STREAMING=true|false` 覆盖。关闭后,终端会在整条模型响应完成后一次性显示结果。
97
98
 
99
+ `contextWindow` 是模型上下文窗口(token),默认 `1048576`(1M,DeepSeek V4 Pro/Flash
100
+ 原生规格);上下文压缩阈值自动 = `contextWindow × 0.8`(超出即把较早消息压缩为摘要)。
101
+ 可通过 `DEEPCCC_CONTEXT_WINDOW` 环境变量覆盖。⚠️ 超过模型/服务端实际上限时请求会被
102
+ API 拒绝(context length exceeded),实际窗口以模型与所用服务端为准(如 litellm 的
103
+ `max_input_tokens`)。
104
+
98
105
  `rawStreamLogs.enabled` 默认 `true`,通过 `DEEPCCC_RAW_STREAM_LOGS` 环境变量或配置 JSON 关闭。
99
106
  开启时,每次对话的原始流按 gzip JSONL 落到 `~/.deepccc/raw-stream-logs/`,供
100
107
  `session_search` 工具在会话被压缩后找回被压缩消息的精确原文(检索时设置
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepccc",
3
- "version": "0.1.24",
3
+ "version": "0.1.25",
4
4
  "description": "A lightweight coding agent with OpenAI-compatible and Anthropic Messages API support.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -14,6 +14,7 @@ export const DEFAULT_CONFIG = {
14
14
  model: "deepseek-v4-pro",
15
15
  effort: "",
16
16
  streaming: true,
17
+ contextWindow: 1_048_576,
17
18
  rawStreamLogs: {
18
19
  // 默认开启:压缩后可通过 session_search(include_raw_logs=true)找回原文。
19
20
  // 如需关闭,在 ~/.deepccc/config.json 中设置 rawStreamLogs.enabled=false。
@@ -67,6 +68,7 @@ function loadConfig() {
67
68
  model: env("DEEPCCC_MODEL") ?? env("DEEPSEEK_MODEL") ?? file.model ?? DEFAULT_CONFIG.model,
68
69
  effort: env("DEEPCCC_EFFORT") ?? env("DEEPSEEK_EFFORT") ?? file.effort ?? DEFAULT_CONFIG.effort,
69
70
  streaming: boolEnv("DEEPCCC_STREAMING") ?? file.streaming ?? DEFAULT_CONFIG.streaming,
71
+ contextWindow: numberEnv("DEEPCCC_CONTEXT_WINDOW") ?? file.contextWindow ?? DEFAULT_CONFIG.contextWindow,
70
72
  rawStreamLogs: {
71
73
  enabled: boolEnv("DEEPCCC_RAW_STREAM_LOGS") ?? rawLogs.enabled ?? DEFAULT_CONFIG.rawStreamLogs.enabled,
72
74
  maxBytesPerTurn: numberEnv("DEEPCCC_RAW_STREAM_MAX_BYTES") ?? rawLogs.maxBytesPerTurn ?? DEFAULT_CONFIG.rawStreamLogs.maxBytesPerTurn,
@@ -3,7 +3,12 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFile
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  export const DEFAULT_BUILTIN_CONTEXT_DIR = join(homedir(), ".deepccc", "sessions");
6
- export const DEFAULT_COMPACT_AT_TOKENS = 128_000;
6
+ /** 默认模型上下文窗口:1M(DeepSeek V4 Pro/Flash 原生规格)。 */
7
+ export const DEFAULT_CONTEXT_WINDOW_TOKENS = 1_048_576;
8
+ /** 压缩阈值 = 窗口 × 0.8(业界惯例:在窗口接近上限前主动压缩,留出安全余量)。 */
9
+ export const COMPACTION_THRESHOLD_RATIO = 0.8;
10
+ /** 默认压缩阈值 = 1M × 0.8。 */
11
+ export const DEFAULT_COMPACT_AT_TOKENS = Math.floor(DEFAULT_CONTEXT_WINDOW_TOKENS * COMPACTION_THRESHOLD_RATIO);
7
12
  export const DEFAULT_KEEP_RECENT_MESSAGES = 16;
8
13
  const RECENT_CONTEXT_BUDGET_RATIO = 0.6;
9
14
  const MAX_COMPACTION_SUMMARY_CHARS = 8_000;
@@ -247,7 +252,8 @@ export class BuiltinContextManager {
247
252
  this.contextDir = options.contextDir ?? DEFAULT_BUILTIN_CONTEXT_DIR;
248
253
  this.sessionId = normalizeBuiltinSessionId(options.sessionId ?? defaultBuiltinSessionId());
249
254
  this.cwd = options.cwd;
250
- this.compactAtTokens = options.compactAtTokens ?? DEFAULT_COMPACT_AT_TOKENS;
255
+ this.compactAtTokens = options.compactAtTokens
256
+ ?? Math.floor((options.contextWindow ?? DEFAULT_CONTEXT_WINDOW_TOKENS) * COMPACTION_THRESHOLD_RATIO);
251
257
  this.keepRecentMessages = Math.max(1, options.keepRecentMessages ?? DEFAULT_KEEP_RECENT_MESSAGES);
252
258
  this.state = this.load();
253
259
  }
@@ -210,8 +210,10 @@ function normalizeMaxSteps(value) {
210
210
  return value;
211
211
  }
212
212
  function normalizeAnthropicBaseURL(baseURL) {
213
- const normalized = baseURL.trim().replace(/\/+$/, "");
214
- return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
213
+ // 完全按用户填写的地址使用,不自动补 /v1(AI SDK 仅对官方 api.anthropic.com
214
+ // 特判补一次 /v1,其他地址原样拼接 /messages)。
215
+ // DeepSeek Anthropic 端点示例:https://api.deepseek.com/anthropic/v1。
216
+ return baseURL.trim().replace(/\/+$/, "");
215
217
  }
216
218
  export class ChatSession {
217
219
  model;
@@ -265,6 +267,7 @@ export class ChatSession {
265
267
  contextDir: options.contextDir,
266
268
  sessionId: options.sessionId ?? defaultBuiltinSessionId(this.cwd),
267
269
  cwd: this.cwd,
270
+ contextWindow: options.contextWindow ?? appConfig.contextWindow,
268
271
  compactAtTokens: options.compactAtTokens,
269
272
  keepRecentMessages: options.keepRecentMessages,
270
273
  });
@@ -8,6 +8,7 @@ function toChatSessionOptions(sessionId, cwd, options) {
8
8
  persist: true,
9
9
  sessionId,
10
10
  contextDir: options.contextDir,
11
+ contextWindow: options.contextWindow,
11
12
  compactAtTokens: options.compactAtTokens,
12
13
  keepRecentMessages: options.keepRecentMessages,
13
14
  compactionTimeoutMs: options.compactionTimeoutMs,
@@ -86,6 +86,7 @@ const CONFIG_SAMPLE_FILE = join(PROJECT_ROOT, "config.sample.json");
86
86
  export const DEFAULT_CCC_DEEPSEEK_BASE_URL = "https://api.deepseek.com/v1";
87
87
  export const DEFAULT_CCC_MODEL = "deepseek-v4-pro";
88
88
  export const DEFAULT_CCC_COMPACTION_TIMEOUT_MS = 5 * 60 * 1000;
89
+ export const DEFAULT_CCC_CONTEXT_WINDOW_TOKENS = 1_048_576;
89
90
  /**
90
91
  * 将旧位置(PROJECT_ROOT)的持久化数据一次性迁移到 USER_DATA_DIR。
91
92
  * 仅当 USER_DATA_DIR 下没有 config.json 且 PROJECT_ROOT 下有旧数据时才执行。
@@ -312,6 +313,7 @@ function loadConfig() {
312
313
  effort: "",
313
314
  provider: "",
314
315
  compactionTimeoutMs: DEFAULT_CCC_COMPACTION_TIMEOUT_MS,
316
+ contextWindow: DEFAULT_CCC_CONTEXT_WINDOW_TOKENS,
315
317
  },
316
318
  };
317
319
  if (!IS_TEST_ENV) {
@@ -503,6 +505,7 @@ function loadConfig() {
503
505
  effort: normalizeOptionalConfigField(cccRaw.effort, { label: "ccc.effort" }),
504
506
  provider: normalizeCccProviderOverride(cccRaw.provider),
505
507
  compactionTimeoutMs: normalizePositiveInteger(cccRaw.compactionTimeoutMs, DEFAULT_CCC_COMPACTION_TIMEOUT_MS),
508
+ contextWindow: normalizePositiveInteger(cccRaw.contextWindow, DEFAULT_CCC_CONTEXT_WINDOW_TOKENS),
506
509
  },
507
510
  };
508
511
  }
@@ -530,6 +530,7 @@ export function getAdapterForTool(tool, sessionId) {
530
530
  model: effectiveModel || undefined,
531
531
  effort: effectiveEffort || undefined,
532
532
  compactionTimeoutMs: config.ccc.compactionTimeoutMs,
533
+ contextWindow: config.ccc.contextWindow,
533
534
  // 留空("")不传 → ChatSession 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER)
534
535
  ...(config.ccc.provider ? { provider: config.ccc.provider } : {}),
535
536
  });
@@ -495,6 +495,10 @@ export function unflattenConfig(flat) {
495
495
  result.ccc = result.ccc || {};
496
496
  result.ccc.provider = val;
497
497
  }
498
+ else if (key === "CHATCCC_CCC_CONTEXT_WINDOW") {
499
+ result.ccc = result.ccc || {};
500
+ result.ccc.contextWindow = parseInt(String(val), 10) || 1048576;
501
+ }
498
502
  else if (key === "CHATCCC_CCC_ENABLED") {
499
503
  result.ccc = result.ccc || {};
500
504
  result.ccc.enabled = val === true || val === "true";
@@ -631,7 +635,7 @@ async function handleClaudeSdkInstall(_req, res) {
631
635
  // ---------------------------------------------------------------------------
632
636
  // HTML page (embedded template)
633
637
  // ---------------------------------------------------------------------------
634
- export const PAGE_HTML = `<!DOCTYPE html>
638
+ export const PAGE_HTML = `<!DOCTYPE html>
635
639
  <html lang="zh-CN">
636
640
  <head>
637
641
  <meta charset="UTF-8">
@@ -886,6 +890,20 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
886
890
  <option value="max">max - 最强推理</option>
887
891
  </select>
888
892
  </div>
893
+ <div class="form-group">
894
+ <label>上下文窗口(模型最大上下文)</label>
895
+ <select id="field-CHATCCC_CCC_CONTEXT_WINDOW" onchange="onContextWindowPresetChange('field-', this.value)">
896
+ <option value="1m">1M(1,048,576 tokens,推荐)</option>
897
+ <option value="512k">512K(524,288 tokens)</option>
898
+ <option value="256k">256K(262,144 tokens)</option>
899
+ <option value="128k">128K(131,072 tokens)</option>
900
+ <option value="custom">自定义(单位 k)</option>
901
+ </select>
902
+ <div id="field-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM_ROW" style="margin-top:6px;display:none">
903
+ <input type="number" id="field-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM" min="1" placeholder="例如 768 表示 768K(786,432 tokens)" style="width:100%;padding:8px 12px;border:1px solid #cbd5e1;border-radius:8px;font-size:14px;outline:none">
904
+ </div>
905
+ <div class="hint">压缩阈值自动 = 窗口 × 80%(超出即把较早消息压缩为摘要)。⚠️ 超过模型/服务端实际上限时请求会被 API 直接拒绝(context length exceeded);实际窗口以模型与所用服务端为准(如 litellm 代理的 max_input_tokens)。</div>
906
+ </div>
889
907
  </fieldset>
890
908
  </div>
891
909
 
@@ -1211,7 +1229,7 @@ const AGENT_FIELDS = {
1211
1229
  claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
1212
1230
  cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL','CHATCCC_CURSOR_ALTERNATIVE_MODEL','CHATCCC_CURSOR_AVATAR_BATTERY_MODE','CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'],
1213
1231
  codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT','CHATCCC_CODEX_FAST_MODE'],
1214
- ccc: ['CHATCCC_CCC_API_KEY','CHATCCC_CCC_BASE_URL','CHATCCC_CCC_MODEL','CHATCCC_CCC_ALTERNATIVE_MODEL','CHATCCC_CCC_EFFORT','CHATCCC_CCC_PROVIDER']
1232
+ ccc: ['CHATCCC_CCC_API_KEY','CHATCCC_CCC_BASE_URL','CHATCCC_CCC_MODEL','CHATCCC_CCC_ALTERNATIVE_MODEL','CHATCCC_CCC_EFFORT','CHATCCC_CCC_PROVIDER','CHATCCC_CCC_CONTEXT_WINDOW']
1215
1233
  };
1216
1234
  const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
1217
1235
  const WEB_UI_FIELDS = ['CHATCCC_WEB_UI_OPEN_ON_START'];
@@ -1221,6 +1239,58 @@ function cursorBatteryModeLabel(value) {
1221
1239
  return value === 'onDemandUse' ? 'On demand use 金额' : 'API 使用比例';
1222
1240
  }
1223
1241
 
1242
+ // 上下文窗口:预设档位 ↔ tokens。下拉值用小写 k / m 标签,自定义输入以 k 为单位。
1243
+ function contextWindowPresetFor(tokens) {
1244
+ if (tokens === 1048576) return '1m';
1245
+ if (tokens === 524288) return '512k';
1246
+ if (tokens === 262144) return '256k';
1247
+ if (tokens === 131072) return '128k';
1248
+ return 'custom';
1249
+ }
1250
+
1251
+ function contextWindowTokensLabel(tokens) {
1252
+ var t = Number(tokens) || 1048576;
1253
+ if (t >= 1048576) return (t / 1048576) + 'M (' + t.toLocaleString('en-US') + ' tokens)';
1254
+ return (t / 1024) + 'K (' + t.toLocaleString('en-US') + ' tokens)';
1255
+ }
1256
+
1257
+ // 根据 tokens 回填预设下拉 + 自定义输入;prefix 为 'field-'(向导)或 'edit-'(编辑弹窗)。
1258
+ function prefillContextWindow(prefix, tokens) {
1259
+ var t = Number(tokens) || 1048576;
1260
+ var selectEl = document.getElementById(prefix + 'CHATCCC_CCC_CONTEXT_WINDOW');
1261
+ if (!selectEl) return;
1262
+ var preset = contextWindowPresetFor(t);
1263
+ selectEl.value = preset;
1264
+ onContextWindowPresetChange(prefix, preset);
1265
+ if (preset === 'custom') {
1266
+ var customEl = document.getElementById(prefix + 'CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM');
1267
+ if (customEl) customEl.value = String(Math.round(t / 1024));
1268
+ }
1269
+ }
1270
+
1271
+ // 预设切换:custom 时显示自定义输入框(单位 k)。
1272
+ function onContextWindowPresetChange(prefix, value) {
1273
+ var rowId = prefix === 'edit-' ? 'edit-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM_ROW' : 'field-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM_ROW';
1274
+ var row = document.getElementById(rowId);
1275
+ if (row) row.style.display = (value === 'custom') ? '' : 'none';
1276
+ }
1277
+
1278
+ // 把预设下拉 + 自定义输入转换为 tokens 数字字符串(用于提交给服务端)。
1279
+ function contextWindowToTokensValue(prefix) {
1280
+ var selectEl = document.getElementById(prefix + 'CHATCCC_CCC_CONTEXT_WINDOW');
1281
+ if (!selectEl || !selectEl.value) return null;
1282
+ var v = selectEl.value;
1283
+ if (v === '1m') return String(1048576);
1284
+ if (v === 'custom') {
1285
+ var customEl = document.getElementById(prefix + 'CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM');
1286
+ var k = parseInt((customEl && customEl.value.trim()) || '', 10);
1287
+ if (!Number.isFinite(k) || k <= 0) k = 1024; // 非法输入回退 1M
1288
+ return String(k * 1024);
1289
+ }
1290
+ var presetK = parseInt(v, 10); // '128k' → 128
1291
+ return String((presetK || 1024) * 1024);
1292
+ }
1293
+
1224
1294
  function configEffectHint(section) {
1225
1295
  if (section === 'feishu') return '生效范围:App ID、App Secret、平台类型或飞书开关变更需要重启 ChatCCC;其它未变更项仅保存。';
1226
1296
  if (section === 'webUi') return '生效范围:下次直接启动生效;内部重启始终不打开。';
@@ -1548,6 +1618,7 @@ function renderStep2() {
1548
1618
  prefillNested('field-CHATCCC_CCC_MODEL', c.ccc.model);
1549
1619
  prefillNested('field-CHATCCC_CCC_ALTERNATIVE_MODEL', c.ccc.alternativeModel);
1550
1620
  prefillNested('field-CHATCCC_CCC_EFFORT', c.ccc.effort);
1621
+ prefillContextWindow('field-', c.ccc.contextWindow);
1551
1622
  }
1552
1623
 
1553
1624
  // 按已有 config 决定每个 Agent 默认是否开启:优先 enabled 字段,缺省时按"任一字段非空"
@@ -1647,6 +1718,11 @@ function collectAllFields() {
1647
1718
  }
1648
1719
  if (state.agentsEnabled.ccc) {
1649
1720
  AGENT_FIELDS.ccc.forEach(function(key){
1721
+ if (key === 'CHATCCC_CCC_CONTEXT_WINDOW') {
1722
+ var cw = contextWindowToTokensValue('field-');
1723
+ if (cw !== null) vars[key] = cw;
1724
+ return;
1725
+ }
1650
1726
  var el = document.getElementById('field-' + key);
1651
1727
  if (el && el.value.trim()) vars[key] = el.value.trim();
1652
1728
  });
@@ -1728,6 +1804,7 @@ function renderStep3() {
1728
1804
  lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CCC_MODEL || '(留空)') + '</span></div>');
1729
1805
  lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CCC_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1730
1806
  lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CCC_EFFORT || '(留空)') + '</span></div>');
1807
+ lines.push('<div class="config-row"><span class="key">上下文窗口</span><span class="val">' + contextWindowTokensLabel(vars.CHATCCC_CCC_CONTEXT_WINDOW || 1048576) + '</span></div>');
1731
1808
  }
1732
1809
  });
1733
1810
  document.getElementById('review-content').innerHTML = lines.join('');
@@ -2023,14 +2100,15 @@ function editSection(section) {
2023
2100
  'CHATCCC_CODEX_FAST_MODE': 'Fast 模式',
2024
2101
  'CHATCCC_CCC_API_KEY': 'API Key', 'CHATCCC_CCC_BASE_URL': 'Base URL',
2025
2102
  'CHATCCC_CCC_PROVIDER': 'API 协议(选填)',
2026
- 'CHATCCC_CCC_MODEL': '模型', 'CHATCCC_CCC_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CCC_EFFORT': 'Effort'
2103
+ 'CHATCCC_CCC_MODEL': '模型', 'CHATCCC_CCC_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CCC_EFFORT': 'Effort', 'CHATCCC_CCC_CONTEXT_WINDOW': '上下文窗口'
2027
2104
  };
2028
2105
  var hintMap = {
2029
2106
  'CHATCCC_WEB_UI_OPEN_ON_START': '关闭后可继续手动访问 http://localhost:<端口>/;/restart、/update 和 Web UI 重启无论此项为何值都不会自动打开。',
2030
2107
  'CHATCCC_CHROME_DEVTOOLS_ENABLED': '依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。',
2031
2108
  'CHATCCC_CHROME_DEVTOOLS_PORT': '默认 15166,健康检查端点为 http://127.0.0.1:15166/json/version。',
2032
2109
  'CHATCCC_CHROME_DEVTOOLS_PATH': '选填。留空时自动探测 Google Chrome。',
2033
- 'CHATCCC_CCC_PROVIDER': '与 Base URL 强相关:OpenAI 兼容端点选 openai;Anthropic Messages 端点选 anthropic。留空 = 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER),改动需重启 ChatCCC 生效。'
2110
+ 'CHATCCC_CCC_PROVIDER': '与 Base URL 强相关:OpenAI 兼容端点选 openai;Anthropic Messages 端点选 anthropic。留空 = 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER),改动需重启 ChatCCC 生效。',
2111
+ 'CHATCCC_CCC_CONTEXT_WINDOW': '压缩阈值自动 = 窗口 × 80%(超出即把较早消息压缩为摘要)。⚠️ 超过模型/服务端实际上限时请求会被 API 直接拒绝(context length exceeded),实际窗口以模型与所用服务端为准(如 litellm 代理的 max_input_tokens);单位 k = 1024 tokens,1M = 1,048,576 tokens。'
2034
2112
  };
2035
2113
 
2036
2114
  if (section === 'chromeDevtools') {
@@ -2076,6 +2154,7 @@ function editSection(section) {
2076
2154
  else if (key === 'CHATCCC_CCC_MODEL') val = state.config.ccc.model || '';
2077
2155
  else if (key === 'CHATCCC_CCC_ALTERNATIVE_MODEL') val = state.config.ccc.alternativeModel || '';
2078
2156
  else if (key === 'CHATCCC_CCC_EFFORT') val = state.config.ccc.effort || '';
2157
+ else if (key === 'CHATCCC_CCC_CONTEXT_WINDOW') val = state.config.ccc.contextWindow || '1048576';
2079
2158
  }
2080
2159
  }
2081
2160
  var isSecret = key.includes('SECRET') || key.includes('API_KEY');
@@ -2102,6 +2181,22 @@ function editSection(section) {
2102
2181
  html += '</select>';
2103
2182
  if (hintMap[key]) html += '<div class="hint" style="margin-top:6px;line-height:1.5">' + hintMap[key] + '</div>';
2104
2183
  html += '</div>';
2184
+ } else if (key === 'CHATCCC_CCC_CONTEXT_WINDOW') {
2185
+ var cwVal = val || '1048576';
2186
+ var cwPreset = contextWindowPresetFor(cwVal);
2187
+ html += '<div class="form-group"><label>' + (labelMap[key] || key) + '</label>';
2188
+ html += '<select id="edit-' + key + '" onchange="onContextWindowPresetChange(\\'edit-\\', this.value)" style="width:100%;padding:8px 12px;border:1px solid #cbd5e1;border-radius:8px;font-size:14px;outline:none">';
2189
+ html += '<option value="1m"' + (cwPreset === '1m' ? ' selected' : '') + '>1M(1,048,576 tokens,推荐)</option>';
2190
+ html += '<option value="512k"' + (cwPreset === '512k' ? ' selected' : '') + '>512K(524,288 tokens)</option>';
2191
+ html += '<option value="256k"' + (cwPreset === '256k' ? ' selected' : '') + '>256K(262,144 tokens)</option>';
2192
+ html += '<option value="128k"' + (cwPreset === '128k' ? ' selected' : '') + '>128K(131,072 tokens)</option>';
2193
+ html += '<option value="custom"' + (cwPreset === 'custom' ? ' selected' : '') + '>自定义(单位 k)</option>';
2194
+ html += '</select>';
2195
+ html += '<div id="edit-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM_ROW" style="margin-top:6px;display:' + (cwPreset === 'custom' ? '' : 'none') + '">';
2196
+ html += '<input type="number" id="edit-CHATCCC_CCC_CONTEXT_WINDOW_CUSTOM" min="1" placeholder="例如 768 表示 768K(786,432 tokens)" style="width:100%;padding:8px 12px;border:1px solid #cbd5e1;border-radius:8px;font-size:14px;outline:none" value="' + (cwPreset === 'custom' ? Math.round(Number(cwVal) / 1024) : '') + '">';
2197
+ html += '</div>';
2198
+ if (hintMap[key]) html += '<div class="hint" style="margin-top:6px;line-height:1.5">' + hintMap[key] + '</div>';
2199
+ html += '</div>';
2105
2200
  } else {
2106
2201
  var rowId = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'
2107
2202
  ? ' id="edit-cursor-on-demand-budget-row"'
@@ -2129,6 +2224,9 @@ function editSection(section) {
2129
2224
  html += '</select></div>';
2130
2225
  }
2131
2226
  document.getElementById('edit-modal-fields').innerHTML = html;
2227
+ if (section === 'ccc') {
2228
+ prefillContextWindow('edit-', (state.config.ccc && state.config.ccc.contextWindow) || 1048576);
2229
+ }
2132
2230
  if (section === 'cursor') {
2133
2231
  var editModeEl = document.getElementById('edit-CHATCCC_CURSOR_AVATAR_BATTERY_MODE');
2134
2232
  onCursorBatteryModeChange('edit-', editModeEl ? editModeEl.value : 'apiPercent');
@@ -2158,6 +2256,10 @@ async function saveEdit() {
2158
2256
  var el = document.getElementById('edit-' + key);
2159
2257
  if (!el) return;
2160
2258
  if (key === 'CHATCCC_WEB_UI_OPEN_ON_START' || key === 'CHATCCC_CHROME_DEVTOOLS_ENABLED' || key === 'CHATCCC_CODEX_FAST_MODE') vars[key] = !!el.checked;
2259
+ else if (key === 'CHATCCC_CCC_CONTEXT_WINDOW') {
2260
+ var cw = contextWindowToTokensValue('edit-');
2261
+ if (cw !== null) vars[key] = cw;
2262
+ }
2161
2263
  else vars[key] = el.value.trim();
2162
2264
  });
2163
2265
  if (editSectionType === 'chromeDevtools' && !vars.CHATCCC_CHROME_DEVTOOLS_PORT) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.255",
3
+ "version": "0.2.256",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",