@musnows/scriverse 0.7.5 → 0.7.7
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.js +88 -13
- package/dist/ai.js.map +1 -1
- package/dist/app.js +44 -45
- package/dist/app.js.map +1 -1
- package/dist/attachment-storage.js +4 -0
- package/dist/attachment-storage.js.map +1 -1
- package/dist/epub-export.js +42 -38
- package/dist/epub-export.js.map +1 -1
- package/dist/public/app.js +150 -50
- package/dist/public/index.html +6 -6
- package/dist/public/outline-board.d.ts +16 -10
- package/dist/public/outline-board.js +11 -105
- package/dist/public/styles.css +2 -2
- package/dist/store.js +239 -63
- package/dist/store.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/zip-stream.js +149 -0
- package/dist/zip-stream.js.map +1 -0
- package/package.json +1 -1
package/dist/ai.js
CHANGED
|
@@ -50,6 +50,9 @@ const AUTO_RUN_MAX_ATTEMPTS = 3;
|
|
|
50
50
|
const AUTO_RUN_RETRY_DELAYS_MS = [5_000, 30_000];
|
|
51
51
|
const AI_INTERACTIVE_TIMEOUT_MS = 60_000;
|
|
52
52
|
const AI_LONG_RUNNING_TIMEOUT_MS = 300_000;
|
|
53
|
+
const FORCE_CONVERSATION_COMPACTION_USAGE_PERCENT = 95;
|
|
54
|
+
const MIN_OUTPUT_RESERVE_TOKENS = 1_024;
|
|
55
|
+
const MIN_CONTEXT_REMAINING_TOKENS = 5_000;
|
|
53
56
|
const analysisTaskTypes = new Set(ANALYSIS_TASK_TYPES);
|
|
54
57
|
const interactiveStreamErrorCodes = new Set([
|
|
55
58
|
"AI_STREAM_IDLE_TIMEOUT",
|
|
@@ -172,6 +175,7 @@ export function autoRunFailureDisposition(error, attemptCount) {
|
|
|
172
175
|
}
|
|
173
176
|
const allowedParameters = new Set(["temperature", "top_p", "max_tokens", "presence_penalty", "frequency_penalty", "seed"]);
|
|
174
177
|
const DEFAULT_MAX_TOKENS = 32_000;
|
|
178
|
+
const MAX_MODEL_OUTPUT_TOKENS = 2_000_000;
|
|
175
179
|
const DEFAULT_CONTEXT_WINDOW = 128_000;
|
|
176
180
|
const RELATIONSHIP_MAX_FUZZY_REFERENCES = 32;
|
|
177
181
|
const RELATIONSHIP_MAX_FUZZY_SOURCES = 200;
|
|
@@ -404,7 +408,7 @@ function sanitizeCompletionTraceResponse(value) {
|
|
|
404
408
|
}
|
|
405
409
|
const MAX_AGENT_TOOL_CALLS = 12;
|
|
406
410
|
const TOOL_CONTEXT_COMPACT_MAX_TOKENS = 1_024;
|
|
407
|
-
const TOOL_CONTEXT_RESPONSE_RESERVE_TOKENS =
|
|
411
|
+
const TOOL_CONTEXT_RESPONSE_RESERVE_TOKENS = MIN_OUTPUT_RESERVE_TOKENS;
|
|
408
412
|
const IMAGE_TOOL_MAX_BYTES = 30 * 1024 * 1024;
|
|
409
413
|
const IMAGE_TOOL_MAX_OUTPUT_TOKENS = 8_192;
|
|
410
414
|
const agentToolCursor = z.number().int().min(0).max(100_000).default(0);
|
|
@@ -737,7 +741,7 @@ function completionPayloadOutputText(payload) {
|
|
|
737
741
|
}
|
|
738
742
|
function normalizeModelPreset(input, modelId = "") {
|
|
739
743
|
const maxTokens = typeof input.max_tokens === "number" && Number.isFinite(input.max_tokens)
|
|
740
|
-
? Math.round(clamp(input.max_tokens, 1,
|
|
744
|
+
? Math.round(clamp(input.max_tokens, 1, MAX_MODEL_OUTPUT_TOKENS))
|
|
741
745
|
: DEFAULT_MAX_TOKENS;
|
|
742
746
|
const temperature = input.temperature;
|
|
743
747
|
const defaultTemperature = isKimiModelId(modelId) && !(typeof temperature === "number" && Number.isFinite(temperature))
|
|
@@ -1950,6 +1954,23 @@ export class AiManager {
|
|
|
1950
1954
|
runningCount: this.store.countRunningTasks(workId)
|
|
1951
1955
|
};
|
|
1952
1956
|
}
|
|
1957
|
+
deleteWork(workId, expectedVersionNo) {
|
|
1958
|
+
const taskIds = this.store.deleteWork(workId, expectedVersionNo);
|
|
1959
|
+
const autoRunTimer = this.autoRunTimers.get(workId);
|
|
1960
|
+
if (autoRunTimer)
|
|
1961
|
+
clearTimeout(autoRunTimer);
|
|
1962
|
+
this.autoRunTimers.delete(workId);
|
|
1963
|
+
this.autoRunStarting.delete(workId);
|
|
1964
|
+
const relationshipIndexTimer = this.relationshipIndexSyncTimers.get(workId);
|
|
1965
|
+
if (relationshipIndexTimer)
|
|
1966
|
+
clearTimeout(relationshipIndexTimer);
|
|
1967
|
+
this.relationshipIndexSyncTimers.delete(workId);
|
|
1968
|
+
for (const taskId of taskIds) {
|
|
1969
|
+
this.taskControllers.get(taskId)?.abort(new Error("作品已移入回收站"));
|
|
1970
|
+
}
|
|
1971
|
+
logger.info("ai.work_tasks.expired", { workId, taskCount: taskIds.length });
|
|
1972
|
+
return taskIds;
|
|
1973
|
+
}
|
|
1953
1974
|
dispose() {
|
|
1954
1975
|
logger.info("ai.manager.disposing", { scheduledWorks: this.autoRunTimers.size, activeTasks: this.taskControllers.size });
|
|
1955
1976
|
if (this.autoRunStartupTimer)
|
|
@@ -3553,7 +3574,7 @@ export class AiManager {
|
|
|
3553
3574
|
const contextWindow = numberValue(model, "context_window") || DEFAULT_CONTEXT_WINDOW;
|
|
3554
3575
|
const preset = safeJsonObject(stringValue(model, "preset_json"));
|
|
3555
3576
|
const configuredOutputTokens = typeof preset.max_tokens === "number" ? preset.max_tokens : DEFAULT_MAX_TOKENS;
|
|
3556
|
-
const outputReserveTokens = Math.max(
|
|
3577
|
+
const outputReserveTokens = Math.max(MIN_OUTPUT_RESERVE_TOKENS, Math.min(configuredOutputTokens, Math.floor(contextWindow * 0.25), contextWindow - MIN_OUTPUT_RESERVE_TOKENS));
|
|
3557
3578
|
const availableInputTokens = Math.max(256, contextWindow - outputReserveTokens - 512);
|
|
3558
3579
|
const conversation = input.conversationId
|
|
3559
3580
|
? this.store.getAiConversationContext(input.conversationId, input.workId, input.excludeConversationMessageId)
|
|
@@ -3572,6 +3593,7 @@ export class AiManager {
|
|
|
3572
3593
|
- functionTokens);
|
|
3573
3594
|
return {
|
|
3574
3595
|
contextWindow,
|
|
3596
|
+
configuredOutputTokens,
|
|
3575
3597
|
outputReserveTokens,
|
|
3576
3598
|
availableInputTokens,
|
|
3577
3599
|
conversation,
|
|
@@ -3601,7 +3623,10 @@ export class AiManager {
|
|
|
3601
3623
|
const threshold = Math.min(90, Math.max(50, Number(this.store.getWorkAiSettings(input.workId).contextCompactThreshold) || 85));
|
|
3602
3624
|
const conversation = budget.conversation;
|
|
3603
3625
|
const conversationUsagePercent = Number(budget.conversationUsagePercent) || 0;
|
|
3626
|
+
const configuredOutputTokens = Number(budget.configuredOutputTokens) || DEFAULT_MAX_TOKENS;
|
|
3627
|
+
const maxOutputUsagePercent = Math.min(100, Math.round(configuredOutputTokens / contextWindow * 100));
|
|
3604
3628
|
const compactableMessageCount = Math.max(0, (conversation?.messages.length ?? 0) - 2);
|
|
3629
|
+
const contextFallbackReached = remainingTokens <= MIN_CONTEXT_REMAINING_TOKENS;
|
|
3605
3630
|
return {
|
|
3606
3631
|
modelId: stringValue(model, "id"),
|
|
3607
3632
|
contextWindow,
|
|
@@ -3610,8 +3635,12 @@ export class AiManager {
|
|
|
3610
3635
|
conversationTokens: Number(budget.conversationTokens),
|
|
3611
3636
|
conversationBudgetTokens: Number(budget.conversationBudgetTokens),
|
|
3612
3637
|
conversationUsagePercent,
|
|
3638
|
+
maxOutputTokens: configuredOutputTokens,
|
|
3639
|
+
maxOutputUsagePercent,
|
|
3640
|
+
maxOutputThresholdReached: maxOutputUsagePercent >= threshold,
|
|
3613
3641
|
outputReserveTokens: Number(budget.outputReserveTokens),
|
|
3614
3642
|
remainingTokens,
|
|
3643
|
+
contextFallbackReached,
|
|
3615
3644
|
usagePercent: Math.min(100, Math.round(inputTokens / contextWindow * 100)),
|
|
3616
3645
|
tokenDistribution: {
|
|
3617
3646
|
systemPromptTokens,
|
|
@@ -3621,7 +3650,8 @@ export class AiManager {
|
|
|
3621
3650
|
leftTokens: remainingTokens
|
|
3622
3651
|
},
|
|
3623
3652
|
compactThreshold: threshold,
|
|
3624
|
-
|
|
3653
|
+
compactableMessageCount,
|
|
3654
|
+
compactRecommended: compactableMessageCount > 0 && (conversationUsagePercent >= threshold || contextFallbackReached),
|
|
3625
3655
|
contextWarningPending: conversation?.warningPending ?? false,
|
|
3626
3656
|
compactedMessageCount: conversation?.compactedMessageCount ?? 0,
|
|
3627
3657
|
includedContextBlocks: contextPlan.includedBlockIds.length,
|
|
@@ -3646,6 +3676,7 @@ export class AiManager {
|
|
|
3646
3676
|
contextWindow,
|
|
3647
3677
|
inputTokens,
|
|
3648
3678
|
remainingTokens,
|
|
3679
|
+
contextFallbackReached: remainingTokens <= MIN_CONTEXT_REMAINING_TOKENS,
|
|
3649
3680
|
usagePercent: Math.min(100, Math.round(inputTokens / contextWindow * 100)),
|
|
3650
3681
|
tokenDistribution: {
|
|
3651
3682
|
systemPromptTokens,
|
|
@@ -3658,14 +3689,21 @@ export class AiManager {
|
|
|
3658
3689
|
}
|
|
3659
3690
|
inspectConversationContext(input) {
|
|
3660
3691
|
const usage = this.getContextUsage({ ...input, taskType: "chat" });
|
|
3661
|
-
const
|
|
3692
|
+
const usagePercent = Number(usage.usagePercent) || 0;
|
|
3693
|
+
const maxOutputThresholdReached = usage.maxOutputThresholdReached === true;
|
|
3694
|
+
const contextFallbackReached = usage.contextFallbackReached === true;
|
|
3695
|
+
const compactableMessageCount = Number(usage.compactableMessageCount) || 0;
|
|
3696
|
+
const outputThresholdNeedsCompaction = (maxOutputThresholdReached || contextFallbackReached) && compactableMessageCount > 0;
|
|
3697
|
+
if (usagePercent >= FORCE_CONVERSATION_COMPACTION_USAGE_PERCENT || outputThresholdNeedsCompaction) {
|
|
3698
|
+
if (compactableMessageCount <= 0) {
|
|
3699
|
+
throw new AppError(409, "AI_CONTEXT_COMPACTION_UNAVAILABLE", "当前请求已占满模型上下文,但没有可压缩的较早对话;请缩短问题、减少引用或新开对话");
|
|
3700
|
+
}
|
|
3701
|
+
return { action: "compact", usage };
|
|
3702
|
+
}
|
|
3662
3703
|
if (!usage.compactRecommended) {
|
|
3663
3704
|
return { action: "ready", usage: { ...usage, contextWarningPending: false } };
|
|
3664
3705
|
}
|
|
3665
|
-
|
|
3666
|
-
return { action: "warn", usage: { ...usage, contextWarningPending: true } };
|
|
3667
|
-
}
|
|
3668
|
-
return { action: "compact", usage };
|
|
3706
|
+
return { action: "warn", usage: { ...usage, contextWarningPending: true } };
|
|
3669
3707
|
}
|
|
3670
3708
|
async prepareConversationContext(input, options = {}) {
|
|
3671
3709
|
const inspection = this.inspectConversationContext(input);
|
|
@@ -3675,13 +3713,41 @@ export class AiManager {
|
|
|
3675
3713
|
this.store.setAiConversationContextWarning(input.conversationId, false);
|
|
3676
3714
|
return inspection;
|
|
3677
3715
|
}
|
|
3678
|
-
if (inspection.action === "warn" && !options.
|
|
3716
|
+
if (inspection.action === "warn" && !options.ignoreWarning) {
|
|
3679
3717
|
this.store.setAiConversationContextWarning(input.conversationId, true);
|
|
3680
3718
|
return inspection;
|
|
3681
3719
|
}
|
|
3720
|
+
if (inspection.action === "warn") {
|
|
3721
|
+
this.store.setAiConversationContextWarning(input.conversationId, false);
|
|
3722
|
+
return {
|
|
3723
|
+
action: "ready",
|
|
3724
|
+
reason: "warning_ignored",
|
|
3725
|
+
usage: { ...inspection.usage, contextWarningPending: false }
|
|
3726
|
+
};
|
|
3727
|
+
}
|
|
3682
3728
|
const compaction = await this.compactConversation(input);
|
|
3729
|
+
if (compaction.changed !== true) {
|
|
3730
|
+
const inputBelowForcedThreshold = (Number(inspection.usage.usagePercent) || 0) < FORCE_CONVERSATION_COMPACTION_USAGE_PERCENT;
|
|
3731
|
+
if ((inspection.usage.maxOutputThresholdReached === true || inspection.usage.contextFallbackReached === true) && inputBelowForcedThreshold) {
|
|
3732
|
+
return {
|
|
3733
|
+
action: "ready",
|
|
3734
|
+
reason: "output_budget_already_fits",
|
|
3735
|
+
usage: { ...inspection.usage, contextWarningPending: false }
|
|
3736
|
+
};
|
|
3737
|
+
}
|
|
3738
|
+
throw new AppError(409, "AI_CONTEXT_COMPACTION_UNAVAILABLE", "当前请求已占满模型上下文,但没有可压缩的较早对话;请缩短问题、减少引用或新开对话");
|
|
3739
|
+
}
|
|
3683
3740
|
const compactedUsage = this.getContextUsage({ ...input, taskType: "chat" });
|
|
3684
|
-
|
|
3741
|
+
if ((Number(compactedUsage.usagePercent) || 0) >= FORCE_CONVERSATION_COMPACTION_USAGE_PERCENT
|
|
3742
|
+
|| compactedUsage.contextFallbackReached === true) {
|
|
3743
|
+
throw new AppError(413, "AI_CONTEXT_STILL_OVER_LIMIT", "自动压缩后当前请求仍占满模型上下文;请缩短问题、减少引用或新开对话");
|
|
3744
|
+
}
|
|
3745
|
+
return {
|
|
3746
|
+
action: "compacted",
|
|
3747
|
+
reason: "forced_usage_threshold",
|
|
3748
|
+
usage: compactedUsage,
|
|
3749
|
+
compaction
|
|
3750
|
+
};
|
|
3685
3751
|
}
|
|
3686
3752
|
/** 解析本轮消息中的自动角色提及;不使用会话累计排除集,也不改写累计注入状态。 */
|
|
3687
3753
|
resolveInstructionMentions(input) {
|
|
@@ -4709,6 +4775,8 @@ export class AiManager {
|
|
|
4709
4775
|
...this.sanitizeParameters({ ...preset, ...(input.parameters ?? {}) }, stringValue(model, "model_id")),
|
|
4710
4776
|
...thinkingParameters(provider, model)
|
|
4711
4777
|
};
|
|
4778
|
+
const configuredOutputTokens = Number(requestedParameters.max_tokens) || DEFAULT_MAX_TOKENS;
|
|
4779
|
+
const contextCompactThreshold = Math.min(90, Math.max(50, Number(this.store.getWorkAiSettings(input.workId).contextCompactThreshold) || 85));
|
|
4712
4780
|
let effectiveInput = input;
|
|
4713
4781
|
let context = this.buildContext(effectiveInput, model);
|
|
4714
4782
|
let messages = this.buildMessages(effectiveInput, context);
|
|
@@ -5125,7 +5193,14 @@ export class AiManager {
|
|
|
5125
5193
|
// 新工具结果可能附带 toolCallQuotaNotice,预估体积时一并计入,避免低估后触发上下文溢出。
|
|
5126
5194
|
const noticeBudgetChars = Math.max(agentToolCallQuotaNoticeBudgetChars(1, agentToolCallLimit), agentToolCallQuotaNoticeBudgetChars(agentToolCallSoftWarningThreshold(agentToolCallLimit), agentToolCallLimit));
|
|
5127
5195
|
const maximumNewToolTokens = Math.ceil((AGENT_TOOL_RESULT_MAX_CHARS + noticeBudgetChars) * 1.1) * Math.max(1, toolCallCount);
|
|
5128
|
-
|
|
5196
|
+
// 这里只按工具结果写入后的 context 剩余判断;输出 max_tokens 由下方独立判断。
|
|
5197
|
+
const projectedContextTokens = currentTokens + maximumNewToolTokens;
|
|
5198
|
+
const projectedUsagePercent = Math.round(projectedContextTokens / contextWindow * 100);
|
|
5199
|
+
const projectedContextRemainingTokens = Math.max(0, contextWindow - projectedContextTokens);
|
|
5200
|
+
const maxOutputThresholdReached = configuredOutputTokens >= contextWindow * contextCompactThreshold / 100;
|
|
5201
|
+
return projectedUsagePercent >= contextCompactThreshold
|
|
5202
|
+
|| maxOutputThresholdReached
|
|
5203
|
+
|| projectedContextRemainingTokens <= MIN_CONTEXT_REMAINING_TOKENS;
|
|
5129
5204
|
};
|
|
5130
5205
|
let payload = await requestCompletion("auto");
|
|
5131
5206
|
let choice = payload.choices?.[0];
|
|
@@ -9145,7 +9220,7 @@ export class AiManager {
|
|
|
9145
9220
|
if (typeof output.top_p === "number")
|
|
9146
9221
|
output.top_p = clamp(output.top_p, 0, 1);
|
|
9147
9222
|
output.max_tokens = typeof output.max_tokens === "number"
|
|
9148
|
-
? Math.round(clamp(output.max_tokens, 1,
|
|
9223
|
+
? Math.round(clamp(output.max_tokens, 1, MAX_MODEL_OUTPUT_TOKENS))
|
|
9149
9224
|
: DEFAULT_MAX_TOKENS;
|
|
9150
9225
|
return output;
|
|
9151
9226
|
}
|