@musnows/scriverse 0.7.6 → 0.7.8
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 +48 -8
- package/dist/ai.js.map +1 -1
- package/dist/app.js +22 -4
- package/dist/app.js.map +1 -1
- package/dist/attachment-storage.js +4 -0
- package/dist/attachment-storage.js.map +1 -1
- package/dist/public/ai-message-meta.js +11 -2
- package/dist/public/app.js +110 -53
- package/dist/public/index.html +5 -5
- package/dist/public/styles.css +14 -9
- package/dist/store.js +24 -0
- package/dist/store.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/ai.js
CHANGED
|
@@ -51,6 +51,8 @@ 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
53
|
const FORCE_CONVERSATION_COMPACTION_USAGE_PERCENT = 95;
|
|
54
|
+
const MIN_OUTPUT_RESERVE_TOKENS = 1_024;
|
|
55
|
+
const MIN_CONTEXT_REMAINING_TOKENS = 5_000;
|
|
54
56
|
const analysisTaskTypes = new Set(ANALYSIS_TASK_TYPES);
|
|
55
57
|
const interactiveStreamErrorCodes = new Set([
|
|
56
58
|
"AI_STREAM_IDLE_TIMEOUT",
|
|
@@ -173,6 +175,7 @@ export function autoRunFailureDisposition(error, attemptCount) {
|
|
|
173
175
|
}
|
|
174
176
|
const allowedParameters = new Set(["temperature", "top_p", "max_tokens", "presence_penalty", "frequency_penalty", "seed"]);
|
|
175
177
|
const DEFAULT_MAX_TOKENS = 32_000;
|
|
178
|
+
const MAX_MODEL_OUTPUT_TOKENS = 2_000_000;
|
|
176
179
|
const DEFAULT_CONTEXT_WINDOW = 128_000;
|
|
177
180
|
const RELATIONSHIP_MAX_FUZZY_REFERENCES = 32;
|
|
178
181
|
const RELATIONSHIP_MAX_FUZZY_SOURCES = 200;
|
|
@@ -405,7 +408,7 @@ function sanitizeCompletionTraceResponse(value) {
|
|
|
405
408
|
}
|
|
406
409
|
const MAX_AGENT_TOOL_CALLS = 12;
|
|
407
410
|
const TOOL_CONTEXT_COMPACT_MAX_TOKENS = 1_024;
|
|
408
|
-
const TOOL_CONTEXT_RESPONSE_RESERVE_TOKENS =
|
|
411
|
+
const TOOL_CONTEXT_RESPONSE_RESERVE_TOKENS = MIN_OUTPUT_RESERVE_TOKENS;
|
|
409
412
|
const IMAGE_TOOL_MAX_BYTES = 30 * 1024 * 1024;
|
|
410
413
|
const IMAGE_TOOL_MAX_OUTPUT_TOKENS = 8_192;
|
|
411
414
|
const agentToolCursor = z.number().int().min(0).max(100_000).default(0);
|
|
@@ -738,7 +741,7 @@ function completionPayloadOutputText(payload) {
|
|
|
738
741
|
}
|
|
739
742
|
function normalizeModelPreset(input, modelId = "") {
|
|
740
743
|
const maxTokens = typeof input.max_tokens === "number" && Number.isFinite(input.max_tokens)
|
|
741
|
-
? Math.round(clamp(input.max_tokens, 1,
|
|
744
|
+
? Math.round(clamp(input.max_tokens, 1, MAX_MODEL_OUTPUT_TOKENS))
|
|
742
745
|
: DEFAULT_MAX_TOKENS;
|
|
743
746
|
const temperature = input.temperature;
|
|
744
747
|
const defaultTemperature = isKimiModelId(modelId) && !(typeof temperature === "number" && Number.isFinite(temperature))
|
|
@@ -3081,7 +3084,9 @@ export class AiManager {
|
|
|
3081
3084
|
const effectiveInput = input.taskType === "continue"
|
|
3082
3085
|
? { ...input, scope: this.enrichContinuationScope(input.workId, input.scope, input.instruction) }
|
|
3083
3086
|
: input;
|
|
3087
|
+
const processStartedAt = process.hrtime.bigint();
|
|
3084
3088
|
const generated = await this.generate(effectiveInput);
|
|
3089
|
+
const processDurationMs = Math.min(86_400_000, Math.max(0, Math.round(Number(process.hrtime.bigint() - processStartedAt) / 1_000_000)));
|
|
3085
3090
|
const chapter = effectiveInput.scope.chapterId ? this.store.getChapter(effectiveInput.scope.chapterId) : null;
|
|
3086
3091
|
const suggestionId = id("suggestion");
|
|
3087
3092
|
this.store.db.run(`INSERT INTO ai_suggestions (id, call_id, work_id, chapter_id, chapter_version, task_type, instruction,
|
|
@@ -3091,6 +3096,7 @@ export class AiManager {
|
|
|
3091
3096
|
return {
|
|
3092
3097
|
...this.getSuggestion(suggestionId),
|
|
3093
3098
|
outputTokens: generated.outputTokens,
|
|
3099
|
+
processDurationMs,
|
|
3094
3100
|
...(generated.cacheHitPercent === undefined ? {} : { cacheHitPercent: generated.cacheHitPercent }),
|
|
3095
3101
|
toolCalls: generated.toolCalls,
|
|
3096
3102
|
processSteps: generated.processSteps,
|
|
@@ -3112,7 +3118,9 @@ export class AiManager {
|
|
|
3112
3118
|
&& firstUserContent
|
|
3113
3119
|
&& titleModelId
|
|
3114
3120
|
&& (conversationBefore?.title === "新对话" || conversationBefore?.title === defaultTitle));
|
|
3121
|
+
const processStartedAt = process.hrtime.bigint();
|
|
3115
3122
|
const generated = await this.generate({ ...input, taskType: "chat" }, onDelta);
|
|
3123
|
+
const processDurationMs = Math.min(86_400_000, Math.max(0, Math.round(Number(process.hrtime.bigint() - processStartedAt) / 1_000_000)));
|
|
3116
3124
|
const chapter = input.scope.chapterId ? this.store.getChapter(input.scope.chapterId) : null;
|
|
3117
3125
|
const suggestionId = id("suggestion");
|
|
3118
3126
|
this.store.db.run(`INSERT INTO ai_suggestions (id, call_id, work_id, chapter_id, chapter_version, task_type, instruction,
|
|
@@ -3126,6 +3134,7 @@ export class AiManager {
|
|
|
3126
3134
|
metadata: {
|
|
3127
3135
|
...(modelDisplayName ? { modelDisplayName } : {}),
|
|
3128
3136
|
outputTokens: generated.outputTokens,
|
|
3137
|
+
processDurationMs,
|
|
3129
3138
|
...(generated.reasoningContent === undefined ? {} : { reasoningContent: generated.reasoningContent }),
|
|
3130
3139
|
...(generated.cacheHitPercent === undefined ? {} : { cacheHitPercent: generated.cacheHitPercent }),
|
|
3131
3140
|
toolCalls: generated.toolCalls,
|
|
@@ -3142,6 +3151,7 @@ export class AiManager {
|
|
|
3142
3151
|
return {
|
|
3143
3152
|
...this.getSuggestion(suggestionId),
|
|
3144
3153
|
outputTokens: generated.outputTokens,
|
|
3154
|
+
processDurationMs,
|
|
3145
3155
|
...(generated.cacheHitPercent === undefined ? {} : { cacheHitPercent: generated.cacheHitPercent }),
|
|
3146
3156
|
toolCalls: generated.toolCalls,
|
|
3147
3157
|
processSteps: generated.processSteps,
|
|
@@ -3571,7 +3581,7 @@ export class AiManager {
|
|
|
3571
3581
|
const contextWindow = numberValue(model, "context_window") || DEFAULT_CONTEXT_WINDOW;
|
|
3572
3582
|
const preset = safeJsonObject(stringValue(model, "preset_json"));
|
|
3573
3583
|
const configuredOutputTokens = typeof preset.max_tokens === "number" ? preset.max_tokens : DEFAULT_MAX_TOKENS;
|
|
3574
|
-
const outputReserveTokens = Math.max(
|
|
3584
|
+
const outputReserveTokens = Math.max(MIN_OUTPUT_RESERVE_TOKENS, Math.min(configuredOutputTokens, Math.floor(contextWindow * 0.25), contextWindow - MIN_OUTPUT_RESERVE_TOKENS));
|
|
3575
3585
|
const availableInputTokens = Math.max(256, contextWindow - outputReserveTokens - 512);
|
|
3576
3586
|
const conversation = input.conversationId
|
|
3577
3587
|
? this.store.getAiConversationContext(input.conversationId, input.workId, input.excludeConversationMessageId)
|
|
@@ -3590,6 +3600,7 @@ export class AiManager {
|
|
|
3590
3600
|
- functionTokens);
|
|
3591
3601
|
return {
|
|
3592
3602
|
contextWindow,
|
|
3603
|
+
configuredOutputTokens,
|
|
3593
3604
|
outputReserveTokens,
|
|
3594
3605
|
availableInputTokens,
|
|
3595
3606
|
conversation,
|
|
@@ -3619,7 +3630,10 @@ export class AiManager {
|
|
|
3619
3630
|
const threshold = Math.min(90, Math.max(50, Number(this.store.getWorkAiSettings(input.workId).contextCompactThreshold) || 85));
|
|
3620
3631
|
const conversation = budget.conversation;
|
|
3621
3632
|
const conversationUsagePercent = Number(budget.conversationUsagePercent) || 0;
|
|
3633
|
+
const configuredOutputTokens = Number(budget.configuredOutputTokens) || DEFAULT_MAX_TOKENS;
|
|
3634
|
+
const maxOutputUsagePercent = Math.min(100, Math.round(configuredOutputTokens / contextWindow * 100));
|
|
3622
3635
|
const compactableMessageCount = Math.max(0, (conversation?.messages.length ?? 0) - 2);
|
|
3636
|
+
const contextFallbackReached = remainingTokens <= MIN_CONTEXT_REMAINING_TOKENS;
|
|
3623
3637
|
return {
|
|
3624
3638
|
modelId: stringValue(model, "id"),
|
|
3625
3639
|
contextWindow,
|
|
@@ -3628,8 +3642,12 @@ export class AiManager {
|
|
|
3628
3642
|
conversationTokens: Number(budget.conversationTokens),
|
|
3629
3643
|
conversationBudgetTokens: Number(budget.conversationBudgetTokens),
|
|
3630
3644
|
conversationUsagePercent,
|
|
3645
|
+
maxOutputTokens: configuredOutputTokens,
|
|
3646
|
+
maxOutputUsagePercent,
|
|
3647
|
+
maxOutputThresholdReached: maxOutputUsagePercent >= threshold,
|
|
3631
3648
|
outputReserveTokens: Number(budget.outputReserveTokens),
|
|
3632
3649
|
remainingTokens,
|
|
3650
|
+
contextFallbackReached,
|
|
3633
3651
|
usagePercent: Math.min(100, Math.round(inputTokens / contextWindow * 100)),
|
|
3634
3652
|
tokenDistribution: {
|
|
3635
3653
|
systemPromptTokens,
|
|
@@ -3640,7 +3658,7 @@ export class AiManager {
|
|
|
3640
3658
|
},
|
|
3641
3659
|
compactThreshold: threshold,
|
|
3642
3660
|
compactableMessageCount,
|
|
3643
|
-
compactRecommended: compactableMessageCount > 0 && conversationUsagePercent >= threshold,
|
|
3661
|
+
compactRecommended: compactableMessageCount > 0 && (conversationUsagePercent >= threshold || contextFallbackReached),
|
|
3644
3662
|
contextWarningPending: conversation?.warningPending ?? false,
|
|
3645
3663
|
compactedMessageCount: conversation?.compactedMessageCount ?? 0,
|
|
3646
3664
|
includedContextBlocks: contextPlan.includedBlockIds.length,
|
|
@@ -3665,6 +3683,7 @@ export class AiManager {
|
|
|
3665
3683
|
contextWindow,
|
|
3666
3684
|
inputTokens,
|
|
3667
3685
|
remainingTokens,
|
|
3686
|
+
contextFallbackReached: remainingTokens <= MIN_CONTEXT_REMAINING_TOKENS,
|
|
3668
3687
|
usagePercent: Math.min(100, Math.round(inputTokens / contextWindow * 100)),
|
|
3669
3688
|
tokenDistribution: {
|
|
3670
3689
|
systemPromptTokens,
|
|
@@ -3678,8 +3697,11 @@ export class AiManager {
|
|
|
3678
3697
|
inspectConversationContext(input) {
|
|
3679
3698
|
const usage = this.getContextUsage({ ...input, taskType: "chat" });
|
|
3680
3699
|
const usagePercent = Number(usage.usagePercent) || 0;
|
|
3700
|
+
const maxOutputThresholdReached = usage.maxOutputThresholdReached === true;
|
|
3701
|
+
const contextFallbackReached = usage.contextFallbackReached === true;
|
|
3681
3702
|
const compactableMessageCount = Number(usage.compactableMessageCount) || 0;
|
|
3682
|
-
|
|
3703
|
+
const outputThresholdNeedsCompaction = (maxOutputThresholdReached || contextFallbackReached) && compactableMessageCount > 0;
|
|
3704
|
+
if (usagePercent >= FORCE_CONVERSATION_COMPACTION_USAGE_PERCENT || outputThresholdNeedsCompaction) {
|
|
3683
3705
|
if (compactableMessageCount <= 0) {
|
|
3684
3706
|
throw new AppError(409, "AI_CONTEXT_COMPACTION_UNAVAILABLE", "当前请求已占满模型上下文,但没有可压缩的较早对话;请缩短问题、减少引用或新开对话");
|
|
3685
3707
|
}
|
|
@@ -3712,10 +3734,19 @@ export class AiManager {
|
|
|
3712
3734
|
}
|
|
3713
3735
|
const compaction = await this.compactConversation(input);
|
|
3714
3736
|
if (compaction.changed !== true) {
|
|
3737
|
+
const inputBelowForcedThreshold = (Number(inspection.usage.usagePercent) || 0) < FORCE_CONVERSATION_COMPACTION_USAGE_PERCENT;
|
|
3738
|
+
if ((inspection.usage.maxOutputThresholdReached === true || inspection.usage.contextFallbackReached === true) && inputBelowForcedThreshold) {
|
|
3739
|
+
return {
|
|
3740
|
+
action: "ready",
|
|
3741
|
+
reason: "output_budget_already_fits",
|
|
3742
|
+
usage: { ...inspection.usage, contextWarningPending: false }
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3715
3745
|
throw new AppError(409, "AI_CONTEXT_COMPACTION_UNAVAILABLE", "当前请求已占满模型上下文,但没有可压缩的较早对话;请缩短问题、减少引用或新开对话");
|
|
3716
3746
|
}
|
|
3717
3747
|
const compactedUsage = this.getContextUsage({ ...input, taskType: "chat" });
|
|
3718
|
-
if ((Number(compactedUsage.usagePercent) || 0) >= FORCE_CONVERSATION_COMPACTION_USAGE_PERCENT
|
|
3748
|
+
if ((Number(compactedUsage.usagePercent) || 0) >= FORCE_CONVERSATION_COMPACTION_USAGE_PERCENT
|
|
3749
|
+
|| compactedUsage.contextFallbackReached === true) {
|
|
3719
3750
|
throw new AppError(413, "AI_CONTEXT_STILL_OVER_LIMIT", "自动压缩后当前请求仍占满模型上下文;请缩短问题、减少引用或新开对话");
|
|
3720
3751
|
}
|
|
3721
3752
|
return {
|
|
@@ -4751,6 +4782,8 @@ export class AiManager {
|
|
|
4751
4782
|
...this.sanitizeParameters({ ...preset, ...(input.parameters ?? {}) }, stringValue(model, "model_id")),
|
|
4752
4783
|
...thinkingParameters(provider, model)
|
|
4753
4784
|
};
|
|
4785
|
+
const configuredOutputTokens = Number(requestedParameters.max_tokens) || DEFAULT_MAX_TOKENS;
|
|
4786
|
+
const contextCompactThreshold = Math.min(90, Math.max(50, Number(this.store.getWorkAiSettings(input.workId).contextCompactThreshold) || 85));
|
|
4754
4787
|
let effectiveInput = input;
|
|
4755
4788
|
let context = this.buildContext(effectiveInput, model);
|
|
4756
4789
|
let messages = this.buildMessages(effectiveInput, context);
|
|
@@ -5167,7 +5200,14 @@ export class AiManager {
|
|
|
5167
5200
|
// 新工具结果可能附带 toolCallQuotaNotice,预估体积时一并计入,避免低估后触发上下文溢出。
|
|
5168
5201
|
const noticeBudgetChars = Math.max(agentToolCallQuotaNoticeBudgetChars(1, agentToolCallLimit), agentToolCallQuotaNoticeBudgetChars(agentToolCallSoftWarningThreshold(agentToolCallLimit), agentToolCallLimit));
|
|
5169
5202
|
const maximumNewToolTokens = Math.ceil((AGENT_TOOL_RESULT_MAX_CHARS + noticeBudgetChars) * 1.1) * Math.max(1, toolCallCount);
|
|
5170
|
-
|
|
5203
|
+
// 这里只按工具结果写入后的 context 剩余判断;输出 max_tokens 由下方独立判断。
|
|
5204
|
+
const projectedContextTokens = currentTokens + maximumNewToolTokens;
|
|
5205
|
+
const projectedUsagePercent = Math.round(projectedContextTokens / contextWindow * 100);
|
|
5206
|
+
const projectedContextRemainingTokens = Math.max(0, contextWindow - projectedContextTokens);
|
|
5207
|
+
const maxOutputThresholdReached = configuredOutputTokens >= contextWindow * contextCompactThreshold / 100;
|
|
5208
|
+
return projectedUsagePercent >= contextCompactThreshold
|
|
5209
|
+
|| maxOutputThresholdReached
|
|
5210
|
+
|| projectedContextRemainingTokens <= MIN_CONTEXT_REMAINING_TOKENS;
|
|
5171
5211
|
};
|
|
5172
5212
|
let payload = await requestCompletion("auto");
|
|
5173
5213
|
let choice = payload.choices?.[0];
|
|
@@ -9187,7 +9227,7 @@ export class AiManager {
|
|
|
9187
9227
|
if (typeof output.top_p === "number")
|
|
9188
9228
|
output.top_p = clamp(output.top_p, 0, 1);
|
|
9189
9229
|
output.max_tokens = typeof output.max_tokens === "number"
|
|
9190
|
-
? Math.round(clamp(output.max_tokens, 1,
|
|
9230
|
+
? Math.round(clamp(output.max_tokens, 1, MAX_MODEL_OUTPUT_TOKENS))
|
|
9191
9231
|
: DEFAULT_MAX_TOKENS;
|
|
9192
9232
|
return output;
|
|
9193
9233
|
}
|