@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.
@@ -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":"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"}
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;AAClD,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAC3C,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAC5C,MAAM,CAAC,MAAM,kCAAkC,GAAG,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,yCAAyC,GAAG,CAAC,CAAC;AAC3D,MAAM,CAAC,MAAM,qCAAqC,GAAG,CAAC,CAAC;AACvD,MAAM,CAAC,MAAM,qCAAqC,GAAG,CAAC,CAAC;AAUvD,MAAM,UAAU,iCAAiC,CAAC,KAAa;IAC7D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,kCAAkC,CAAC;IACrF,OAAO,IAAI,CAAC,GAAG,CAAC,kCAAkC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,kCAAkC,CAAC,KAAa;IAC9D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,kCAAkC,CAAC,KAAc;IAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,yCAAyC,CAAC;IAChF,OAAO,IAAI,CAAC,GAAG,CACb,qCAAqC,EACrC,IAAI,CAAC,GAAG,CAAC,qCAAqC,EAAE,OAAO,CAAC,CACzD,CAAC;AACJ,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,wBAAwB,CAAC,KAAa,EAAE,UAAkB;IACxE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9D,MAAM,cAAc,GAAG,kCAAkC,CAAC,UAAU,CAAC,CAAC;IACtE,OAAO,SAAS,GAAG,cAAc,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,UAAkB,EAAE,cAAsB,EAAE,WAAmB;IACzG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IACnH,IAAI,cAAc,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,OAAO,UAAU,GAAG,cAAc,GAAG,WAAW,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,SAAiB,EAAE,KAAa;IAC5E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/D,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;QACpB,OAAO,gFAAgF,CAAC;IAC1F,CAAC;IACD,IAAI,SAAS,IAAI,iCAAiC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1D,OAAO,kCAAkC,SAAS,4BAA4B,CAAC;IACjF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,mCAAmC,CAAC,SAAiB,EAAE,KAAa;IAClF,MAAM,MAAM,GAAG,6BAA6B,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC/D,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IACtB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,yBAAyB,CAAC,EAAE,mBAAmB,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;AACrF,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,MAA+B,EAC/B,SAAiB,EACjB,KAAa;IAEb,MAAM,MAAM,GAAG,6BAA6B,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC/D,IAAI,CAAC,MAAM;QAAE,OAAO,MAAM,CAAC;IAC3B,OAAO,EAAE,GAAG,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAE,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,aAAqB,EAAE,cAAsB,EAAE,KAAa;IACrG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAChH,IAAI,cAAc,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,aAAa,GAAG,cAAc,GAAG,KAAK;QAAE,OAAO,IAAI,CAAC;IACxD,+CAA+C;IAC/C,IAAI,aAAa,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,KAAK,CAAC;AACf,CAAC;AAOD,MAAM,4BAA4B,GAAG,KAAK,CAAC;AAC3C,MAAM,2BAA2B,GAAG,IAAI,GAAG,CAAC;IAC1C,IAAI;IACJ,WAAW;IACX,WAAW;IACX,aAAa;IACb,eAAe;IACf,WAAW;IACX,gBAAgB;IAChB,MAAM;IACN,OAAO;IACP,MAAM;IACN,YAAY;IACZ,WAAW;CACZ,CAAC,CAAC;AAEH,MAAM,UAAU,yBAAyB,CAAC,KAAc;IACtD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;AACtC,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa,EAAE,MAAc,EAAE,YAAoB;IAC7E,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,IAAI,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;IACjC,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5C,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;QACvD,IAAI,yBAAyB,CAAC,SAAS,CAAC,IAAI,YAAY,EAAE,CAAC;YACzD,QAAQ,GAAG,SAAS,CAAC;YACrB,GAAG,GAAG,MAAM,GAAG,CAAC,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IACrC,MAAM,YAAY,GAAG,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9D,MAAM,YAAY,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,YAAY,IAAI,MAAM,IAAI,YAAY,IAAI,MAAM,IAAI,YAAY,IAAI,MAAM,IAAI,YAAY,IAAI,MAAM,EAAE,CAAC;QACzG,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,wBAAwB,CAAC,KAAc,EAAE,YAAoB,EAAE,IAAY;IAClF,IAAI,yBAAyB,CAAC,KAAK,CAAC,IAAI,YAAY;QAAE,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/E,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAyB,EAAE,CAAC;QAC3C,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,OAAO,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;YAC9D,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;YAChG,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;YACzC,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI,MAAM,IAAI,UAAU,GAAG,EAAE,CAAC,CAAC;YAC3E,MAAM,GAAG,UAAU,CAAC;QACtB,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,SAAS,GAAyB,EAAE,CAAC;QAC3C,IAAI,OAAO,GAAc,EAAE,CAAC;QAC5B,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,MAAM,KAAK,GAAG,CAAC,GAAW,EAAQ,EAAE;YAClC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YACjC,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI,YAAY,IAAI,GAAG,GAAG,EAAE,CAAC,CAAC;YAC5E,OAAO,GAAG,EAAE,CAAC;QACf,CAAC,CAAC;QACF,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YACrD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1B,MAAM,SAAS,GAAG,CAAC,GAAG,OAAO,EAAE,IAAI,CAAC,CAAC;YACrC,IAAI,yBAAyB,CAAC,SAAS,CAAC,IAAI,YAAY,EAAE,CAAC;gBACzD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;oBAAE,YAAY,GAAG,KAAK,CAAC;gBAC/C,OAAO,GAAG,SAAS,CAAC;gBACpB,SAAS;YACX,CAAC;YACD,KAAK,CAAC,KAAK,CAAC,CAAC;YACb,IAAI,yBAAyB,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,YAAY,EAAE,CAAC;gBACtD,YAAY,GAAG,KAAK,CAAC;gBACrB,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;gBACjB,SAAS;YACX,CAAC;YACD,MAAM,MAAM,GAAG,wBAAwB,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,GAAG,CAAC,CAAC,EAAE,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC;YACnG,KAAK,MAAM,QAAQ,IAAI,MAAM;gBAAE,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QAClG,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACpB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,SAAS,GAAyB,EAAE,CAAC;QAC3C,IAAI,OAAO,GAA4B,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,GAAS,EAAE;YACvB,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YAC9C,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACzC,OAAO,GAAG,EAAE,CAAC;QACf,CAAC,CAAC;QACF,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAChD,MAAM,SAAS,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;YAC9C,IAAI,yBAAyB,CAAC,SAAS,CAAC,IAAI,YAAY,EAAE,CAAC;gBACzD,OAAO,GAAG,SAAS,CAAC;gBACpB,SAAS;YACX,CAAC;YACD,KAAK,EAAE,CAAC;YACR,IAAI,yBAAyB,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,IAAI,YAAY,EAAE,CAAC;gBAC/D,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;gBAC1B,SAAS;YACX,CAAC;YACD,MAAM,WAAW,GAAG,yBAAyB,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAC;YACjG,MAAM,MAAM,GAAG,wBAAwB,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,GAAG,WAAW,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACvH,KAAK,MAAM,QAAQ,IAAI,MAAM;gBAAE,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3G,CAAC;QACD,KAAK,EAAE,CAAC;QACR,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,OAAkC,EAClC,YAAY,GAAG,4BAA4B;IAE3C,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;QAChC,IAAI,yBAAyB,CAAC,MAAM,CAAC,IAAI,YAAY;YAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QACvE,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,2BAA2B,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACpH,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,2BAA2B,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACpH,MAAM,aAAa,GAAG,yBAAyB,CAAC,QAAQ,CAAC,CAAC;QAC1D,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,GAAG,aAAa,GAAG,GAAG,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,wBAAwB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;QACvE,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE;YACvC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,IAAI,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;gBACpG,CAAC,CAAC,QAAQ,CAAC,KAAgC;gBAC3C,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG;gBACb,GAAG,QAAQ;gBACX,GAAG,OAAO;gBACV,SAAS,EAAE;oBACT,KAAK;oBACL,KAAK,EAAE,SAAS,CAAC,MAAM;oBACvB,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,IAAI;iBAC5B;aACF,CAAC;YACF,IAAI,yBAAyB,CAAC,MAAM,CAAC,GAAG,YAAY,EAAE,CAAC;gBACrD,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAC;YACtF,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,OAAkC,EAClC,MAAc,EACd,WAAgH,EAChH,YAAY,GAAG,2BAA2B;IAE1C,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7E,MAAM,IAAI,GAA8B,EAAE,CAAC;IAC3C,IAAI,SAAS,GAAG,UAAU,CAAC;IAC3B,OAAO,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QAClC,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,SAAS,CAA4B,CAAC,CAAC;QAC/E,MAAM,mBAAmB,GAAG,SAAS,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAClF,MAAM,SAAS,GAAG,WAAW,CAAC,aAAa,EAAE;YAC3C,MAAM,EAAE,UAAU;YAClB,UAAU,EAAE,mBAAmB;YAC/B,QAAQ,EAAE,YAAY;SACvB,CAAC,CAAC;QACH,IAAI,yBAAyB,CAAC,SAAS,CAAC,GAAG,YAAY;YAAE,MAAM;QAC/D,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAA4B,CAAC,CAAC;QACzD,SAAS,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,MAAM,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC;IAC7F,IAAI,yBAAyB,CAAC,MAAM,CAAC,GAAG,YAAY,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
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 this.getTokenUsage(workId, timezoneOffset, false);
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.text();
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.json());
1693
+ payload = JSON.parse(await readResponseTextLimited(response));
1636
1694
  break;
1637
1695
  }
1638
- const message = await response.text();
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 generated = this.enabledAgentTools(input.workId, "chat").length
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 (this.enabledAgentTools(input.workId, "chat").length)
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 enabled = new Set(this.store.getWorkAiSettings(workId).agentTools
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
- let tools = input.disableTools ? [] : this.enabledAgentTools(input.workId, input.taskType, input.agentToolIds);
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.text() };
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
- const maximumNewToolTokens = Math.ceil(AGENT_TOOL_RESULT_MAX_CHARS * 1.1) * Math.max(1, toolCallCount);
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 (executedToolCalls.length + toolCalls.length > agentToolCallLimit) {
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
- const forceFinalAnswer = toolRound >= MAX_AGENT_TOOL_ROUNDS;
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.text() };
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() ?? "";