@code-yeongyu/senpi 2026.9.12-2 → 2026.9.12
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/CHANGELOG.md +0 -17
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +1 -5
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +9 -13
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +121 -54
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/models.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/models.js +1 -2
- package/dist/core/extensions/builtin/cursor-cli-oauth/models.js.map +1 -1
- package/dist/core/session-manager.d.ts +0 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +13 -22
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-write-reservation.d.ts +0 -9
- package/dist/core/session-write-reservation.d.ts.map +1 -1
- package/dist/core/session-write-reservation.js +0 -33
- package/dist/core/session-write-reservation.js.map +1 -1
- package/dist/modes/interactive/components/ask-user-async-widget.d.ts +0 -2
- package/dist/modes/interactive/components/ask-user-async-widget.d.ts.map +1 -1
- package/dist/modes/interactive/components/ask-user-async-widget.js +1 -17
- package/dist/modes/interactive/components/ask-user-async-widget.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -2
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +1 -2
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js +0 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/rpc/session-registry.d.ts +1 -3
- package/dist/modes/rpc/session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/session-registry.js.map +1 -1
- package/dist/modes/rpc/session-worker-client.d.ts +8 -10
- package/dist/modes/rpc/session-worker-client.d.ts.map +1 -1
- package/dist/modes/rpc/session-worker-client.js +23 -14
- package/dist/modes/rpc/session-worker-client.js.map +1 -1
- package/dist/modes/rpc/session-worker-protocol.d.ts +0 -10
- package/dist/modes/rpc/session-worker-protocol.d.ts.map +1 -1
- package/dist/modes/rpc/session-worker-protocol.js +0 -2
- package/dist/modes/rpc/session-worker-protocol.js.map +1 -1
- package/dist/modes/rpc/session-worker.js +16 -12
- package/dist/modes/rpc/session-worker.js.map +1 -1
- package/dist/modes/rpc/worker-session-registry.d.ts +0 -12
- package/dist/modes/rpc/worker-session-registry.d.ts.map +1 -1
- package/dist/modes/rpc/worker-session-registry.js +24 -51
- package/dist/modes/rpc/worker-session-registry.js.map +1 -1
- package/docs/rpc.md +6 -14
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +0 -12
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts +0 -11
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js +0 -18
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +1 -5
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/cursor/store-migration.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/cursor/store-migration.js +1 -2
- package/node_modules/@earendil-works/pi-ai/dist/cursor/store-migration.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cursor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/cursor.js +1 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/cursor.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
- package/dist/core/cursor-history-admission.d.ts +0 -50
- package/dist/core/cursor-history-admission.d.ts.map +0 -1
- package/dist/core/cursor-history-admission.js +0 -130
- package/dist/core/cursor-history-admission.js.map +0 -1
- package/dist/modes/rpc/session-path-reservations.d.ts +0 -30
- package/dist/modes/rpc/session-path-reservations.d.ts.map +0 -1
- package/dist/modes/rpc/session-path-reservations.js +0 -58
- package/dist/modes/rpc/session-path-reservations.js.map +0 -1
- package/dist/modes/rpc/session-worker-credit.d.ts +0 -17
- package/dist/modes/rpc/session-worker-credit.d.ts.map +0 -1
- package/dist/modes/rpc/session-worker-credit.js +0 -39
- package/dist/modes/rpc/session-worker-credit.js.map +0 -1
- package/dist/modes/rpc/session-worker-signals.d.ts +0 -7
- package/dist/modes/rpc/session-worker-signals.d.ts.map +0 -1
- package/dist/modes/rpc/session-worker-signals.js +0 -19
- package/dist/modes/rpc/session-worker-signals.js.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/cursor/context-limit-store.d.ts +0 -32
- package/node_modules/@earendil-works/pi-ai/dist/cursor/context-limit-store.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/cursor/context-limit-store.js +0 -62
- package/node_modules/@earendil-works/pi-ai/dist/cursor/context-limit-store.js.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/cursor-context-limit.d.ts +0 -12
- package/node_modules/@earendil-works/pi-ai/dist/utils/cursor-context-limit.d.ts.map +0 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/cursor-context-limit.js +0 -93
- package/node_modules/@earendil-works/pi-ai/dist/utils/cursor-context-limit.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earendil-works/pi-ai",
|
|
3
|
-
"version": "2026.9.12
|
|
3
|
+
"version": "2026.9.12",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"dependencies": {
|
|
77
77
|
"@anthropic-ai/sdk": "0.123.0",
|
|
78
78
|
"@aws-sdk/client-bedrock-runtime": "3.1116.0",
|
|
79
|
-
"@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.9.12
|
|
79
|
+
"@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.9.12",
|
|
80
80
|
"@google/genai": "2.18.0",
|
|
81
81
|
"@smithy/node-http-handler": "4.11.3",
|
|
82
82
|
"http-proxy-agent": "9.1.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi",
|
|
3
|
-
"version": "2026.9.12
|
|
3
|
+
"version": "2026.9.12",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"qa:app-server": "node scripts/qa-app-server/run-all.mjs"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.9.12
|
|
52
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.12
|
|
53
|
-
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.9.12
|
|
51
|
+
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.9.12",
|
|
52
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.9.12",
|
|
53
|
+
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.9.12",
|
|
54
54
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
55
55
|
"chalk": "6.0.0",
|
|
56
56
|
"cross-spawn": "7.0.6",
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
"@anthropic-ai/sdk": "0.123.0",
|
|
71
71
|
"@aws-sdk/client-bedrock-runtime": "3.1116.0",
|
|
72
72
|
"@bufbuild/protobuf": "2.14.0",
|
|
73
|
-
"@code-yeongyu/senpi-codemode": "2026.9.12
|
|
74
|
-
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.9.12
|
|
73
|
+
"@code-yeongyu/senpi-codemode": "2026.9.12",
|
|
74
|
+
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.9.12",
|
|
75
75
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
76
76
|
"@mozilla/readability": "0.6.0",
|
|
77
77
|
"@opentelemetry/api": "1.9.1",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"@csstools/css-parser-algorithms": "4.0.0",
|
|
120
120
|
"@csstools/css-syntax-patches-for-csstree": "1.1.8",
|
|
121
121
|
"@csstools/css-tokenizer": "4.0.0",
|
|
122
|
-
"@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.9.12
|
|
122
|
+
"@earendil-works/pi-telemetry": "npm:@code-yeongyu/senpi-telemetry@2026.9.12",
|
|
123
123
|
"@exodus/bytes": "1.15.1",
|
|
124
124
|
"@google/genai": "2.18.0",
|
|
125
125
|
"@hono/node-server": "2.1.1",
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cursor request admission: what the transport is allowed to send in one turn.
|
|
3
|
-
*
|
|
4
|
-
* Cursor rebuilds the whole conversation on every hop and rejects large
|
|
5
|
-
* verbatim tool payloads, so a turn is bounded twice - each tool result is
|
|
6
|
-
* capped on its own, and the aggregate model input is held under the model's
|
|
7
|
-
* context window. Admission NEVER deletes a turn (senpi#1603): dropping the
|
|
8
|
-
* oldest turns to reach a byte cap silently amputated the conversation, and a
|
|
9
|
-
* history that still exceeds the budget after blanking is admitted as-is so the
|
|
10
|
-
* existing Cursor overflow path (a 0-token `resource_exhausted` surfaced to the
|
|
11
|
-
* session layer) compacts it with the session's own policy.
|
|
12
|
-
*/
|
|
13
|
-
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
14
|
-
import type { Message } from "@earendil-works/pi-ai/compat";
|
|
15
|
-
/** Cursor ingest rejects large verbatim tool payloads. The bound is graphemes. */
|
|
16
|
-
export declare const CURSOR_TOOL_RESULT_MAX_CHARS = 2000;
|
|
17
|
-
/** Aggregate admission budget for a model whose window is `contextWindowTokens`. */
|
|
18
|
-
export declare function cursorAdmissionBudgetBytes(contextWindowTokens: number): number;
|
|
19
|
-
export type CursorAdmissionRequest = {
|
|
20
|
-
readonly messages: AgentMessage[] | undefined;
|
|
21
|
-
/** Serialized model-input bytes this turn may occupy. */
|
|
22
|
-
readonly budgetBytes: number;
|
|
23
|
-
readonly maxChars?: number;
|
|
24
|
-
readonly convert?: (candidate: AgentMessage[]) => Message[];
|
|
25
|
-
};
|
|
26
|
-
export type CursorAdmissionResult = {
|
|
27
|
-
readonly messages: AgentMessage[] | undefined;
|
|
28
|
-
readonly changed: boolean;
|
|
29
|
-
readonly blankedToolResults: number;
|
|
30
|
-
readonly bytesBefore: number;
|
|
31
|
-
readonly bytesAfter: number;
|
|
32
|
-
/** True when the history still exceeds the budget; the request is still sent. */
|
|
33
|
-
readonly overBudget: boolean;
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* Bounds one Cursor request: per-result caps first, then blanking the oldest
|
|
37
|
-
* tool result bodies while the model input exceeds `budgetBytes`. Conversation
|
|
38
|
-
* turns are never removed.
|
|
39
|
-
*/
|
|
40
|
-
export declare function admitCursorHistory(request: CursorAdmissionRequest): CursorAdmissionResult;
|
|
41
|
-
/**
|
|
42
|
-
* Positional entry point kept for existing callers. An omitted `maxBytes`
|
|
43
|
-
* applies the per-result cap only; the aggregate budget belongs to the caller
|
|
44
|
-
* that knows the model window.
|
|
45
|
-
*/
|
|
46
|
-
export declare function truncateToolResultBodies(messages: AgentMessage[] | undefined, maxChars?: number, maxBytes?: number, convert?: (candidate: AgentMessage[]) => Message[]): {
|
|
47
|
-
messages: AgentMessage[] | undefined;
|
|
48
|
-
changed: boolean;
|
|
49
|
-
};
|
|
50
|
-
//# sourceMappingURL=cursor-history-admission.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cursor-history-admission.d.ts","sourceRoot":"","sources":["../../src/core/cursor-history-admission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAG5D,kFAAkF;AAClF,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAKjD,oFAAoF;AACpF,wBAAgB,0BAA0B,CAAC,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAG9E;AAED,MAAM,MAAM,sBAAsB,GAAG;IACpC,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,SAAS,CAAC;IAC9C,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,YAAY,EAAE,KAAK,OAAO,EAAE,CAAC;CAC5D,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IACnC,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC7B,CAAC;AA2EF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAoCzF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACvC,QAAQ,EAAE,YAAY,EAAE,GAAG,SAAS,EACpC,QAAQ,SAA+B,EACvC,QAAQ,SAA2B,EACnC,OAAO,eAAe,YAAY,EAAE,cAA4B,GAC9D;IAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAG5D"}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { measureCursorModelInputSerializedBytes } from "@earendil-works/pi-ai";
|
|
2
|
-
import { convertToLlm } from "./messages.js";
|
|
3
|
-
/** Cursor ingest rejects large verbatim tool payloads. The bound is graphemes. */
|
|
4
|
-
export const CURSOR_TOOL_RESULT_MAX_CHARS = 2000;
|
|
5
|
-
const CURSOR_TRUNCATION_MARKER = "\n...[truncated]";
|
|
6
|
-
/** Repo-wide estimate, matching `estimateTokens` in core/compaction. */
|
|
7
|
-
const CHARS_PER_TOKEN = 4;
|
|
8
|
-
/** Aggregate admission budget for a model whose window is `contextWindowTokens`. */
|
|
9
|
-
export function cursorAdmissionBudgetBytes(contextWindowTokens) {
|
|
10
|
-
if (!Number.isFinite(contextWindowTokens) || contextWindowTokens <= 0)
|
|
11
|
-
return 0;
|
|
12
|
-
return Math.floor(contextWindowTokens) * CHARS_PER_TOKEN;
|
|
13
|
-
}
|
|
14
|
-
function capToolResultBodies(messages, maxChars) {
|
|
15
|
-
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
16
|
-
const markerChars = [...segmenter.segment(CURSOR_TRUNCATION_MARKER)].length;
|
|
17
|
-
const result = messages.slice();
|
|
18
|
-
let changed = false;
|
|
19
|
-
for (let messageIndex = result.length - 1; messageIndex >= 0; messageIndex--) {
|
|
20
|
-
const message = result[messageIndex];
|
|
21
|
-
if (message.role !== "toolResult" || !Array.isArray(message.content))
|
|
22
|
-
continue;
|
|
23
|
-
let content = message.content;
|
|
24
|
-
for (let partIndex = content.length - 1; partIndex >= 0; partIndex--) {
|
|
25
|
-
const part = content[partIndex];
|
|
26
|
-
if (part.type === "image" && typeof part.data === "string")
|
|
27
|
-
continue;
|
|
28
|
-
if (part.type !== "text" || typeof part.text !== "string")
|
|
29
|
-
continue;
|
|
30
|
-
const graphemes = [...segmenter.segment(part.text)].map((item) => item.segment);
|
|
31
|
-
if (graphemes.length <= maxChars)
|
|
32
|
-
continue;
|
|
33
|
-
const kept = graphemes.slice(0, Math.max(0, maxChars - markerChars)).join("");
|
|
34
|
-
content = content === message.content ? content.slice() : content;
|
|
35
|
-
content[partIndex] = { ...part, text: kept + CURSOR_TRUNCATION_MARKER };
|
|
36
|
-
result[messageIndex] = { ...message, content };
|
|
37
|
-
changed = true;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return { messages: result, changed };
|
|
41
|
-
}
|
|
42
|
-
function emptyToolResult(message) {
|
|
43
|
-
if (message.role !== "toolResult" || !Array.isArray(message.content))
|
|
44
|
-
return message;
|
|
45
|
-
const emptiedContent = message.content.map((part) => part.type === "text" ? { ...part, text: "" } : part.type === "image" ? { ...part, data: "" } : part);
|
|
46
|
-
const content = emptiedContent.filter((part, index) => {
|
|
47
|
-
if (index === 0)
|
|
48
|
-
return true;
|
|
49
|
-
const previous = emptiedContent[index - 1];
|
|
50
|
-
const empty = part.type === "text" ? part.text === "" : part.type === "image" && part.data === "";
|
|
51
|
-
const previousEmpty = previous.type === "text" ? previous.text === "" : previous.type === "image" && previous.data === "";
|
|
52
|
-
return !empty || !previousEmpty;
|
|
53
|
-
});
|
|
54
|
-
return { ...message, content };
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Empties the oldest result bodies until the rest fits. The prefix search is
|
|
58
|
-
* monotonic, so admission stays bounded instead of reserializing the whole
|
|
59
|
-
* history once per tool result.
|
|
60
|
-
*/
|
|
61
|
-
function blankOldestToolResults(messages, fits) {
|
|
62
|
-
const toolResultIndexes = messages.flatMap((message, index) => message.role === "toolResult" && Array.isArray(message.content) ? [index] : []);
|
|
63
|
-
const withEmptyPrefix = (count) => {
|
|
64
|
-
const candidate = messages.slice();
|
|
65
|
-
for (let i = 0; i < count; i++)
|
|
66
|
-
candidate[toolResultIndexes[i]] = emptyToolResult(candidate[toolResultIndexes[i]]);
|
|
67
|
-
return candidate;
|
|
68
|
-
};
|
|
69
|
-
let low = 0;
|
|
70
|
-
let high = toolResultIndexes.length;
|
|
71
|
-
while (low < high) {
|
|
72
|
-
const middle = Math.floor((low + high) / 2);
|
|
73
|
-
if (fits(withEmptyPrefix(middle + 1)))
|
|
74
|
-
high = middle;
|
|
75
|
-
else
|
|
76
|
-
low = middle + 1;
|
|
77
|
-
}
|
|
78
|
-
const count = Math.min(low + 1, toolResultIndexes.length);
|
|
79
|
-
return { messages: count > 0 ? withEmptyPrefix(count) : messages, count };
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Bounds one Cursor request: per-result caps first, then blanking the oldest
|
|
83
|
-
* tool result bodies while the model input exceeds `budgetBytes`. Conversation
|
|
84
|
-
* turns are never removed.
|
|
85
|
-
*/
|
|
86
|
-
export function admitCursorHistory(request) {
|
|
87
|
-
const { messages, budgetBytes } = request;
|
|
88
|
-
if (!Array.isArray(messages) || messages.length === 0) {
|
|
89
|
-
return { messages, changed: false, blankedToolResults: 0, bytesBefore: 0, bytesAfter: 0, overBudget: false };
|
|
90
|
-
}
|
|
91
|
-
const convert = request.convert ?? ((candidate) => convertToLlm(candidate));
|
|
92
|
-
const capped = capToolResultBodies(messages, request.maxChars ?? CURSOR_TOOL_RESULT_MAX_CHARS);
|
|
93
|
-
const measure = (candidate) => {
|
|
94
|
-
const converted = convert(candidate);
|
|
95
|
-
const activeUserMessageIndex = converted.at(-1)?.role === "user" ? converted.length - 1 : -1;
|
|
96
|
-
return measureCursorModelInputSerializedBytes(converted, activeUserMessageIndex);
|
|
97
|
-
};
|
|
98
|
-
const bytesBefore = measure(capped.messages);
|
|
99
|
-
if (bytesBefore <= budgetBytes) {
|
|
100
|
-
return {
|
|
101
|
-
messages: capped.changed ? capped.messages : messages,
|
|
102
|
-
changed: capped.changed,
|
|
103
|
-
blankedToolResults: 0,
|
|
104
|
-
bytesBefore,
|
|
105
|
-
bytesAfter: bytesBefore,
|
|
106
|
-
overBudget: false,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
const blanked = blankOldestToolResults(capped.messages, (candidate) => measure(candidate) <= budgetBytes);
|
|
110
|
-
const bytesAfter = blanked.count > 0 ? measure(blanked.messages) : bytesBefore;
|
|
111
|
-
const changed = capped.changed || blanked.count > 0;
|
|
112
|
-
return {
|
|
113
|
-
messages: changed ? blanked.messages : messages,
|
|
114
|
-
changed,
|
|
115
|
-
blankedToolResults: blanked.count,
|
|
116
|
-
bytesBefore,
|
|
117
|
-
bytesAfter,
|
|
118
|
-
overBudget: bytesAfter > budgetBytes,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Positional entry point kept for existing callers. An omitted `maxBytes`
|
|
123
|
-
* applies the per-result cap only; the aggregate budget belongs to the caller
|
|
124
|
-
* that knows the model window.
|
|
125
|
-
*/
|
|
126
|
-
export function truncateToolResultBodies(messages, maxChars = CURSOR_TOOL_RESULT_MAX_CHARS, maxBytes = Number.POSITIVE_INFINITY, convert = (candidate) => convertToLlm(candidate)) {
|
|
127
|
-
const admission = admitCursorHistory({ messages, budgetBytes: maxBytes, maxChars, convert });
|
|
128
|
-
return { messages: admission.messages, changed: admission.changed };
|
|
129
|
-
}
|
|
130
|
-
//# sourceMappingURL=cursor-history-admission.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cursor-history-admission.js","sourceRoot":"","sources":["../../src/core/cursor-history-admission.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Cursor request admission: what the transport is allowed to send in one turn.\n *\n * Cursor rebuilds the whole conversation on every hop and rejects large\n * verbatim tool payloads, so a turn is bounded twice - each tool result is\n * capped on its own, and the aggregate model input is held under the model's\n * context window. Admission NEVER deletes a turn (senpi#1603): dropping the\n * oldest turns to reach a byte cap silently amputated the conversation, and a\n * history that still exceeds the budget after blanking is admitted as-is so the\n * existing Cursor overflow path (a 0-token `resource_exhausted` surfaced to the\n * session layer) compacts it with the session's own policy.\n */\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport { measureCursorModelInputSerializedBytes } from \"@earendil-works/pi-ai\";\nimport type { Message } from \"@earendil-works/pi-ai/compat\";\nimport { convertToLlm } from \"./messages.ts\";\n\n/** Cursor ingest rejects large verbatim tool payloads. The bound is graphemes. */\nexport const CURSOR_TOOL_RESULT_MAX_CHARS = 2000;\nconst CURSOR_TRUNCATION_MARKER = \"\\n...[truncated]\";\n/** Repo-wide estimate, matching `estimateTokens` in core/compaction. */\nconst CHARS_PER_TOKEN = 4;\n\n/** Aggregate admission budget for a model whose window is `contextWindowTokens`. */\nexport function cursorAdmissionBudgetBytes(contextWindowTokens: number): number {\n\tif (!Number.isFinite(contextWindowTokens) || contextWindowTokens <= 0) return 0;\n\treturn Math.floor(contextWindowTokens) * CHARS_PER_TOKEN;\n}\n\nexport type CursorAdmissionRequest = {\n\treadonly messages: AgentMessage[] | undefined;\n\t/** Serialized model-input bytes this turn may occupy. */\n\treadonly budgetBytes: number;\n\treadonly maxChars?: number;\n\treadonly convert?: (candidate: AgentMessage[]) => Message[];\n};\n\nexport type CursorAdmissionResult = {\n\treadonly messages: AgentMessage[] | undefined;\n\treadonly changed: boolean;\n\treadonly blankedToolResults: number;\n\treadonly bytesBefore: number;\n\treadonly bytesAfter: number;\n\t/** True when the history still exceeds the budget; the request is still sent. */\n\treadonly overBudget: boolean;\n};\n\nfunction capToolResultBodies(\n\tmessages: AgentMessage[],\n\tmaxChars: number,\n): { readonly messages: AgentMessage[]; readonly changed: boolean } {\n\tconst segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n\tconst markerChars = [...segmenter.segment(CURSOR_TRUNCATION_MARKER)].length;\n\tconst result = messages.slice();\n\tlet changed = false;\n\tfor (let messageIndex = result.length - 1; messageIndex >= 0; messageIndex--) {\n\t\tconst message = result[messageIndex];\n\t\tif (message.role !== \"toolResult\" || !Array.isArray(message.content)) continue;\n\t\tlet content = message.content;\n\t\tfor (let partIndex = content.length - 1; partIndex >= 0; partIndex--) {\n\t\t\tconst part = content[partIndex];\n\t\t\tif (part.type === \"image\" && typeof part.data === \"string\") continue;\n\t\t\tif (part.type !== \"text\" || typeof part.text !== \"string\") continue;\n\t\t\tconst graphemes = [...segmenter.segment(part.text)].map((item) => item.segment);\n\t\t\tif (graphemes.length <= maxChars) continue;\n\t\t\tconst kept = graphemes.slice(0, Math.max(0, maxChars - markerChars)).join(\"\");\n\t\t\tcontent = content === message.content ? content.slice() : content;\n\t\t\tcontent[partIndex] = { ...part, text: kept + CURSOR_TRUNCATION_MARKER };\n\t\t\tresult[messageIndex] = { ...message, content };\n\t\t\tchanged = true;\n\t\t}\n\t}\n\treturn { messages: result, changed };\n}\n\nfunction emptyToolResult(message: AgentMessage): AgentMessage {\n\tif (message.role !== \"toolResult\" || !Array.isArray(message.content)) return message;\n\tconst emptiedContent = message.content.map((part) =>\n\t\tpart.type === \"text\" ? { ...part, text: \"\" } : part.type === \"image\" ? { ...part, data: \"\" } : part,\n\t);\n\tconst content = emptiedContent.filter((part, index) => {\n\t\tif (index === 0) return true;\n\t\tconst previous = emptiedContent[index - 1];\n\t\tconst empty = part.type === \"text\" ? part.text === \"\" : part.type === \"image\" && part.data === \"\";\n\t\tconst previousEmpty =\n\t\t\tprevious.type === \"text\" ? previous.text === \"\" : previous.type === \"image\" && previous.data === \"\";\n\t\treturn !empty || !previousEmpty;\n\t});\n\treturn { ...message, content };\n}\n\n/**\n * Empties the oldest result bodies until the rest fits. The prefix search is\n * monotonic, so admission stays bounded instead of reserializing the whole\n * history once per tool result.\n */\nfunction blankOldestToolResults(\n\tmessages: AgentMessage[],\n\tfits: (candidate: AgentMessage[]) => boolean,\n): { readonly messages: AgentMessage[]; readonly count: number } {\n\tconst toolResultIndexes = messages.flatMap((message, index) =>\n\t\tmessage.role === \"toolResult\" && Array.isArray(message.content) ? [index] : [],\n\t);\n\tconst withEmptyPrefix = (count: number): AgentMessage[] => {\n\t\tconst candidate = messages.slice();\n\t\tfor (let i = 0; i < count; i++)\n\t\t\tcandidate[toolResultIndexes[i]] = emptyToolResult(candidate[toolResultIndexes[i]]);\n\t\treturn candidate;\n\t};\n\tlet low = 0;\n\tlet high = toolResultIndexes.length;\n\twhile (low < high) {\n\t\tconst middle = Math.floor((low + high) / 2);\n\t\tif (fits(withEmptyPrefix(middle + 1))) high = middle;\n\t\telse low = middle + 1;\n\t}\n\tconst count = Math.min(low + 1, toolResultIndexes.length);\n\treturn { messages: count > 0 ? withEmptyPrefix(count) : messages, count };\n}\n\n/**\n * Bounds one Cursor request: per-result caps first, then blanking the oldest\n * tool result bodies while the model input exceeds `budgetBytes`. Conversation\n * turns are never removed.\n */\nexport function admitCursorHistory(request: CursorAdmissionRequest): CursorAdmissionResult {\n\tconst { messages, budgetBytes } = request;\n\tif (!Array.isArray(messages) || messages.length === 0) {\n\t\treturn { messages, changed: false, blankedToolResults: 0, bytesBefore: 0, bytesAfter: 0, overBudget: false };\n\t}\n\tconst convert = request.convert ?? ((candidate: AgentMessage[]) => convertToLlm(candidate));\n\tconst capped = capToolResultBodies(messages, request.maxChars ?? CURSOR_TOOL_RESULT_MAX_CHARS);\n\tconst measure = (candidate: AgentMessage[]): number => {\n\t\tconst converted = convert(candidate);\n\t\tconst activeUserMessageIndex = converted.at(-1)?.role === \"user\" ? converted.length - 1 : -1;\n\t\treturn measureCursorModelInputSerializedBytes(converted, activeUserMessageIndex);\n\t};\n\n\tconst bytesBefore = measure(capped.messages);\n\tif (bytesBefore <= budgetBytes) {\n\t\treturn {\n\t\t\tmessages: capped.changed ? capped.messages : messages,\n\t\t\tchanged: capped.changed,\n\t\t\tblankedToolResults: 0,\n\t\t\tbytesBefore,\n\t\t\tbytesAfter: bytesBefore,\n\t\t\toverBudget: false,\n\t\t};\n\t}\n\n\tconst blanked = blankOldestToolResults(capped.messages, (candidate) => measure(candidate) <= budgetBytes);\n\tconst bytesAfter = blanked.count > 0 ? measure(blanked.messages) : bytesBefore;\n\tconst changed = capped.changed || blanked.count > 0;\n\treturn {\n\t\tmessages: changed ? blanked.messages : messages,\n\t\tchanged,\n\t\tblankedToolResults: blanked.count,\n\t\tbytesBefore,\n\t\tbytesAfter,\n\t\toverBudget: bytesAfter > budgetBytes,\n\t};\n}\n\n/**\n * Positional entry point kept for existing callers. An omitted `maxBytes`\n * applies the per-result cap only; the aggregate budget belongs to the caller\n * that knows the model window.\n */\nexport function truncateToolResultBodies(\n\tmessages: AgentMessage[] | undefined,\n\tmaxChars = CURSOR_TOOL_RESULT_MAX_CHARS,\n\tmaxBytes = Number.POSITIVE_INFINITY,\n\tconvert = (candidate: AgentMessage[]) => convertToLlm(candidate),\n): { messages: AgentMessage[] | undefined; changed: boolean } {\n\tconst admission = admitCursorHistory({ messages, budgetBytes: maxBytes, maxChars, convert });\n\treturn { messages: admission.messages, changed: admission.changed };\n}\n"]}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { type SessionWriteGrant } from "./session-worker-protocol.ts";
|
|
2
|
-
/** What a worker still writes: the paths of its live session writers plus its current session path. */
|
|
3
|
-
export interface LiveWorkerPaths {
|
|
4
|
-
readonly livePaths: readonly string[];
|
|
5
|
-
readonly sessionPath?: string;
|
|
6
|
-
}
|
|
7
|
-
/** Wire code each denial is reported with; distinct so clients can retry only the retryable one. */
|
|
8
|
-
export declare const RESERVATION_DENIAL_CODES: {
|
|
9
|
-
readonly conflict: "session_path_in_use";
|
|
10
|
-
readonly limit: "session_reservation_limit";
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Canonical session-path ownership for the shared host: one path, one worker handle.
|
|
14
|
-
*
|
|
15
|
-
* A grant lives as long as the writer that holds it. Reconciliation against a worker's
|
|
16
|
-
* reported live writers releases superseded paths, so a session replaced by /new, /resume
|
|
17
|
-
* or /fork no longer keeps its previous file hostage or spends the per-worker budget.
|
|
18
|
-
*/
|
|
19
|
-
export declare class SessionPathReservations {
|
|
20
|
-
private readonly owners;
|
|
21
|
-
owner(path: string): string | undefined;
|
|
22
|
-
count(handle: string): number;
|
|
23
|
-
/** Grants `path` to `handle`; at the budget cap, the live view first releases superseded paths. */
|
|
24
|
-
reserve(handle: string, path: string, live?: LiveWorkerPaths): SessionWriteGrant;
|
|
25
|
-
/** Releases every path of `handle` that none of its live writers owns anymore. */
|
|
26
|
-
reconcile(handle: string, live: LiveWorkerPaths): void;
|
|
27
|
-
/** Drops the whole budget of a handle; only a real worker exit may call this. */
|
|
28
|
-
releaseAll(handle: string): void;
|
|
29
|
-
}
|
|
30
|
-
//# sourceMappingURL=session-path-reservations.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-path-reservations.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/session-path-reservations.ts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAE7F,uGAAuG;AACvG,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,oGAAoG;AACpG,eAAO,MAAM,wBAAwB;uBAC1B,qBAAqB;oBACxB,2BAA2B;CACgE,CAAC;AAEpG;;;;;;GAMG;AACH,qBAAa,uBAAuB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IAEpD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEtC;IAED,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAI5B;IAED,mGAAmG;IACnG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,eAAe,GAAG,iBAAiB,CAU/E;IAED,kFAAkF;IAClF,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,IAAI,CAKrD;IAED,iFAAiF;IACjF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE/B;CACD"}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { SESSION_WORKER_LIMITS } from "./session-worker-protocol.js";
|
|
2
|
-
/** Wire code each denial is reported with; distinct so clients can retry only the retryable one. */
|
|
3
|
-
export const RESERVATION_DENIAL_CODES = {
|
|
4
|
-
conflict: "session_path_in_use",
|
|
5
|
-
limit: "session_reservation_limit",
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* Canonical session-path ownership for the shared host: one path, one worker handle.
|
|
9
|
-
*
|
|
10
|
-
* A grant lives as long as the writer that holds it. Reconciliation against a worker's
|
|
11
|
-
* reported live writers releases superseded paths, so a session replaced by /new, /resume
|
|
12
|
-
* or /fork no longer keeps its previous file hostage or spends the per-worker budget.
|
|
13
|
-
*/
|
|
14
|
-
export class SessionPathReservations {
|
|
15
|
-
constructor() {
|
|
16
|
-
this.owners = new Map();
|
|
17
|
-
}
|
|
18
|
-
owner(path) {
|
|
19
|
-
return this.owners.get(path);
|
|
20
|
-
}
|
|
21
|
-
count(handle) {
|
|
22
|
-
let count = 0;
|
|
23
|
-
for (const owner of this.owners.values())
|
|
24
|
-
if (owner === handle)
|
|
25
|
-
count++;
|
|
26
|
-
return count;
|
|
27
|
-
}
|
|
28
|
-
/** Grants `path` to `handle`; at the budget cap, the live view first releases superseded paths. */
|
|
29
|
-
reserve(handle, path, live) {
|
|
30
|
-
const owner = this.owners.get(path);
|
|
31
|
-
if (owner)
|
|
32
|
-
return owner === handle ? "granted" : "conflict";
|
|
33
|
-
if (this.count(handle) >= SESSION_WORKER_LIMITS.reservations) {
|
|
34
|
-
if (!live)
|
|
35
|
-
return "limit";
|
|
36
|
-
this.reconcile(handle, live);
|
|
37
|
-
if (this.count(handle) >= SESSION_WORKER_LIMITS.reservations)
|
|
38
|
-
return "limit";
|
|
39
|
-
}
|
|
40
|
-
this.owners.set(path, handle);
|
|
41
|
-
return "granted";
|
|
42
|
-
}
|
|
43
|
-
/** Releases every path of `handle` that none of its live writers owns anymore. */
|
|
44
|
-
reconcile(handle, live) {
|
|
45
|
-
for (const [path, owner] of this.owners) {
|
|
46
|
-
if (owner !== handle || path === live.sessionPath || live.livePaths.includes(path))
|
|
47
|
-
continue;
|
|
48
|
-
this.owners.delete(path);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
/** Drops the whole budget of a handle; only a real worker exit may call this. */
|
|
52
|
-
releaseAll(handle) {
|
|
53
|
-
for (const [path, owner] of this.owners)
|
|
54
|
-
if (owner === handle)
|
|
55
|
-
this.owners.delete(path);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
//# sourceMappingURL=session-path-reservations.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-path-reservations.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-path-reservations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAA0B,MAAM,8BAA8B,CAAC;AAQ7F,oGAAoG;AACpG,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACvC,QAAQ,EAAE,qBAAqB;IAC/B,KAAK,EAAE,2BAA2B;CACgE,CAAC;AAEpG;;;;;;GAMG;AACH,MAAM,OAAO,uBAAuB;IAApC;QACkB,WAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAqCrD,CAAC;IAnCA,KAAK,CAAC,IAAY;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,MAAc;QACnB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,KAAK,KAAK,MAAM;gBAAE,KAAK,EAAE,CAAC;QACxE,OAAO,KAAK,CAAC;IACd,CAAC;IAED,mGAAmG;IACnG,OAAO,CAAC,MAAc,EAAE,IAAY,EAAE,IAAsB;QAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK;YAAE,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;QAC5D,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,qBAAqB,CAAC,YAAY,EAAE,CAAC;YAC9D,IAAI,CAAC,IAAI;gBAAE,OAAO,OAAO,CAAC;YAC1B,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAC7B,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,qBAAqB,CAAC,YAAY;gBAAE,OAAO,OAAO,CAAC;QAC9E,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC9B,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,kFAAkF;IAClF,SAAS,CAAC,MAAc,EAAE,IAAqB;QAC9C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACzC,IAAI,KAAK,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC7F,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;IACF,CAAC;IAED,iFAAiF;IACjF,UAAU,CAAC,MAAc;QACxB,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM;YAAE,IAAI,KAAK,KAAK,MAAM;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACzF,CAAC;CACD","sourcesContent":["import type { RpcSessionRegistryError } from \"./session-registry.ts\";\nimport { SESSION_WORKER_LIMITS, type SessionWriteGrant } from \"./session-worker-protocol.ts\";\n\n/** What a worker still writes: the paths of its live session writers plus its current session path. */\nexport interface LiveWorkerPaths {\n\treadonly livePaths: readonly string[];\n\treadonly sessionPath?: string;\n}\n\n/** Wire code each denial is reported with; distinct so clients can retry only the retryable one. */\nexport const RESERVATION_DENIAL_CODES = {\n\tconflict: \"session_path_in_use\",\n\tlimit: \"session_reservation_limit\",\n} as const satisfies Record<Exclude<SessionWriteGrant, \"granted\">, RpcSessionRegistryError[\"code\"]>;\n\n/**\n * Canonical session-path ownership for the shared host: one path, one worker handle.\n *\n * A grant lives as long as the writer that holds it. Reconciliation against a worker's\n * reported live writers releases superseded paths, so a session replaced by /new, /resume\n * or /fork no longer keeps its previous file hostage or spends the per-worker budget.\n */\nexport class SessionPathReservations {\n\tprivate readonly owners = new Map<string, string>();\n\n\towner(path: string): string | undefined {\n\t\treturn this.owners.get(path);\n\t}\n\n\tcount(handle: string): number {\n\t\tlet count = 0;\n\t\tfor (const owner of this.owners.values()) if (owner === handle) count++;\n\t\treturn count;\n\t}\n\n\t/** Grants `path` to `handle`; at the budget cap, the live view first releases superseded paths. */\n\treserve(handle: string, path: string, live?: LiveWorkerPaths): SessionWriteGrant {\n\t\tconst owner = this.owners.get(path);\n\t\tif (owner) return owner === handle ? \"granted\" : \"conflict\";\n\t\tif (this.count(handle) >= SESSION_WORKER_LIMITS.reservations) {\n\t\t\tif (!live) return \"limit\";\n\t\t\tthis.reconcile(handle, live);\n\t\t\tif (this.count(handle) >= SESSION_WORKER_LIMITS.reservations) return \"limit\";\n\t\t}\n\t\tthis.owners.set(path, handle);\n\t\treturn \"granted\";\n\t}\n\n\t/** Releases every path of `handle` that none of its live writers owns anymore. */\n\treconcile(handle: string, live: LiveWorkerPaths): void {\n\t\tfor (const [path, owner] of this.owners) {\n\t\t\tif (owner !== handle || path === live.sessionPath || live.livePaths.includes(path)) continue;\n\t\t\tthis.owners.delete(path);\n\t\t}\n\t}\n\n\t/** Drops the whole budget of a handle; only a real worker exit may call this. */\n\treleaseAll(handle: string): void {\n\t\tfor (const [path, owner] of this.owners) if (owner === handle) this.owners.delete(path);\n\t}\n}\n"]}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { type SessionWorkerToHost } from "./session-worker-protocol.ts";
|
|
2
|
-
/** Builds the host message around the wait signal it must carry. */
|
|
3
|
-
export type CreditRequest = (signal: SharedArrayBuffer) => SessionWorkerToHost;
|
|
4
|
-
export interface WorkerCredit {
|
|
5
|
-
/** Blocks the worker until the host answers; a denial fails the worker with `deniedError`. */
|
|
6
|
-
exchange(message: CreditRequest, deniedError?: string, signal?: SharedArrayBuffer): void;
|
|
7
|
-
/** Installs the isolate's session-write grant, reporting denials as domain errors. */
|
|
8
|
-
installWriteReservation(canonical: (path: string) => string): void;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Synchronous host credit for a session worker.
|
|
12
|
-
*
|
|
13
|
-
* Every exchange blocks the worker thread on its own wait signal, so the host answers
|
|
14
|
-
* before the worker touches a writer, publishes output, or resizes a display.
|
|
15
|
-
*/
|
|
16
|
-
export declare function createWorkerCredit(send: (message: SessionWorkerToHost) => void, fail: (error: string) => never): WorkerCredit;
|
|
17
|
-
//# sourceMappingURL=session-worker-credit.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-worker-credit.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/session-worker-credit.ts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,mBAAmB,EAAuB,MAAM,8BAA8B,CAAC;AAEpH,oEAAoE;AACpE,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,iBAAiB,KAAK,mBAAmB,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC5B,8FAA8F;IAC9F,QAAQ,CAAC,OAAO,EAAE,aAAa,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACzF,sFAAsF;IACtF,uBAAuB,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACnE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,EAC5C,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,GAC5B,YAAY,CA2Bd"}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { installSessionWriteReservation } from "../../core/session-write-reservation.js";
|
|
2
|
-
import { SESSION_WORKER_LIMITS, WORKER_CREDIT_CODES } from "./session-worker-protocol.js";
|
|
3
|
-
/**
|
|
4
|
-
* Synchronous host credit for a session worker.
|
|
5
|
-
*
|
|
6
|
-
* Every exchange blocks the worker thread on its own wait signal, so the host answers
|
|
7
|
-
* before the worker touches a writer, publishes output, or resizes a display.
|
|
8
|
-
*/
|
|
9
|
-
export function createWorkerCredit(send, fail) {
|
|
10
|
-
const request = (message, signal) => {
|
|
11
|
-
const state = new Int32Array(signal);
|
|
12
|
-
send(message(signal));
|
|
13
|
-
Atomics.wait(state, 0, 0, SESSION_WORKER_LIMITS.controlMs);
|
|
14
|
-
return Atomics.load(state, 0);
|
|
15
|
-
};
|
|
16
|
-
return {
|
|
17
|
-
exchange(message, deniedError = "session_worker_output_denied", signal = new SharedArrayBuffer(4)) {
|
|
18
|
-
const result = request(message, signal);
|
|
19
|
-
if (result === WORKER_CREDIT_CODES.conflict)
|
|
20
|
-
fail(deniedError);
|
|
21
|
-
if (result !== WORKER_CREDIT_CODES.granted)
|
|
22
|
-
fail("session_worker_credit_timeout");
|
|
23
|
-
},
|
|
24
|
-
installWriteReservation(canonical) {
|
|
25
|
-
installSessionWriteReservation((path) => {
|
|
26
|
-
const result = request((signal) => ({ type: "reserve", path: canonical(path), signal }), new SharedArrayBuffer(4));
|
|
27
|
-
// Both denials are session-level failures the caller reports, never worker-fatal:
|
|
28
|
-
// another owner holds the path, or this worker's own budget is exhausted.
|
|
29
|
-
if (result === WORKER_CREDIT_CODES.conflict)
|
|
30
|
-
throw new Error("session_path_in_use");
|
|
31
|
-
if (result === WORKER_CREDIT_CODES.limit)
|
|
32
|
-
throw new Error("session_reservation_limit");
|
|
33
|
-
if (result !== WORKER_CREDIT_CODES.granted)
|
|
34
|
-
fail("session_worker_credit_timeout");
|
|
35
|
-
});
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
//# sourceMappingURL=session-worker-credit.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-worker-credit.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-worker-credit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,EAAE,qBAAqB,EAA4B,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAYpH;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CACjC,IAA4C,EAC5C,IAA8B;IAE9B,MAAM,OAAO,GAAG,CAAC,OAAsB,EAAE,MAAyB,EAAU,EAAE;QAC7E,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;QAC3D,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC/B,CAAC,CAAC;IACF,OAAO;QACN,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,8BAA8B,EAAE,MAAM,GAAG,IAAI,iBAAiB,CAAC,CAAC,CAAC;YAChG,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACxC,IAAI,MAAM,KAAK,mBAAmB,CAAC,QAAQ;gBAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YAC/D,IAAI,MAAM,KAAK,mBAAmB,CAAC,OAAO;gBAAE,IAAI,CAAC,+BAA+B,CAAC,CAAC;QACnF,CAAC;QACD,uBAAuB,CAAC,SAAS;YAChC,8BAA8B,CAAC,CAAC,IAAI,EAAE,EAAE;gBACvC,MAAM,MAAM,GAAG,OAAO,CACrB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAChE,IAAI,iBAAiB,CAAC,CAAC,CAAC,CACxB,CAAC;gBACF,kFAAkF;gBAClF,0EAA0E;gBAC1E,IAAI,MAAM,KAAK,mBAAmB,CAAC,QAAQ;oBAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;gBACpF,IAAI,MAAM,KAAK,mBAAmB,CAAC,KAAK;oBAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;gBACvF,IAAI,MAAM,KAAK,mBAAmB,CAAC,OAAO;oBAAE,IAAI,CAAC,+BAA+B,CAAC,CAAC;YACnF,CAAC,CAAC,CAAC;QACJ,CAAC;KACD,CAAC;AACH,CAAC","sourcesContent":["import { installSessionWriteReservation } from \"../../core/session-write-reservation.ts\";\nimport { SESSION_WORKER_LIMITS, type SessionWorkerToHost, WORKER_CREDIT_CODES } from \"./session-worker-protocol.ts\";\n\n/** Builds the host message around the wait signal it must carry. */\nexport type CreditRequest = (signal: SharedArrayBuffer) => SessionWorkerToHost;\n\nexport interface WorkerCredit {\n\t/** Blocks the worker until the host answers; a denial fails the worker with `deniedError`. */\n\texchange(message: CreditRequest, deniedError?: string, signal?: SharedArrayBuffer): void;\n\t/** Installs the isolate's session-write grant, reporting denials as domain errors. */\n\tinstallWriteReservation(canonical: (path: string) => string): void;\n}\n\n/**\n * Synchronous host credit for a session worker.\n *\n * Every exchange blocks the worker thread on its own wait signal, so the host answers\n * before the worker touches a writer, publishes output, or resizes a display.\n */\nexport function createWorkerCredit(\n\tsend: (message: SessionWorkerToHost) => void,\n\tfail: (error: string) => never,\n): WorkerCredit {\n\tconst request = (message: CreditRequest, signal: SharedArrayBuffer): number => {\n\t\tconst state = new Int32Array(signal);\n\t\tsend(message(signal));\n\t\tAtomics.wait(state, 0, 0, SESSION_WORKER_LIMITS.controlMs);\n\t\treturn Atomics.load(state, 0);\n\t};\n\treturn {\n\t\texchange(message, deniedError = \"session_worker_output_denied\", signal = new SharedArrayBuffer(4)) {\n\t\t\tconst result = request(message, signal);\n\t\t\tif (result === WORKER_CREDIT_CODES.conflict) fail(deniedError);\n\t\t\tif (result !== WORKER_CREDIT_CODES.granted) fail(\"session_worker_credit_timeout\");\n\t\t},\n\t\tinstallWriteReservation(canonical) {\n\t\t\tinstallSessionWriteReservation((path) => {\n\t\t\t\tconst result = request(\n\t\t\t\t\t(signal) => ({ type: \"reserve\", path: canonical(path), signal }),\n\t\t\t\t\tnew SharedArrayBuffer(4),\n\t\t\t\t);\n\t\t\t\t// Both denials are session-level failures the caller reports, never worker-fatal:\n\t\t\t\t// another owner holds the path, or this worker's own budget is exhausted.\n\t\t\t\tif (result === WORKER_CREDIT_CODES.conflict) throw new Error(\"session_path_in_use\");\n\t\t\t\tif (result === WORKER_CREDIT_CODES.limit) throw new Error(\"session_reservation_limit\");\n\t\t\t\tif (result !== WORKER_CREDIT_CODES.granted) fail(\"session_worker_credit_timeout\");\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type SessionWriteGrant, type WorkerDisplay } from "./session-worker-protocol.ts";
|
|
2
|
-
/** Releases a worker blocked in Atomics.wait with the host's decision. */
|
|
3
|
-
export declare function acknowledgeGrant(signal: SharedArrayBuffer, grant: SessionWriteGrant): void;
|
|
4
|
-
export declare function acknowledge(signal: SharedArrayBuffer, granted: boolean): void;
|
|
5
|
-
/** Publishes the host's current width and revision into a display exchange, then releases it. */
|
|
6
|
-
export declare function respondDisplay(signal: SharedArrayBuffer, display: WorkerDisplay): void;
|
|
7
|
-
//# sourceMappingURL=session-worker-signals.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-worker-signals.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/session-worker-signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAuB,KAAK,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE/G,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAI1F;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAE7E;AAED,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI,CAMtF"}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { WORKER_CREDIT_CODES } from "./session-worker-protocol.js";
|
|
2
|
-
/** Releases a worker blocked in Atomics.wait with the host's decision. */
|
|
3
|
-
export function acknowledgeGrant(signal, grant) {
|
|
4
|
-
const state = new Int32Array(signal);
|
|
5
|
-
Atomics.store(state, 0, WORKER_CREDIT_CODES[grant]);
|
|
6
|
-
Atomics.notify(state, 0);
|
|
7
|
-
}
|
|
8
|
-
export function acknowledge(signal, granted) {
|
|
9
|
-
acknowledgeGrant(signal, granted ? "granted" : "conflict");
|
|
10
|
-
}
|
|
11
|
-
/** Publishes the host's current width and revision into a display exchange, then releases it. */
|
|
12
|
-
export function respondDisplay(signal, display) {
|
|
13
|
-
const values = new Float64Array(signal);
|
|
14
|
-
values[1] = display.width;
|
|
15
|
-
values[2] = display.revision;
|
|
16
|
-
Atomics.store(new Int32Array(signal), 1, display.rendered ? 1 : 0);
|
|
17
|
-
acknowledge(signal, true);
|
|
18
|
-
}
|
|
19
|
-
//# sourceMappingURL=session-worker-signals.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session-worker-signals.js","sourceRoot":"","sources":["../../../src/modes/rpc/session-worker-signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,mBAAmB,EAAsB,MAAM,8BAA8B,CAAC;AAE/G,0EAA0E;AAC1E,MAAM,UAAU,gBAAgB,CAAC,MAAyB,EAAE,KAAwB;IACnF,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACrC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAyB,EAAE,OAAgB;IACtE,gBAAgB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;AAC5D,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,cAAc,CAAC,MAAyB,EAAE,OAAsB;IAC/E,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC;IAC1B,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;IAC7B,OAAO,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC3B,CAAC","sourcesContent":["import { type SessionWriteGrant, WORKER_CREDIT_CODES, type WorkerDisplay } from \"./session-worker-protocol.ts\";\n\n/** Releases a worker blocked in Atomics.wait with the host's decision. */\nexport function acknowledgeGrant(signal: SharedArrayBuffer, grant: SessionWriteGrant): void {\n\tconst state = new Int32Array(signal);\n\tAtomics.store(state, 0, WORKER_CREDIT_CODES[grant]);\n\tAtomics.notify(state, 0);\n}\n\nexport function acknowledge(signal: SharedArrayBuffer, granted: boolean): void {\n\tacknowledgeGrant(signal, granted ? \"granted\" : \"conflict\");\n}\n\n/** Publishes the host's current width and revision into a display exchange, then releases it. */\nexport function respondDisplay(signal: SharedArrayBuffer, display: WorkerDisplay): void {\n\tconst values = new Float64Array(signal);\n\tvalues[1] = display.width;\n\tvalues[2] = display.revision;\n\tAtomics.store(new Int32Array(signal), 1, display.rendered ? 1 : 0);\n\tacknowledge(signal, true);\n}\n"]}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Context ceilings Cursor itself reported, keyed by model id.
|
|
3
|
-
*
|
|
4
|
-
* `GetUsableModels` carries no window, so `CURSOR_MODEL_CAPABILITIES` is a
|
|
5
|
-
* committed guess at what each family accepts. The server states the truth on
|
|
6
|
-
* every conversation checkpoint (`tokenDetails.maxTokens`), so once a model has
|
|
7
|
-
* been observed, that value outranks the catalog for every later request.
|
|
8
|
-
*
|
|
9
|
-
* This module stays browser-safe: `providers/cursor.ts` and
|
|
10
|
-
* `cursor/store-migration.ts` materialize catalog windows and are bundled for
|
|
11
|
-
* the browser. Node processes install file persistence through
|
|
12
|
-
* `installCursorContextLimitPersistence` - see `utils/cursor-context-limit.ts`.
|
|
13
|
-
*/
|
|
14
|
-
export type CursorContextLimitPersistence = {
|
|
15
|
-
/** Limits observed by earlier processes. Called at most once per install. */
|
|
16
|
-
readonly load: () => ReadonlyMap<string, number>;
|
|
17
|
-
/** Called only when a recorded limit actually changed the store. */
|
|
18
|
-
readonly save: (limits: ReadonlyMap<string, number>) => void;
|
|
19
|
-
};
|
|
20
|
-
/** Installs the process-wide persistence port. Idempotent per port identity. */
|
|
21
|
-
export declare function installCursorContextLimitPersistence(port: CursorContextLimitPersistence): void;
|
|
22
|
-
/**
|
|
23
|
-
* Records the server-reported ceiling for `modelId`. The first checkpoint of a
|
|
24
|
-
* conversation reports 0, so non-positive and non-finite values are ignored.
|
|
25
|
-
*/
|
|
26
|
-
export declare function recordCursorContextLimit(modelId: string, maxTokens: number | undefined): void;
|
|
27
|
-
export declare function getCursorContextLimit(modelId: string): number | undefined;
|
|
28
|
-
/** The window to trust for `modelId`: what the server reported, else the catalog. */
|
|
29
|
-
export declare function resolveCursorContextWindow(modelId: string, catalogWindow: number): number;
|
|
30
|
-
/** Drops in-memory state so the next read re-hydrates from the installed port. */
|
|
31
|
-
export declare function resetCursorContextLimitStoreForTest(): void;
|
|
32
|
-
//# sourceMappingURL=context-limit-store.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context-limit-store.d.ts","sourceRoot":"","sources":["../../src/cursor/context-limit-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,6BAA6B,GAAG;IAC3C,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;CAC7D,CAAC;AAMF,gFAAgF;AAChF,wBAAgB,oCAAoC,CAAC,IAAI,EAAE,6BAA6B,GAAG,IAAI,CAI9F;AAYD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAM7F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGzE;AAED,qFAAqF;AACrF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAEzF;AAED,kFAAkF;AAClF,wBAAgB,mCAAmC,IAAI,IAAI,CAG1D"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Context ceilings Cursor itself reported, keyed by model id.
|
|
3
|
-
*
|
|
4
|
-
* `GetUsableModels` carries no window, so `CURSOR_MODEL_CAPABILITIES` is a
|
|
5
|
-
* committed guess at what each family accepts. The server states the truth on
|
|
6
|
-
* every conversation checkpoint (`tokenDetails.maxTokens`), so once a model has
|
|
7
|
-
* been observed, that value outranks the catalog for every later request.
|
|
8
|
-
*
|
|
9
|
-
* This module stays browser-safe: `providers/cursor.ts` and
|
|
10
|
-
* `cursor/store-migration.ts` materialize catalog windows and are bundled for
|
|
11
|
-
* the browser. Node processes install file persistence through
|
|
12
|
-
* `installCursorContextLimitPersistence` - see `utils/cursor-context-limit.ts`.
|
|
13
|
-
*/
|
|
14
|
-
const observedLimits = new Map();
|
|
15
|
-
let persistence;
|
|
16
|
-
let hydrated = false;
|
|
17
|
-
/** Installs the process-wide persistence port. Idempotent per port identity. */
|
|
18
|
-
export function installCursorContextLimitPersistence(port) {
|
|
19
|
-
if (persistence === port)
|
|
20
|
-
return;
|
|
21
|
-
persistence = port;
|
|
22
|
-
hydrated = false;
|
|
23
|
-
}
|
|
24
|
-
function hydrate() {
|
|
25
|
-
if (hydrated)
|
|
26
|
-
return;
|
|
27
|
-
// Set before loading: a load that observes nothing must not retry per read.
|
|
28
|
-
hydrated = true;
|
|
29
|
-
if (!persistence)
|
|
30
|
-
return;
|
|
31
|
-
for (const [modelId, maxTokens] of persistence.load()) {
|
|
32
|
-
if (!observedLimits.has(modelId))
|
|
33
|
-
observedLimits.set(modelId, maxTokens);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Records the server-reported ceiling for `modelId`. The first checkpoint of a
|
|
38
|
-
* conversation reports 0, so non-positive and non-finite values are ignored.
|
|
39
|
-
*/
|
|
40
|
-
export function recordCursorContextLimit(modelId, maxTokens) {
|
|
41
|
-
if (maxTokens === undefined || !Number.isFinite(maxTokens) || maxTokens <= 0)
|
|
42
|
-
return;
|
|
43
|
-
hydrate();
|
|
44
|
-
if (observedLimits.get(modelId) === maxTokens)
|
|
45
|
-
return;
|
|
46
|
-
observedLimits.set(modelId, maxTokens);
|
|
47
|
-
persistence?.save(observedLimits);
|
|
48
|
-
}
|
|
49
|
-
export function getCursorContextLimit(modelId) {
|
|
50
|
-
hydrate();
|
|
51
|
-
return observedLimits.get(modelId);
|
|
52
|
-
}
|
|
53
|
-
/** The window to trust for `modelId`: what the server reported, else the catalog. */
|
|
54
|
-
export function resolveCursorContextWindow(modelId, catalogWindow) {
|
|
55
|
-
return getCursorContextLimit(modelId) ?? catalogWindow;
|
|
56
|
-
}
|
|
57
|
-
/** Drops in-memory state so the next read re-hydrates from the installed port. */
|
|
58
|
-
export function resetCursorContextLimitStoreForTest() {
|
|
59
|
-
observedLimits.clear();
|
|
60
|
-
hydrated = false;
|
|
61
|
-
}
|
|
62
|
-
//# sourceMappingURL=context-limit-store.js.map
|