pi-plus 0.1.8 → 0.1.10
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 +9 -0
- package/README.md +55 -151
- package/anthropic.js +82 -0
- package/bedrock-converse-stream.js +90 -0
- package/core/export-html/template.css +1066 -0
- package/core/export-html/template.html +55 -0
- package/core/export-html/template.js +1864 -0
- package/core/export-html/vendor/highlight.min.js +1213 -0
- package/core/export-html/vendor/marked.min.js +78 -0
- package/github-copilot.js +9 -0
- package/image-resize-worker.js +9 -0
- package/kimi-coding.js +9 -0
- package/modes/interactive/assets/clankolas.png +0 -0
- package/modes/interactive/theme/dark.json +91 -0
- package/modes/interactive/theme/light.json +90 -0
- package/modes/interactive/theme/theme-schema.json +356 -0
- package/openai-codex.js +82 -0
- package/openrouter.js +82 -0
- package/package.json +23 -43
- package/pp.js +2040 -0
- package/radius.js +82 -0
- package/xai.js +9 -0
- package/LICENSE +0 -21
- package/src/auto-compact.ts +0 -43
- package/src/context-usage.ts +0 -91
- package/src/footer-render.ts +0 -248
- package/src/footer.ts +0 -139
- package/src/index.ts +0 -172
- package/src/logger.ts +0 -56
- package/src/model-config.ts +0 -184
- package/src/percentages.ts +0 -99
- package/src/settings.ts +0 -101
- package/src/usage.ts +0 -201
package/src/index.ts
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pi-plus: Claude Code-exact context usage detection.
|
|
3
|
-
*
|
|
4
|
-
* A pi extension that measures context usage with the same arithmetic as
|
|
5
|
-
* Claude Code (usage-based prefix count + rough tail estimation, CC's context
|
|
6
|
-
* window resolution and warning thresholds) and shows it in the footer's
|
|
7
|
-
* context field via a custom footer. When pi's built-in auto-compaction is
|
|
8
|
-
* disabled, the plugin takes over and calls ctx.compact() at CC's
|
|
9
|
-
* auto-compact threshold (effective window − 13,000 tokens).
|
|
10
|
-
*
|
|
11
|
-
* Install: copy/symlink this repo's src into ~/.pi/agent/extensions/pi-plus/
|
|
12
|
-
* or run `pi -e ./src/index.ts`.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
16
|
-
import type { SettingDefinition } from "@juanibiapina/pi-extension-settings";
|
|
17
|
-
import { initialTriggerState, shouldFireAutoCompact, type AutoCompactTriggerState } from "./auto-compact.ts";
|
|
18
|
-
import { computePluginContextUsage } from "./context-usage.ts";
|
|
19
|
-
import { installFooter } from "./footer.ts";
|
|
20
|
-
import { isAutoCompactEnabled } from "./percentages.ts";
|
|
21
|
-
import { appendUsageLog } from "./logger.ts";
|
|
22
|
-
import { loadPiPlusSettings } from "./settings.ts";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export default function (pi: ExtensionAPI) {
|
|
26
|
-
// Register our settings so they appear in the /extension-settings UI
|
|
27
|
-
// (provided by @juanibiapina/pi-extension-settings when installed).
|
|
28
|
-
pi.events.emit("pi-extension-settings:register", {
|
|
29
|
-
name: "pi-plus",
|
|
30
|
-
settings: [
|
|
31
|
-
{
|
|
32
|
-
id: "autoCompact",
|
|
33
|
-
label: "Auto-compact at threshold",
|
|
34
|
-
description: "Call /compact automatically when usage crosses the auto-compact threshold",
|
|
35
|
-
defaultValue: "true",
|
|
36
|
-
values: ["true", "false"],
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: "contextWindow",
|
|
40
|
-
label: "Context window override",
|
|
41
|
-
description: "Override the model-derived context window (tokens). Empty = model default.",
|
|
42
|
-
defaultValue: "",
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: "autoCompactThreshold",
|
|
46
|
-
label: "Auto-compact threshold",
|
|
47
|
-
description: "Absolute token threshold for auto-compact. Empty = effective window − 13k.",
|
|
48
|
-
defaultValue: "",
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
id: "autoCompactPercent",
|
|
52
|
-
label: "Auto-compact percent",
|
|
53
|
-
description: "Threshold as % of effective window (1–100, testing). Empty = off.",
|
|
54
|
-
defaultValue: "",
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: "autoCompactWindowCap",
|
|
58
|
-
label: "Auto-compact window cap",
|
|
59
|
-
description: "Cap the window before threshold math (tokens). Empty = off.",
|
|
60
|
-
defaultValue: "",
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
id: "maxOutputTokens",
|
|
64
|
-
label: "Max output tokens",
|
|
65
|
-
description: "Override max output tokens (bounded by the model's limit). Empty = default.",
|
|
66
|
-
defaultValue: "",
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
id: "disableOutputTokenCap",
|
|
70
|
-
label: "Disable 8k output cap",
|
|
71
|
-
description: "Use the model's native default max output instead of the 8k slot cap",
|
|
72
|
-
defaultValue: "false",
|
|
73
|
-
values: ["false", "true"],
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
id: "blockingLimit",
|
|
77
|
-
label: "Blocking limit",
|
|
78
|
-
description: "Absolute token usage that blocks further work. Empty = effective window − 3k.",
|
|
79
|
-
defaultValue: "",
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
id: "customFooter",
|
|
83
|
-
label: "Custom footer (context field)",
|
|
84
|
-
description:
|
|
85
|
-
"Replace pi's built-in footer so its context field shows pi-plus's CC-exact usage (% used of the auto-compact threshold). Takes effect on the next session or /reload.",
|
|
86
|
-
defaultValue: "true",
|
|
87
|
-
values: ["true", "false"],
|
|
88
|
-
},
|
|
89
|
-
] satisfies SettingDefinition[],
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Edge-trigger state for the CC-threshold auto-compact. Reset per session;
|
|
93
|
-
// re-arms automatically once post-compact usage drops below the threshold.
|
|
94
|
-
let compactTrigger: AutoCompactTriggerState = initialTriggerState();
|
|
95
|
-
|
|
96
|
-
function logUsageCheck(ctx: ExtensionContext): void {
|
|
97
|
-
const model = ctx.model;
|
|
98
|
-
if (!model) return;
|
|
99
|
-
const settings = loadPiPlusSettings();
|
|
100
|
-
const computed = computePluginContextUsage(ctx, settings);
|
|
101
|
-
if (!computed) return;
|
|
102
|
-
const state = computed.state;
|
|
103
|
-
appendUsageLog({
|
|
104
|
-
event: "usage_check",
|
|
105
|
-
source: "indicator",
|
|
106
|
-
model: model.id,
|
|
107
|
-
tokens: computed.tokens,
|
|
108
|
-
percentLeft: state.percentLeft,
|
|
109
|
-
aboveWarning: state.isAboveWarningThreshold,
|
|
110
|
-
aboveError: state.isAboveErrorThreshold,
|
|
111
|
-
aboveAutoCompact: state.isAboveAutoCompactThreshold,
|
|
112
|
-
atBlockingLimit: state.isAtBlockingLimit,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Check the token overflow against CC's auto-compact threshold and fire
|
|
118
|
-
* ctx.compact() on a fresh crossing. Called on every tool task finish
|
|
119
|
-
* (tool_execution_end) — pi performs the compaction between agent steps
|
|
120
|
-
* and continues the turn with the compacted context.
|
|
121
|
-
*
|
|
122
|
-
* Set PI_PLUS_NO_AUTO_COMPACT=1 to disable, and disable pi's
|
|
123
|
-
* built-in auto-compaction (settings.json "compaction": {"enabled": false})
|
|
124
|
-
* so this threshold governs instead of pi's window − 16,384.
|
|
125
|
-
*/
|
|
126
|
-
function maybeAutoCompact(ctx: ExtensionContext): void {
|
|
127
|
-
const model = ctx.model;
|
|
128
|
-
if (!model) return;
|
|
129
|
-
const settings = loadPiPlusSettings();
|
|
130
|
-
if (!isAutoCompactEnabled(settings)) return;
|
|
131
|
-
if (process.env.PI_PLUS_NO_AUTO_COMPACT !== undefined) return;
|
|
132
|
-
const computed = computePluginContextUsage(ctx, settings);
|
|
133
|
-
if (!computed) return;
|
|
134
|
-
const tokenCount = computed.tokens;
|
|
135
|
-
const threshold = computed.threshold;
|
|
136
|
-
const fired = shouldFireAutoCompact(compactTrigger, tokenCount, threshold);
|
|
137
|
-
appendUsageLog({
|
|
138
|
-
event: "usage_check",
|
|
139
|
-
source: "auto_compact",
|
|
140
|
-
model: model.id,
|
|
141
|
-
tokens: tokenCount,
|
|
142
|
-
threshold,
|
|
143
|
-
belowThreshold: tokenCount < threshold,
|
|
144
|
-
fired,
|
|
145
|
-
});
|
|
146
|
-
if (!fired) return;
|
|
147
|
-
try {
|
|
148
|
-
ctx.compact();
|
|
149
|
-
appendUsageLog({ event: "auto_compact_fired", model: model.id, tokens: tokenCount, threshold });
|
|
150
|
-
} catch (error) {
|
|
151
|
-
ctx.ui.notify?.(`pi-plus: auto-compact failed to start: ${String(error)}`, "warning");
|
|
152
|
-
appendUsageLog({ event: "auto_compact_error", model: model.id, error: String(error) });
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
pi.on("session_start", (_event, ctx) => {
|
|
157
|
-
compactTrigger = initialTriggerState();
|
|
158
|
-
installFooter(ctx);
|
|
159
|
-
logUsageCheck(ctx);
|
|
160
|
-
maybeAutoCompact(ctx);
|
|
161
|
-
});
|
|
162
|
-
pi.on("turn_end", (_event, ctx) => logUsageCheck(ctx));
|
|
163
|
-
pi.on("agent_settled", (_event, ctx) => logUsageCheck(ctx));
|
|
164
|
-
// Check token overflow on every tool task finish.
|
|
165
|
-
pi.on("tool_execution_end", (_event, ctx) => {
|
|
166
|
-
logUsageCheck(ctx);
|
|
167
|
-
maybeAutoCompact(ctx);
|
|
168
|
-
});
|
|
169
|
-
pi.on("message_end", (_event, ctx) => logUsageCheck(ctx));
|
|
170
|
-
pi.on("model_select", (_event, ctx) => logUsageCheck(ctx));
|
|
171
|
-
pi.on("session_compact", (_event, ctx) => logUsageCheck(ctx));
|
|
172
|
-
}
|
package/src/logger.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Usage-detection logging to a log file inside pi's agent directory
|
|
3
|
-
* (`$PI_CODING_AGENT_DIR` or `~/.pi/agent`, same resolution as pi's
|
|
4
|
-
* getAgentDir() in coding-agent config.ts).
|
|
5
|
-
*
|
|
6
|
-
* pi itself has no general runtime agent log (only an on-demand debug log
|
|
7
|
-
* and a TUI crash log), so this plugin appends its own `pi-plus.log` there.
|
|
8
|
-
* Lines are JSON — one object per usage check — so they can be inspected or
|
|
9
|
-
* replayed easily.
|
|
10
|
-
*
|
|
11
|
-
* Logging is strictly best-effort: any filesystem error is swallowed so the
|
|
12
|
-
* plugin can never crash or disturb the host agent.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { appendFileSync } from "node:fs";
|
|
16
|
-
import { homedir } from "node:os";
|
|
17
|
-
import { join } from "node:path";
|
|
18
|
-
|
|
19
|
-
/** Mirrors pi's ENV_AGENT_DIR (`PI_CODING_AGENT_DIR` for the stock build). */
|
|
20
|
-
const ENV_AGENT_DIR = "PI_CODING_AGENT_DIR";
|
|
21
|
-
|
|
22
|
-
/** Set PI_PLUS_NO_LOG=1 to disable file logging. */
|
|
23
|
-
export function isLogEnabled(): boolean {
|
|
24
|
-
const v = process.env.PI_PLUS_NO_LOG;
|
|
25
|
-
return v === undefined || v === "" || v === "0" || v.toLowerCase() === "false";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function expandTildePath(p: string): string {
|
|
29
|
-
if (p === "~") return homedir();
|
|
30
|
-
if (p.startsWith("~/")) return join(homedir(), p.slice(2));
|
|
31
|
-
return p;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function getAgentDir(): string {
|
|
35
|
-
const envDir = process.env[ENV_AGENT_DIR];
|
|
36
|
-
return envDir ? expandTildePath(envDir) : join(homedir(), ".pi", "agent");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function getPiPlusLogPath(): string {
|
|
40
|
-
return join(getAgentDir(), "pi-plus.log");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type UsageLogFields = Record<string, string | number | boolean | null | undefined>;
|
|
44
|
-
|
|
45
|
-
export function formatUsageLogLine(fields: UsageLogFields): string {
|
|
46
|
-
return JSON.stringify({ ts: new Date().toISOString(), ...fields }) + "\n";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function appendUsageLog(fields: UsageLogFields): void {
|
|
50
|
-
if (!isLogEnabled()) return;
|
|
51
|
-
try {
|
|
52
|
-
appendFileSync(getPiPlusLogPath(), formatUsageLogLine(fields));
|
|
53
|
-
} catch {
|
|
54
|
-
// Best-effort: never propagate logging failures into the host agent.
|
|
55
|
-
}
|
|
56
|
-
}
|
package/src/model-config.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ports of Claude Code's context-window and max-output-token resolution.
|
|
3
|
-
*
|
|
4
|
-
* Sources (claude-code repo):
|
|
5
|
-
* - src/utils/context.ts — getContextWindowForModel, getModelMaxOutputTokens
|
|
6
|
-
* - src/services/api/claude.ts — getMaxOutputTokensForModel (slot-reservation cap)
|
|
7
|
-
* - src/services/compact/autoCompact.ts — getEffectiveContextWindowSize, getAutoCompactThreshold
|
|
8
|
-
*
|
|
9
|
-
* This plugin intentionally supports NO environment variables (CC's
|
|
10
|
-
* CLAUDE_CODE_* env overrides included). Everything user-configurable lives
|
|
11
|
-
* in the `pi-plus` section of pi's settings.json — see settings.ts.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
export const MODEL_CONTEXT_WINDOW_DEFAULT = 200_000;
|
|
15
|
-
export const COMPACT_MAX_OUTPUT_TOKENS = 20_000;
|
|
16
|
-
export const CAPPED_DEFAULT_MAX_TOKENS = 8_000;
|
|
17
|
-
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000;
|
|
18
|
-
|
|
19
|
-
const MAX_OUTPUT_TOKENS_DEFAULT = 32_000;
|
|
20
|
-
const MAX_OUTPUT_TOKENS_UPPER_LIMIT = 64_000;
|
|
21
|
-
|
|
22
|
-
const ONE_MILLION = 1_000_000;
|
|
23
|
-
|
|
24
|
-
/** The `pi-plus` settings.json section, as it affects model config. */
|
|
25
|
-
export interface ModelConfigSettings {
|
|
26
|
-
/** Override the model-derived context window (tokens). */
|
|
27
|
-
contextWindow?: number;
|
|
28
|
-
/** Cap the window before threshold math (CC: CLAUDE_CODE_AUTO_COMPACT_WINDOW). */
|
|
29
|
-
autoCompactWindowCap?: number;
|
|
30
|
-
/** Override max output tokens, bounded by the model's upper limit. */
|
|
31
|
-
maxOutputTokens?: number;
|
|
32
|
-
/** Disable the 8k slot-reservation cap on default max output tokens. */
|
|
33
|
-
disableOutputTokenCap?: boolean;
|
|
34
|
-
/** Override the auto-compact threshold directly (absolute tokens). */
|
|
35
|
-
autoCompactThreshold?: number;
|
|
36
|
-
/** Threshold as a percent (1–100) of the effective window (testing). */
|
|
37
|
-
autoCompactPercent?: number;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** CC: has1mContext — explicit `[1m]` model suffix opt-in. */
|
|
41
|
-
export function has1mContext(modelId: string): boolean {
|
|
42
|
-
return /\[1m\]/i.test(modelId);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* CC: getContextWindowForModel.
|
|
47
|
-
* Resolution order: `[1m]` suffix → model capability (pi's registry
|
|
48
|
-
* `contextWindow` plays the role of CC's `max_input_tokens` capability,
|
|
49
|
-
* with the same >= 100k gate) → 200k default.
|
|
50
|
-
*/
|
|
51
|
-
export function resolveContextWindow(modelId: string, modelContextWindow?: number): number {
|
|
52
|
-
if (has1mContext(modelId)) return ONE_MILLION;
|
|
53
|
-
|
|
54
|
-
if (modelContextWindow && modelContextWindow >= 100_000) {
|
|
55
|
-
return modelContextWindow;
|
|
56
|
-
}
|
|
57
|
-
return MODEL_CONTEXT_WINDOW_DEFAULT;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* CC: getModelMaxOutputTokens — hardcoded per-model table, overridden by the
|
|
62
|
-
* model capability's max_tokens (pi's registry `maxTokens`) when >= 4096.
|
|
63
|
-
*/
|
|
64
|
-
export function getModelMaxOutputTokens(
|
|
65
|
-
modelId: string,
|
|
66
|
-
modelMaxTokens?: number,
|
|
67
|
-
): { default: number; upperLimit: number } {
|
|
68
|
-
const m = modelId.toLowerCase();
|
|
69
|
-
let defaultTokens: number;
|
|
70
|
-
let upperLimit: number;
|
|
71
|
-
|
|
72
|
-
if (m.includes("opus-4-6")) {
|
|
73
|
-
defaultTokens = 64_000;
|
|
74
|
-
upperLimit = 128_000;
|
|
75
|
-
} else if (m.includes("sonnet-4-6")) {
|
|
76
|
-
defaultTokens = 32_000;
|
|
77
|
-
upperLimit = 128_000;
|
|
78
|
-
} else if (m.includes("opus-4-5") || m.includes("sonnet-4") || m.includes("haiku-4")) {
|
|
79
|
-
defaultTokens = 32_000;
|
|
80
|
-
upperLimit = 64_000;
|
|
81
|
-
} else if (m.includes("opus-4-1") || m.includes("opus-4")) {
|
|
82
|
-
defaultTokens = 32_000;
|
|
83
|
-
upperLimit = 32_000;
|
|
84
|
-
} else if (m.includes("claude-3-opus")) {
|
|
85
|
-
defaultTokens = 4_096;
|
|
86
|
-
upperLimit = 4_096;
|
|
87
|
-
} else if (m.includes("claude-3-sonnet")) {
|
|
88
|
-
defaultTokens = 8_192;
|
|
89
|
-
upperLimit = 8_192;
|
|
90
|
-
} else if (m.includes("claude-3-haiku")) {
|
|
91
|
-
defaultTokens = 4_096;
|
|
92
|
-
upperLimit = 4_096;
|
|
93
|
-
} else if (m.includes("3-5-sonnet") || m.includes("3-5-haiku")) {
|
|
94
|
-
defaultTokens = 8_192;
|
|
95
|
-
upperLimit = 8_192;
|
|
96
|
-
} else if (m.includes("3-7-sonnet")) {
|
|
97
|
-
defaultTokens = 32_000;
|
|
98
|
-
upperLimit = 64_000;
|
|
99
|
-
} else {
|
|
100
|
-
defaultTokens = MAX_OUTPUT_TOKENS_DEFAULT;
|
|
101
|
-
upperLimit = MAX_OUTPUT_TOKENS_UPPER_LIMIT;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (modelMaxTokens && modelMaxTokens >= 4_096) {
|
|
105
|
-
upperLimit = modelMaxTokens;
|
|
106
|
-
defaultTokens = Math.min(defaultTokens, upperLimit);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return { default: defaultTokens, upperLimit };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* CC: getMaxOutputTokensForModel — applies the slot-reservation cap
|
|
114
|
-
* (tengu_otk_slot_v1: default output dropped to 8k), unless disabled in
|
|
115
|
-
* settings, then the `maxOutputTokens` bounded settings override.
|
|
116
|
-
*/
|
|
117
|
-
export function getMaxOutputTokensForModel(
|
|
118
|
-
modelId: string,
|
|
119
|
-
modelMaxTokens?: number,
|
|
120
|
-
settings?: ModelConfigSettings,
|
|
121
|
-
): number {
|
|
122
|
-
const { default: modelDefault, upperLimit } = getModelMaxOutputTokens(modelId, modelMaxTokens);
|
|
123
|
-
// Math.min keeps models with lower native defaults (e.g. claude-3-opus at
|
|
124
|
-
// 4k) at their native value.
|
|
125
|
-
const defaultTokens = settings?.disableOutputTokenCap
|
|
126
|
-
? modelDefault
|
|
127
|
-
: Math.min(modelDefault, CAPPED_DEFAULT_MAX_TOKENS);
|
|
128
|
-
|
|
129
|
-
if (settings?.maxOutputTokens !== undefined && settings.maxOutputTokens > 0) {
|
|
130
|
-
return Math.min(settings.maxOutputTokens, upperLimit);
|
|
131
|
-
}
|
|
132
|
-
return defaultTokens;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* CC: getEffectiveContextWindowSize — context window minus the reserved
|
|
137
|
-
* output tokens (min(maxOutput, 20k)), optionally capped by
|
|
138
|
-
* `autoCompactWindowCap` from settings.
|
|
139
|
-
*
|
|
140
|
-
* `settings.contextWindow` replaces the model-derived window first.
|
|
141
|
-
*/
|
|
142
|
-
export function getEffectiveContextWindowSize(
|
|
143
|
-
modelId: string,
|
|
144
|
-
modelContextWindow?: number,
|
|
145
|
-
modelMaxTokens?: number,
|
|
146
|
-
settings?: ModelConfigSettings,
|
|
147
|
-
): number {
|
|
148
|
-
const reservedTokensForSummary = Math.min(
|
|
149
|
-
getMaxOutputTokensForModel(modelId, modelMaxTokens, settings),
|
|
150
|
-
COMPACT_MAX_OUTPUT_TOKENS,
|
|
151
|
-
);
|
|
152
|
-
let contextWindow = settings?.contextWindow ?? resolveContextWindow(modelId, modelContextWindow);
|
|
153
|
-
const windowCap = settings?.autoCompactWindowCap;
|
|
154
|
-
if (windowCap !== undefined && windowCap > 0) {
|
|
155
|
-
contextWindow = Math.min(contextWindow, windowCap);
|
|
156
|
-
}
|
|
157
|
-
return contextWindow - reservedTokensForSummary;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* CC: getAutoCompactThreshold — effective window minus the 13k autocompact
|
|
162
|
-
* buffer. `autoCompactPercent` (1–100) lowers it for testing.
|
|
163
|
-
*
|
|
164
|
-
* `settings.autoCompactThreshold` (absolute tokens) wins outright;
|
|
165
|
-
* otherwise `settings.contextWindow` / `autoCompactWindowCap` feed the
|
|
166
|
-
* CC math via getEffectiveContextWindowSize.
|
|
167
|
-
*/
|
|
168
|
-
export function getAutoCompactThreshold(
|
|
169
|
-
modelId: string,
|
|
170
|
-
modelContextWindow?: number,
|
|
171
|
-
modelMaxTokens?: number,
|
|
172
|
-
settings?: ModelConfigSettings,
|
|
173
|
-
): number {
|
|
174
|
-
if (settings?.autoCompactThreshold !== undefined) return settings.autoCompactThreshold;
|
|
175
|
-
const effective = getEffectiveContextWindowSize(modelId, modelContextWindow, modelMaxTokens, settings);
|
|
176
|
-
const autocompactThreshold = effective - AUTOCOMPACT_BUFFER_TOKENS;
|
|
177
|
-
|
|
178
|
-
const percent = settings?.autoCompactPercent;
|
|
179
|
-
if (percent !== undefined && !Number.isNaN(percent) && percent > 0 && percent <= 100) {
|
|
180
|
-
const percentageThreshold = Math.floor(effective * (percent / 100));
|
|
181
|
-
return Math.min(percentageThreshold, autocompactThreshold);
|
|
182
|
-
}
|
|
183
|
-
return autocompactThreshold;
|
|
184
|
-
}
|
package/src/percentages.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ports of Claude Code's statusline percentage and token-warning thresholds.
|
|
3
|
-
*
|
|
4
|
-
* Sources (claude-code repo):
|
|
5
|
-
* - src/utils/context.ts — calculateContextPercentages
|
|
6
|
-
* - src/services/compact/autoCompact.ts — calculateTokenWarningState, isAutoCompactEnabled
|
|
7
|
-
*
|
|
8
|
-
* No environment variables: the auto-compact on/off switch and blocking-limit
|
|
9
|
-
* override live in the `pi-plus` settings.json section (see settings.ts).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
getAutoCompactThreshold,
|
|
14
|
-
getEffectiveContextWindowSize,
|
|
15
|
-
type ModelConfigSettings,
|
|
16
|
-
} from "./model-config.ts";
|
|
17
|
-
import type { UsageSnapshot } from "./usage.ts";
|
|
18
|
-
|
|
19
|
-
export const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000;
|
|
20
|
-
export const ERROR_THRESHOLD_BUFFER_TOKENS = 20_000;
|
|
21
|
-
export const MANUAL_COMPACT_BUFFER_TOKENS = 3_000;
|
|
22
|
-
|
|
23
|
-
/** pi-plus settings keys that affect the warning state. */
|
|
24
|
-
export interface PercentagesSettings extends ModelConfigSettings {
|
|
25
|
-
/** Master switch for auto-compaction (default true). */
|
|
26
|
-
autoCompact?: boolean;
|
|
27
|
-
/** Override the blocking limit (absolute tokens). */
|
|
28
|
-
blockingLimit?: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* CC: isAutoCompactEnabled — CC consults env vars and a user-config flag;
|
|
33
|
-
* this port uses only the `autoCompact` settings.json flag (default true).
|
|
34
|
-
*/
|
|
35
|
-
export function isAutoCompactEnabled(settings?: Pick<PercentagesSettings, "autoCompact">): boolean {
|
|
36
|
-
return settings?.autoCompact !== false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* CC: calculateContextPercentages — the statusline percentage. Numerator is
|
|
41
|
-
* input + cache creation + cache read (output EXCLUDED), denominator is the
|
|
42
|
-
* raw context window (not the effective one). pi's `cacheWrite` includes the
|
|
43
|
-
* 1h cache subset, matching CC's cache_creation_input_tokens.
|
|
44
|
-
*/
|
|
45
|
-
export function calculateContextPercentages(
|
|
46
|
-
currentUsage: Pick<UsageSnapshot, "input" | "cacheRead" | "cacheWrite"> | null,
|
|
47
|
-
contextWindowSize: number,
|
|
48
|
-
): { used: number | null; remaining: number | null } {
|
|
49
|
-
if (!currentUsage) return { used: null, remaining: null };
|
|
50
|
-
const totalInputTokens = currentUsage.input + currentUsage.cacheWrite + currentUsage.cacheRead;
|
|
51
|
-
const usedPercentage = Math.round((totalInputTokens / contextWindowSize) * 100);
|
|
52
|
-
const clampedUsed = Math.min(100, Math.max(0, usedPercentage));
|
|
53
|
-
return { used: clampedUsed, remaining: 100 - clampedUsed };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface TokenWarningState {
|
|
57
|
-
percentLeft: number;
|
|
58
|
-
isAboveWarningThreshold: boolean;
|
|
59
|
-
isAboveErrorThreshold: boolean;
|
|
60
|
-
isAboveAutoCompactThreshold: boolean;
|
|
61
|
-
isAtBlockingLimit: boolean;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* CC: calculateTokenWarningState — percent left until the (auto-compact or
|
|
66
|
-
* effective) threshold and the warning/error/blocking threshold crossings.
|
|
67
|
-
* Warning and error buffers are both 20k in CC, so the two crossings are
|
|
68
|
-
* identical unless overrides differ.
|
|
69
|
-
*/
|
|
70
|
-
export function calculateTokenWarningState(
|
|
71
|
-
tokenUsage: number,
|
|
72
|
-
modelId: string,
|
|
73
|
-
modelContextWindow?: number,
|
|
74
|
-
modelMaxTokens?: number,
|
|
75
|
-
settings?: PercentagesSettings,
|
|
76
|
-
): TokenWarningState {
|
|
77
|
-
const autoCompact = isAutoCompactEnabled(settings);
|
|
78
|
-
const autoCompactThreshold = getAutoCompactThreshold(modelId, modelContextWindow, modelMaxTokens, settings);
|
|
79
|
-
const effectiveContextWindow = getEffectiveContextWindowSize(modelId, modelContextWindow, modelMaxTokens, settings);
|
|
80
|
-
const threshold = autoCompact ? autoCompactThreshold : effectiveContextWindow;
|
|
81
|
-
|
|
82
|
-
const percentLeft = Math.max(0, Math.round(((threshold - tokenUsage) / threshold) * 100));
|
|
83
|
-
|
|
84
|
-
const warningThreshold = threshold - WARNING_THRESHOLD_BUFFER_TOKENS;
|
|
85
|
-
const errorThreshold = threshold - ERROR_THRESHOLD_BUFFER_TOKENS;
|
|
86
|
-
|
|
87
|
-
const defaultBlockingLimit = effectiveContextWindow - MANUAL_COMPACT_BUFFER_TOKENS;
|
|
88
|
-
const configuredBlocking = settings?.blockingLimit;
|
|
89
|
-
const blockingLimit =
|
|
90
|
-
configuredBlocking !== undefined && configuredBlocking > 0 ? configuredBlocking : defaultBlockingLimit;
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
percentLeft,
|
|
94
|
-
isAboveWarningThreshold: tokenUsage >= warningThreshold,
|
|
95
|
-
isAboveErrorThreshold: tokenUsage >= errorThreshold,
|
|
96
|
-
isAboveAutoCompactThreshold: autoCompact && tokenUsage >= autoCompactThreshold,
|
|
97
|
-
isAtBlockingLimit: tokenUsage >= blockingLimit,
|
|
98
|
-
};
|
|
99
|
-
}
|
package/src/settings.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pi-plus settings, read through the pi ecosystem's standard settings
|
|
3
|
-
* functions (`getSetting` from @juanibiapina/pi-extension-settings) rather
|
|
4
|
-
* than by locating settings files ourselves.
|
|
5
|
-
*
|
|
6
|
-
* Storage: global values live in `~/.pi/agent/settings-extensions.json`,
|
|
7
|
-
* project values in `.pi/settings-extensions.json` (project wins) — both
|
|
8
|
-
* under a `"pi-plus"` extension section:
|
|
9
|
-
*
|
|
10
|
-
* ```json
|
|
11
|
-
* {
|
|
12
|
-
* "pi-plus": {
|
|
13
|
-
* "autoCompact": "true",
|
|
14
|
-
* "contextWindow": "256000",
|
|
15
|
-
* "autoCompactWindowCap": "200000",
|
|
16
|
-
* "maxOutputTokens": "8192",
|
|
17
|
-
* "disableOutputTokenCap": "false",
|
|
18
|
-
* "autoCompactThreshold": "235000",
|
|
19
|
-
* "autoCompactPercent": "50",
|
|
20
|
-
* "blockingLimit": "190000"
|
|
21
|
-
* }
|
|
22
|
-
* }
|
|
23
|
-
* ```
|
|
24
|
-
*
|
|
25
|
-
* Values are strings (the extension-settings convention); each accessor
|
|
26
|
-
* below parses and validates. Unset or invalid values fall back to the
|
|
27
|
-
* hardcoded CC-derived defaults in model-config.ts / percentages.ts.
|
|
28
|
-
*
|
|
29
|
-
* This plugin supports NO Claude Code environment variables — this settings
|
|
30
|
-
* system is the only configuration surface. (The plugin's own
|
|
31
|
-
* PI_PLUS_NO_AUTO_COMPACT and PI_PLUS_NO_LOG env switches live in index.ts /
|
|
32
|
-
* logger.ts.)
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
import { getSetting, type SettingStorageOptions } from "@juanibiapina/pi-extension-settings";
|
|
36
|
-
import type { ModelConfigSettings } from "./model-config.ts";
|
|
37
|
-
|
|
38
|
-
export interface PiPlusSettings extends ModelConfigSettings {
|
|
39
|
-
/** Master switch for auto-compaction (default true). */
|
|
40
|
-
autoCompact?: boolean;
|
|
41
|
-
/** Override the blocking limit (absolute tokens). */
|
|
42
|
-
blockingLimit?: number;
|
|
43
|
-
/** Master switch for the custom footer takeover (default true). */
|
|
44
|
-
customFooter?: boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const EXTENSION_NAME = "pi-plus";
|
|
48
|
-
|
|
49
|
-
function raw(settingId: string, options?: SettingStorageOptions): string | undefined {
|
|
50
|
-
const value = getSetting(EXTENSION_NAME, settingId, undefined, options);
|
|
51
|
-
return value === undefined || value === "" ? undefined : value;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function positiveInt(settingId: string, options?: SettingStorageOptions): number | undefined {
|
|
55
|
-
const value = raw(settingId, options);
|
|
56
|
-
if (value === undefined) return undefined;
|
|
57
|
-
const parsed = Number(value);
|
|
58
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
|
|
59
|
-
return Math.floor(parsed);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function percentValue(settingId: string, options?: SettingStorageOptions): number | undefined {
|
|
63
|
-
const parsed = positiveInt(settingId, options);
|
|
64
|
-
if (parsed === undefined || parsed > 100) return undefined;
|
|
65
|
-
return parsed;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function booleanValue(settingId: string, options?: SettingStorageOptions): boolean | undefined {
|
|
69
|
-
const value = raw(settingId, options);
|
|
70
|
-
if (value === undefined) return undefined;
|
|
71
|
-
const normalized = value.trim().toLowerCase();
|
|
72
|
-
if (["true", "1", "yes", "on"].includes(normalized)) return true;
|
|
73
|
-
if (["false", "0", "no", "off"].includes(normalized)) return false;
|
|
74
|
-
return undefined;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Load the merged pi-plus settings (local overrides global). */
|
|
78
|
-
export function loadPiPlusSettings(options?: SettingStorageOptions): PiPlusSettings {
|
|
79
|
-
const settings: PiPlusSettings = {};
|
|
80
|
-
|
|
81
|
-
const contextWindow = positiveInt("contextWindow", options);
|
|
82
|
-
const autoCompactWindowCap = positiveInt("autoCompactWindowCap", options);
|
|
83
|
-
const maxOutputTokens = positiveInt("maxOutputTokens", options);
|
|
84
|
-
const autoCompactThreshold = positiveInt("autoCompactThreshold", options);
|
|
85
|
-
const autoCompactPercent = percentValue("autoCompactPercent", options);
|
|
86
|
-
const blockingLimit = positiveInt("blockingLimit", options);
|
|
87
|
-
const autoCompact = booleanValue("autoCompact", options);
|
|
88
|
-
const disableOutputTokenCap = booleanValue("disableOutputTokenCap", options);
|
|
89
|
-
const customFooter = booleanValue("customFooter", options);
|
|
90
|
-
|
|
91
|
-
if (contextWindow !== undefined) settings.contextWindow = contextWindow;
|
|
92
|
-
if (autoCompactWindowCap !== undefined) settings.autoCompactWindowCap = autoCompactWindowCap;
|
|
93
|
-
if (maxOutputTokens !== undefined) settings.maxOutputTokens = maxOutputTokens;
|
|
94
|
-
if (autoCompactThreshold !== undefined) settings.autoCompactThreshold = autoCompactThreshold;
|
|
95
|
-
if (autoCompactPercent !== undefined) settings.autoCompactPercent = autoCompactPercent;
|
|
96
|
-
if (blockingLimit !== undefined) settings.blockingLimit = blockingLimit;
|
|
97
|
-
if (autoCompact !== undefined) settings.autoCompact = autoCompact;
|
|
98
|
-
if (disableOutputTokenCap !== undefined) settings.disableOutputTokenCap = disableOutputTokenCap;
|
|
99
|
-
if (customFooter !== undefined) settings.customFooter = customFooter;
|
|
100
|
-
return settings;
|
|
101
|
-
}
|