@musnows/scriverse 0.6.2 → 0.6.3
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/ai-tool-results.js +71 -0
- package/dist/ai-tool-results.js.map +1 -1
- package/dist/ai.js +163 -38
- package/dist/ai.js.map +1 -1
- package/dist/app.js +155 -36
- package/dist/app.js.map +1 -1
- package/dist/cli-core.js +23 -13
- package/dist/cli-core.js.map +1 -1
- package/dist/database.js +123 -1
- package/dist/database.js.map +1 -1
- package/dist/docx-export.js +89 -0
- package/dist/docx-export.js.map +1 -0
- package/dist/image-captcha.js +8 -5
- package/dist/image-captcha.js.map +1 -1
- package/dist/public/ai-context-meter.js +4 -0
- package/dist/public/ai-message-time.js +3 -9
- package/dist/public/ai-tool-call.js +4 -0
- package/dist/public/app.js +659 -89
- package/dist/public/index.html +47 -16
- package/dist/public/page-route.js +2 -2
- package/dist/public/styles.css +77 -19
- package/dist/public/system-status.d.ts +12 -0
- package/dist/public/system-status.js +16 -0
- package/dist/public/theme-init.js +2 -2
- package/dist/security.js +101 -9
- package/dist/security.js.map +1 -1
- package/dist/store.js +111 -20
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +41 -22
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/writing-progress-time.js +15 -0
- package/dist/writing-progress-time.js.map +1 -1
- package/package.json +2 -1
package/dist/ai-tool-results.js
CHANGED
|
@@ -1,4 +1,75 @@
|
|
|
1
1
|
export const AGENT_TOOL_RESULT_MAX_CHARS = 10_000;
|
|
2
|
+
export const MIN_AGENT_TOOL_CALL_LIMIT = 5;
|
|
3
|
+
export const MAX_AGENT_TOOL_CALL_LIMIT = 48;
|
|
4
|
+
export const AGENT_TOOL_CALL_SOFT_WARNING_FLOOR = 3;
|
|
5
|
+
export const DEFAULT_AGENT_TOOL_CALL_GLOBAL_MULTIPLIER = 3;
|
|
6
|
+
export const MIN_AGENT_TOOL_CALL_GLOBAL_MULTIPLIER = 1;
|
|
7
|
+
export const MAX_AGENT_TOOL_CALL_GLOBAL_MULTIPLIER = 6;
|
|
8
|
+
export function agentToolCallSoftWarningThreshold(limit) {
|
|
9
|
+
if (!Number.isFinite(limit) || limit <= 0)
|
|
10
|
+
return AGENT_TOOL_CALL_SOFT_WARNING_FLOOR;
|
|
11
|
+
return Math.max(AGENT_TOOL_CALL_SOFT_WARNING_FLOOR, Math.floor(limit * 0.2) + 1);
|
|
12
|
+
}
|
|
13
|
+
export function agentToolCallQuotaUsedAfterCompact(limit) {
|
|
14
|
+
if (!Number.isFinite(limit) || limit <= 0)
|
|
15
|
+
return 0;
|
|
16
|
+
return Math.floor(limit * 0.2);
|
|
17
|
+
}
|
|
18
|
+
export function clampAgentToolCallGlobalMultiplier(value) {
|
|
19
|
+
const numeric = Math.round(Number(value));
|
|
20
|
+
if (!Number.isFinite(numeric))
|
|
21
|
+
return DEFAULT_AGENT_TOOL_CALL_GLOBAL_MULTIPLIER;
|
|
22
|
+
return Math.min(MAX_AGENT_TOOL_CALL_GLOBAL_MULTIPLIER, Math.max(MIN_AGENT_TOOL_CALL_GLOBAL_MULTIPLIER, numeric));
|
|
23
|
+
}
|
|
24
|
+
/** 单次响应周期内的全局工具调用上限;计数器只增不减,不受 compact 影响。 */
|
|
25
|
+
export function agentToolCallGlobalLimit(limit, multiplier) {
|
|
26
|
+
const safeLimit = Math.max(1, Math.floor(Number(limit) || 0));
|
|
27
|
+
const safeMultiplier = clampAgentToolCallGlobalMultiplier(multiplier);
|
|
28
|
+
return safeLimit * safeMultiplier;
|
|
29
|
+
}
|
|
30
|
+
export function shouldRejectGlobalToolCalls(globalUsed, requestedCount, globalLimit) {
|
|
31
|
+
if (!Number.isFinite(globalUsed) || !Number.isFinite(requestedCount) || !Number.isFinite(globalLimit))
|
|
32
|
+
return true;
|
|
33
|
+
if (requestedCount <= 0)
|
|
34
|
+
return false;
|
|
35
|
+
return globalUsed + requestedCount > globalLimit;
|
|
36
|
+
}
|
|
37
|
+
export function buildAgentToolCallQuotaNotice(remaining, limit) {
|
|
38
|
+
if (!Number.isFinite(remaining) || remaining <= 0)
|
|
39
|
+
return null;
|
|
40
|
+
if (remaining === 1) {
|
|
41
|
+
return "[critical] 重要提示:现在没有可用的工具调用次数了。请立即根据已有工具结果直接总结作答,不得再请求任何工具。若继续发起工具调用,系统将拒绝并报错。";
|
|
42
|
+
}
|
|
43
|
+
if (remaining <= agentToolCallSoftWarningThreshold(limit)) {
|
|
44
|
+
return `[warning] 提醒:本轮工具调用配额即将用尽,当前剩余 ${remaining} 次。请尽快收敛并准备最终答案,避免继续大规模检索。`;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
/** 估算把 toolCallQuotaNotice 并入工具结果后,额外占用的字符数(用于 compact 体积预估)。 */
|
|
49
|
+
export function agentToolCallQuotaNoticeBudgetChars(remaining, limit) {
|
|
50
|
+
const notice = buildAgentToolCallQuotaNotice(remaining, limit);
|
|
51
|
+
if (!notice)
|
|
52
|
+
return 0;
|
|
53
|
+
return Math.max(0, serializedToolResultChars({ toolCallQuotaNotice: notice }) - 2);
|
|
54
|
+
}
|
|
55
|
+
export function withAgentToolCallQuotaNotice(result, remaining, limit) {
|
|
56
|
+
const notice = buildAgentToolCallQuotaNotice(remaining, limit);
|
|
57
|
+
if (!notice)
|
|
58
|
+
return result;
|
|
59
|
+
return { ...result, toolCallQuotaNotice: notice };
|
|
60
|
+
}
|
|
61
|
+
export function shouldRejectAgentToolCalls(executedCount, requestedCount, limit) {
|
|
62
|
+
if (!Number.isFinite(executedCount) || !Number.isFinite(requestedCount) || !Number.isFinite(limit))
|
|
63
|
+
return true;
|
|
64
|
+
if (requestedCount <= 0)
|
|
65
|
+
return false;
|
|
66
|
+
if (executedCount + requestedCount > limit)
|
|
67
|
+
return true;
|
|
68
|
+
// 最后一档配额保留给硬拒绝:在倒数第一次配额注入 critical 后,再请求工具即失败。
|
|
69
|
+
if (executedCount >= limit - 1)
|
|
70
|
+
return true;
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
2
73
|
const TOOL_RESULT_RECORD_MAX_CHARS = 6_000;
|
|
3
74
|
const TOOL_RESULT_IDENTITY_FIELDS = new Set([
|
|
4
75
|
"id",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-tool-results.js","sourceRoot":"","sources":["../src/ai-tool-results.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-tool-results.js","sourceRoot":"","sources":["../src/ai-tool-results.ts"],"names":[],"mappings":"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"}
|
package/dist/ai.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildCompletionRequestBody, normalizeProviderBaseUrl, parseCompletionPayload, providerCompletionEndpoint, providerModelEndpoints, providerRequestHeaders } from "./ai-protocol.js";
|
|
2
|
-
import { AGENT_TOOL_RESULT_MAX_CHARS, paginateToolResultRecords, structuralToolResultRecords } from "./ai-tool-results.js";
|
|
2
|
+
import { AGENT_TOOL_RESULT_MAX_CHARS, DEFAULT_AGENT_TOOL_CALL_GLOBAL_MULTIPLIER, MIN_AGENT_TOOL_CALL_LIMIT, agentToolCallGlobalLimit, agentToolCallQuotaNoticeBudgetChars, agentToolCallQuotaUsedAfterCompact, agentToolCallSoftWarningThreshold, clampAgentToolCallGlobalMultiplier, paginateToolResultRecords, shouldRejectAgentToolCalls, shouldRejectGlobalToolCalls, structuralToolResultRecords, withAgentToolCallQuotaNotice } from "./ai-tool-results.js";
|
|
3
3
|
import { PLATFORM_AI_WORK_ID } from "./database.js";
|
|
4
4
|
import { AppError, notFound } from "./errors.js";
|
|
5
5
|
import { HYBRID_SEARCH_TYPES, buildHybridSearchSnippet, documentParagraphLineRange, fuseHybridSearchChannels } from "./hybrid-search.js";
|
|
@@ -9,6 +9,7 @@ import { currentRequestActor } from "./request-context.js";
|
|
|
9
9
|
import { fetchSafeAiEndpoint } from "./security.js";
|
|
10
10
|
import { defaultAiConversationTitle } from "./store.js";
|
|
11
11
|
import { canReadWorkModule } from "./work-permissions.js";
|
|
12
|
+
import { buildWritingCalendar, resolveServerTimeZone } from "./writing-progress-time.js";
|
|
12
13
|
import { RELATIONSHIP_SEARCH_POLICY_VERSION, RelationshipApproximateMatchLimitError, findApproximateNameMatchesChunked, ftsPhrase, isRelationshipPhoneticReference, normalizeRelationshipSearchText, relationshipCharacterTokenText, relationshipCharacterTokens, relationshipPinyinSearchTokens, relationshipPinyinTokenText, relationshipPinyinTokens } from "./relationship-search.js";
|
|
13
14
|
import { clamp, id, json, maskSecret, now } from "./utils.js";
|
|
14
15
|
import { z } from "zod";
|
|
@@ -26,6 +27,33 @@ const AUTO_RUN_MAX_ATTEMPTS = 3;
|
|
|
26
27
|
const AUTO_RUN_RETRY_DELAYS_MS = [5_000, 30_000];
|
|
27
28
|
const AI_INTERACTIVE_TIMEOUT_MS = 60_000;
|
|
28
29
|
const AI_LONG_RUNNING_TIMEOUT_MS = 300_000;
|
|
30
|
+
/** 出站 AI 响应体上限,防止恶意或故障供应商推送超大响应拖垮进程。 */
|
|
31
|
+
export const AI_RESPONSE_MAX_BYTES = 8 * 1024 * 1024;
|
|
32
|
+
export async function readResponseTextLimited(response, maximumBytes = AI_RESPONSE_MAX_BYTES) {
|
|
33
|
+
const declared = response.headers.get("content-length");
|
|
34
|
+
if (declared && /^\d+$/u.test(declared) && Number(declared) > maximumBytes) {
|
|
35
|
+
throw new AppError(502, "AI_RESPONSE_TOO_LARGE", `AI 供应商响应超过 ${maximumBytes} 字节上限`);
|
|
36
|
+
}
|
|
37
|
+
if (!response.body)
|
|
38
|
+
return response.text();
|
|
39
|
+
const reader = response.body.getReader();
|
|
40
|
+
const chunks = [];
|
|
41
|
+
let total = 0;
|
|
42
|
+
while (true) {
|
|
43
|
+
const { done, value } = await reader.read();
|
|
44
|
+
if (done)
|
|
45
|
+
break;
|
|
46
|
+
if (!value?.byteLength)
|
|
47
|
+
continue;
|
|
48
|
+
total += value.byteLength;
|
|
49
|
+
if (total > maximumBytes) {
|
|
50
|
+
await reader.cancel().catch(() => undefined);
|
|
51
|
+
throw new AppError(502, "AI_RESPONSE_TOO_LARGE", `AI 供应商响应超过 ${maximumBytes} 字节上限`);
|
|
52
|
+
}
|
|
53
|
+
chunks.push(value);
|
|
54
|
+
}
|
|
55
|
+
return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))).toString("utf8");
|
|
56
|
+
}
|
|
29
57
|
const AUTO_RUN_FATAL_CODES = new Set([
|
|
30
58
|
"CREDENTIAL_DECRYPT_FAILED",
|
|
31
59
|
"MODEL_REQUIRED",
|
|
@@ -243,7 +271,6 @@ function sanitizeCompletionTraceResponse(value) {
|
|
|
243
271
|
...(response.usage && typeof response.usage === "object" && !Array.isArray(response.usage) ? { usage: response.usage } : {})
|
|
244
272
|
};
|
|
245
273
|
}
|
|
246
|
-
const MAX_AGENT_TOOL_ROUNDS = 6;
|
|
247
274
|
const MAX_AGENT_TOOL_CALLS = 12;
|
|
248
275
|
const MAX_CONFIGURED_AGENT_TOOL_CALLS = 48;
|
|
249
276
|
const TOOL_CONTEXT_COMPACT_MAX_TOKENS = 1_024;
|
|
@@ -1107,7 +1134,29 @@ export class AiManager {
|
|
|
1107
1134
|
}
|
|
1108
1135
|
getWorkTokenUsage(workId, timezoneOffset) {
|
|
1109
1136
|
this.store.getWork(workId);
|
|
1110
|
-
return
|
|
1137
|
+
return {
|
|
1138
|
+
...this.getTokenUsage(workId, timezoneOffset, false),
|
|
1139
|
+
quota: this.getWorkDailyTokenQuotaStatus(workId)
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
getWorkDailyTokenQuotaStatus(workId, referenceDate = new Date()) {
|
|
1143
|
+
const settings = this.store.getWorkAiSettings(workId);
|
|
1144
|
+
const dailyTokenQuota = settings.dailyTokenQuota === null
|
|
1145
|
+
? null
|
|
1146
|
+
: Number(settings.dailyTokenQuota);
|
|
1147
|
+
const calendar = buildWritingCalendar(referenceDate, 1, resolveServerTimeZone());
|
|
1148
|
+
const usage = this.store.db.get(`SELECT COALESCE(SUM(input_tokens + output_tokens), 0) AS used_tokens
|
|
1149
|
+
FROM ai_calls WHERE work_id = ? AND created_at >= ? AND created_at < ?`, workId, calendar.startInclusive, calendar.endExclusive);
|
|
1150
|
+
const usedTokens = numberValue(usage ?? {}, "used_tokens");
|
|
1151
|
+
return {
|
|
1152
|
+
dailyTokenQuota,
|
|
1153
|
+
usedTokens,
|
|
1154
|
+
remainingTokens: dailyTokenQuota === null ? null : Math.max(0, dailyTokenQuota - usedTokens),
|
|
1155
|
+
reached: dailyTokenQuota !== null && usedTokens >= dailyTokenQuota,
|
|
1156
|
+
dayStartedAt: calendar.startInclusive,
|
|
1157
|
+
resetsAt: calendar.endExclusive,
|
|
1158
|
+
timezone: calendar.timeZone
|
|
1159
|
+
};
|
|
1111
1160
|
}
|
|
1112
1161
|
async searchWork(workId, query, options = {}) {
|
|
1113
1162
|
this.store.getWork(workId);
|
|
@@ -1435,6 +1484,15 @@ export class AiManager {
|
|
|
1435
1484
|
const settings = this.store.getWorkAiSettings(workId);
|
|
1436
1485
|
if (!settings.autoRunEnabled || settings.autoRunPaused)
|
|
1437
1486
|
return;
|
|
1487
|
+
const tokenQuota = this.getWorkDailyTokenQuotaStatus(workId);
|
|
1488
|
+
if (tokenQuota.reached) {
|
|
1489
|
+
const dailyTokenQuota = Number(tokenQuota.dailyTokenQuota);
|
|
1490
|
+
const resumeAt = String(tokenQuota.resetsAt);
|
|
1491
|
+
this.store.pauseAutoRun(workId, `已达到每日 Token 额度 ${dailyTokenQuota}`, resumeAt);
|
|
1492
|
+
this.scheduleAutoRun(workId);
|
|
1493
|
+
logger.info("ai.auto_run.token_quota_reached", { workId, dailyTokenQuota, resumeAt });
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1438
1496
|
const dailyTaskLimit = Number(settings.autoRunDailyTaskLimit);
|
|
1439
1497
|
if (dailyTaskLimit > 0 && this.store.countAutoRunAttemptsToday(workId) >= dailyTaskLimit) {
|
|
1440
1498
|
const resumeAt = new Date();
|
|
@@ -1530,7 +1588,7 @@ export class AiManager {
|
|
|
1530
1588
|
})),
|
|
1531
1589
|
signal
|
|
1532
1590
|
});
|
|
1533
|
-
const body = await response
|
|
1591
|
+
const body = await readResponseTextLimited(response);
|
|
1534
1592
|
if (!response.ok)
|
|
1535
1593
|
throw new Error(`HTTP ${response.status}: ${body.slice(0, 300)}`);
|
|
1536
1594
|
let payload;
|
|
@@ -1632,10 +1690,10 @@ export class AiManager {
|
|
|
1632
1690
|
signal: controller.signal
|
|
1633
1691
|
});
|
|
1634
1692
|
if (response.ok) {
|
|
1635
|
-
payload = (await response
|
|
1693
|
+
payload = JSON.parse(await readResponseTextLimited(response));
|
|
1636
1694
|
break;
|
|
1637
1695
|
}
|
|
1638
|
-
const message = await response
|
|
1696
|
+
const message = await readResponseTextLimited(response);
|
|
1639
1697
|
lastFailure = `HTTP ${response.status}: ${message.slice(0, 300)}`;
|
|
1640
1698
|
if (response.status !== 404 || index === endpoints.length - 1)
|
|
1641
1699
|
break;
|
|
@@ -2105,10 +2163,11 @@ export class AiManager {
|
|
|
2105
2163
|
&& firstUserContent
|
|
2106
2164
|
&& titleModelId
|
|
2107
2165
|
&& (conversationBefore?.title === "新对话" || conversationBefore?.title === defaultTitle));
|
|
2108
|
-
const
|
|
2166
|
+
const chatTools = this.enabledAgentTools(input.workId, "chat", undefined, input.conversationId);
|
|
2167
|
+
const generated = chatTools.length
|
|
2109
2168
|
? await this.generate({ ...input, taskType: "chat" })
|
|
2110
2169
|
: await this.generateStream({ ...input, taskType: "chat" }, onDelta);
|
|
2111
|
-
if (
|
|
2170
|
+
if (chatTools.length)
|
|
2112
2171
|
onDelta(generated.content);
|
|
2113
2172
|
const chapter = input.scope.chapterId ? this.store.getChapter(input.scope.chapterId) : null;
|
|
2114
2173
|
const suggestionId = id("suggestion");
|
|
@@ -2572,7 +2631,7 @@ export class AiManager {
|
|
|
2572
2631
|
: 0;
|
|
2573
2632
|
const conversationBudgetTokens = Math.max(256, Math.floor(availableInputTokens * 0.32));
|
|
2574
2633
|
const instructionTokens = estimateAiTokens(input.instruction);
|
|
2575
|
-
const functionTokens = estimateAiTokens(JSON.stringify(this.enabledAgentTools(input.workId, input.taskType, input.agentToolIds)));
|
|
2634
|
+
const functionTokens = estimateAiTokens(JSON.stringify(this.enabledAgentTools(input.workId, input.taskType, input.agentToolIds, input.conversationId)));
|
|
2576
2635
|
const workContextBudgetTokens = Math.max(256, availableInputTokens
|
|
2577
2636
|
- Math.min(conversationTokens, conversationBudgetTokens)
|
|
2578
2637
|
- Math.min(instructionTokens, Math.floor(availableInputTokens * 0.25))
|
|
@@ -2596,7 +2655,7 @@ export class AiManager {
|
|
|
2596
2655
|
const contextPlan = this.buildContextPlan(input, model, budget);
|
|
2597
2656
|
const context = contextPlan.context;
|
|
2598
2657
|
const messages = this.buildMessages(input, context);
|
|
2599
|
-
const tools = this.enabledAgentTools(input.workId, input.taskType);
|
|
2658
|
+
const tools = this.enabledAgentTools(input.workId, input.taskType, input.agentToolIds, input.conversationId);
|
|
2600
2659
|
const contextWindow = numberValue(model, "context_window") || DEFAULT_CONTEXT_WINDOW;
|
|
2601
2660
|
const messageTokens = messages.reduce((total, message) => total + estimateAiTokens(message.content ?? ""), 0);
|
|
2602
2661
|
const systemPromptTokens = estimateAiTokens(messages[0]?.content ?? "");
|
|
@@ -2750,7 +2809,7 @@ export class AiManager {
|
|
|
2750
2809
|
buildMessages(input, context) {
|
|
2751
2810
|
const platformPrompt = String(this.store.getPlatformAiSettings().systemPrompt ?? "").trim();
|
|
2752
2811
|
const workPrompt = String(this.store.getWorkAiSettings(input.workId).systemPrompt ?? "").trim();
|
|
2753
|
-
const enabledToolIds = this.enabledAgentToolIds(input.workId, input.taskType, input.agentToolIds);
|
|
2812
|
+
const enabledToolIds = this.enabledAgentToolIds(input.workId, input.taskType, input.agentToolIds, input.conversationId);
|
|
2754
2813
|
const toolGuidance = enabledToolIds.length > 0
|
|
2755
2814
|
? [
|
|
2756
2815
|
`当前可用作品查询工具:${enabledToolIds.join("、")}。`,
|
|
@@ -2821,19 +2880,23 @@ export class AiManager {
|
|
|
2821
2880
|
buildContext(input, model) {
|
|
2822
2881
|
return collapseAiBlankLines(this.buildContextPlan(input, model).context);
|
|
2823
2882
|
}
|
|
2824
|
-
enabledAgentToolIds(workId, taskType, requestedToolIds) {
|
|
2883
|
+
enabledAgentToolIds(workId, taskType, requestedToolIds, conversationId) {
|
|
2825
2884
|
if (taskType !== "chat" && requestedToolIds === undefined)
|
|
2826
2885
|
return [];
|
|
2827
|
-
const
|
|
2886
|
+
const sourceTools = conversationId && taskType === "chat"
|
|
2887
|
+
? this.store.ensureAiConversationAgentTools(conversationId, workId)
|
|
2888
|
+
: this.store.getWorkAiSettings(workId).agentTools;
|
|
2889
|
+
const enabled = new Set(sourceTools
|
|
2828
2890
|
.filter((item) => typeof item === "string" && AGENT_TOOL_IDS.includes(item)));
|
|
2891
|
+
// 对话锁定只替换「作品当前设置」作为来源;若调用方显式传入 requestedToolIds(含空数组禁用),仍取交集。
|
|
2829
2892
|
const requested = requestedToolIds ? new Set(requestedToolIds) : null;
|
|
2830
2893
|
const permissions = this.store.getWork(workId).modulePermissions;
|
|
2831
2894
|
return AGENT_TOOL_IDS.filter((toolId) => enabled.has(toolId)
|
|
2832
2895
|
&& (!requested || requested.has(toolId))
|
|
2833
2896
|
&& this.canReadWithAgentTool(permissions, toolId));
|
|
2834
2897
|
}
|
|
2835
|
-
enabledAgentTools(workId, taskType, requestedToolIds) {
|
|
2836
|
-
return this.enabledAgentToolIds(workId, taskType, requestedToolIds).map((toolId) => AGENT_TOOL_DEFINITIONS[toolId]);
|
|
2898
|
+
enabledAgentTools(workId, taskType, requestedToolIds, conversationId) {
|
|
2899
|
+
return this.enabledAgentToolIds(workId, taskType, requestedToolIds, conversationId).map((toolId) => AGENT_TOOL_DEFINITIONS[toolId]);
|
|
2837
2900
|
}
|
|
2838
2901
|
canReadWithAgentTool(permissions, toolId) {
|
|
2839
2902
|
if (toolId === "search_story_entities") {
|
|
@@ -2846,7 +2909,7 @@ export class AiManager {
|
|
|
2846
2909
|
.filter(([, module]) => canReadWorkModule(permissions, module))
|
|
2847
2910
|
.map(([category]) => category));
|
|
2848
2911
|
}
|
|
2849
|
-
async executeAgentTool(workId, toolCall, maximumResultChars = AGENT_TOOL_RESULT_MAX_CHARS) {
|
|
2912
|
+
async executeAgentTool(workId, toolCall, maximumResultChars = AGENT_TOOL_RESULT_MAX_CHARS, allowedToolIds) {
|
|
2850
2913
|
const name = toolCall.function.name;
|
|
2851
2914
|
const calledAt = now();
|
|
2852
2915
|
const maximumRecordChars = Math.max(128, Math.min(6_000, maximumResultChars - 500));
|
|
@@ -2877,7 +2940,7 @@ export class AiManager {
|
|
|
2877
2940
|
: name === "search_drafts" ? searchDraftsArguments
|
|
2878
2941
|
: null;
|
|
2879
2942
|
const toolId = AGENT_TOOL_IDS.includes(name) ? name : null;
|
|
2880
|
-
const enabledTools = new Set(this.store.getWorkAiSettings(workId).agentTools
|
|
2943
|
+
const enabledTools = allowedToolIds ?? new Set(this.store.getWorkAiSettings(workId).agentTools
|
|
2881
2944
|
.filter((item) => typeof item === "string" && AGENT_TOOL_IDS.includes(item)));
|
|
2882
2945
|
const permissions = this.store.getWork(workId).modulePermissions;
|
|
2883
2946
|
if (!schema || !toolId || !enabledTools.has(toolId) || !this.canReadWithAgentTool(permissions, toolId)) {
|
|
@@ -3122,6 +3185,30 @@ export class AiManager {
|
|
|
3122
3185
|
max_tokens: Math.min(Number(parameters.max_tokens) || DEFAULT_MAX_TOKENS, contextWindow - inputTokens)
|
|
3123
3186
|
};
|
|
3124
3187
|
}
|
|
3188
|
+
constrainParametersForDailyTokenQuota(workId, messages, parameters, tools = [], additionalUsedTokens = 0) {
|
|
3189
|
+
const status = this.getWorkDailyTokenQuotaStatus(workId);
|
|
3190
|
+
if (status.dailyTokenQuota === null)
|
|
3191
|
+
return parameters;
|
|
3192
|
+
const dailyTokenQuota = Number(status.dailyTokenQuota);
|
|
3193
|
+
const usedTokens = Number(status.usedTokens) + Math.max(0, additionalUsedTokens);
|
|
3194
|
+
const remainingTokens = Math.max(0, dailyTokenQuota - usedTokens);
|
|
3195
|
+
const estimatedInputTokens = estimateAiTokens(JSON.stringify(messages))
|
|
3196
|
+
+ (tools.length > 0 ? estimateAiTokens(JSON.stringify(tools)) : 0);
|
|
3197
|
+
if (remainingTokens <= estimatedInputTokens) {
|
|
3198
|
+
throw new AppError(429, "DAILY_TOKEN_QUOTA_EXCEEDED", `本书今日剩余 Token 额度不足以发起本次请求(已用 ${usedTokens.toLocaleString("zh-CN")} / ${dailyTokenQuota.toLocaleString("zh-CN")})`, {
|
|
3199
|
+
dailyTokenQuota,
|
|
3200
|
+
usedTokens,
|
|
3201
|
+
remainingTokens,
|
|
3202
|
+
estimatedInputTokens,
|
|
3203
|
+
resetsAt: status.resetsAt,
|
|
3204
|
+
timezone: status.timezone
|
|
3205
|
+
});
|
|
3206
|
+
}
|
|
3207
|
+
return {
|
|
3208
|
+
...parameters,
|
|
3209
|
+
max_tokens: Math.min(Number(parameters.max_tokens) || DEFAULT_MAX_TOKENS, remainingTokens - estimatedInputTokens)
|
|
3210
|
+
};
|
|
3211
|
+
}
|
|
3125
3212
|
generateTaggedJson(input) {
|
|
3126
3213
|
const userRequirement = "将最终 JSON 放在唯一一对 <json> 和 </json> 标签中;标签外不要输出任何内容,也不要使用 Markdown 代码块。";
|
|
3127
3214
|
const systemRequirement = "结构化响应要求:最终 JSON 必须且只能放在唯一一对 <json> 和 </json> 标签中。";
|
|
@@ -3141,7 +3228,10 @@ export class AiManager {
|
|
|
3141
3228
|
let effectiveInput = input;
|
|
3142
3229
|
let context = this.buildContext(effectiveInput, model);
|
|
3143
3230
|
let messages = this.buildMessages(effectiveInput, context);
|
|
3144
|
-
|
|
3231
|
+
const allowedToolIds = new Set(this.enabledAgentToolIds(input.workId, input.taskType, input.agentToolIds, input.conversationId));
|
|
3232
|
+
let tools = input.disableTools
|
|
3233
|
+
? []
|
|
3234
|
+
: this.enabledAgentTools(input.workId, input.taskType, input.agentToolIds, input.conversationId);
|
|
3145
3235
|
let parameters;
|
|
3146
3236
|
try {
|
|
3147
3237
|
parameters = this.constrainParametersForContext(model, messages, requestedParameters, tools);
|
|
@@ -3155,6 +3245,7 @@ export class AiManager {
|
|
|
3155
3245
|
context = this.buildContext(effectiveInput, model);
|
|
3156
3246
|
messages = this.buildMessages(effectiveInput, context);
|
|
3157
3247
|
tools = [];
|
|
3248
|
+
allowedToolIds.clear();
|
|
3158
3249
|
try {
|
|
3159
3250
|
parameters = this.constrainParametersForContext(model, messages, requestedParameters);
|
|
3160
3251
|
}
|
|
@@ -3169,6 +3260,7 @@ export class AiManager {
|
|
|
3169
3260
|
modelId: stringValue(model, "id")
|
|
3170
3261
|
});
|
|
3171
3262
|
}
|
|
3263
|
+
parameters = this.constrainParametersForDailyTokenQuota(input.workId, messages, parameters, tools);
|
|
3172
3264
|
const completionMessages = [...messages];
|
|
3173
3265
|
const callId = id("call");
|
|
3174
3266
|
const timestamp = now();
|
|
@@ -3237,7 +3329,7 @@ export class AiManager {
|
|
|
3237
3329
|
const requestParameters = options.parameters ?? parameters;
|
|
3238
3330
|
const purpose = options.purpose ?? "generation";
|
|
3239
3331
|
const requestTools = toolChoice === "auto" ? tools : [];
|
|
3240
|
-
const roundParameters = this.constrainParametersForContext(model, requestMessages, requestParameters, requestTools);
|
|
3332
|
+
const roundParameters = this.constrainParametersForDailyTokenQuota(input.workId, requestMessages, this.constrainParametersForContext(model, requestMessages, requestParameters, requestTools), requestTools, trackedInputTokens + trackedOutputTokens);
|
|
3241
3333
|
const traceRound = {
|
|
3242
3334
|
round: traceRounds.length + 1,
|
|
3243
3335
|
requestedAt: now(),
|
|
@@ -3289,7 +3381,7 @@ export class AiManager {
|
|
|
3289
3381
|
})),
|
|
3290
3382
|
signal: controller.signal
|
|
3291
3383
|
});
|
|
3292
|
-
return { ok: response.ok, status: response.status, body: await response
|
|
3384
|
+
return { ok: response.ok, status: response.status, body: await readResponseTextLimited(response) };
|
|
3293
3385
|
}
|
|
3294
3386
|
finally {
|
|
3295
3387
|
clearTimeout(timeout);
|
|
@@ -3372,6 +3464,14 @@ export class AiManager {
|
|
|
3372
3464
|
let toolContextStartIndex = baseMessageCount;
|
|
3373
3465
|
let compactedToolContextMessage = null;
|
|
3374
3466
|
const contextWindow = numberValue(model, "context_window") || DEFAULT_CONTEXT_WINDOW;
|
|
3467
|
+
const configuredToolCallLimit = Math.min(MAX_CONFIGURED_AGENT_TOOL_CALLS, Math.max(MIN_AGENT_TOOL_CALL_LIMIT, Number(this.store.getWorkAiSettings(input.workId).agentToolCallLimit) || MAX_AGENT_TOOL_CALLS));
|
|
3468
|
+
const agentToolCallLimit = Math.round(clamp(input.agentToolCallLimit ?? configuredToolCallLimit, MIN_AGENT_TOOL_CALL_LIMIT, MAX_CONFIGURED_AGENT_TOOL_CALLS));
|
|
3469
|
+
const agentToolCallGlobalMultiplier = clampAgentToolCallGlobalMultiplier(this.store.getWorkAiSettings(input.workId).agentToolCallGlobalMultiplier ?? DEFAULT_AGENT_TOOL_CALL_GLOBAL_MULTIPLIER);
|
|
3470
|
+
const globalToolCallLimit = agentToolCallGlobalLimit(agentToolCallLimit, agentToolCallGlobalMultiplier);
|
|
3471
|
+
let toolCallQuotaUsed = 0;
|
|
3472
|
+
let globalToolCallUsed = 0;
|
|
3473
|
+
let toolContextCompactCount = 0;
|
|
3474
|
+
// 配额与全局熔断只控制循环是否继续,不得改写 tools 定义、tool_choice 或系统前缀(否则破坏 prompt cache)。
|
|
3375
3475
|
const compactToolContext = async (additionalMessages = [], round = 1) => {
|
|
3376
3476
|
const existingToolContext = completionMessages.slice(toolContextStartIndex);
|
|
3377
3477
|
const sourceMessages = [
|
|
@@ -3420,13 +3520,20 @@ export class AiManager {
|
|
|
3420
3520
|
};
|
|
3421
3521
|
completionMessages.splice(0, completionMessages.length, ...messages.slice(0, compactedMessageIndex), compactedToolContextMessage, ...messages.slice(compactedMessageIndex));
|
|
3422
3522
|
toolContextStartIndex = completionMessages.length;
|
|
3523
|
+
toolCallQuotaUsed = agentToolCallQuotaUsedAfterCompact(agentToolCallLimit);
|
|
3524
|
+
toolContextCompactCount += 1;
|
|
3423
3525
|
const sourceChars = JSON.stringify(sourceMessages).length;
|
|
3424
3526
|
const contextUsage = this.completionContextUsage(effectiveInput, model, completionMessages, tools);
|
|
3425
3527
|
logger.info("ai.tool_context.compacted", {
|
|
3426
3528
|
callId,
|
|
3427
3529
|
sourceMessageCount: sourceMessages.length,
|
|
3428
3530
|
sourceChars,
|
|
3429
|
-
summaryChars: summary.length
|
|
3531
|
+
summaryChars: summary.length,
|
|
3532
|
+
toolCallQuotaUsed,
|
|
3533
|
+
agentToolCallLimit,
|
|
3534
|
+
globalToolCallUsed,
|
|
3535
|
+
globalToolCallLimit,
|
|
3536
|
+
toolContextCompactCount
|
|
3430
3537
|
});
|
|
3431
3538
|
const step = {
|
|
3432
3539
|
id: id("process"),
|
|
@@ -3459,13 +3566,14 @@ export class AiManager {
|
|
|
3459
3566
|
return false;
|
|
3460
3567
|
const currentTokens = estimateAiTokens(JSON.stringify([...completionMessages, assistantMessage]))
|
|
3461
3568
|
+ estimateAiTokens(JSON.stringify(tools));
|
|
3462
|
-
|
|
3569
|
+
// 新工具结果可能附带 toolCallQuotaNotice,预估体积时一并计入,避免低估后触发上下文溢出。
|
|
3570
|
+
const noticeBudgetChars = Math.max(agentToolCallQuotaNoticeBudgetChars(1, agentToolCallLimit), agentToolCallQuotaNoticeBudgetChars(agentToolCallSoftWarningThreshold(agentToolCallLimit), agentToolCallLimit));
|
|
3571
|
+
const maximumNewToolTokens = Math.ceil((AGENT_TOOL_RESULT_MAX_CHARS + noticeBudgetChars) * 1.1) * Math.max(1, toolCallCount);
|
|
3463
3572
|
return currentTokens + maximumNewToolTokens + TOOL_CONTEXT_RESPONSE_RESERVE_TOKENS >= contextWindow;
|
|
3464
3573
|
};
|
|
3465
3574
|
let payload = await requestCompletion("auto");
|
|
3466
3575
|
let choice = payload.choices?.[0];
|
|
3467
3576
|
const executedToolCalls = [];
|
|
3468
|
-
const agentToolCallLimit = Math.round(clamp(input.agentToolCallLimit ?? MAX_AGENT_TOOL_CALLS, 1, MAX_CONFIGURED_AGENT_TOOL_CALLS));
|
|
3469
3577
|
const recordChoiceProcess = (currentChoice, round, includeIntermediate) => {
|
|
3470
3578
|
const reasoning = currentChoice?.message?.reasoning_content;
|
|
3471
3579
|
if (reasoning?.trim()) {
|
|
@@ -3485,7 +3593,21 @@ export class AiManager {
|
|
|
3485
3593
|
const round = toolRound + 1;
|
|
3486
3594
|
recordChoiceProcess(choice, round, true);
|
|
3487
3595
|
const toolCalls = choice.message.tool_calls;
|
|
3488
|
-
if (
|
|
3596
|
+
if (shouldRejectGlobalToolCalls(globalToolCallUsed, toolCalls.length, globalToolCallLimit)) {
|
|
3597
|
+
logger.warn("ai.tool_call.global_limit_reached", {
|
|
3598
|
+
callId,
|
|
3599
|
+
workId: input.workId,
|
|
3600
|
+
agentToolCallLimit,
|
|
3601
|
+
globalLimit: globalToolCallLimit,
|
|
3602
|
+
actualCalls: globalToolCallUsed,
|
|
3603
|
+
requestedCalls: toolCalls.length,
|
|
3604
|
+
compactCount: toolContextCompactCount,
|
|
3605
|
+
turnQuotaUsed: toolCallQuotaUsed,
|
|
3606
|
+
toolsCalled: executedToolCalls.map((item) => item.name)
|
|
3607
|
+
});
|
|
3608
|
+
throw new Error(`AI exceeded the global tool call limit of ${globalToolCallLimit} in one response cycle.`);
|
|
3609
|
+
}
|
|
3610
|
+
if (shouldRejectAgentToolCalls(toolCallQuotaUsed, toolCalls.length, agentToolCallLimit)) {
|
|
3489
3611
|
throw new Error(`AI requested more than ${agentToolCallLimit} tool calls in one response cycle.`);
|
|
3490
3612
|
}
|
|
3491
3613
|
const normalizedToolCalls = toolCalls.map((toolCall) => ({
|
|
@@ -3509,7 +3631,7 @@ export class AiManager {
|
|
|
3509
3631
|
const maximumResultChars = toolResultMaximumChars(assistantToolMessage, toolCalls.length);
|
|
3510
3632
|
const currentRoundMessages = [assistantToolMessage];
|
|
3511
3633
|
for (const toolCall of toolCalls) {
|
|
3512
|
-
const execution = await this.executeAgentTool(input.workId, toolCall, maximumResultChars);
|
|
3634
|
+
const execution = await this.executeAgentTool(input.workId, toolCall, maximumResultChars, allowedToolIds);
|
|
3513
3635
|
logger.info("ai.tool_call.completed", {
|
|
3514
3636
|
callId,
|
|
3515
3637
|
toolName: execution.name,
|
|
@@ -3518,6 +3640,10 @@ export class AiManager {
|
|
|
3518
3640
|
maximumResultChars
|
|
3519
3641
|
});
|
|
3520
3642
|
executedToolCalls.push(execution);
|
|
3643
|
+
toolCallQuotaUsed += 1;
|
|
3644
|
+
globalToolCallUsed += 1;
|
|
3645
|
+
const remainingToolCalls = Math.max(0, agentToolCallLimit - toolCallQuotaUsed);
|
|
3646
|
+
execution.result = withAgentToolCallQuotaNotice(execution.result, remainingToolCalls, agentToolCallLimit);
|
|
3521
3647
|
toolTraceRound?.toolExecutions.push(execution);
|
|
3522
3648
|
saveTrace();
|
|
3523
3649
|
processSteps.push({ id: id("process"), type: "tool", round, toolCall: execution, createdAt: execution.calledAt });
|
|
@@ -3535,18 +3661,8 @@ export class AiManager {
|
|
|
3535
3661
|
await compactToolContext(currentRoundMessages, round);
|
|
3536
3662
|
}
|
|
3537
3663
|
toolRound += 1;
|
|
3538
|
-
|
|
3539
|
-
if (forceFinalAnswer) {
|
|
3540
|
-
completionMessages.push({
|
|
3541
|
-
role: "user",
|
|
3542
|
-
content: "工具调用阶段已经结束,不得再请求任何工具。请立即根据已有工具结果生成最终答案,并严格遵守最初用户消息要求的输出格式。"
|
|
3543
|
-
});
|
|
3544
|
-
}
|
|
3545
|
-
payload = await requestCompletion(forceFinalAnswer ? "none" : "auto");
|
|
3664
|
+
payload = await requestCompletion("auto");
|
|
3546
3665
|
choice = payload.choices?.[0];
|
|
3547
|
-
if (forceFinalAnswer && choice?.message?.tool_calls?.length) {
|
|
3548
|
-
throw new Error(`AI returned tool calls after tool_choice was set to none at the ${MAX_AGENT_TOOL_ROUNDS}-round safety limit.`);
|
|
3549
|
-
}
|
|
3550
3666
|
}
|
|
3551
3667
|
recordChoiceProcess(choice, toolRound + 1, false);
|
|
3552
3668
|
const content = choice?.message?.content;
|
|
@@ -3609,7 +3725,7 @@ export class AiManager {
|
|
|
3609
3725
|
durationMs: Number(process.hrtime.bigint() - callStartedAt) / 1_000_000,
|
|
3610
3726
|
error: aiErrorForLog(error)
|
|
3611
3727
|
});
|
|
3612
|
-
if (error instanceof AppError && error.code === "CONTEXT_WINDOW_EXCEEDED") {
|
|
3728
|
+
if (error instanceof AppError && (error.code === "CONTEXT_WINDOW_EXCEEDED" || error.code === "DAILY_TOKEN_QUOTA_EXCEEDED")) {
|
|
3613
3729
|
throw new AppError(error.status, error.code, error.message, {
|
|
3614
3730
|
callId,
|
|
3615
3731
|
...(error.details && typeof error.details === "object" ? error.details : {}),
|
|
@@ -3636,6 +3752,7 @@ export class AiManager {
|
|
|
3636
3752
|
throw error;
|
|
3637
3753
|
throw initialContextWindowError(error, provider, model);
|
|
3638
3754
|
}
|
|
3755
|
+
parameters = this.constrainParametersForDailyTokenQuota(input.workId, messages, parameters);
|
|
3639
3756
|
const callId = id("call");
|
|
3640
3757
|
this.store.db.run(`INSERT INTO ai_calls (id, work_id, task_type, provider_id, model_id, context_scope_json, parameters_json,
|
|
3641
3758
|
status, input_chars, created_at, created_by_user_id) VALUES (?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?)`, callId, input.workId, input.taskType, stringValue(provider, "id"), stringValue(model, "id"), JSON.stringify(input.scope), JSON.stringify(parameters), context.length + input.instruction.length, now(), currentRequestActor()?.userId ?? null);
|
|
@@ -3689,7 +3806,7 @@ export class AiManager {
|
|
|
3689
3806
|
signal: controller.signal
|
|
3690
3807
|
});
|
|
3691
3808
|
if (!response.ok)
|
|
3692
|
-
return { ok: false, status: response.status, body: await response
|
|
3809
|
+
return { ok: false, status: response.status, body: await readResponseTextLimited(response) };
|
|
3693
3810
|
const streamed = await this.readCompletionStream(response, protocol, estimateAiTokens(JSON.stringify(messages)), apiKey, (delta) => {
|
|
3694
3811
|
emitted = true;
|
|
3695
3812
|
onDelta(delta);
|
|
@@ -3946,8 +4063,16 @@ export class AiManager {
|
|
|
3946
4063
|
appendContent(delta);
|
|
3947
4064
|
}
|
|
3948
4065
|
};
|
|
4066
|
+
let receivedBytes = 0;
|
|
3949
4067
|
while (true) {
|
|
3950
4068
|
const chunk = await reader.read();
|
|
4069
|
+
if (chunk.value?.byteLength) {
|
|
4070
|
+
receivedBytes += chunk.value.byteLength;
|
|
4071
|
+
if (receivedBytes > AI_RESPONSE_MAX_BYTES) {
|
|
4072
|
+
await reader.cancel().catch(() => undefined);
|
|
4073
|
+
throw new AppError(502, "AI_RESPONSE_TOO_LARGE", `AI 供应商响应超过 ${AI_RESPONSE_MAX_BYTES} 字节上限`);
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
3951
4076
|
buffer += decoder.decode(chunk.value, { stream: !chunk.done });
|
|
3952
4077
|
const events = buffer.split(/\r?\n\r?\n/u);
|
|
3953
4078
|
buffer = events.pop() ?? "";
|