zigmini 0.8.0-beta.5 → 0.8.0-beta.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/README.ko.md +78 -19
- package/README.md +79 -19
- package/assets/agent/gateway-request-rules.md +1 -0
- package/assets/agent/gateway-rules.md +6 -0
- package/assets/android/zigmini.apk +0 -0
- package/assets/android/zigmini.apk.json +3 -3
- package/assets/build.json +1 -1
- package/docs/manual.ko.md +25 -2
- package/docs/manual.md +25 -2
- package/host/omo-bridge/task-control.ts +4 -0
- package/host/omo-check/patches/omo-5.1.35-compaction-idle.patch +77 -0
- package/host/omo-check/patches/omo-5.1.35-compaction-intent.patch +212 -0
- package/host/omo-check/patches/omo-5.1.35-compaction-model.patch +299 -0
- package/host/omo-check/patches/omo-5.1.35-compaction-preservation.patch +1063 -0
- package/host/omo-check/patches/omo-5.1.35-compaction-refusal.patch +83 -0
- package/host/omo-check/patches/omo-5.1.35-gateway-port.manifest.json +171 -8
- package/host/omo-check/patches/omo-5.1.35-phantom-attach.patch +31 -0
- package/host/omo-check/patches/omo-5.1.35-request-rules.patch +109 -0
- package/host/omo-check/patches/omo-5.1.35-turn-stats.patch +76 -0
- package/package.json +4 -1
- package/scripts/secret-rules.ts +101 -0
- package/scripts/secret-scan.allow.json +155 -0
- package/scripts/secret-scan.rules.json +30 -0
- package/src/agent-request-rules.ts +82 -0
- package/src/cli/access.ts +31 -34
- package/src/cli/launchd.ts +30 -4
- package/src/cli/main.ts +13 -39
- package/src/cli/messages.ts +160 -0
- package/src/cli/pair.ts +32 -25
- package/src/cli/rotate-main.ts +411 -0
- package/src/cli/setup.ts +164 -75
- package/src/decisions.ts +20 -1
- package/src/hub.ts +140 -30
- package/src/main.ts +45 -4
- package/src/omo/client.ts +28 -0
- package/src/protocol.ts +33 -1
- package/src/reply-notifications.ts +144 -0
- package/src/runtime/paths.ts +2 -0
- package/src/security-check.ts +241 -0
- package/src/store.ts +71 -1
- package/src/turn-stats.ts +49 -0
- package/src/work-watch.ts +36 -10
- package/src/work.ts +29 -4
- package/web/app.js +197 -10
- package/web/decisions.js +3 -0
- package/web/gc.d.ts +2 -0
- package/web/gc.js +11 -0
- package/web/index.html +26 -10
- package/web/notifications.d.ts +25 -0
- package/web/notifications.js +118 -0
- package/web/style.css +16 -0
- package/web/tooltip.d.ts +0 -6
- package/web/tooltip.js +8 -11
- package/web/turn-stats.d.ts +26 -0
- package/web/turn-stats.js +58 -0
- package/web/update.d.ts +1 -1
- package/web/update.js +10 -3
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/index.js
|
|
2
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/index.js
|
|
3
|
+
@@ -138,1 +138,1 @@
|
|
4
|
+
- const result = await applyGeneratedCompaction(ctx, job.snapshot, () => speculativeGeneration, compaction);
|
|
5
|
+
+ const result = await applyGeneratedCompaction(ctx, job.snapshot, () => speculativeGeneration, compaction, undefined, false);
|
|
6
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative.js
|
|
7
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative.js
|
|
8
|
+
@@ -303,1 +303,1 @@
|
|
9
|
+
-export async function applyGeneratedCompaction(context, snapshot, getCurrentGeneration, compaction, signal) {
|
|
10
|
+
+export async function applyGeneratedCompaction(context, snapshot, getCurrentGeneration, compaction, signal, blocking = true) {
|
|
11
|
+
@@ -319,3 +319,4 @@
|
|
12
|
+
return await context.applyCompaction(compaction, {
|
|
13
|
+
reason: "extension",
|
|
14
|
+
+ blocking,
|
|
15
|
+
...(revisionUnchanged || !warmAnchor
|
|
16
|
+
--- a/@code-yeongyu/senpi/dist/core/agent-session.js
|
|
17
|
+
+++ b/@code-yeongyu/senpi/dist/core/agent-session.js
|
|
18
|
+
@@ -6195,4 +6195,5 @@
|
|
19
|
+
this._emit({
|
|
20
|
+
type: "compaction_start",
|
|
21
|
+
reason: options.reason,
|
|
22
|
+
+ blocking: options.blocking,
|
|
23
|
+
requestId,
|
|
24
|
+
@@ -6208,4 +6209,5 @@
|
|
25
|
+
owner: "compaction",
|
|
26
|
+
reason: options.reason,
|
|
27
|
+
+ blocking: options.blocking,
|
|
28
|
+
requestId,
|
|
29
|
+
willRetry: false,
|
|
30
|
+
@@ -6227,4 +6229,5 @@
|
|
31
|
+
this._emit({
|
|
32
|
+
type: "compaction_end",
|
|
33
|
+
reason: options.reason,
|
|
34
|
+
+ blocking: options.blocking,
|
|
35
|
+
result: undefined,
|
|
36
|
+
@@ -6479,4 +6482,5 @@
|
|
37
|
+
this._emit({
|
|
38
|
+
type: "compaction_end",
|
|
39
|
+
reason: request.reason,
|
|
40
|
+
+ blocking: request.blocking,
|
|
41
|
+
result: compactionResult,
|
|
42
|
+
@@ -6591,4 +6595,5 @@
|
|
43
|
+
this._emit({
|
|
44
|
+
type: "compaction_end",
|
|
45
|
+
reason: request.reason,
|
|
46
|
+
+ blocking: request.blocking,
|
|
47
|
+
result: undefined,
|
|
48
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/types.d.ts
|
|
49
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/types.d.ts
|
|
50
|
+
@@ -297,3 +297,4 @@
|
|
51
|
+
export interface ApplyCompactionOptions {
|
|
52
|
+
reason: CompactionReason;
|
|
53
|
+
+ blocking?: boolean;
|
|
54
|
+
expectedRevision?: number;
|
|
55
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative.d.ts
|
|
56
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative.d.ts
|
|
57
|
+
@@ -22,3 +22,4 @@
|
|
58
|
+
applyCompaction(precomputed: CompactionResult, options: {
|
|
59
|
+
reason: "extension";
|
|
60
|
+
+ blocking?: boolean;
|
|
61
|
+
expectedRevision?: number;
|
|
62
|
+
@@ -110,1 +111,1 @@
|
|
63
|
+
-export declare function applyGeneratedCompaction(context: SpeculativeCompactionContext, snapshot: SpeculativeCompactionSnapshot | undefined, getCurrentGeneration: () => number, compaction: CompactionResult | undefined, signal?: AbortSignal): Promise<SpeculativeCompactionResult>;
|
|
64
|
+
+export declare function applyGeneratedCompaction(context: SpeculativeCompactionContext, snapshot: SpeculativeCompactionSnapshot | undefined, getCurrentGeneration: () => number, compaction: CompactionResult | undefined, signal?: AbortSignal, blocking?: boolean): Promise<SpeculativeCompactionResult>;
|
|
65
|
+
--- a/@code-yeongyu/senpi/dist/core/agent-session.d.ts
|
|
66
|
+
+++ b/@code-yeongyu/senpi/dist/core/agent-session.d.ts
|
|
67
|
+
@@ -140,3 +140,4 @@
|
|
68
|
+
type: "compaction_start";
|
|
69
|
+
reason: CompactionReason;
|
|
70
|
+
+ blocking?: boolean;
|
|
71
|
+
requestId?: string;
|
|
72
|
+
@@ -229,4 +230,5 @@
|
|
73
|
+
type: "compaction_end";
|
|
74
|
+
failure?: CompactionFailure;
|
|
75
|
+
reason: CompactionReason;
|
|
76
|
+
+ blocking?: boolean;
|
|
77
|
+
result: CompactionResult | undefined;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative.js
|
|
2
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative.js
|
|
3
|
+
@@ -13,1 +13,1 @@
|
|
4
|
+
-import { extractTaskIntent, resolveInheritedTaskIntent } from "./task-intent.js";
|
|
5
|
+
+import { extractTaskIntent, resolveInheritedTaskIntent, taskIntentSummary, taskIntentMetadata } from "./task-intent.js";
|
|
6
|
+
@@ -268,1 +268,5 @@
|
|
7
|
+
- const summary = parsedSummary.summaryText;
|
|
8
|
+
+ const inheritedIntent = resolveInheritedTaskIntent(snapshot.branchEntries ?? []);
|
|
9
|
+
+ if (inheritedIntent && parsedSummary.taskIntent && parsedSummary.taskIntent !== inheritedIntent)
|
|
10
|
+
+ throw new SummaryGenerationError("incomplete", "Compaction changed protected task intent");
|
|
11
|
+
+ const taskIntent = inheritedIntent ?? parsedSummary.taskIntent;
|
|
12
|
+
+ const summary = taskIntentSummary(parsedSummary.summaryText, taskIntent);
|
|
13
|
+
@@ -273,1 +277,0 @@
|
|
14
|
+
- const taskIntent = parsedSummary.taskIntent ?? resolveInheritedTaskIntent(snapshot.branchEntries ?? []);
|
|
15
|
+
@@ -275,1 +278,1 @@
|
|
16
|
+
- summary: parsedSummary.summaryText,
|
|
17
|
+
+ summary,
|
|
18
|
+
@@ -291,1 +294,1 @@
|
|
19
|
+
- summary: parsedSummary.summaryText,
|
|
20
|
+
+ summary,
|
|
21
|
+
@@ -295,1 +298,1 @@
|
|
22
|
+
- ...(taskIntent ? { taskIntent } : {}),
|
|
23
|
+
+ ...(taskIntent ? { taskIntent: taskIntentMetadata(taskIntent) } : {}),
|
|
24
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/openai-remote.js
|
|
25
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/openai-remote.js
|
|
26
|
+
@@ -4,0 +4,3 @@
|
|
27
|
+
+import { collectIntentProtection, protectedRemoteInput, taskIntentSummary, retainedUserText } from "./task-intent.js";
|
|
28
|
+
+import { estimateTokens } from "../../../compaction/index.js";
|
|
29
|
+
+import { resolveEffectiveReserveTokens } from "./policy.js";
|
|
30
|
+
@@ -23,1 +26,1 @@
|
|
31
|
+
- const input = options.messages
|
|
32
|
+
+ const converted = options.messages
|
|
33
|
+
@@ -26,0 +29,1 @@
|
|
34
|
+
+ const input = protectedRemoteInput(converted, collectIntentProtection(options.branchEntries));
|
|
35
|
+
@@ -55,0 +59,1 @@
|
|
36
|
+
+ ...(typeof options.value.model === "string" ? { observedModel: options.value.model } : {}),
|
|
37
|
+
@@ -71,0 +76,1 @@
|
|
38
|
+
+ ...(options.response.observedModel ? { observedProvider: options.model.provider, observedModel: options.response.observedModel } : {}),
|
|
39
|
+
@@ -140,0 +146,1 @@
|
|
40
|
+
+ ...(options.response.responseModel ? { observedProvider: options.response.provider, observedModel: options.response.responseModel } : {}),
|
|
41
|
+
@@ -177,0 +184,1 @@
|
|
42
|
+
+ streamKind: "auxiliary",
|
|
43
|
+
@@ -334,0 +342,22 @@
|
|
44
|
+
+ const protection = collectIntentProtection(event.branchEntries);
|
|
45
|
+
+ const protectedInput = protectedRemoteInput([], protection);
|
|
46
|
+
+ const model = ctx.model;
|
|
47
|
+
+ const settings = ctx.getCompactionSettings?.() ?? event.preparation.settings;
|
|
48
|
+
+ const protectionTokens = estimateTokens({ role: "user", content: JSON.stringify(protectedInput) });
|
|
49
|
+
+ if (model && protectionTokens > model.contextWindow - resolveEffectiveReserveTokens(model.contextWindow, settings))
|
|
50
|
+
+ return { status: "ineligible" }; // Before dispatch only: same-model text compaction is permitted.
|
|
51
|
+
+ const attempt = await runOpenAiRemoteCompactionAttempt(ctx, event, emit, dependencies);
|
|
52
|
+
+ if (attempt.status !== "accepted") return attempt;
|
|
53
|
+
+ const result = attempt.compaction;
|
|
54
|
+
+ const checkpointTexts = new Set(convertPendingMessages(buildSessionContext(event.branchEntries).messages.filter((message) => message.role === "compactionSummary"), model).map(retainedUserText));
|
|
55
|
+
+ // The accepted native envelope already covers the request checkpoint. Keep its original intent outside it, not a second raw copy of the old summary.
|
|
56
|
+
+ const retained = result.details.replacementInput.filter((item) => !checkpointTexts.has(retainedUserText(item)));
|
|
57
|
+
+ const replacementInput = protectedRemoteInput(retained, protection);
|
|
58
|
+
+ return { status: "accepted", compaction: { ...result,
|
|
59
|
+
+ summary: taskIntentSummary(result.summary, protection.taskIntent) + (protection.sources.length ? `\n<source-user-text>\n${JSON.stringify(protection.sources)}\n</source-user-text>` : ""),
|
|
60
|
+
+ details: { ...result.details, intentProtection: protection, replacementInput, retainedInputItemCount: replacementInput.length,
|
|
61
|
+
+ selectionMode: settings.model === undefined || settings.model === "auto" ? "auto" : "explicit",
|
|
62
|
+
+ requestedProvider: model.provider, requestedModel: model.id },
|
|
63
|
+
+ } };
|
|
64
|
+
+}
|
|
65
|
+
+async function runOpenAiRemoteCompactionAttempt(ctx, event, emit, dependencies = {}) {
|
|
66
|
+
@@ -410,0 +440,4 @@
|
|
67
|
+
+ const requestTokens = estimateTokens({ role: "user", content: JSON.stringify(request.body) });
|
|
68
|
+
+ const requestSettings = ctx.getCompactionSettings?.() ?? event.preparation.settings;
|
|
69
|
+
+ if (requestTokens > requestModel.contextWindow - resolveEffectiveReserveTokens(requestModel.contextWindow, requestSettings))
|
|
70
|
+
+ return undefined;
|
|
71
|
+
@@ -765,1 +799,1 @@
|
|
72
|
+
- ...remote.details.replacementInput,
|
|
73
|
+
+ ...protectedRemoteInput(remote.details.replacementInput, remote.details.intentProtection),
|
|
74
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/openai-remote-responses-v2.js
|
|
75
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/openai-remote-responses-v2.js
|
|
76
|
+
@@ -109,0 +109,1 @@
|
|
77
|
+
+ ...(assistant.responseModel ? { observedProvider: assistant.provider, observedModel: assistant.responseModel } : {}),
|
|
78
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/rejected-recovery.js
|
|
79
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/rejected-recovery.js
|
|
80
|
+
@@ -6,0 +6,2 @@
|
|
81
|
+
+import { getOpenAiRemoteCompactionDetails, convertPendingMessages } from "./openai-remote-convert.js";
|
|
82
|
+
+import { protectedRemoteInput } from "./task-intent.js";
|
|
83
|
+
@@ -33,1 +35,6 @@
|
|
84
|
+
- const tokens = filterContextExcludedMessages(messages).reduce((total, message) => total + estimateTokens(message), 0);
|
|
85
|
+
+ const remote = getOpenAiRemoteCompactionDetails(compactionResult.details);
|
|
86
|
+
+ const budgetMessages = remote ? [{ role: "user", content: JSON.stringify([
|
|
87
|
+
+ ...protectedRemoteInput(remote.replacementInput, remote.intentProtection),
|
|
88
|
+
+ ...convertPendingMessages(pendingMessages, model),
|
|
89
|
+
+ ]) }] : messages;
|
|
90
|
+
+ const tokens = filterContextExcludedMessages(budgetMessages).reduce((total, message) => total + estimateTokens(message), 0);
|
|
91
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/openai-remote-convert.js
|
|
92
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/openai-remote-convert.js
|
|
93
|
+
@@ -1,0 +1,1 @@
|
|
94
|
+
+import { protectedRemoteInput } from "./task-intent.js";
|
|
95
|
+
@@ -2,1 +3,1 @@
|
|
96
|
+
-import { sessionEntryToContextMessages } from "../../../session-manager.js";
|
|
97
|
+
+import { sessionEntryToContextMessages, getSessionContextEntryId, SESSION_CONTEXT_ENTRY_ID } from "../../../session-manager.js";
|
|
98
|
+
@@ -142,2 +143,6 @@
|
|
99
|
+
- case "user":
|
|
100
|
+
- return [{ role: "user", content: convertUserContent(message.content) }];
|
|
101
|
+
+ case "user": {
|
|
102
|
+
+ const item = { role: "user", content: convertUserContent(message.content) };
|
|
103
|
+
+ const sourceId = getSessionContextEntryId(message) ?? message[SESSION_CONTEXT_ENTRY_ID];
|
|
104
|
+
+ if (sourceId) Object.defineProperty(item, "__senpiCompactionSourceId", { value: sourceId });
|
|
105
|
+
+ return [item];
|
|
106
|
+
+ }
|
|
107
|
+
@@ -188,1 +193,1 @@
|
|
108
|
+
- items.push(...details.replacementInput);
|
|
109
|
+
+ items.push(...protectedRemoteInput(details.replacementInput, details.intentProtection));
|
|
110
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/prompts.js
|
|
111
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/prompts.js
|
|
112
|
+
@@ -9,2 +9,2 @@
|
|
113
|
+
-Immutable provenance of the original task. Do not rewrite it. Newer explicit user steering overrides it.
|
|
114
|
+
-</task-intent>`;
|
|
115
|
+
+</task-intent>
|
|
116
|
+
+Immutable provenance of the original task. Do not rewrite it. Newer explicit user steering overrides it.`;
|
|
117
|
+
@@ -13,2 +13,2 @@
|
|
118
|
+
-Immutable provenance of the original task. Do not rewrite it. Newer explicit user steering overrides it.
|
|
119
|
+
-</task-intent>`;
|
|
120
|
+
+</task-intent>
|
|
121
|
+
+Immutable provenance of the original task. Do not rewrite it. Newer explicit user steering overrides it.`;
|
|
122
|
+
@@ -22,1 +22,1 @@
|
|
123
|
+
-R3. Where a previous summary is supplied, treat its User Requests, Final Goal, and Constraints fields as IMMUTABLE. Append, never rewrite, those three sections.
|
|
124
|
+
+R3. Preserve original requests and durable constraints as quoted provenance. Latest explicit user steering governs the current goal and next action. An answer-local restriction applies only to that answer.
|
|
125
|
+
@@ -86,1 +86,1 @@
|
|
126
|
+
-R3 enforcement: R3. Where a previous summary is supplied, treat its User Requests, Final Goal, and Constraints fields as IMMUTABLE. Append, never rewrite, those three sections.
|
|
127
|
+
+Preserve original requests and durable constraints as quoted provenance. Latest explicit user steering governs the current goal and next action. Keep answer-local restrictions local to their answer.
|
|
128
|
+
@@ -179,1 +179,1 @@
|
|
129
|
+
-Silently determine the current task intent and the minimum context needed for the next turn.
|
|
130
|
+
+Preserve the original request and durable constraints from the entire history together with the split turn. Latest explicit steering governs the current goal and next action. Do not promote answer-local restrictions into durable constraints.
|
|
131
|
+
@@ -217,3 +217,1 @@
|
|
132
|
+
- return options.taskIntent
|
|
133
|
+
- ? MERGED_COMPACTION_PROMPT_TURN_PREFIX
|
|
134
|
+
- : mergeTaskIntent(MERGED_COMPACTION_PROMPT_TURN_PREFIX, promptFamilyText(options.promptFamily, TASK_INTENT_ACQUISITION_CLAUDE, TASK_INTENT_ACQUISITION_GPT));
|
|
135
|
+
+ return buildUserPrompt({ ...options, variant: options.previousSummary || options.taskIntent ? "update" : "default" });
|
|
136
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/task-intent.js
|
|
137
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/task-intent.js
|
|
138
|
+
@@ -16,1 +16,1 @@
|
|
139
|
+
- taskIntent = capUtf8Bytes(block.inner.trim(), TASK_INTENT_BYTE_CAP);
|
|
140
|
+
+ taskIntent = block.inner.trim();
|
|
141
|
+
@@ -44,1 +44,1 @@
|
|
142
|
+
- if (schema === REMOTE_SCHEMA || schema !== SUMMARY_SCHEMA)
|
|
143
|
+
+ if (schema !== REMOTE_SCHEMA && schema !== SUMMARY_SCHEMA)
|
|
144
|
+
@@ -46,1 +46,2 @@
|
|
145
|
+
- const taskIntent = typedDetails.taskIntent;
|
|
146
|
+
+ const fullIntent = typeof entry.summary === "string" ? extractTaskIntent(entry.summary).taskIntent : undefined;
|
|
147
|
+
+ const taskIntent = fullIntent ?? typedDetails.intentProtection?.taskIntent ?? typedDetails.taskIntent;
|
|
148
|
+
@@ -52,1 +53,1 @@
|
|
149
|
+
- return capUtf8Bytes(trimmed, TASK_INTENT_BYTE_CAP);
|
|
150
|
+
+ return trimmed;
|
|
151
|
+
@@ -92,0 +93,57 @@
|
|
152
|
+
+
|
|
153
|
+
+/** Full-body provenance is separate from the bounded display metadata. */
|
|
154
|
+
+export function taskIntentSummary(body, taskIntent) {
|
|
155
|
+
+ return taskIntent ? `<task-intent>
|
|
156
|
+
+${taskIntent}
|
|
157
|
+
+</task-intent>
|
|
158
|
+
+
|
|
159
|
+
+Quoted original-task provenance, not a renewed instruction. Latest explicit user steering governs current action. Answer-local restrictions do not become durable constraints.
|
|
160
|
+
+
|
|
161
|
+
+${body}` : body;
|
|
162
|
+
+}
|
|
163
|
+
+export function taskIntentMetadata(taskIntent) {
|
|
164
|
+
+ return taskIntent ? capUtf8Bytes(taskIntent, TASK_INTENT_BYTE_CAP) : undefined;
|
|
165
|
+
+}
|
|
166
|
+
+export function collectIntentProtection(branchEntries) {
|
|
167
|
+
+ const sources = new Map();
|
|
168
|
+
+ const previous = branchEntries.findLast((entry) => entry.type === "compaction")?.details?.intentProtection;
|
|
169
|
+
+ for (const source of previous?.sources ?? []) sources.set(source.id, source);
|
|
170
|
+
+ for (const entry of branchEntries) {
|
|
171
|
+
+ for (const source of entry.details?.intentProtection?.sources ?? [])
|
|
172
|
+
+ sources.set(source.id, source);
|
|
173
|
+
+ if (entry.type !== "message" || entry.message?.role !== "user")
|
|
174
|
+
+ continue;
|
|
175
|
+
+ const content = entry.message.content;
|
|
176
|
+
+ const text = typeof content === "string" ? content : content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
177
|
+
+ if (text) sources.set(entry.id, { id: entry.id, text });
|
|
178
|
+
+ }
|
|
179
|
+
+ return { taskIntent: resolveInheritedTaskIntent(branchEntries), sources: [...sources.values()] };
|
|
180
|
+
+}
|
|
181
|
+
+export function retainedUserText(item) {
|
|
182
|
+
+ if (item.role !== "user") return undefined;
|
|
183
|
+
+ if (typeof item.content === "string") return item.content;
|
|
184
|
+
+ if (!Array.isArray(item.content) || !item.content.every((block) => block.type === "input_text")) return undefined;
|
|
185
|
+
+ return item.content.map((block) => block.text).join("\n");
|
|
186
|
+
+}
|
|
187
|
+
+export function protectedRemoteInput(replacementInput, protection) {
|
|
188
|
+
+ if (!protection) return replacementInput;
|
|
189
|
+
+ const input = replacementInput.filter((item) => {
|
|
190
|
+
+ const text = retainedUserText(item);
|
|
191
|
+
+ if (!text) return true;
|
|
192
|
+
+ if (protection.sources.some((source) => source.id === item.__senpiCompactionSourceId && source.text === text)) return false;
|
|
193
|
+
+ if (protection.taskIntent && text === taskIntentSummary("", protection.taskIntent)) return false;
|
|
194
|
+
+ const start = text.indexOf("<source-user-text>\n");
|
|
195
|
+
+ const end = text.indexOf("\n</source-user-text>", start);
|
|
196
|
+
+ if (start < 0 || end < 0) return true;
|
|
197
|
+
+ let records;
|
|
198
|
+
+ try { records = JSON.parse(text.slice(start + 19, end)); } catch { return true; }
|
|
199
|
+
+ if (!Array.isArray(records) || !records.every((source) => protection.sources.some((known) => known.id === source.id && known.text === source.text))) return true;
|
|
200
|
+
+ const canonical = taskIntentSummary(`Quoted user source records; newer explicit steering takes precedence.\n<source-user-text>\n${JSON.stringify(records)}\n</source-user-text>`, protection.taskIntent);
|
|
201
|
+
+ return text !== canonical;
|
|
202
|
+
+ });
|
|
203
|
+
+ const remaining = protection.sources;
|
|
204
|
+
+ const text = taskIntentSummary(remaining.length ? `Quoted user source records; newer explicit steering takes precedence.\n<source-user-text>
|
|
205
|
+
+${JSON.stringify(remaining)}
|
|
206
|
+
+</source-user-text>` : "", protection.taskIntent);
|
|
207
|
+
+ return text ? [...input, { type: "message", role: "user", content: [{ type: "input_text", text }] }] : input;
|
|
208
|
+
+}
|
|
209
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/openai-remote-schema.js
|
|
210
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/openai-remote-schema.js
|
|
211
|
+
@@ -52,0 +52,1 @@
|
|
212
|
+
+ ...(isRecord(value.intentProtection) ? { intentProtection: value.intentProtection } : {}),
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
--- a/@code-yeongyu/senpi/dist/core/agent-session.js
|
|
2
|
+
+++ b/@code-yeongyu/senpi/dist/core/agent-session.js
|
|
3
|
+
@@ -758,2 +758,2 @@
|
|
4
|
+
- * Unset, malformed, or unknown overrides keep a usable session model.
|
|
5
|
+
- * A spent account instead uses the held target or another authenticated model.
|
|
6
|
+
+ * Auto selection stays within the active provider; explicit values never fall back.
|
|
7
|
+
+ * Selection uses the availability snapshot. Authentication remains request-time.
|
|
8
|
+
@@ -761,27 +761,50 @@
|
|
9
|
+
- _resolveCompactionModel(sessionModel) {
|
|
10
|
+
- const override = this._getCompactionSettings().model;
|
|
11
|
+
- const slash = override?.indexOf("/") ?? -1;
|
|
12
|
+
- const configured = override && slash > 0
|
|
13
|
+
- ? this._modelRuntime.getModel(override.slice(0, slash), override.slice(slash + 1))
|
|
14
|
+
- : undefined;
|
|
15
|
+
- const usable = (model) => !this._retryFallback.isProviderSpent(model.provider);
|
|
16
|
+
- // Preserve the explicit override and original-model geometry on healthy sessions.
|
|
17
|
+
- // A held downswitch uses its target only when the original account cannot serve.
|
|
18
|
+
- if (usable(sessionModel))
|
|
19
|
+
- return configured && usable(configured) ? configured : sessionModel;
|
|
20
|
+
- const pending = this._pendingModelSwitch?.model;
|
|
21
|
+
- if (pending && usable(pending))
|
|
22
|
+
- return pending;
|
|
23
|
+
- if (configured && usable(configured))
|
|
24
|
+
- return configured;
|
|
25
|
+
- const requiredContextWindow = estimateMessagesTokens(filterContextExcludedMessages(this._runtimeMessages())) +
|
|
26
|
+
- this._getCompactionSettings().reserveTokens;
|
|
27
|
+
- const available = this._modelRuntime
|
|
28
|
+
- .getModels()
|
|
29
|
+
- .find((model) => usable(model) &&
|
|
30
|
+
- model.contextWindow >= requiredContextWindow &&
|
|
31
|
+
- this._modelRuntime.hasConfiguredAuth(model.provider));
|
|
32
|
+
- if (available)
|
|
33
|
+
- return available;
|
|
34
|
+
- throw new AccountUsageLimitError(sessionModel.provider, this._retryFallback.accountError(sessionModel.provider));
|
|
35
|
+
- }
|
|
36
|
+
+ _resolveCompactionModel(sessionModel, inputTokens = 0) {
|
|
37
|
+
+ const settings = this._getCompactionSettings();
|
|
38
|
+
+ const override = settings.model;
|
|
39
|
+
+ const explicit = override !== undefined && override !== "auto";
|
|
40
|
+
+ const fail = (kind, model = sessionModel) => {
|
|
41
|
+
+ throw Object.assign(new Error(
|
|
42
|
+
+ explicit ? "Compaction model unavailable; check compaction.model or set it to auto."
|
|
43
|
+
+ : "No usable compaction model within the active provider."),
|
|
44
|
+
+ { failure: { kind, provider: model.provider, model: model.id } });
|
|
45
|
+
+ };
|
|
46
|
+
+ const chat = this._modelRuntime.getModels();
|
|
47
|
+
+ const available = this._modelRuntime.getAvailableSnapshot();
|
|
48
|
+
+ const same = (left, right) => left.provider === right.provider && left.id === right.id;
|
|
49
|
+
+ const usable = (model) => chat.some((entry) => same(entry, model)) && (!available.some((entry) => entry.provider === model.provider) || available.some((entry) => same(entry, model))) &&
|
|
50
|
+
+ !this._retryFallback.isProviderSpent(model.provider) &&
|
|
51
|
+
+ this._modelRuntime.isFallbackEligible(model.provider);
|
|
52
|
+
+ const fits = (model) => {
|
|
53
|
+
+ const output = Math.min(32768, model.maxTokens > 0 ? model.maxTokens : 32768, Math.floor(model.contextWindow / 2));
|
|
54
|
+
+ return model.contextWindow > 0 && inputTokens + Math.max(settings.reserveTokens, output) <= model.contextWindow;
|
|
55
|
+
+ };
|
|
56
|
+
+ if (explicit) {
|
|
57
|
+
+ if (typeof override !== "string" || !/^[^/\s]+\/\S+$/.test(override))
|
|
58
|
+
+ return fail("invalid_model");
|
|
59
|
+
+ const slash = override.indexOf("/");
|
|
60
|
+
+ const configured = this._modelRuntime.getModel(override.slice(0, slash), override.slice(slash + 1));
|
|
61
|
+
+ if (!configured || !chat.some((model) => same(model, configured)))
|
|
62
|
+
+ return fail("invalid_model");
|
|
63
|
+
+ if (this._retryFallback.isProviderSpent(configured.provider))
|
|
64
|
+
+ return fail("quota", configured);
|
|
65
|
+
+ if (!usable(configured))
|
|
66
|
+
+ return fail("model_unavailable", configured);
|
|
67
|
+
+ if (!fits(configured))
|
|
68
|
+
+ return fail("context_budget", configured);
|
|
69
|
+
+ return configured;
|
|
70
|
+
+ }
|
|
71
|
+
+ if (this._retryFallback.isProviderSpent(sessionModel.provider))
|
|
72
|
+
+ return fail("quota");
|
|
73
|
+
+ const family = ["anthropic", "anthropic-subscription"].includes(sessionModel.provider) && /^claude-/.test(sessionModel.id) ? /^claude-sonnet-/
|
|
74
|
+
+ : ["openai", "chatgpt-subscription"].includes(sessionModel.provider) && /^gpt-/.test(sessionModel.id) ? /^gpt-.*-luna(?:-fast)?$/ : undefined;
|
|
75
|
+
+ const preferred = family ? available.filter((model) =>
|
|
76
|
+
+ model.provider === sessionModel.provider && family.test(model.id)) : [];
|
|
77
|
+
+ // Stable sort preserves registry order within the regular and fast Luna groups.
|
|
78
|
+
+ preferred.sort((left, right) => Number(/-luna-fast$/.test(left.id)) - Number(/-luna-fast$/.test(right.id)));
|
|
79
|
+
+ const candidates = [...(family?.test(sessionModel.id) ? [sessionModel] : []), ...preferred, sessionModel];
|
|
80
|
+
+ for (const candidate of candidates) {
|
|
81
|
+
+ if (candidate.provider === sessionModel.provider && usable(candidate) && fits(candidate))
|
|
82
|
+
+ return candidate;
|
|
83
|
+
+ }
|
|
84
|
+
+ return fail(usable(sessionModel) ? "context_budget" : "model_unavailable");
|
|
85
|
+
+ }
|
|
86
|
+
@@ -788,7 +811,6 @@
|
|
87
|
+
- async _getCompactionRequestAuth(model, signal, callbacks) {
|
|
88
|
+
- const auth = await this._getSummarizationRequestAuth(model, signal, callbacks);
|
|
89
|
+
- return {
|
|
90
|
+
- ...auth,
|
|
91
|
+
- extraBody: this._modelRuntime.getCompatibilityRequestConfig(model).extraBody,
|
|
92
|
+
- };
|
|
93
|
+
- }
|
|
94
|
+
+ async _getCompactionRequestAuth(model, signal, callbacks) {
|
|
95
|
+
+ const prepared = await retryTransientCall(() => this._modelRuntime.prepareRequest(model, { signal }),
|
|
96
|
+
+ isOAuthRefreshUnavailableError, this.settingsManager.getRetrySettings(), signal,
|
|
97
|
+
+ callbacks ?? this._summarizationRetryCallbacks({ source: "compaction" }));
|
|
98
|
+
+ return { ...prepared.options, model: prepared.model, thinkingLevel: this.thinkingLevel };
|
|
99
|
+
+ }
|
|
100
|
+
@@ -6305,1 +6327,0 @@
|
|
101
|
+
- const compactionModel = this._resolveCompactionModel(model);
|
|
102
|
+
@@ -6336,5 +6357,6 @@
|
|
103
|
+
- // A configured compaction.model override redirects only the
|
|
104
|
+
- // summarization call to that model; every other part of the session
|
|
105
|
+
- // (lifecycle record, preparation, branch) still tracks the session
|
|
106
|
+
- // model. Falls back to the session model when the override is unset
|
|
107
|
+
- // or cannot be resolved.
|
|
108
|
+
+ // Size the complete source, including control text, before selecting a model.
|
|
109
|
+
+ // The final serialized request is checked again at the dispatch boundary.
|
|
110
|
+
+ const inputTokens = estimateMessagesTokens([...preparation.messagesToSummarize, ...preparation.turnPrefixMessages]) +
|
|
111
|
+
+ Math.ceil(JSON.stringify([this.agent.state.systemPrompt, this.agent.state.tools,
|
|
112
|
+
+ preparation.previousSummary, request.customInstructions]).length / 4) + 8192;
|
|
113
|
+
+ const compactionModel = this._resolveCompactionModel(model, inputTokens);
|
|
114
|
+
@@ -6342,1 +6364,4 @@
|
|
115
|
+
- compactionResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, extraBody, request.customInstructions, signal, env, request.reason, thinkingLevel);
|
|
116
|
+
+ compactionResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, extraBody, request.customInstructions, signal, env, request.reason, thinkingLevel);
|
|
117
|
+
+ compactionResult.details = { ...compactionResult.details,
|
|
118
|
+
+ selectionMode: settings.model === undefined || settings.model === "auto" ? "auto" : "explicit",
|
|
119
|
+
+ requestedProvider: compactionModel.provider, requestedModel: compactionModel.id };
|
|
120
|
+
@@ -7704,3 +7729,4 @@
|
|
121
|
+
- getCompactionModel: () => this.model && this._retryFallback.isProviderSpent(this.model.provider)
|
|
122
|
+
- ? this._resolveCompactionModel(this.model)
|
|
123
|
+
- : this.model,
|
|
124
|
+
+ getCompactionModel: (inputTokens) => {
|
|
125
|
+
+ const model = this._limitsModel();
|
|
126
|
+
+ return model && this._resolveCompactionModel(model, inputTokens);
|
|
127
|
+
+ },
|
|
128
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/runner.js
|
|
129
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/runner.js
|
|
130
|
+
@@ -987,3 +987,3 @@
|
|
131
|
+
- ? () => {
|
|
132
|
+
- runner.assertActive();
|
|
133
|
+
- return runner.getCompactionModelFn?.();
|
|
134
|
+
+ ? (inputTokens) => {
|
|
135
|
+
+ runner.assertActive();
|
|
136
|
+
+ return runner.getCompactionModelFn?.(inputTokens);
|
|
137
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/types.d.ts
|
|
138
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/types.d.ts
|
|
139
|
+
@@ -446,1 +446,1 @@
|
|
140
|
+
- getCompactionModel?(): Model<Api> | undefined;
|
|
141
|
+
+ getCompactionModel?(inputTokens?: number): Model<Api> | undefined;
|
|
142
|
+
@@ -2415,1 +2415,1 @@
|
|
143
|
+
- getCompactionModel?: () => Model<Api> | undefined;
|
|
144
|
+
+ getCompactionModel?: (inputTokens?: number) => Model<Api> | undefined;
|
|
145
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/lane-policy.js
|
|
146
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/lane-policy.js
|
|
147
|
+
@@ -94,2 +94,3 @@
|
|
148
|
+
- if (context.getCompactionSettings?.().model)
|
|
149
|
+
- return false;
|
|
150
|
+
+ const override = context.getCompactionSettings?.().model;
|
|
151
|
+
+ if (typeof override === "string" && /^[^/\s]+\/\S+$/.test(override))
|
|
152
|
+
+ return false;
|
|
153
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative.js
|
|
154
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative.js
|
|
155
|
+
@@ -8,1 +8,1 @@
|
|
156
|
+
-import { allowOverflowRetry, boundSummarizationInput, SUMMARIZATION_INPUT_BUDGET_RATIO, SummarizationOverflowExhaustedError, shrinkSummarizationInputForOverflowRetry, } from "./overflow-retry.js";
|
|
157
|
+
+import { estimateTotalTokens, SummarizationOverflowExhaustedError } from "./overflow-retry.js";
|
|
158
|
+
@@ -143,26 +143,44 @@
|
|
159
|
+
- const selectedModel = context.getCompactionModel?.() ?? snapshot.model;
|
|
160
|
+
- const selectedSnapshot = selectedModel === snapshot.model
|
|
161
|
+
- ? snapshot
|
|
162
|
+
- : { ...snapshot, model: selectedModel, contextWindow: selectedModel.contextWindow };
|
|
163
|
+
- const auth = await context.modelRegistry?.getApiKeyAndHeaders(selectedModel);
|
|
164
|
+
- if (signal?.aborted)
|
|
165
|
+
- return undefined;
|
|
166
|
+
- // A provider is authenticated for summarization by a resolved key, a credential
|
|
167
|
+
- // request header, or ambient request-time auth. The ambient marker comes from the
|
|
168
|
+
- // same provider resolution normal turns use; an unconfigured keyed provider still
|
|
169
|
+
- // has none of these and is rejected before a request.
|
|
170
|
+
- if (!auth?.ok || !(auth.apiKey || hasCredentialHeaders(auth.headers) || auth.ambient)) {
|
|
171
|
+
- const detail = auth && !auth.ok ? auth.error : `no credentials resolved for provider "${selectedModel.provider}"`;
|
|
172
|
+
- throw new SummaryGenerationError("auth", `summarization credentials unavailable: ${detail}`);
|
|
173
|
+
- }
|
|
174
|
+
- const requestSnapshot = auth.baseUrl
|
|
175
|
+
- ? { ...selectedSnapshot, model: { ...selectedModel, baseUrl: auth.baseUrl } }
|
|
176
|
+
- : selectedSnapshot;
|
|
177
|
+
- const prompt = buildPrompt({
|
|
178
|
+
- variant: requestSnapshot.promptVariant,
|
|
179
|
+
- previousSummary: requestSnapshot.preparation.previousSummary,
|
|
180
|
+
- taskIntent: resolveInheritedTaskIntent(requestSnapshot.branchEntries ?? []),
|
|
181
|
+
- customInstructions: requestSnapshot.customInstructions,
|
|
182
|
+
- });
|
|
183
|
+
- const promptTokens = approxTokens(prompt.user);
|
|
184
|
+
- let messages = boundSummarizationInput(pruneToolResults([...requestSnapshot.preparation.messagesToSummarize, ...requestSnapshot.preparation.turnPrefixMessages], requestSnapshot.contextWindow, SUMMARIZATION_INPUT_BUDGET_RATIO), requestSnapshot.contextWindow, promptTokens);
|
|
185
|
+
+ const prompt = buildPrompt({
|
|
186
|
+
+ variant: snapshot.promptVariant,
|
|
187
|
+
+ previousSummary: snapshot.preparation.previousSummary,
|
|
188
|
+
+ taskIntent: resolveInheritedTaskIntent(snapshot.branchEntries ?? []),
|
|
189
|
+
+ customInstructions: snapshot.customInstructions,
|
|
190
|
+
+ });
|
|
191
|
+
+ const messages = [...snapshot.preparation.messagesToSummarize, ...snapshot.preparation.turnPrefixMessages];
|
|
192
|
+
+ const inputTokens = estimateTotalTokens([
|
|
193
|
+
+ ...messages,
|
|
194
|
+
+ { role: "user", content: prompt.user },
|
|
195
|
+
+ { role: "user", content: snapshot.systemPrompt ?? prompt.system },
|
|
196
|
+
+ { role: "user", content: JSON.stringify(snapshot.tools ?? []) },
|
|
197
|
+
+ ]);
|
|
198
|
+
+ const selectedModel = context.getCompactionModel?.(inputTokens) ?? snapshot.model;
|
|
199
|
+
+ const selectedSnapshot = { ...snapshot, model: selectedModel, contextWindow: selectedModel.contextWindow, selectionMode: context.getCompactionSettings?.().model === undefined || context.getCompactionSettings?.().model === "auto" ? "auto" : "explicit" };
|
|
200
|
+
+ let auth;
|
|
201
|
+
+ try {
|
|
202
|
+
+ const runtime = context.modelRegistry?.modelRuntime;
|
|
203
|
+
+ if (runtime) {
|
|
204
|
+
+ const prepared = await runtime.prepareRequest(selectedModel, { signal });
|
|
205
|
+
+ auth = { ok: true, ...prepared.options, baseUrl: prepared.model.baseUrl, ambient: true };
|
|
206
|
+
+ }
|
|
207
|
+
+ else {
|
|
208
|
+
+ auth = await context.modelRegistry?.getApiKeyAndHeaders(selectedModel);
|
|
209
|
+
+ }
|
|
210
|
+
+ }
|
|
211
|
+
+ catch (error) {
|
|
212
|
+
+ if (signal?.aborted)
|
|
213
|
+
+ return undefined;
|
|
214
|
+
+ const kind = error?.failure?.kind ?? error?.kind;
|
|
215
|
+
+ const known = ["auth_missing", "auth_expired", "quota", "org_denied", "model_unavailable"];
|
|
216
|
+
+ const failure = { kind: kind === "auth" ? "auth_missing" : known.includes(kind) ? kind : "provider",
|
|
217
|
+
+ provider: selectedModel.provider, model: selectedModel.id };
|
|
218
|
+
+ throw Object.assign(new SummaryGenerationError(failure.kind, "Compaction request authentication failed"), { failure, cause: error });
|
|
219
|
+
+ }
|
|
220
|
+
+ if (signal?.aborted)
|
|
221
|
+
+ return undefined;
|
|
222
|
+
+ if (!auth?.ok || !(auth.apiKey || hasCredentialHeaders(auth.headers) || auth.ambient)) {
|
|
223
|
+
+ const failure = { kind: "auth_missing", provider: selectedModel.provider, model: selectedModel.id };
|
|
224
|
+
+ throw Object.assign(new SummaryGenerationError(failure.kind, "Compaction request authentication unavailable"), { failure });
|
|
225
|
+
+ }
|
|
226
|
+
+ const requestSnapshot = auth.baseUrl
|
|
227
|
+
+ ? { ...selectedSnapshot, model: { ...selectedModel, baseUrl: auth.baseUrl } }
|
|
228
|
+
+ : selectedSnapshot;
|
|
229
|
+
@@ -173,2 +191,0 @@
|
|
230
|
+
- const overflowRetryStartMs = Date.now();
|
|
231
|
+
- let overflowAttempts = 0;
|
|
232
|
+
@@ -216,1 +232,2 @@
|
|
233
|
+
- extraBody: auth.extraBody,
|
|
234
|
+
+ extraBody: auth.extraBody,
|
|
235
|
+
+ env: auth.env,
|
|
236
|
+
@@ -223,1 +240,1 @@
|
|
237
|
+
- !isContextOverflow(attempt, snapshot.contextWindow)) {
|
|
238
|
+
+ !isContextOverflow(attempt, requestSnapshot.contextWindow)) {
|
|
239
|
+
@@ -240,12 +257,4 @@
|
|
240
|
+
- if (isAssistantMessage(response) && isContextOverflow(response, snapshot.contextWindow)) {
|
|
241
|
+
- overflowAttempts++;
|
|
242
|
+
- const elapsedMs = Date.now() - overflowRetryStartMs;
|
|
243
|
+
- const retryMessages = allowOverflowRetry(overflowAttempts, elapsedMs)
|
|
244
|
+
- ? shrinkSummarizationInputForOverflowRetry(messages, snapshot.contextWindow, promptTokens)
|
|
245
|
+
- : undefined;
|
|
246
|
+
- if (!retryMessages) {
|
|
247
|
+
- throw new SummarizationOverflowExhaustedError(overflowAttempts, elapsedMs);
|
|
248
|
+
- }
|
|
249
|
+
- messages = retryMessages;
|
|
250
|
+
- continue;
|
|
251
|
+
- }
|
|
252
|
+
+ if (isAssistantMessage(response) && isContextOverflow(response, requestSnapshot.contextWindow)) {
|
|
253
|
+
+ const failure = { kind: "context_budget", provider: selectedModel.provider, model: selectedModel.id };
|
|
254
|
+
+ throw Object.assign(new SummaryGenerationError(failure.kind, "Compaction input exceeds the provider context budget"), { failure });
|
|
255
|
+
+ }
|
|
256
|
+
@@ -303,1 +312,6 @@
|
|
257
|
+
- schema: SUMMARY_SCHEMA,
|
|
258
|
+
+ schema: SUMMARY_SCHEMA,
|
|
259
|
+
+ selectionMode: selectedSnapshot.selectionMode,
|
|
260
|
+
+ requestedProvider: selectedModel.provider,
|
|
261
|
+
+ requestedModel: selectedModel.id,
|
|
262
|
+
+ ...(isAssistantMessage(response) && response.provider && response.responseModel
|
|
263
|
+
+ ? { observedProvider: response.provider, observedModel: response.responseModel } : {}),
|
|
264
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative-summary.js
|
|
265
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/compaction/speculative-summary.js
|
|
266
|
+
@@ -6,1 +6,2 @@
|
|
267
|
+
-import { repairOrphanedToolResults } from "./repair-tool-pairs.js";
|
|
268
|
+
+import { repairOrphanedToolResults } from "./repair-tool-pairs.js";
|
|
269
|
+
+import { estimateTotalTokens } from "./overflow-retry.js";
|
|
270
|
+
@@ -109,1 +110,10 @@
|
|
271
|
+
- const headers = providerRequest
|
|
272
|
+
+ const inputTokens = estimateTotalTokens([
|
|
273
|
+
+ ...requestContext.messages,
|
|
274
|
+
+ { role: "user", content: requestContext.systemPrompt },
|
|
275
|
+
+ { role: "user", content: JSON.stringify(requestContext.tools ?? []) },
|
|
276
|
+
+ ]);
|
|
277
|
+
+ if (inputTokens + summaryMaxTokens(options.snapshot.model, options.snapshot.contextWindow) > options.snapshot.contextWindow) {
|
|
278
|
+
+ throw Object.assign(new Error("Compaction request exceeds the selected model context budget"),
|
|
279
|
+
+ { failure: { kind: "context_budget", provider: options.snapshot.model.provider, model: options.snapshot.model.id } });
|
|
280
|
+
+ }
|
|
281
|
+
+ const headers = providerRequest
|
|
282
|
+
@@ -115,1 +125,3 @@
|
|
283
|
+
- extraBody: options.auth.extraBody,
|
|
284
|
+
+ extraBody: options.auth.extraBody,
|
|
285
|
+
+ env: options.auth.env,
|
|
286
|
+
+ streamKind: "auxiliary",
|
|
287
|
+
--- a/@code-yeongyu/senpi/dist/core/compaction/compaction.js
|
|
288
|
+
+++ b/@code-yeongyu/senpi/dist/core/compaction/compaction.js
|
|
289
|
+
@@ -635,1 +635,2 @@
|
|
290
|
+
- affinitySessionId: options.affinitySessionId ?? options.sessionId,
|
|
291
|
+
+ affinitySessionId: options.affinitySessionId ?? options.sessionId,
|
|
292
|
+
+ streamKind: "auxiliary",
|
|
293
|
+
@@ -638,1 +639,5 @@
|
|
294
|
+
- const callerSignal = options.signal;
|
|
295
|
+
+ if (model.contextWindow <= 0 || estimateProviderContextTokens(context).tokens + options.maxTokens > model.contextWindow) {
|
|
296
|
+
+ throw Object.assign(new Error("Compaction request exceeds the selected model context budget"),
|
|
297
|
+
+ { failure: { kind: "context_budget", provider: model.provider, model: model.id } });
|
|
298
|
+
+ }
|
|
299
|
+
+ const callerSignal = options.signal;
|