@diegopetrucci/pi-extensions 0.1.74 → 0.1.78
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 +2 -0
- package/README.md +17 -12
- package/extensions/agent-workflow-audit/README.md +1 -1
- package/extensions/agent-workflow-audit/index.ts +1631 -1009
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/README.md +3 -1
- package/extensions/annotate-git-diff/clipboard.ts +109 -100
- package/extensions/annotate-git-diff/git.ts +846 -768
- package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
- package/extensions/annotate-git-diff/index.ts +712 -671
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-git-diff/prompt.ts +45 -45
- package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
- package/extensions/annotate-git-diff/review-server.ts +337 -311
- package/extensions/annotate-git-diff/types.ts +132 -132
- package/extensions/annotate-git-diff/ui.ts +27 -27
- package/extensions/annotate-git-diff/watch.ts +74 -74
- package/extensions/annotate-git-diff/web/app.js +1947 -1884
- package/extensions/annotate-git-diff/web/index.html +5 -6
- package/extensions/annotate-last-message/README.md +1 -1
- package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
- package/extensions/annotate-last-message/index.ts +210 -206
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/annotate-last-message/prompt.ts +87 -66
- package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
- package/extensions/annotate-last-message/session.ts +94 -89
- package/extensions/annotate-last-message/theme.ts +199 -193
- package/extensions/annotate-last-message/types.ts +23 -23
- package/extensions/annotate-last-message/ui.ts +27 -25
- package/extensions/annotate-last-message/web/app.js +4 -7
- package/extensions/annotate-last-message/web/index.html +3 -3
- package/extensions/annotate-last-message/web/md-renderer.js +1 -1
- package/extensions/brrr/README.md +1 -1
- package/extensions/brrr/index.ts +213 -191
- package/extensions/brrr/package.json +1 -1
- package/extensions/claude-fast/README.md +5 -79
- package/extensions/claude-fast/index.ts +31 -289
- package/extensions/claude-fast/package.json +2 -3
- package/extensions/code-reviewer/README.md +6 -6
- package/extensions/code-reviewer/index.ts +1825 -1373
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/README.md +1 -1
- package/extensions/confirm-destructive/index.ts +50 -52
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/README.md +9 -9
- package/extensions/context-cap/index.ts +313 -305
- package/extensions/context-cap/package.json +2 -2
- package/extensions/context-inspector/index.ts +1211 -1064
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/README.md +4 -3
- package/extensions/contrarian/index.ts +1863 -1737
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/index.ts +37 -41
- package/extensions/dirty-repo-guard/package.json +1 -1
- package/extensions/dynamic-context-pruning/README.md +11 -43
- package/extensions/dynamic-context-pruning/index.ts +2131 -2105
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +52 -14
- package/extensions/fast/index.ts +513 -317
- package/extensions/fast/package.json +1 -1
- package/extensions/git-footer/README.md +1 -1
- package/extensions/git-footer/index.ts +458 -411
- package/extensions/git-footer/package.json +2 -3
- package/extensions/gnosis/index.ts +180 -130
- package/extensions/gnosis/package.json +1 -1
- package/extensions/inline-bash/index.ts +87 -103
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/README.md +1 -1
- package/extensions/librarian/index.ts +1877 -1419
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/README.md +5 -5
- package/extensions/minimal-footer/index.ts +987 -1012
- package/extensions/minimal-footer/minimal-footer.example.json +1 -1
- package/extensions/minimal-footer/openai-usage.ts +195 -190
- package/extensions/minimal-footer/package.json +1 -1
- package/extensions/notify/index.ts +222 -230
- package/extensions/notify/package.json +1 -1
- package/extensions/openai-fast/README.md +5 -73
- package/extensions/openai-fast/index.ts +32 -272
- package/extensions/openai-fast/package.json +2 -3
- package/extensions/oracle/README.md +4 -3
- package/extensions/oracle/index.ts +1740 -1627
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/README.md +3 -0
- package/extensions/permission-gate/index.ts +785 -612
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/permission-gate/powershell-safety.ts +840 -793
- package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
- package/extensions/project-mcp-json/README.md +199 -0
- package/extensions/project-mcp-json/config.ts +564 -0
- package/extensions/project-mcp-json/index.ts +119 -0
- package/extensions/project-mcp-json/package.json +38 -0
- package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
- package/extensions/quiet-tools/README.md +30 -19
- package/extensions/quiet-tools/index.ts +376 -291
- package/extensions/quiet-tools/package.json +2 -2
- package/extensions/review/README.md +2 -2
- package/extensions/review/index.ts +1745 -1872
- package/extensions/review/package.json +1 -1
- package/extensions/todo/index.ts +280 -280
- package/extensions/todo/package.json +1 -1
- package/extensions/triage-comments/README.md +6 -4
- package/extensions/triage-comments/index.ts +2620 -1888
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +9 -6
- package/assets/social-preview.png +0 -0
- package/extensions/claude-fast/claude-fast.example.json +0 -4
- package/extensions/openai-fast/openai-fast.example.json +0 -4
package/extensions/fast/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
CONFIG_DIR_NAME,
|
|
5
|
+
getAgentDir,
|
|
6
|
+
type ExtensionAPI,
|
|
7
|
+
type ExtensionContext,
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
const EXTENSION_ID = "fast";
|
|
@@ -21,402 +21,598 @@ const OPENAI_PROVIDER_ID = "openai-codex";
|
|
|
21
21
|
const OPENAI_API_ID = "openai-codex-responses";
|
|
22
22
|
const OPENAI_FAST_SERVICE_TIER = "priority";
|
|
23
23
|
const OPENAI_SUPPORTED_MODELS = new Set([
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
"gpt-5.5",
|
|
25
|
+
"gpt-5.6-sol",
|
|
26
|
+
"gpt-5.6-terra",
|
|
27
|
+
"gpt-5.6-luna",
|
|
28
|
+
"gpt-6-astra",
|
|
29
|
+
"gpt-6.1-sol",
|
|
30
|
+
"gpt-6-sol",
|
|
31
|
+
"gpt-6-luna",
|
|
32
32
|
]);
|
|
33
33
|
const OPENAI_API_PROVIDER_ID = "openai";
|
|
34
34
|
const OPENAI_API_IDS = new Set(["openai-responses", "openai-completions"]);
|
|
35
35
|
const OPENAI_API_FAST_SERVICE_TIER = "fast";
|
|
36
|
-
const OPENAI_API_SUPPORTED_MODELS = new Set([
|
|
36
|
+
const OPENAI_API_SUPPORTED_MODELS = new Set([
|
|
37
|
+
"gpt-6.1-sol",
|
|
38
|
+
"gpt-6-astra",
|
|
39
|
+
"gpt-6-sol",
|
|
40
|
+
"gpt-6-luna",
|
|
41
|
+
]);
|
|
42
|
+
const OPENAI_ULTRAFAST_MODEL = "gpt-6-astra";
|
|
43
|
+
const OPENAI_ULTRAFAST_API_ID = "openai-responses";
|
|
44
|
+
const OPENAI_ULTRAFAST_SERVICE_TIER = "ultrafast";
|
|
45
|
+
const OPENAI_CANONICAL_BASE_URL = "https://api.openai.com/v1";
|
|
37
46
|
|
|
38
47
|
const DEFAULT_CONFIG: FastConfig = {
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
enabled: false,
|
|
49
|
+
showStatus: true,
|
|
41
50
|
};
|
|
42
51
|
|
|
43
52
|
type FastOverride = "auto" | "on" | "off";
|
|
44
53
|
type FastProvider = "anthropic" | "openai";
|
|
45
54
|
|
|
46
55
|
type FastConfig = {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
/** Default Fast-mode state when there is no session override. */
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
/** Show a compact `fast` status when Fast mode is active for the current model. */
|
|
59
|
+
showStatus: boolean;
|
|
51
60
|
};
|
|
52
61
|
|
|
53
62
|
type SessionState = {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
config: FastConfig;
|
|
64
|
+
override: FastOverride;
|
|
65
|
+
/** Session-only Ultrafast selection; never loaded from configuration. */
|
|
66
|
+
ultrafast: boolean;
|
|
67
|
+
lastInjectedAt?: number;
|
|
68
|
+
lastInjectedModel?: string;
|
|
58
69
|
};
|
|
59
70
|
|
|
60
71
|
type ProjectConfigContext = {
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
cwd: string;
|
|
73
|
+
isProjectTrusted?: () => boolean;
|
|
63
74
|
};
|
|
64
75
|
|
|
65
76
|
type RecursivePartial<T> = {
|
|
66
|
-
|
|
77
|
+
[P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
|
|
67
78
|
};
|
|
68
79
|
|
|
69
80
|
type PayloadRecord = Record<string, unknown>;
|
|
70
81
|
|
|
71
82
|
type AnthropicHeaderCompat = {
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
forceAdaptiveThinking?: boolean;
|
|
84
|
+
supportsEagerToolInputStreaming?: boolean;
|
|
74
85
|
};
|
|
75
86
|
|
|
76
87
|
type Eligibility = {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
eligible: boolean;
|
|
89
|
+
modelKey: string;
|
|
90
|
+
provider?: FastProvider;
|
|
91
|
+
serviceTier?: string;
|
|
92
|
+
reason?: string;
|
|
82
93
|
};
|
|
83
94
|
|
|
84
95
|
function readConfigFile(path: string): RecursivePartial<FastConfig> {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
if (!existsSync(path)) return {};
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
100
|
+
return isPayloadRecord(parsed) ? (parsed as RecursivePartial<FastConfig>) : {};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(`Warning: Could not parse ${path}: ${error}`);
|
|
103
|
+
return {};
|
|
104
|
+
}
|
|
94
105
|
}
|
|
95
106
|
|
|
96
107
|
function normalizeBoolean(value: unknown, fallback: boolean): boolean {
|
|
97
|
-
|
|
108
|
+
return typeof value === "boolean" ? value : fallback;
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
function mergeConfig(base: FastConfig, overrides: RecursivePartial<FastConfig>): FastConfig {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
return {
|
|
113
|
+
enabled: normalizeBoolean(overrides.enabled, base.enabled),
|
|
114
|
+
showStatus: normalizeBoolean(overrides.showStatus, base.showStatus),
|
|
115
|
+
};
|
|
105
116
|
}
|
|
106
117
|
|
|
107
118
|
function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
|
|
108
|
-
|
|
119
|
+
return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
function findProjectConfigPath(cwd: string): string {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
let current = cwd;
|
|
124
|
+
while (true) {
|
|
125
|
+
const candidate = join(current, CONFIG_DIR_NAME, "fast.json");
|
|
126
|
+
if (existsSync(candidate)) return candidate;
|
|
127
|
+
|
|
128
|
+
const parent = dirname(current);
|
|
129
|
+
if (parent === current) return join(cwd, CONFIG_DIR_NAME, "fast.json");
|
|
130
|
+
current = parent;
|
|
131
|
+
}
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
function loadConfig(ctx: ProjectConfigContext): FastConfig {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "fast.json"));
|
|
136
|
+
const projectConfig = canReadProjectConfig(ctx)
|
|
137
|
+
? readConfigFile(findProjectConfigPath(ctx.cwd))
|
|
138
|
+
: {};
|
|
139
|
+
return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
|
|
127
140
|
}
|
|
128
141
|
|
|
129
142
|
function isPayloadRecord(payload: unknown): payload is PayloadRecord {
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function modelKey(ctx: ExtensionContext): string {
|
|
134
|
-
const model = ctx.model;
|
|
135
|
-
return model ? `${model.provider}/${model.id}` : "no-model";
|
|
143
|
+
return typeof payload === "object" && payload !== null && !Array.isArray(payload);
|
|
136
144
|
}
|
|
137
145
|
|
|
138
146
|
function isFastEnabled(state: SessionState): boolean {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
147
|
+
if (state.override === "on") return true;
|
|
148
|
+
if (state.override === "off") return false;
|
|
149
|
+
return state.config.enabled;
|
|
142
150
|
}
|
|
143
151
|
|
|
144
152
|
function describeMode(state: SessionState): string {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
if (state.override === "on") return "on (session override)";
|
|
154
|
+
if (state.override === "off") return "off (session override)";
|
|
155
|
+
return state.config.enabled ? "on (config default)" : "off (config default)";
|
|
148
156
|
}
|
|
149
157
|
|
|
150
158
|
function getEligibility(ctx: ExtensionContext): Eligibility {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
159
|
+
const model = ctx.model;
|
|
160
|
+
if (!model) {
|
|
161
|
+
return { eligible: false, modelKey: "no-model", reason: "no model is selected" };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const key = `${model.provider}/${model.id}`;
|
|
165
|
+
if (model.provider === ANTHROPIC_PROVIDER_ID) {
|
|
166
|
+
if (model.api !== ANTHROPIC_API_ID) {
|
|
167
|
+
return {
|
|
168
|
+
eligible: false,
|
|
169
|
+
modelKey: key,
|
|
170
|
+
reason: `current API is ${model.api}, not ${ANTHROPIC_API_ID}`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!ANTHROPIC_SUPPORTED_MODELS.has(model.id)) {
|
|
175
|
+
return {
|
|
176
|
+
eligible: false,
|
|
177
|
+
modelKey: key,
|
|
178
|
+
reason: "Fast mode is only enabled for Claude Opus 4.8, Claude Opus 5, and Claude Opus 5.5",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { eligible: true, modelKey: key, provider: "anthropic" };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (model.provider === OPENAI_PROVIDER_ID) {
|
|
186
|
+
if (model.api !== OPENAI_API_ID) {
|
|
187
|
+
return {
|
|
188
|
+
eligible: false,
|
|
189
|
+
modelKey: key,
|
|
190
|
+
reason: `current API is ${model.api}, not ${OPENAI_API_ID}`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!OPENAI_SUPPORTED_MODELS.has(model.id)) {
|
|
195
|
+
return {
|
|
196
|
+
eligible: false,
|
|
197
|
+
modelKey: key,
|
|
198
|
+
reason: "Fast mode is not enabled for this OpenAI Codex model",
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!ctx.modelRegistry.isUsingOAuth(model)) {
|
|
203
|
+
return {
|
|
204
|
+
eligible: false,
|
|
205
|
+
modelKey: key,
|
|
206
|
+
reason: "ChatGPT OAuth auth is required; API-key auth is intentionally not used",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
eligible: true,
|
|
212
|
+
modelKey: key,
|
|
213
|
+
provider: "openai",
|
|
214
|
+
serviceTier: OPENAI_FAST_SERVICE_TIER,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (model.provider === OPENAI_API_PROVIDER_ID) {
|
|
219
|
+
if (!OPENAI_API_IDS.has(model.api)) {
|
|
220
|
+
return {
|
|
221
|
+
eligible: false,
|
|
222
|
+
modelKey: key,
|
|
223
|
+
reason: `current API is ${model.api}, not openai-responses or openai-completions`,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!OPENAI_API_SUPPORTED_MODELS.has(model.id)) {
|
|
228
|
+
return {
|
|
229
|
+
eligible: false,
|
|
230
|
+
modelKey: key,
|
|
231
|
+
reason:
|
|
232
|
+
"Fast mode is only enabled for GPT-6.1 Sol, GPT-6 Astra, GPT-6 Sol, and GPT-6 Luna on direct OpenAI APIs",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
eligible: true,
|
|
238
|
+
modelKey: key,
|
|
239
|
+
provider: "openai",
|
|
240
|
+
serviceTier: OPENAI_API_FAST_SERVICE_TIER,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
eligible: false,
|
|
246
|
+
modelKey: key,
|
|
247
|
+
reason: `current provider ${model.provider} does not support this extension's Fast mode`,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function isCanonicalOpenAIEndpoint(baseUrl: unknown): boolean {
|
|
252
|
+
return baseUrl === OPENAI_CANONICAL_BASE_URL || baseUrl === `${OPENAI_CANONICAL_BASE_URL}/`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getUltrafastEligibility(ctx: ExtensionContext): Eligibility {
|
|
256
|
+
const model = ctx.model;
|
|
257
|
+
if (!model) {
|
|
258
|
+
return { eligible: false, modelKey: "no-model", reason: "no model is selected" };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const key = `${model.provider}/${model.id}`;
|
|
262
|
+
if (model.provider !== OPENAI_API_PROVIDER_ID) {
|
|
263
|
+
return {
|
|
264
|
+
eligible: false,
|
|
265
|
+
modelKey: key,
|
|
266
|
+
reason: "Ultrafast mode requires the direct OpenAI provider",
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (model.api !== OPENAI_ULTRAFAST_API_ID) {
|
|
271
|
+
return {
|
|
272
|
+
eligible: false,
|
|
273
|
+
modelKey: key,
|
|
274
|
+
reason: `Ultrafast mode requires API ${OPENAI_ULTRAFAST_API_ID}`,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (model.id !== OPENAI_ULTRAFAST_MODEL) {
|
|
279
|
+
return {
|
|
280
|
+
eligible: false,
|
|
281
|
+
modelKey: key,
|
|
282
|
+
reason: `Ultrafast mode is only enabled for ${OPENAI_ULTRAFAST_MODEL}`,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (!isCanonicalOpenAIEndpoint(model.baseUrl)) {
|
|
287
|
+
return {
|
|
288
|
+
eligible: false,
|
|
289
|
+
modelKey: key,
|
|
290
|
+
reason: `Ultrafast mode requires the canonical endpoint ${OPENAI_CANONICAL_BASE_URL}`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
if (ctx.modelRegistry.isUsingOAuth(model)) {
|
|
296
|
+
return {
|
|
297
|
+
eligible: false,
|
|
298
|
+
modelKey: key,
|
|
299
|
+
reason: "Ultrafast mode requires direct OpenAI API-key auth; OAuth is unsupported",
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
} catch {
|
|
303
|
+
return {
|
|
304
|
+
eligible: false,
|
|
305
|
+
modelKey: key,
|
|
306
|
+
reason: "Ultrafast mode requires direct OpenAI API-key auth; auth could not be verified",
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
eligible: true,
|
|
312
|
+
modelKey: key,
|
|
313
|
+
provider: "openai",
|
|
314
|
+
serviceTier: OPENAI_ULTRAFAST_SERVICE_TIER,
|
|
315
|
+
};
|
|
240
316
|
}
|
|
241
317
|
|
|
242
318
|
function updateStatus(ctx: ExtensionContext, state: SessionState): void {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
319
|
+
if (!ctx.hasUI) return;
|
|
320
|
+
if (!state.config.showStatus) {
|
|
321
|
+
ctx.ui.setStatus(EXTENSION_ID, undefined);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (state.ultrafast) {
|
|
326
|
+
const eligibility = getUltrafastEligibility(ctx);
|
|
327
|
+
ctx.ui.setStatus(EXTENSION_ID, eligibility.eligible ? "ultrafast" : undefined);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const eligibility = getEligibility(ctx);
|
|
332
|
+
ctx.ui.setStatus(EXTENSION_ID, isFastEnabled(state) && eligibility.eligible ? "fast" : undefined);
|
|
254
333
|
}
|
|
255
334
|
|
|
256
335
|
function getStatusMessage(ctx: ExtensionContext, state: SessionState): string {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
336
|
+
const injected = state.lastInjectedAt
|
|
337
|
+
? ` Last injected for ${state.lastInjectedModel ?? "unknown model"} ${Math.max(0, Math.round((Date.now() - state.lastInjectedAt) / 1000))}s ago.`
|
|
338
|
+
: "";
|
|
339
|
+
|
|
340
|
+
if (state.ultrafast) {
|
|
341
|
+
const eligibility = getUltrafastEligibility(ctx);
|
|
342
|
+
if (eligibility.eligible) {
|
|
343
|
+
return `Ultrafast mode is on (session only) and eligible for ${eligibility.modelKey}; requested service_tier=${OPENAI_ULTRAFAST_SERVICE_TIER}. Actual API pricing is 6x Standard, but Pi's current cost display omits this premium.${injected}`;
|
|
344
|
+
}
|
|
345
|
+
return `Ultrafast mode is on (session only), but not eligible for ${eligibility.modelKey}: ${eligibility.reason}.${injected}`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const enabled = isFastEnabled(state);
|
|
349
|
+
const eligibility = getEligibility(ctx);
|
|
350
|
+
const active = enabled && eligibility.eligible;
|
|
351
|
+
|
|
352
|
+
if (active) {
|
|
353
|
+
const wireSetting =
|
|
354
|
+
eligibility.provider === "anthropic"
|
|
355
|
+
? `speed=${ANTHROPIC_FAST_SPEED}`
|
|
356
|
+
: `service_tier=${eligibility.serviceTier}`;
|
|
357
|
+
return `Fast mode is ${describeMode(state)} and active for ${eligibility.modelKey}; requests will use ${wireSetting}.${injected}`;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (enabled) {
|
|
361
|
+
return `Fast mode is ${describeMode(state)}, but inactive for ${eligibility.modelKey}: ${eligibility.reason}.${injected}`;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return `Fast mode is ${describeMode(state)}. Current model: ${eligibility.modelKey}.${injected}`;
|
|
277
365
|
}
|
|
278
366
|
|
|
279
367
|
function splitBetaHeader(value: string | null | undefined): string[] {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
368
|
+
return (value ?? "")
|
|
369
|
+
.split(",")
|
|
370
|
+
.map((part) => part.trim())
|
|
371
|
+
.filter(Boolean);
|
|
284
372
|
}
|
|
285
373
|
|
|
286
374
|
async function isAnthropicOAuth(ctx: ExtensionContext): Promise<boolean> {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
375
|
+
try {
|
|
376
|
+
const resolution = await ctx.modelRegistry.getProviderAuth(ANTHROPIC_PROVIDER_ID);
|
|
377
|
+
const apiKey = resolution?.auth.apiKey;
|
|
378
|
+
if (typeof apiKey === "string") return apiKey.includes("sk-ant-oat");
|
|
379
|
+
} catch {
|
|
380
|
+
// Fall back to the synchronous registry snapshot below.
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return ctx.model ? ctx.modelRegistry.isUsingOAuth(ctx.model) : false;
|
|
296
384
|
}
|
|
297
385
|
|
|
298
386
|
function getAnthropicProviderBetas(ctx: ExtensionContext, hasActiveTools: boolean): string[] {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
387
|
+
const model = ctx.model;
|
|
388
|
+
if (!model) return [];
|
|
389
|
+
const compat = model.compat as AnthropicHeaderCompat | undefined;
|
|
390
|
+
|
|
391
|
+
const betas: string[] = [];
|
|
392
|
+
if (hasActiveTools && compat?.supportsEagerToolInputStreaming === false) {
|
|
393
|
+
betas.push(FINE_GRAINED_TOOL_STREAMING_BETA);
|
|
394
|
+
}
|
|
395
|
+
if (compat?.forceAdaptiveThinking !== true) {
|
|
396
|
+
betas.push(INTERLEAVED_THINKING_BETA);
|
|
397
|
+
}
|
|
398
|
+
return betas;
|
|
311
399
|
}
|
|
312
400
|
|
|
313
401
|
async function injectAnthropicFastHeader(
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
402
|
+
headers: Record<string, string | null>,
|
|
403
|
+
ctx: ExtensionContext,
|
|
404
|
+
state: SessionState,
|
|
405
|
+
hasActiveTools: boolean,
|
|
318
406
|
): Promise<void> {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
407
|
+
if (state.ultrafast || !isFastEnabled(state)) return;
|
|
408
|
+
const eligibility = getEligibility(ctx);
|
|
409
|
+
if (!eligibility.eligible || eligibility.provider !== "anthropic") return;
|
|
410
|
+
|
|
411
|
+
const matchingKeys = Object.keys(headers).filter(
|
|
412
|
+
(name) => name.toLowerCase() === "anthropic-beta",
|
|
413
|
+
);
|
|
414
|
+
const existing = matchingKeys.flatMap((name) => splitBetaHeader(headers[name]));
|
|
415
|
+
const matchingModelEntries = Object.entries(ctx.model?.headers ?? {}).filter(
|
|
416
|
+
([name]) => name.toLowerCase() === "anthropic-beta",
|
|
417
|
+
);
|
|
418
|
+
const modelBetas = matchingModelEntries.flatMap(([, value]) => splitBetaHeader(value));
|
|
419
|
+
const providerBetas = getAnthropicProviderBetas(ctx, hasActiveTools);
|
|
420
|
+
const oauthBetas = (await isAnthropicOAuth(ctx)) ? CLAUDE_CODE_OAUTH_BETAS : [];
|
|
421
|
+
const next = Array.from(
|
|
422
|
+
new Set([...modelBetas, ...existing, ...oauthBetas, ...providerBetas, ANTHROPIC_FAST_BETA]),
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
for (const key of matchingKeys) delete headers[key];
|
|
426
|
+
const outputKey = matchingModelEntries.at(-1)?.[0] ?? "anthropic-beta";
|
|
427
|
+
headers[outputKey] = next.join(",");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function injectUltrafastPayload(
|
|
431
|
+
payload: unknown,
|
|
432
|
+
ctx: ExtensionContext,
|
|
433
|
+
state: SessionState,
|
|
434
|
+
): PayloadRecord | undefined {
|
|
435
|
+
if (!state.ultrafast) return undefined;
|
|
436
|
+
const eligibility = getUltrafastEligibility(ctx);
|
|
437
|
+
if (!eligibility.eligible || !eligibility.serviceTier) return undefined;
|
|
438
|
+
if (!isPayloadRecord(payload)) return undefined;
|
|
439
|
+
if (payload.model !== ctx.model?.id) return undefined;
|
|
440
|
+
if ("service_tier" in payload) return undefined;
|
|
441
|
+
|
|
442
|
+
state.lastInjectedAt = Date.now();
|
|
443
|
+
state.lastInjectedModel = eligibility.modelKey;
|
|
444
|
+
return { ...payload, service_tier: eligibility.serviceTier };
|
|
338
445
|
}
|
|
339
446
|
|
|
340
447
|
function injectFastPayload(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
448
|
+
payload: unknown,
|
|
449
|
+
ctx: ExtensionContext,
|
|
450
|
+
state: SessionState,
|
|
344
451
|
): PayloadRecord | undefined {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
452
|
+
if (state.ultrafast || !isFastEnabled(state)) return undefined;
|
|
453
|
+
const eligibility = getEligibility(ctx);
|
|
454
|
+
if (!eligibility.eligible || !eligibility.provider) return undefined;
|
|
455
|
+
if (!isPayloadRecord(payload)) return undefined;
|
|
456
|
+
if (payload.model !== ctx.model?.id) return undefined;
|
|
457
|
+
|
|
458
|
+
if (eligibility.provider === "anthropic") {
|
|
459
|
+
if ("speed" in payload) return undefined;
|
|
460
|
+
state.lastInjectedAt = Date.now();
|
|
461
|
+
state.lastInjectedModel = eligibility.modelKey;
|
|
462
|
+
return { ...payload, speed: ANTHROPIC_FAST_SPEED };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if ("service_tier" in payload) return undefined;
|
|
466
|
+
if (!eligibility.serviceTier) return undefined;
|
|
467
|
+
state.lastInjectedAt = Date.now();
|
|
468
|
+
state.lastInjectedModel = eligibility.modelKey;
|
|
469
|
+
return { ...payload, service_tier: eligibility.serviceTier };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function disableUltrafast(ctx: ExtensionContext, state: SessionState, reason?: string): void {
|
|
473
|
+
state.ultrafast = false;
|
|
474
|
+
// Do not fall back to ordinary Fast when a selected Ultrafast model becomes ineligible.
|
|
475
|
+
state.override = "off";
|
|
476
|
+
updateStatus(ctx, state);
|
|
477
|
+
if (reason) {
|
|
478
|
+
ctx.ui.notify(
|
|
479
|
+
`Ultrafast mode disabled: ${reason}. It remains off until manually enabled.`,
|
|
480
|
+
"warning",
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function toggleUltrafast(ctx: ExtensionContext, state: SessionState): void {
|
|
486
|
+
if (state.ultrafast) {
|
|
487
|
+
disableUltrafast(ctx, state);
|
|
488
|
+
ctx.ui.notify(
|
|
489
|
+
"Ultrafast mode is off (session only); regular Fast remains off until /fast is toggled.",
|
|
490
|
+
"info",
|
|
491
|
+
);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const eligibility = getUltrafastEligibility(ctx);
|
|
496
|
+
if (!eligibility.eligible) {
|
|
497
|
+
ctx.ui.notify(
|
|
498
|
+
`Cannot enable Ultrafast mode for ${eligibility.modelKey}: ${eligibility.reason}.`,
|
|
499
|
+
"error",
|
|
500
|
+
);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
state.ultrafast = true;
|
|
505
|
+
updateStatus(ctx, state);
|
|
506
|
+
ctx.ui.notify(getStatusMessage(ctx, state), "info");
|
|
363
507
|
}
|
|
364
508
|
|
|
365
509
|
export default function fastExtension(pi: ExtensionAPI) {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
510
|
+
const states = new WeakMap<object, SessionState>();
|
|
511
|
+
|
|
512
|
+
function getState(ctx: ExtensionContext): SessionState {
|
|
513
|
+
let state = states.get(ctx.sessionManager);
|
|
514
|
+
if (!state) {
|
|
515
|
+
state = {
|
|
516
|
+
config: loadConfig(ctx),
|
|
517
|
+
override: "auto",
|
|
518
|
+
ultrafast: false,
|
|
519
|
+
};
|
|
520
|
+
states.set(ctx.sessionManager, state);
|
|
521
|
+
}
|
|
522
|
+
return state;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
pi.on("session_start", (_event, ctx) => {
|
|
526
|
+
const state: SessionState = {
|
|
527
|
+
config: loadConfig(ctx),
|
|
528
|
+
override: "auto",
|
|
529
|
+
ultrafast: false,
|
|
530
|
+
};
|
|
531
|
+
states.set(ctx.sessionManager, state);
|
|
532
|
+
updateStatus(ctx, state);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
pi.on("model_select", (_event, ctx) => {
|
|
536
|
+
const state = getState(ctx);
|
|
537
|
+
if (state.ultrafast) {
|
|
538
|
+
const eligibility = getUltrafastEligibility(ctx);
|
|
539
|
+
if (!eligibility.eligible) {
|
|
540
|
+
disableUltrafast(
|
|
541
|
+
ctx,
|
|
542
|
+
state,
|
|
543
|
+
`the selected model ${eligibility.modelKey} is unsupported (${eligibility.reason})`,
|
|
544
|
+
);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
updateStatus(ctx, state);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
pi.on("before_provider_headers", async (event, ctx) => {
|
|
552
|
+
const hasActiveTools =
|
|
553
|
+
typeof pi.getActiveTools === "function" && pi.getActiveTools().length > 0;
|
|
554
|
+
await injectAnthropicFastHeader(event.headers, ctx, getState(ctx), hasActiveTools);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
558
|
+
const state = getState(ctx);
|
|
559
|
+
if (state.ultrafast) {
|
|
560
|
+
const eligibility = getUltrafastEligibility(ctx);
|
|
561
|
+
if (!eligibility.eligible) {
|
|
562
|
+
disableUltrafast(
|
|
563
|
+
ctx,
|
|
564
|
+
state,
|
|
565
|
+
`the current model ${eligibility.modelKey} is unsupported (${eligibility.reason})`,
|
|
566
|
+
);
|
|
567
|
+
return undefined;
|
|
568
|
+
}
|
|
569
|
+
const nextPayload = injectUltrafastPayload(event.payload, ctx, state);
|
|
570
|
+
updateStatus(ctx, state);
|
|
571
|
+
return nextPayload;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const nextPayload = injectFastPayload(event.payload, ctx, state);
|
|
575
|
+
updateStatus(ctx, state);
|
|
576
|
+
return nextPayload;
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
pi.registerCommand("ultrafast", {
|
|
580
|
+
description: "Toggle session-only Ultrafast mode for direct OpenAI API-key GPT-6 Astra",
|
|
581
|
+
getArgumentCompletions: () => null,
|
|
582
|
+
handler: async (args, ctx) => {
|
|
583
|
+
if (args.trim()) {
|
|
584
|
+
ctx.ui.notify("Usage: /ultrafast", "warning");
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
toggleUltrafast(ctx, getState(ctx));
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
pi.registerCommand("fast", {
|
|
592
|
+
description:
|
|
593
|
+
"Toggle Fast mode for supported direct OpenAI, OpenAI Codex, and Anthropic Claude models",
|
|
594
|
+
getArgumentCompletions: () => null,
|
|
595
|
+
handler: async (args, ctx) => {
|
|
596
|
+
const state = getState(ctx);
|
|
597
|
+
const action = args.trim();
|
|
598
|
+
|
|
599
|
+
if (action === "ultrafast") {
|
|
600
|
+
toggleUltrafast(ctx, state);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (!action) {
|
|
605
|
+
if (state.ultrafast) {
|
|
606
|
+
toggleUltrafast(ctx, state);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
state.override = isFastEnabled(state) ? "off" : "on";
|
|
610
|
+
updateStatus(ctx, state);
|
|
611
|
+
ctx.ui.notify(getStatusMessage(ctx, state), "info");
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
ctx.ui.notify("Usage: /fast [ultrafast]", "warning");
|
|
616
|
+
},
|
|
617
|
+
});
|
|
422
618
|
}
|