@aliaksei-raketski/pi-fast-mode 0.1.0 → 0.2.0
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/LICENSE +21 -0
- package/README.md +9 -1
- package/package.json +39 -38
- package/{index.ts → src/core.ts} +110 -131
- package/src/index.ts +122 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aliaksei Raketski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -22,6 +22,12 @@ pi install npm:@aliaksei-raketski/pi-fast-mode
|
|
|
22
22
|
pi install -l npm:@aliaksei-raketski/pi-fast-mode
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
Try locally from this repository:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pi -e ./packages/fast-mode
|
|
29
|
+
```
|
|
30
|
+
|
|
25
31
|
## Behavior
|
|
26
32
|
|
|
27
33
|
- Start Pi with fast mode enabled:
|
|
@@ -39,4 +45,6 @@ When fast mode is enabled but the current model is not supported, status stays a
|
|
|
39
45
|
|
|
40
46
|
- `fast on` in gray (so you can see it is enabled, but inactive for current model)
|
|
41
47
|
|
|
42
|
-
When you switch to a supported model, fast mode is applied automatically if it is enabled.
|
|
48
|
+
When you switch to a supported model, fast mode is applied automatically if it is enabled.
|
|
49
|
+
|
|
50
|
+
The fast mode toggle is stored in the current session, so it survives `/reload`, resume, and branch navigation.
|
package/package.json
CHANGED
|
@@ -1,40 +1,41 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
2
|
+
"name": "@aliaksei-raketski/pi-fast-mode",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Pi extension that enables fast-mode payload tuning for supported Claude/OpenAI models.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"fast-mode"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"homepage": "https://github.com/aliaksei-raketski/pi-packages/tree/main/packages/fast-mode",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/aliaksei-raketski/pi-packages/issues"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/aliaksei-raketski/pi-packages.git",
|
|
19
|
+
"directory": "packages/fast-mode"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"registry": "https://registry.npmjs.org/"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
30
|
+
},
|
|
31
|
+
"pi": {
|
|
32
|
+
"extensions": [
|
|
33
|
+
"./src/index.ts"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"src",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
]
|
|
40
41
|
}
|
package/{index.ts → src/core.ts}
RENAMED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
export const FAST_COMMAND = "fast";
|
|
2
|
+
export const FAST_FLAG = "fast";
|
|
3
|
+
export const FAST_STATUS_KEY = "fast";
|
|
4
|
+
export const FAST_STATE_CUSTOM_TYPE = "fast";
|
|
2
5
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const FAST_STATUS_KEY = "fast";
|
|
6
|
-
|
|
7
|
-
const FAST_ON_TEXT = "fast on";
|
|
8
|
-
const FAST_OFF_TEXT = "fast off";
|
|
6
|
+
export const FAST_ON_TEXT = "fast on";
|
|
7
|
+
export const FAST_OFF_TEXT = "fast off";
|
|
9
8
|
|
|
10
9
|
const FAST_SPEED = "fast";
|
|
11
10
|
const FAST_BETA = "fast-mode-2026-02-01";
|
|
@@ -17,41 +16,65 @@ const CLAUDE_API = "anthropic-messages";
|
|
|
17
16
|
const OPENAI_PROVIDER = "openai-codex";
|
|
18
17
|
const OPENAI_API = "openai-codex-responses";
|
|
19
18
|
|
|
20
|
-
type
|
|
19
|
+
export type FastModel = {
|
|
20
|
+
provider: string;
|
|
21
|
+
api?: string;
|
|
22
|
+
id: string;
|
|
23
|
+
headers?: Record<string, string>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type FastContext = {
|
|
27
|
+
model?: FastModel;
|
|
28
|
+
modelRegistry: {
|
|
29
|
+
isUsingOAuth(model: FastModel): boolean;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type FastFeature = {
|
|
21
34
|
provider: string;
|
|
22
35
|
api: string;
|
|
23
36
|
supportedModels: Set<string>;
|
|
24
37
|
injectionKey: string;
|
|
25
38
|
injectionValue: string;
|
|
26
39
|
unsupportedModelMessage: string;
|
|
27
|
-
isEligible?: (ctx:
|
|
40
|
+
isEligible?: (ctx: FastContext) => string | undefined;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type FastModeState = {
|
|
44
|
+
enabled: boolean;
|
|
28
45
|
};
|
|
29
46
|
|
|
30
|
-
type
|
|
47
|
+
export type FastStateEntryData = {
|
|
31
48
|
enabled: boolean;
|
|
32
49
|
};
|
|
33
50
|
|
|
34
|
-
type
|
|
51
|
+
export type FastSessionEntry = {
|
|
52
|
+
type: string;
|
|
53
|
+
customType?: string;
|
|
54
|
+
data?: unknown;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type CurrentModelStatus = {
|
|
35
58
|
feature?: FastFeature;
|
|
36
59
|
isSupported: boolean;
|
|
37
60
|
reason?: string;
|
|
38
61
|
};
|
|
39
62
|
|
|
40
|
-
type
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
headers?: Record<string, string>;
|
|
63
|
+
export type FastStatusView = {
|
|
64
|
+
text: string;
|
|
65
|
+
color: "accent" | "muted";
|
|
44
66
|
};
|
|
45
67
|
|
|
46
|
-
|
|
68
|
+
type PayloadRecord = Record<string, unknown>;
|
|
69
|
+
|
|
70
|
+
export const FEATURES: FastFeature[] = [
|
|
47
71
|
{
|
|
48
72
|
provider: CLAUDE_PROVIDER,
|
|
49
73
|
api: CLAUDE_API,
|
|
50
74
|
supportedModels: new Set(["claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8"]),
|
|
51
75
|
injectionKey: "speed",
|
|
52
76
|
injectionValue: FAST_SPEED,
|
|
53
|
-
unsupportedModelMessage:
|
|
54
|
-
"Fast mode is only available for Claude Opus 4.6, 4.7, and 4.8",
|
|
77
|
+
unsupportedModelMessage: "Fast mode is only available for Claude Opus 4.6, 4.7, and 4.8",
|
|
55
78
|
},
|
|
56
79
|
{
|
|
57
80
|
provider: OPENAI_PROVIDER,
|
|
@@ -67,29 +90,30 @@ const FEATURES: FastFeature[] = [
|
|
|
67
90
|
},
|
|
68
91
|
];
|
|
69
92
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
function getSessionState(ctx: ExtensionContext): FastModeState {
|
|
73
|
-
let state = sessionStates.get(ctx.sessionManager);
|
|
74
|
-
if (!state) {
|
|
75
|
-
state = { enabled: false };
|
|
76
|
-
sessionStates.set(ctx.sessionManager, state);
|
|
77
|
-
}
|
|
78
|
-
return state;
|
|
93
|
+
export function createFastModeState(enabled = false): FastModeState {
|
|
94
|
+
return { enabled };
|
|
79
95
|
}
|
|
80
96
|
|
|
81
|
-
function
|
|
82
|
-
return
|
|
97
|
+
export function createFastStateEntryData(state: FastModeState): FastStateEntryData {
|
|
98
|
+
return { enabled: state.enabled };
|
|
83
99
|
}
|
|
84
100
|
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
export function restoreFastModeState(
|
|
102
|
+
entries: Iterable<FastSessionEntry>,
|
|
103
|
+
defaultEnabled = false,
|
|
104
|
+
): FastModeState {
|
|
105
|
+
let enabled = defaultEnabled;
|
|
106
|
+
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (entry.type !== "custom" || entry.customType !== FAST_STATE_CUSTOM_TYPE) continue;
|
|
109
|
+
if (!isPayloadRecord(entry.data) || typeof entry.data.enabled !== "boolean") continue;
|
|
110
|
+
enabled = entry.data.enabled;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return createFastModeState(enabled);
|
|
90
114
|
}
|
|
91
115
|
|
|
92
|
-
function getCurrentModelStatus(ctx:
|
|
116
|
+
export function getCurrentModelStatus(ctx: FastContext): CurrentModelStatus {
|
|
93
117
|
const model = ctx.model;
|
|
94
118
|
if (!model) {
|
|
95
119
|
return {
|
|
@@ -110,11 +134,15 @@ function getCurrentModelStatus(ctx: ExtensionContext): CurrentModelStatus {
|
|
|
110
134
|
};
|
|
111
135
|
}
|
|
112
136
|
|
|
113
|
-
const matchingFeature = featuresForBackend.find((feature) =>
|
|
137
|
+
const matchingFeature = featuresForBackend.find((feature) =>
|
|
138
|
+
feature.supportedModels.has(model.id),
|
|
139
|
+
);
|
|
114
140
|
if (!matchingFeature) {
|
|
115
141
|
return {
|
|
116
142
|
isSupported: false,
|
|
117
|
-
reason:
|
|
143
|
+
reason:
|
|
144
|
+
featuresForBackend[0]?.unsupportedModelMessage ??
|
|
145
|
+
"Current model does not support fast mode",
|
|
118
146
|
};
|
|
119
147
|
}
|
|
120
148
|
|
|
@@ -135,15 +163,50 @@ function getCurrentModelStatus(ctx: ExtensionContext): CurrentModelStatus {
|
|
|
135
163
|
};
|
|
136
164
|
}
|
|
137
165
|
|
|
166
|
+
export function syncFeatureState(ctx: FastContext, state: FastModeState): CurrentModelStatus {
|
|
167
|
+
const modelStatus = getCurrentModelStatus(ctx);
|
|
168
|
+
syncClaudeBetaHeader(ctx, state, modelStatus);
|
|
169
|
+
return modelStatus;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getStatusView(
|
|
173
|
+
state: FastModeState,
|
|
174
|
+
modelStatus: CurrentModelStatus,
|
|
175
|
+
): FastStatusView {
|
|
176
|
+
return {
|
|
177
|
+
text: state.enabled ? FAST_ON_TEXT : FAST_OFF_TEXT,
|
|
178
|
+
color: state.enabled && modelStatus.isSupported ? "accent" : "muted",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function getFastPayload(
|
|
183
|
+
payload: unknown,
|
|
184
|
+
ctx: FastContext,
|
|
185
|
+
state: FastModeState,
|
|
186
|
+
modelStatus: CurrentModelStatus,
|
|
187
|
+
): PayloadRecord | undefined {
|
|
188
|
+
if (!state.enabled) return undefined;
|
|
189
|
+
if (!modelStatus.isSupported || !modelStatus.feature) return undefined;
|
|
190
|
+
if (!isPayloadRecord(payload)) return undefined;
|
|
191
|
+
if (payload.model !== ctx.model?.id) return undefined;
|
|
192
|
+
if (modelStatus.feature.injectionKey in payload) return undefined;
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
...payload,
|
|
196
|
+
[modelStatus.feature.injectionKey]: modelStatus.feature.injectionValue,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
138
200
|
function syncClaudeBetaHeader(
|
|
139
|
-
ctx:
|
|
201
|
+
ctx: FastContext,
|
|
140
202
|
state: FastModeState,
|
|
141
203
|
modelStatus: CurrentModelStatus,
|
|
142
204
|
): void {
|
|
143
|
-
const model = ctx.model
|
|
205
|
+
const model = ctx.model;
|
|
144
206
|
if (!model || model.provider !== CLAUDE_PROVIDER || model.api !== CLAUDE_API) return;
|
|
145
207
|
|
|
146
|
-
const shouldEnable =
|
|
208
|
+
const shouldEnable =
|
|
209
|
+
state.enabled && modelStatus.isSupported && modelStatus.feature?.provider === CLAUDE_PROVIDER;
|
|
147
210
|
const headers = { ...(model.headers ?? {}) };
|
|
148
211
|
const existing = splitBetaHeader(headers["anthropic-beta"] ?? headers["Anthropic-Beta"]);
|
|
149
212
|
const requiredBase = ctx.modelRegistry.isUsingOAuth(model) ? CLAUDE_CODE_OAUTH_BETAS : [];
|
|
@@ -160,97 +223,13 @@ function syncClaudeBetaHeader(
|
|
|
160
223
|
model.headers = headers;
|
|
161
224
|
}
|
|
162
225
|
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
function updateStatus(ctx: ExtensionContext, state: FastModeState, modelStatus: CurrentModelStatus): void {
|
|
170
|
-
if (!ctx.hasUI) return;
|
|
171
|
-
|
|
172
|
-
const statusText = state.enabled ? FAST_ON_TEXT : FAST_OFF_TEXT;
|
|
173
|
-
const isActiveForCurrentModel = state.enabled && modelStatus.isSupported;
|
|
174
|
-
ctx.ui.setStatus(
|
|
175
|
-
FAST_STATUS_KEY,
|
|
176
|
-
ctx.ui.theme.fg(isActiveForCurrentModel ? "accent" : "muted", statusText),
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function getFastPayload(
|
|
181
|
-
payload: unknown,
|
|
182
|
-
ctx: ExtensionContext,
|
|
183
|
-
state: FastModeState,
|
|
184
|
-
modelStatus: CurrentModelStatus,
|
|
185
|
-
): PayloadRecord | undefined {
|
|
186
|
-
if (!state.enabled) return undefined;
|
|
187
|
-
if (!modelStatus.isSupported || !modelStatus.feature) return undefined;
|
|
188
|
-
if (!isPayloadRecord(payload)) return undefined;
|
|
189
|
-
if (payload.model !== ctx.model?.id) return undefined;
|
|
190
|
-
if (modelStatus.feature.injectionKey in payload) return undefined;
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
...payload,
|
|
194
|
-
[modelStatus.feature.injectionKey]: modelStatus.feature.injectionValue,
|
|
195
|
-
};
|
|
226
|
+
function splitBetaHeader(value: string | undefined): string[] {
|
|
227
|
+
return (value ?? "")
|
|
228
|
+
.split(",")
|
|
229
|
+
.map((value) => value.trim())
|
|
230
|
+
.filter(Boolean);
|
|
196
231
|
}
|
|
197
232
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
description: "Start with fast mode enabled",
|
|
201
|
-
type: "boolean",
|
|
202
|
-
default: false,
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
pi.on("session_start", (_event, ctx) => {
|
|
206
|
-
const state = getSessionState(ctx);
|
|
207
|
-
state.enabled = pi.getFlag(FAST_FLAG) === true;
|
|
208
|
-
const modelStatus = syncFeatureState(ctx, state);
|
|
209
|
-
updateStatus(ctx, state, modelStatus);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
pi.on("model_select", (_event, ctx) => {
|
|
213
|
-
const state = getSessionState(ctx);
|
|
214
|
-
const modelStatus = syncFeatureState(ctx, state);
|
|
215
|
-
updateStatus(ctx, state, modelStatus);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
pi.on("before_provider_request", (event, ctx) => {
|
|
219
|
-
const state = getSessionState(ctx);
|
|
220
|
-
const modelStatus = syncFeatureState(ctx, state);
|
|
221
|
-
updateStatus(ctx, state, modelStatus);
|
|
222
|
-
return getFastPayload(event.payload, ctx, state, modelStatus);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
pi.on("session_shutdown", (_event, ctx) => {
|
|
226
|
-
if (!ctx.hasUI) return;
|
|
227
|
-
ctx.ui.setStatus(FAST_STATUS_KEY, undefined);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
pi.registerCommand(FAST_COMMAND, {
|
|
231
|
-
description: "Toggle fast mode",
|
|
232
|
-
getArgumentCompletions: () => null,
|
|
233
|
-
handler: async (args, ctx) => {
|
|
234
|
-
if (args.trim()) {
|
|
235
|
-
ctx.ui.notify("Usage: /fast", "warning");
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const state = getSessionState(ctx);
|
|
240
|
-
state.enabled = !state.enabled;
|
|
241
|
-
|
|
242
|
-
const modelStatus = syncFeatureState(ctx, state);
|
|
243
|
-
updateStatus(ctx, state, modelStatus);
|
|
244
|
-
|
|
245
|
-
ctx.ui.notify(`Fast mode is now ${state.enabled ? "on" : "off"}.`, "info");
|
|
246
|
-
|
|
247
|
-
if (state.enabled && !modelStatus.isSupported) {
|
|
248
|
-
const detail = modelStatus.reason ? ` (${modelStatus.reason})` : "";
|
|
249
|
-
ctx.ui.notify(
|
|
250
|
-
`Current model is not supported for fast mode${detail}. Fast mode will apply automatically once you switch to a supported model.`,
|
|
251
|
-
"warning",
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
},
|
|
255
|
-
});
|
|
233
|
+
function isPayloadRecord(payload: unknown): payload is PayloadRecord {
|
|
234
|
+
return typeof payload === "object" && payload !== null && !Array.isArray(payload);
|
|
256
235
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
FAST_COMMAND,
|
|
4
|
+
FAST_FLAG,
|
|
5
|
+
FAST_STATE_CUSTOM_TYPE,
|
|
6
|
+
FAST_STATUS_KEY,
|
|
7
|
+
createFastModeState,
|
|
8
|
+
createFastStateEntryData,
|
|
9
|
+
getFastPayload,
|
|
10
|
+
getStatusView,
|
|
11
|
+
restoreFastModeState,
|
|
12
|
+
syncFeatureState,
|
|
13
|
+
type FastContext,
|
|
14
|
+
type FastModeState,
|
|
15
|
+
type FastModel,
|
|
16
|
+
type CurrentModelStatus,
|
|
17
|
+
} from "./core.ts";
|
|
18
|
+
|
|
19
|
+
const sessionStates = new WeakMap<object, FastModeState>();
|
|
20
|
+
|
|
21
|
+
function getSessionState(ctx: ExtensionContext): FastModeState {
|
|
22
|
+
let state = sessionStates.get(ctx.sessionManager);
|
|
23
|
+
if (!state) {
|
|
24
|
+
state = createFastModeState();
|
|
25
|
+
sessionStates.set(ctx.sessionManager, state);
|
|
26
|
+
}
|
|
27
|
+
return state;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function restoreSessionState(ctx: ExtensionContext, defaultEnabled: boolean): FastModeState {
|
|
31
|
+
const state = restoreFastModeState(ctx.sessionManager.getBranch(), defaultEnabled);
|
|
32
|
+
sessionStates.set(ctx.sessionManager, state);
|
|
33
|
+
return state;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function toFastContext(ctx: ExtensionContext): FastContext {
|
|
37
|
+
return {
|
|
38
|
+
model: ctx.model as FastModel | undefined,
|
|
39
|
+
modelRegistry: {
|
|
40
|
+
isUsingOAuth: (model) =>
|
|
41
|
+
ctx.modelRegistry.isUsingOAuth(model as NonNullable<typeof ctx.model>),
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function updateStatus(
|
|
47
|
+
ctx: ExtensionContext,
|
|
48
|
+
state: FastModeState,
|
|
49
|
+
modelStatus: CurrentModelStatus,
|
|
50
|
+
): void {
|
|
51
|
+
if (!ctx.hasUI) return;
|
|
52
|
+
|
|
53
|
+
const status = getStatusView(state, modelStatus);
|
|
54
|
+
ctx.ui.setStatus(FAST_STATUS_KEY, ctx.ui.theme.fg(status.color, status.text));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default function fastMode(pi: ExtensionAPI) {
|
|
58
|
+
pi.registerFlag(FAST_FLAG, {
|
|
59
|
+
description: "Start with fast mode enabled",
|
|
60
|
+
type: "boolean",
|
|
61
|
+
default: false,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
pi.on("session_start", (_event, ctx) => {
|
|
65
|
+
const state = restoreSessionState(ctx, pi.getFlag(FAST_FLAG) === true);
|
|
66
|
+
const modelStatus = syncFeatureState(toFastContext(ctx), state);
|
|
67
|
+
updateStatus(ctx, state, modelStatus);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
pi.on("model_select", (_event, ctx) => {
|
|
71
|
+
const state = getSessionState(ctx);
|
|
72
|
+
const modelStatus = syncFeatureState(toFastContext(ctx), state);
|
|
73
|
+
updateStatus(ctx, state, modelStatus);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
77
|
+
const state = restoreSessionState(ctx, pi.getFlag(FAST_FLAG) === true);
|
|
78
|
+
const modelStatus = syncFeatureState(toFastContext(ctx), state);
|
|
79
|
+
updateStatus(ctx, state, modelStatus);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
83
|
+
const state = getSessionState(ctx);
|
|
84
|
+
const fastContext = toFastContext(ctx);
|
|
85
|
+
const modelStatus = syncFeatureState(fastContext, state);
|
|
86
|
+
updateStatus(ctx, state, modelStatus);
|
|
87
|
+
return getFastPayload(event.payload, fastContext, state, modelStatus);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
91
|
+
if (!ctx.hasUI) return;
|
|
92
|
+
ctx.ui.setStatus(FAST_STATUS_KEY, undefined);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
pi.registerCommand(FAST_COMMAND, {
|
|
96
|
+
description: "Toggle fast mode",
|
|
97
|
+
getArgumentCompletions: () => null,
|
|
98
|
+
handler: async (args, ctx) => {
|
|
99
|
+
if (args.trim()) {
|
|
100
|
+
ctx.ui.notify("Usage: /fast", "warning");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const state = getSessionState(ctx);
|
|
105
|
+
state.enabled = !state.enabled;
|
|
106
|
+
pi.appendEntry(FAST_STATE_CUSTOM_TYPE, createFastStateEntryData(state));
|
|
107
|
+
|
|
108
|
+
const modelStatus = syncFeatureState(toFastContext(ctx), state);
|
|
109
|
+
updateStatus(ctx, state, modelStatus);
|
|
110
|
+
|
|
111
|
+
ctx.ui.notify(`Fast mode is now ${state.enabled ? "on" : "off"}.`, "info");
|
|
112
|
+
|
|
113
|
+
if (state.enabled && !modelStatus.isSupported) {
|
|
114
|
+
const detail = modelStatus.reason ? ` (${modelStatus.reason})` : "";
|
|
115
|
+
ctx.ui.notify(
|
|
116
|
+
`Current model is not supported for fast mode${detail}. Fast mode will apply automatically once you switch to a supported model.`,
|
|
117
|
+
"warning",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|