jinzd-ai-cli 0.4.58 → 0.4.60
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/{chunk-7MQXQDVV.js → chunk-2DWWB4KD.js} +1 -1
- package/dist/{chunk-ZHURWJEW.js → chunk-3YVHYAXK.js} +108 -14
- package/dist/{chunk-HDNVCYD6.js → chunk-C32FFHMY.js} +16 -1
- package/dist/{chunk-5CQPX74I.js → chunk-X4GL6D5L.js} +1 -1
- package/dist/{hub-MRK53S5O.js → hub-JTMNY7JR.js} +1 -1
- package/dist/index.js +104 -9
- package/dist/{run-tests-ZC6WLEE4.js → run-tests-QGJHXL5Z.js} +1 -1
- package/dist/{run-tests-DL6HGIK3.js → run-tests-WGBDMO4H.js} +1 -1
- package/dist/{server-SFDOVFUN.js → server-L2XJYXMB.js} +118 -11
- package/dist/{task-orchestrator-BWFYT4Q5.js → task-orchestrator-Z4IK3UEA.js} +2 -2
- package/package.json +1 -1
|
@@ -6,8 +6,9 @@ import {
|
|
|
6
6
|
ProviderError,
|
|
7
7
|
ProviderNotFoundError,
|
|
8
8
|
RateLimitError,
|
|
9
|
-
schemaToJsonSchema
|
|
10
|
-
|
|
9
|
+
schemaToJsonSchema,
|
|
10
|
+
truncateForPersist
|
|
11
|
+
} from "./chunk-C32FFHMY.js";
|
|
11
12
|
import {
|
|
12
13
|
APP_NAME,
|
|
13
14
|
CONFIG_DIR_NAME,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
MCP_TOOL_PREFIX,
|
|
21
22
|
PLUGINS_DIR_NAME,
|
|
22
23
|
VERSION
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-2DWWB4KD.js";
|
|
24
25
|
|
|
25
26
|
// src/config/config-manager.ts
|
|
26
27
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
@@ -309,9 +310,12 @@ var BaseProvider = class {
|
|
|
309
310
|
/**
|
|
310
311
|
* 将 Message[] 转换为 OpenAI API 格式的消息数组。
|
|
311
312
|
* content 为 string 时直接传递;为 MessageContentPart[] 时保留数组格式(vision 请求)。
|
|
313
|
+
*
|
|
314
|
+
* 自动跳过 role='tool' 和带 toolCalls 的 assistant 消息——
|
|
315
|
+
* 这些是 v0.4.60+ 持久化的工具历史,由 _extraMessages 机制单独注入。
|
|
312
316
|
*/
|
|
313
317
|
normalizeMessages(messages) {
|
|
314
|
-
return messages.map((m) => ({ role: m.role, content: m.content }));
|
|
318
|
+
return messages.filter((m) => m.role !== "tool" && !m.toolCalls).map((m) => ({ role: m.role, content: m.content }));
|
|
315
319
|
}
|
|
316
320
|
};
|
|
317
321
|
|
|
@@ -475,7 +479,7 @@ var ClaudeProvider = class extends BaseProvider {
|
|
|
475
479
|
}
|
|
476
480
|
async chat(request) {
|
|
477
481
|
try {
|
|
478
|
-
const messages = request.messages.filter((m) => m.role !== "system").map((m) => ({
|
|
482
|
+
const messages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
|
|
479
483
|
role: m.role,
|
|
480
484
|
content: this.contentToClaudeParts(m.content)
|
|
481
485
|
}));
|
|
@@ -500,7 +504,7 @@ var ClaudeProvider = class extends BaseProvider {
|
|
|
500
504
|
}
|
|
501
505
|
async *chatStream(request) {
|
|
502
506
|
try {
|
|
503
|
-
const messages = request.messages.filter((m) => m.role !== "system").map((m) => ({
|
|
507
|
+
const messages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
|
|
504
508
|
role: m.role,
|
|
505
509
|
content: this.contentToClaudeParts(m.content)
|
|
506
510
|
}));
|
|
@@ -557,7 +561,7 @@ var ClaudeProvider = class extends BaseProvider {
|
|
|
557
561
|
}
|
|
558
562
|
}))
|
|
559
563
|
);
|
|
560
|
-
const baseMessages = request.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: this.contentToClaudeParts(m.content) }));
|
|
564
|
+
const baseMessages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({ role: m.role, content: this.contentToClaudeParts(m.content) }));
|
|
561
565
|
const extraMessages = request._extraMessages ?? [];
|
|
562
566
|
const allMessages = [...baseMessages, ...extraMessages];
|
|
563
567
|
const { thinking, temperature } = this.buildThinkingParams(request);
|
|
@@ -869,7 +873,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
869
873
|
return parts.length > 0 ? parts : [{ text: "" }];
|
|
870
874
|
}
|
|
871
875
|
toGeminiHistory(messages) {
|
|
872
|
-
return messages.filter((m) => m.role !== "system").map((m) => ({
|
|
876
|
+
return messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
|
|
873
877
|
role: m.role === "assistant" ? "model" : "user",
|
|
874
878
|
parts: this.contentToGeminiParts(m.content)
|
|
875
879
|
}));
|
|
@@ -2521,10 +2525,19 @@ var Session = class _Session {
|
|
|
2521
2525
|
messageIndex: c.messageIndex,
|
|
2522
2526
|
timestamp: c.timestamp.toISOString()
|
|
2523
2527
|
})),
|
|
2524
|
-
messages: this.messages.map((m) =>
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
+
messages: this.messages.map((m) => {
|
|
2529
|
+
const out = {
|
|
2530
|
+
role: m.role,
|
|
2531
|
+
content: m.content,
|
|
2532
|
+
timestamp: m.timestamp.toISOString()
|
|
2533
|
+
};
|
|
2534
|
+
if (m.toolCalls) out.toolCalls = m.toolCalls;
|
|
2535
|
+
if (m.reasoningContent !== void 0) out.reasoningContent = m.reasoningContent;
|
|
2536
|
+
if (m.toolCallId) out.toolCallId = m.toolCallId;
|
|
2537
|
+
if (m.toolName) out.toolName = m.toolName;
|
|
2538
|
+
if (m.isError !== void 0) out.isError = m.isError;
|
|
2539
|
+
return out;
|
|
2540
|
+
})
|
|
2528
2541
|
};
|
|
2529
2542
|
}
|
|
2530
2543
|
/**
|
|
@@ -2594,11 +2607,17 @@ var Session = class _Session {
|
|
|
2594
2607
|
}
|
|
2595
2608
|
session.messages = d.messages.map((m) => {
|
|
2596
2609
|
const ts = new Date(m.timestamp);
|
|
2597
|
-
|
|
2610
|
+
const msg = {
|
|
2598
2611
|
role: m.role ?? "user",
|
|
2599
|
-
content: m.content,
|
|
2612
|
+
content: Array.isArray(m.content) ? m.content : String(m.content ?? ""),
|
|
2600
2613
|
timestamp: isNaN(ts.getTime()) ? /* @__PURE__ */ new Date() : ts
|
|
2601
2614
|
};
|
|
2615
|
+
if (Array.isArray(m.toolCalls)) msg.toolCalls = m.toolCalls;
|
|
2616
|
+
if (typeof m.reasoningContent === "string") msg.reasoningContent = m.reasoningContent;
|
|
2617
|
+
if (typeof m.toolCallId === "string") msg.toolCallId = m.toolCallId;
|
|
2618
|
+
if (typeof m.toolName === "string") msg.toolName = m.toolName;
|
|
2619
|
+
if (typeof m.isError === "boolean") msg.isError = m.isError;
|
|
2620
|
+
return msg;
|
|
2602
2621
|
});
|
|
2603
2622
|
return session;
|
|
2604
2623
|
}
|
|
@@ -3667,6 +3686,78 @@ function formatCost(amount) {
|
|
|
3667
3686
|
return `$${amount.toFixed(2)}`;
|
|
3668
3687
|
}
|
|
3669
3688
|
|
|
3689
|
+
// src/session/tool-history.ts
|
|
3690
|
+
function persistToolRound(session, toolCalls, toolResults, opts) {
|
|
3691
|
+
session.addMessage({
|
|
3692
|
+
role: "assistant",
|
|
3693
|
+
content: opts?.assistantContent ?? "",
|
|
3694
|
+
toolCalls,
|
|
3695
|
+
reasoningContent: opts?.reasoningContent,
|
|
3696
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
3697
|
+
});
|
|
3698
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
3699
|
+
const tc = toolCalls[i];
|
|
3700
|
+
const tr = toolResults[i];
|
|
3701
|
+
if (!tr) continue;
|
|
3702
|
+
session.addMessage({
|
|
3703
|
+
role: "tool",
|
|
3704
|
+
content: truncateForPersist(tr.content),
|
|
3705
|
+
toolCallId: tr.callId,
|
|
3706
|
+
toolName: tc.name,
|
|
3707
|
+
isError: tr.isError,
|
|
3708
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
3709
|
+
});
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
function isToolMessage(m) {
|
|
3713
|
+
return m.role === "tool" || !!(m.toolCalls && m.toolCalls.length > 0);
|
|
3714
|
+
}
|
|
3715
|
+
function extractToolHistory(messages) {
|
|
3716
|
+
const baseMessages = [];
|
|
3717
|
+
const toolHistory = [];
|
|
3718
|
+
for (const m of messages) {
|
|
3719
|
+
if (isToolMessage(m)) {
|
|
3720
|
+
toolHistory.push(m);
|
|
3721
|
+
} else {
|
|
3722
|
+
baseMessages.push(m);
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
return { baseMessages, toolHistory };
|
|
3726
|
+
}
|
|
3727
|
+
function rebuildExtraMessages(provider, toolHistory) {
|
|
3728
|
+
if (toolHistory.length === 0) return [];
|
|
3729
|
+
const result = [];
|
|
3730
|
+
let i = 0;
|
|
3731
|
+
while (i < toolHistory.length) {
|
|
3732
|
+
const msg = toolHistory[i];
|
|
3733
|
+
if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
3734
|
+
const toolCalls = msg.toolCalls;
|
|
3735
|
+
const toolResults = [];
|
|
3736
|
+
let j = i + 1;
|
|
3737
|
+
while (j < toolHistory.length && toolHistory[j].role === "tool") {
|
|
3738
|
+
const tm = toolHistory[j];
|
|
3739
|
+
toolResults.push({
|
|
3740
|
+
callId: tm.toolCallId ?? "",
|
|
3741
|
+
content: typeof tm.content === "string" ? tm.content : getContentText(tm.content),
|
|
3742
|
+
isError: tm.isError ?? false
|
|
3743
|
+
});
|
|
3744
|
+
j++;
|
|
3745
|
+
}
|
|
3746
|
+
result.push(
|
|
3747
|
+
...provider.buildToolResultMessages(toolCalls, toolResults, msg.reasoningContent)
|
|
3748
|
+
);
|
|
3749
|
+
i = j;
|
|
3750
|
+
} else {
|
|
3751
|
+
result.push({
|
|
3752
|
+
role: msg.role,
|
|
3753
|
+
content: typeof msg.content === "string" ? msg.content : getContentText(msg.content)
|
|
3754
|
+
});
|
|
3755
|
+
i++;
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
return result;
|
|
3759
|
+
}
|
|
3760
|
+
|
|
3670
3761
|
// src/repl/dev-state.ts
|
|
3671
3762
|
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, mkdirSync as mkdirSync4 } from "fs";
|
|
3672
3763
|
import { join as join5 } from "path";
|
|
@@ -3777,6 +3868,9 @@ export {
|
|
|
3777
3868
|
computeCost,
|
|
3778
3869
|
formatCost,
|
|
3779
3870
|
parseSimpleYaml,
|
|
3871
|
+
persistToolRound,
|
|
3872
|
+
extractToolHistory,
|
|
3873
|
+
rebuildExtraMessages,
|
|
3780
3874
|
SNAPSHOT_PROMPT,
|
|
3781
3875
|
sessionHasMeaningfulContent,
|
|
3782
3876
|
saveDevState,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
11
11
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
12
12
|
runTestsTool
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-2DWWB4KD.js";
|
|
14
14
|
|
|
15
15
|
// src/tools/builtin/bash.ts
|
|
16
16
|
import { execSync } from "child_process";
|
|
@@ -1098,6 +1098,20 @@ function snapToLineBoundary(content, target, direction) {
|
|
|
1098
1098
|
return target;
|
|
1099
1099
|
}
|
|
1100
1100
|
}
|
|
1101
|
+
var PERSIST_MAX_CHARS = 8192;
|
|
1102
|
+
function truncateForPersist(content, maxChars = PERSIST_MAX_CHARS) {
|
|
1103
|
+
if (content.length <= maxChars) return content;
|
|
1104
|
+
const headSize = Math.floor(maxChars * 0.7);
|
|
1105
|
+
const tailSize = Math.floor(maxChars * 0.2);
|
|
1106
|
+
const head = content.slice(0, headSize);
|
|
1107
|
+
const tail = content.slice(-tailSize);
|
|
1108
|
+
const omitted = content.length - headSize - tailSize;
|
|
1109
|
+
return `${head}
|
|
1110
|
+
|
|
1111
|
+
[... ${omitted} chars omitted for storage ...]
|
|
1112
|
+
|
|
1113
|
+
${tail}`;
|
|
1114
|
+
}
|
|
1101
1115
|
function truncateOutput(content, toolName, maxChars) {
|
|
1102
1116
|
const limit = maxChars ?? activeMaxChars;
|
|
1103
1117
|
if (content.length <= limit) return content;
|
|
@@ -4208,6 +4222,7 @@ export {
|
|
|
4208
4222
|
checkPermission,
|
|
4209
4223
|
setMaxOutputCap,
|
|
4210
4224
|
setContextWindow,
|
|
4225
|
+
truncateForPersist,
|
|
4211
4226
|
truncateOutput,
|
|
4212
4227
|
ToolExecutor,
|
|
4213
4228
|
lastResponseStore,
|
|
@@ -385,7 +385,7 @@ ${content}`);
|
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
387
|
async function runTaskMode(config, providers, configManager, topic) {
|
|
388
|
-
const { TaskOrchestrator } = await import("./task-orchestrator-
|
|
388
|
+
const { TaskOrchestrator } = await import("./task-orchestrator-Z4IK3UEA.js");
|
|
389
389
|
const orchestrator = new TaskOrchestrator(config, providers, configManager);
|
|
390
390
|
let interrupted = false;
|
|
391
391
|
const onSigint = () => {
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
clearDevState,
|
|
14
14
|
computeCost,
|
|
15
15
|
detectsHallucinatedFileOp,
|
|
16
|
+
extractToolHistory,
|
|
16
17
|
extractWrittenFilePaths,
|
|
17
18
|
findPhantomClaims,
|
|
18
19
|
formatCost,
|
|
@@ -24,10 +25,12 @@ import {
|
|
|
24
25
|
hadPreviousWriteToolCalls,
|
|
25
26
|
loadDevState,
|
|
26
27
|
parseSimpleYaml,
|
|
28
|
+
persistToolRound,
|
|
29
|
+
rebuildExtraMessages,
|
|
27
30
|
saveDevState,
|
|
28
31
|
sessionHasMeaningfulContent,
|
|
29
32
|
setupProxy
|
|
30
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-3YVHYAXK.js";
|
|
31
34
|
import {
|
|
32
35
|
ToolExecutor,
|
|
33
36
|
ToolRegistry,
|
|
@@ -41,7 +44,7 @@ import {
|
|
|
41
44
|
spawnAgentContext,
|
|
42
45
|
theme,
|
|
43
46
|
undoStack
|
|
44
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-C32FFHMY.js";
|
|
45
48
|
import {
|
|
46
49
|
fileCheckpoints
|
|
47
50
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -66,7 +69,7 @@ import {
|
|
|
66
69
|
SKILLS_DIR_NAME,
|
|
67
70
|
VERSION,
|
|
68
71
|
buildUserIdentityPrompt
|
|
69
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-2DWWB4KD.js";
|
|
70
73
|
|
|
71
74
|
// src/index.ts
|
|
72
75
|
import { program } from "commander";
|
|
@@ -2161,7 +2164,7 @@ ${hint}` : "")
|
|
|
2161
2164
|
usage: "/test [command|filter]",
|
|
2162
2165
|
async execute(args, ctx) {
|
|
2163
2166
|
try {
|
|
2164
|
-
const { executeTests } = await import("./run-tests-
|
|
2167
|
+
const { executeTests } = await import("./run-tests-QGJHXL5Z.js");
|
|
2165
2168
|
const argStr = args.join(" ").trim();
|
|
2166
2169
|
let testArgs = {};
|
|
2167
2170
|
if (argStr) {
|
|
@@ -4322,7 +4325,29 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4322
4325
|
sendNotification("ai-cli", `Task completed in ${Math.round(elapsed / 1e3)}s`);
|
|
4323
4326
|
}
|
|
4324
4327
|
} catch (err) {
|
|
4325
|
-
|
|
4328
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4329
|
+
const isCtxLengthError = /maximum context length|context_length_exceeded|context window|too many tokens|reduce the length of the messages/i.test(errMsg);
|
|
4330
|
+
if (isCtxLengthError) {
|
|
4331
|
+
process.stderr.write(
|
|
4332
|
+
theme.error(`
|
|
4333
|
+
\u26A0 Context length exceeded \u2014 the conversation is too long for this model.
|
|
4334
|
+
`)
|
|
4335
|
+
);
|
|
4336
|
+
process.stderr.write(theme.dim(` Details: ${errMsg.split("\n")[0]}
|
|
4337
|
+
`));
|
|
4338
|
+
process.stderr.write(
|
|
4339
|
+
theme.dim(
|
|
4340
|
+
` Recovery options:
|
|
4341
|
+
1. Run /compact to summarize old messages and free context
|
|
4342
|
+
2. Run /clear to start a fresh session (keeps this terminal)
|
|
4343
|
+
3. Run /model to switch to a model with a larger context window
|
|
4344
|
+
|
|
4345
|
+
`
|
|
4346
|
+
)
|
|
4347
|
+
);
|
|
4348
|
+
} else {
|
|
4349
|
+
this.renderer.renderError(err);
|
|
4350
|
+
}
|
|
4326
4351
|
}
|
|
4327
4352
|
}
|
|
4328
4353
|
/**
|
|
@@ -4397,6 +4422,23 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4397
4422
|
}
|
|
4398
4423
|
return total;
|
|
4399
4424
|
}
|
|
4425
|
+
/**
|
|
4426
|
+
* 估算 agentic 循环当前请求的 token 数(包含 session messages + extraMessages + system prompt)。
|
|
4427
|
+
* extraMessages 结构复杂(含 tool_calls、tool 结果等),这里用 JSON.stringify 后除以字符/token 比。
|
|
4428
|
+
* 用于 handleChatWithTools 循环内每轮发 API 前做上下文压力检查。
|
|
4429
|
+
*/
|
|
4430
|
+
estimateRequestTokens(systemPrompt, extraMessages) {
|
|
4431
|
+
let total = this.estimateConversationTokens();
|
|
4432
|
+
if (extraMessages.length > 0) {
|
|
4433
|
+
try {
|
|
4434
|
+
const serialized = JSON.stringify(extraMessages);
|
|
4435
|
+
total += this.estimateTokens(serialized);
|
|
4436
|
+
} catch {
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4439
|
+
void systemPrompt;
|
|
4440
|
+
return total;
|
|
4441
|
+
}
|
|
4400
4442
|
/**
|
|
4401
4443
|
* 获取当前模型的 context window 大小。
|
|
4402
4444
|
*/
|
|
@@ -4830,8 +4872,9 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4830
4872
|
if (this.blockedTools) {
|
|
4831
4873
|
toolDefs = toolDefs.filter((t) => !this.blockedTools.has(t.name));
|
|
4832
4874
|
}
|
|
4833
|
-
const
|
|
4834
|
-
const
|
|
4875
|
+
const { baseMessages: cleanMessages, toolHistory } = extractToolHistory(messages);
|
|
4876
|
+
const apiMessages = [...cleanMessages];
|
|
4877
|
+
const extraMessages = toolHistory.length > 0 ? rebuildExtraMessages(provider, toolHistory) : [];
|
|
4835
4878
|
const maxToolRounds = this.maxToolRoundsOverride ?? this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
|
|
4836
4879
|
const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
|
|
4837
4880
|
const autoPauseInterval = typeof autoPauseIntervalRaw === "number" ? autoPauseIntervalRaw : DEFAULT_AUTO_PAUSE_INTERVAL;
|
|
@@ -4865,6 +4908,7 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
4865
4908
|
let lastToolCallSignature = "";
|
|
4866
4909
|
let repeatedToolCallCount = 0;
|
|
4867
4910
|
let emptyResponseRetries = 0;
|
|
4911
|
+
let warnedCtx80 = false;
|
|
4868
4912
|
const roundToolHistory = [];
|
|
4869
4913
|
this.setupInterjectionListener();
|
|
4870
4914
|
try {
|
|
@@ -4923,6 +4967,52 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
4923
4967
|
`));
|
|
4924
4968
|
extraMessages.push({ role: "user", content: msg });
|
|
4925
4969
|
}
|
|
4970
|
+
const ctxWindow = this.getContextWindowSize();
|
|
4971
|
+
if (ctxWindow > 0) {
|
|
4972
|
+
const reqTokens = this.estimateRequestTokens(systemPrompt, extraMessages);
|
|
4973
|
+
const reqRatio = reqTokens / ctxWindow;
|
|
4974
|
+
if (reqRatio >= 0.95) {
|
|
4975
|
+
spinner.stop();
|
|
4976
|
+
process.stderr.write(
|
|
4977
|
+
theme.error(
|
|
4978
|
+
`
|
|
4979
|
+
\u26A0 Context at ${Math.round(reqRatio * 100)}% of ${fmtTokens(ctxWindow)} \u2014 aborting agentic loop before API rejection.
|
|
4980
|
+
`
|
|
4981
|
+
)
|
|
4982
|
+
);
|
|
4983
|
+
process.stderr.write(
|
|
4984
|
+
theme.dim(
|
|
4985
|
+
` Too much tool output accumulated this turn. Your work so far is preserved.
|
|
4986
|
+
Recovery: run /compact to shrink history, then ask the AI to continue.
|
|
4987
|
+
|
|
4988
|
+
`
|
|
4989
|
+
)
|
|
4990
|
+
);
|
|
4991
|
+
if (roundUsage.inputTokens > 0 || roundUsage.outputTokens > 0) {
|
|
4992
|
+
this.addSessionUsage(roundUsage);
|
|
4993
|
+
session.addTokenUsage(roundUsage);
|
|
4994
|
+
if (this.shouldShowTokens()) {
|
|
4995
|
+
this.renderer.renderUsage(roundUsage, this.sessionTokenUsage);
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
return;
|
|
4999
|
+
} else if (reqRatio >= 0.8 && !warnedCtx80) {
|
|
5000
|
+
warnedCtx80 = true;
|
|
5001
|
+
spinner.stop();
|
|
5002
|
+
process.stdout.write(
|
|
5003
|
+
theme.warning(
|
|
5004
|
+
`
|
|
5005
|
+
\u26A0 Context at ${Math.round(reqRatio * 100)}% of ${fmtTokens(ctxWindow)} \u2014 asking AI to wrap up.
|
|
5006
|
+
`
|
|
5007
|
+
)
|
|
5008
|
+
);
|
|
5009
|
+
extraMessages.push({
|
|
5010
|
+
role: "user",
|
|
5011
|
+
content: `\u26A0\uFE0F Context pressure: ~${Math.round(reqRatio * 100)}% of the ${fmtTokens(ctxWindow)} context window is used. Avoid reading more files or running broad scans. Finish the current critical step, then produce a final summary. Every unnecessary tool call now risks breaking the conversation.`
|
|
5012
|
+
});
|
|
5013
|
+
spinner.start(`Thinking... (round ${round + 1}/${maxToolRounds})`);
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
4926
5016
|
let result;
|
|
4927
5017
|
let alreadyRendered = false;
|
|
4928
5018
|
const chatRequest = {
|
|
@@ -5174,6 +5264,11 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
5174
5264
|
const reasoningContent = "reasoningContent" in result ? result.reasoningContent : void 0;
|
|
5175
5265
|
const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
|
|
5176
5266
|
extraMessages.push(...newMsgs);
|
|
5267
|
+
const streamedContent = "content" in result ? result.content : void 0;
|
|
5268
|
+
persistToolRound(session, result.toolCalls, toolResults, {
|
|
5269
|
+
assistantContent: streamedContent,
|
|
5270
|
+
reasoningContent
|
|
5271
|
+
});
|
|
5177
5272
|
const thisRoundHadWrite = result.toolCalls.some(
|
|
5178
5273
|
(tc) => tc.name === "write_file" || tc.name === "edit_file"
|
|
5179
5274
|
);
|
|
@@ -5609,7 +5704,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
5609
5704
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
5610
5705
|
process.exit(1);
|
|
5611
5706
|
}
|
|
5612
|
-
const { startWebServer } = await import("./server-
|
|
5707
|
+
const { startWebServer } = await import("./server-L2XJYXMB.js");
|
|
5613
5708
|
await startWebServer({ port, host: options.host });
|
|
5614
5709
|
});
|
|
5615
5710
|
program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
|
|
@@ -5842,7 +5937,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
|
|
|
5842
5937
|
}),
|
|
5843
5938
|
config.get("customProviders")
|
|
5844
5939
|
);
|
|
5845
|
-
const { startHub } = await import("./hub-
|
|
5940
|
+
const { startHub } = await import("./hub-JTMNY7JR.js");
|
|
5846
5941
|
await startHub(
|
|
5847
5942
|
{
|
|
5848
5943
|
topic: topic ?? "",
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
TOOL_CALL_REMINDER,
|
|
10
10
|
computeCost,
|
|
11
11
|
detectsHallucinatedFileOp,
|
|
12
|
+
extractToolHistory,
|
|
12
13
|
formatCost,
|
|
13
14
|
formatGitContextForPrompt,
|
|
14
15
|
getContentText,
|
|
@@ -16,8 +17,10 @@ import {
|
|
|
16
17
|
getGitRoot,
|
|
17
18
|
hadPreviousWriteToolCalls,
|
|
18
19
|
loadDevState,
|
|
20
|
+
persistToolRound,
|
|
21
|
+
rebuildExtraMessages,
|
|
19
22
|
setupProxy
|
|
20
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-3YVHYAXK.js";
|
|
21
24
|
import {
|
|
22
25
|
AuthManager
|
|
23
26
|
} from "./chunk-BYNY5JPB.js";
|
|
@@ -36,7 +39,7 @@ import {
|
|
|
36
39
|
spawnAgentContext,
|
|
37
40
|
truncateOutput,
|
|
38
41
|
undoStack
|
|
39
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-C32FFHMY.js";
|
|
40
43
|
import "./chunk-4BKXL7SM.js";
|
|
41
44
|
import {
|
|
42
45
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
@@ -56,7 +59,7 @@ import {
|
|
|
56
59
|
SKILLS_DIR_NAME,
|
|
57
60
|
VERSION,
|
|
58
61
|
buildUserIdentityPrompt
|
|
59
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-2DWWB4KD.js";
|
|
60
63
|
|
|
61
64
|
// src/web/server.ts
|
|
62
65
|
import express from "express";
|
|
@@ -642,6 +645,49 @@ var SessionHandler = class _SessionHandler {
|
|
|
642
645
|
} catch {
|
|
643
646
|
}
|
|
644
647
|
}
|
|
648
|
+
/** 获取当前模型的 context window 大小(0 表示未知)*/
|
|
649
|
+
getContextWindowSize() {
|
|
650
|
+
try {
|
|
651
|
+
const provider = this.providers.get(this.currentProvider);
|
|
652
|
+
const modelInfo = provider?.info.models.find((m) => m.id === this.currentModel);
|
|
653
|
+
return modelInfo?.contextWindow ?? 0;
|
|
654
|
+
} catch {
|
|
655
|
+
return 0;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/** 粗略估算文本 token 数(2.5 chars/token)*/
|
|
659
|
+
estTokens(text) {
|
|
660
|
+
return Math.ceil(text.length / 2.5);
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* 估算当前 agentic 请求总 token 数(session messages + extraMessages + system prompt)。
|
|
664
|
+
* 用于 handleChat 循环内每轮发 API 前的压力检查。
|
|
665
|
+
*/
|
|
666
|
+
estimateRequestTokens(systemPrompt, extraMessages) {
|
|
667
|
+
let total = 0;
|
|
668
|
+
if (systemPrompt) total += this.estTokens(systemPrompt);
|
|
669
|
+
const session = this.sessions.current;
|
|
670
|
+
if (session) {
|
|
671
|
+
for (const msg of session.messages) {
|
|
672
|
+
if (typeof msg.content === "string") {
|
|
673
|
+
total += this.estTokens(msg.content);
|
|
674
|
+
} else if (Array.isArray(msg.content)) {
|
|
675
|
+
for (const part of msg.content) {
|
|
676
|
+
if (part.type === "text" && part.text) {
|
|
677
|
+
total += this.estTokens(part.text);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (extraMessages.length > 0) {
|
|
684
|
+
try {
|
|
685
|
+
total += this.estTokens(JSON.stringify(extraMessages));
|
|
686
|
+
} catch {
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return total;
|
|
690
|
+
}
|
|
645
691
|
/** Save session only if it exists and has messages (never persist empty "Untitled" sessions). */
|
|
646
692
|
saveIfNeeded() {
|
|
647
693
|
if (this.sessions.current && this.sessions.current.messages.length > 0) {
|
|
@@ -771,8 +817,9 @@ var SessionHandler = class _SessionHandler {
|
|
|
771
817
|
}
|
|
772
818
|
async handleChatWithTools(provider, messages, toolDefs) {
|
|
773
819
|
const session = this.sessions.current;
|
|
774
|
-
const
|
|
775
|
-
const
|
|
820
|
+
const { baseMessages: cleanMessages, toolHistory } = extractToolHistory(messages);
|
|
821
|
+
const apiMessages = [...cleanMessages];
|
|
822
|
+
const extraMessages = toolHistory.length > 0 ? rebuildExtraMessages(provider, toolHistory) : [];
|
|
776
823
|
const maxToolRounds = this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
|
|
777
824
|
const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
|
|
778
825
|
const autoPauseInterval = typeof autoPauseIntervalRaw === "number" ? autoPauseIntervalRaw : 50;
|
|
@@ -801,6 +848,7 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
801
848
|
let warnedLow = false;
|
|
802
849
|
let warnedCritical = false;
|
|
803
850
|
let emptyResponseRetries = 0;
|
|
851
|
+
let warnedCtx80 = false;
|
|
804
852
|
const ac = new AbortController();
|
|
805
853
|
this.abortController = ac;
|
|
806
854
|
try {
|
|
@@ -836,6 +884,38 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
836
884
|
this.send({ type: "info", message: `\u26A1 Interjection: "${msg}"` });
|
|
837
885
|
extraMessages.push({ role: "user", content: msg });
|
|
838
886
|
}
|
|
887
|
+
const ctxWindow = this.getContextWindowSize();
|
|
888
|
+
if (ctxWindow > 0) {
|
|
889
|
+
const reqTokens = this.estimateRequestTokens(systemPrompt, extraMessages);
|
|
890
|
+
const reqRatio = reqTokens / ctxWindow;
|
|
891
|
+
if (reqRatio >= 0.95) {
|
|
892
|
+
this.send({
|
|
893
|
+
type: "response_done",
|
|
894
|
+
content: `\u26A0 Context at ${Math.round(reqRatio * 100)}% of ${ctxWindow.toLocaleString()} tokens \u2014 aborting before API rejection.
|
|
895
|
+
|
|
896
|
+
Too much tool output accumulated this turn. Your work so far is preserved.
|
|
897
|
+
|
|
898
|
+
**Recovery**:
|
|
899
|
+
1. Run \`/compact\` to shrink history, then ask the AI to continue
|
|
900
|
+
2. Run \`/clear\` to start fresh
|
|
901
|
+
3. Switch to a larger-context model`,
|
|
902
|
+
usage: roundUsage
|
|
903
|
+
});
|
|
904
|
+
this.addWebSessionUsage(roundUsage);
|
|
905
|
+
session.addTokenUsage(roundUsage);
|
|
906
|
+
return;
|
|
907
|
+
} else if (reqRatio >= 0.8 && !warnedCtx80) {
|
|
908
|
+
warnedCtx80 = true;
|
|
909
|
+
this.send({
|
|
910
|
+
type: "info",
|
|
911
|
+
message: `\u26A0 Context at ${Math.round(reqRatio * 100)}% \u2014 asking AI to wrap up`
|
|
912
|
+
});
|
|
913
|
+
extraMessages.push({
|
|
914
|
+
role: "user",
|
|
915
|
+
content: `\u26A0\uFE0F Context pressure: ~${Math.round(reqRatio * 100)}% of the ${ctxWindow.toLocaleString()}-token context window is used. Avoid reading more files or running broad scans. Finish the current critical step, then produce a final summary. Every unnecessary tool call now risks breaking the conversation.`
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
}
|
|
839
919
|
const chatRequest = {
|
|
840
920
|
messages: apiMessages,
|
|
841
921
|
model: this.currentModel,
|
|
@@ -850,11 +930,34 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
850
930
|
...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
|
|
851
931
|
};
|
|
852
932
|
let result;
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
933
|
+
try {
|
|
934
|
+
if (supportsStreamingTools) {
|
|
935
|
+
const streamGen = provider.chatWithToolsStream(chatRequest, toolDefs);
|
|
936
|
+
result = await this.consumeToolStream(streamGen, ac);
|
|
937
|
+
} else {
|
|
938
|
+
result = await provider.chatWithTools(chatRequest, toolDefs);
|
|
939
|
+
}
|
|
940
|
+
} catch (providerErr) {
|
|
941
|
+
const errMsg = providerErr instanceof Error ? providerErr.message : String(providerErr);
|
|
942
|
+
const isCtxLengthError = /maximum context length|context_length_exceeded|context window|too many tokens|reduce the length of the messages/i.test(errMsg);
|
|
943
|
+
if (isCtxLengthError) {
|
|
944
|
+
this.send({
|
|
945
|
+
type: "response_done",
|
|
946
|
+
content: `\u26A0 Context length exceeded \u2014 the conversation is too long for this model.
|
|
947
|
+
|
|
948
|
+
Details: ${errMsg.split("\n")[0]}
|
|
949
|
+
|
|
950
|
+
**Recovery options**:
|
|
951
|
+
1. Run \`/compact\` to summarize old messages and free context
|
|
952
|
+
2. Run \`/clear\` to start a fresh session
|
|
953
|
+
3. Run \`/model\` to switch to a model with a larger context window`,
|
|
954
|
+
usage: roundUsage
|
|
955
|
+
});
|
|
956
|
+
this.addWebSessionUsage(roundUsage);
|
|
957
|
+
session.addTokenUsage(roundUsage);
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
throw providerErr;
|
|
858
961
|
}
|
|
859
962
|
if (ac.signal.aborted) break;
|
|
860
963
|
if (result.usage) {
|
|
@@ -921,6 +1024,10 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
|
|
|
921
1024
|
const reasoningContent = result.reasoningContent;
|
|
922
1025
|
const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
|
|
923
1026
|
extraMessages.push(...newMsgs);
|
|
1027
|
+
persistToolRound(session, result.toolCalls, toolResults, {
|
|
1028
|
+
assistantContent: result.content,
|
|
1029
|
+
reasoningContent
|
|
1030
|
+
});
|
|
924
1031
|
const allFree = result.toolCalls.every((tc) => FREE_ROUND_TOOLS.has(tc.name));
|
|
925
1032
|
if (allFree) {
|
|
926
1033
|
consecutiveFreeRounds++;
|
|
@@ -1816,7 +1923,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
1816
1923
|
case "test": {
|
|
1817
1924
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
1818
1925
|
try {
|
|
1819
|
-
const { executeTests } = await import("./run-tests-
|
|
1926
|
+
const { executeTests } = await import("./run-tests-QGJHXL5Z.js");
|
|
1820
1927
|
const argStr = args.join(" ").trim();
|
|
1821
1928
|
let testArgs = {};
|
|
1822
1929
|
if (argStr) {
|
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
getDangerLevel,
|
|
5
5
|
googleSearchContext,
|
|
6
6
|
truncateOutput
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-C32FFHMY.js";
|
|
8
8
|
import "./chunk-4BKXL7SM.js";
|
|
9
9
|
import {
|
|
10
10
|
SUBAGENT_ALLOWED_TOOLS
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-2DWWB4KD.js";
|
|
12
12
|
|
|
13
13
|
// src/hub/task-orchestrator.ts
|
|
14
14
|
import { createInterface } from "readline";
|