@diegopetrucci/pi-extensions 0.1.73 → 0.1.76

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.
Files changed (93) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +9 -4
  3. package/extensions/agent-workflow-audit/index.ts +1398 -1009
  4. package/extensions/agent-workflow-audit/package.json +1 -1
  5. package/extensions/annotate-git-diff/README.md +3 -1
  6. package/extensions/annotate-git-diff/clipboard.ts +109 -100
  7. package/extensions/annotate-git-diff/git.ts +853 -767
  8. package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
  9. package/extensions/annotate-git-diff/index.ts +712 -671
  10. package/extensions/annotate-git-diff/package.json +1 -1
  11. package/extensions/annotate-git-diff/prompt.ts +45 -45
  12. package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
  13. package/extensions/annotate-git-diff/review-server.ts +337 -311
  14. package/extensions/annotate-git-diff/types.ts +132 -132
  15. package/extensions/annotate-git-diff/ui.ts +27 -27
  16. package/extensions/annotate-git-diff/watch.ts +74 -74
  17. package/extensions/annotate-git-diff/web/app.js +1947 -1884
  18. package/extensions/annotate-git-diff/web/index.html +5 -6
  19. package/extensions/annotate-last-message/README.md +1 -1
  20. package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
  21. package/extensions/annotate-last-message/index.ts +210 -206
  22. package/extensions/annotate-last-message/package.json +1 -1
  23. package/extensions/annotate-last-message/prompt.ts +87 -66
  24. package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
  25. package/extensions/annotate-last-message/session.ts +94 -89
  26. package/extensions/annotate-last-message/theme.ts +199 -193
  27. package/extensions/annotate-last-message/types.ts +23 -23
  28. package/extensions/annotate-last-message/ui.ts +27 -25
  29. package/extensions/annotate-last-message/web/app.js +4 -7
  30. package/extensions/annotate-last-message/web/md-renderer.js +1 -1
  31. package/extensions/brrr/README.md +1 -1
  32. package/extensions/brrr/index.ts +213 -191
  33. package/extensions/brrr/package.json +1 -1
  34. package/extensions/claude-fast/README.md +5 -79
  35. package/extensions/claude-fast/index.ts +31 -289
  36. package/extensions/claude-fast/package.json +2 -3
  37. package/extensions/code-reviewer/README.md +1 -1
  38. package/extensions/code-reviewer/index.ts +1598 -1350
  39. package/extensions/code-reviewer/package.json +1 -1
  40. package/extensions/confirm-destructive/README.md +1 -1
  41. package/extensions/confirm-destructive/index.ts +55 -51
  42. package/extensions/confirm-destructive/package.json +1 -1
  43. package/extensions/context-cap/index.ts +312 -304
  44. package/extensions/context-cap/package.json +1 -1
  45. package/extensions/context-inspector/index.ts +1211 -1064
  46. package/extensions/context-inspector/package.json +1 -1
  47. package/extensions/contrarian/index.ts +1854 -1735
  48. package/extensions/contrarian/package.json +1 -1
  49. package/extensions/dirty-repo-guard/index.ts +41 -41
  50. package/extensions/dynamic-context-pruning/README.md +6 -38
  51. package/extensions/dynamic-context-pruning/index.ts +2124 -1980
  52. package/extensions/dynamic-context-pruning/package.json +1 -1
  53. package/extensions/fast/README.md +71 -21
  54. package/extensions/fast/index.ts +513 -313
  55. package/extensions/fast/package.json +1 -1
  56. package/extensions/git-footer/README.md +1 -1
  57. package/extensions/git-footer/index.ts +458 -411
  58. package/extensions/git-footer/package.json +2 -3
  59. package/extensions/gnosis/index.ts +180 -130
  60. package/extensions/gnosis/package.json +1 -1
  61. package/extensions/inline-bash/index.ts +88 -88
  62. package/extensions/inline-bash/package.json +1 -1
  63. package/extensions/librarian/index.ts +1669 -1419
  64. package/extensions/librarian/package.json +1 -1
  65. package/extensions/minimal-footer/index.ts +994 -1012
  66. package/extensions/minimal-footer/openai-usage.ts +195 -190
  67. package/extensions/notify/index.ts +222 -230
  68. package/extensions/notify/package.json +1 -1
  69. package/extensions/openai-fast/README.md +5 -73
  70. package/extensions/openai-fast/index.ts +32 -272
  71. package/extensions/openai-fast/package.json +2 -3
  72. package/extensions/oracle/index.ts +1731 -1625
  73. package/extensions/oracle/package.json +1 -1
  74. package/extensions/permission-gate/index.ts +658 -608
  75. package/extensions/permission-gate/package.json +1 -1
  76. package/extensions/permission-gate/powershell-safety.ts +840 -793
  77. package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
  78. package/extensions/project-mcp-json/README.md +199 -0
  79. package/extensions/project-mcp-json/config.ts +564 -0
  80. package/extensions/project-mcp-json/index.ts +119 -0
  81. package/extensions/project-mcp-json/package.json +38 -0
  82. package/extensions/quiet-tools/index.ts +282 -264
  83. package/extensions/review/README.md +2 -2
  84. package/extensions/review/index.ts +1745 -1872
  85. package/extensions/review/package.json +1 -1
  86. package/extensions/todo/index.ts +284 -265
  87. package/extensions/triage-comments/README.md +2 -2
  88. package/extensions/triage-comments/index.ts +2294 -1882
  89. package/extensions/triage-comments/package.json +1 -1
  90. package/package.json +6 -3
  91. package/assets/social-preview.png +0 -0
  92. package/extensions/claude-fast/claude-fast.example.json +0 -4
  93. package/extensions/openai-fast/openai-fast.example.json +0 -4
@@ -1,10 +1,10 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import {
4
- CONFIG_DIR_NAME,
5
- getAgentDir,
6
- type ExtensionAPI,
7
- type ExtensionContext,
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,398 +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
- "gpt-5.5",
25
- "gpt-5.6-sol",
26
- "gpt-5.6-terra",
27
- "gpt-5.6-luna",
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",
28
32
  ]);
29
33
  const OPENAI_API_PROVIDER_ID = "openai";
30
34
  const OPENAI_API_IDS = new Set(["openai-responses", "openai-completions"]);
31
35
  const OPENAI_API_FAST_SERVICE_TIER = "fast";
32
- const OPENAI_API_SUPPORTED_MODELS = new Set(["gpt-6.1-sol", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna"]);
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";
33
46
 
34
47
  const DEFAULT_CONFIG: FastConfig = {
35
- enabled: false,
36
- showStatus: true,
48
+ enabled: false,
49
+ showStatus: true,
37
50
  };
38
51
 
39
52
  type FastOverride = "auto" | "on" | "off";
40
53
  type FastProvider = "anthropic" | "openai";
41
54
 
42
55
  type FastConfig = {
43
- /** Default Fast-mode state when there is no session override. */
44
- enabled: boolean;
45
- /** Show a compact `fast` status when Fast mode is active for the current model. */
46
- showStatus: boolean;
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;
47
60
  };
48
61
 
49
62
  type SessionState = {
50
- config: FastConfig;
51
- override: FastOverride;
52
- lastInjectedAt?: number;
53
- lastInjectedModel?: string;
63
+ config: FastConfig;
64
+ override: FastOverride;
65
+ /** Session-only Ultrafast selection; never loaded from configuration. */
66
+ ultrafast: boolean;
67
+ lastInjectedAt?: number;
68
+ lastInjectedModel?: string;
54
69
  };
55
70
 
56
71
  type ProjectConfigContext = {
57
- cwd: string;
58
- isProjectTrusted?: () => boolean;
72
+ cwd: string;
73
+ isProjectTrusted?: () => boolean;
59
74
  };
60
75
 
61
76
  type RecursivePartial<T> = {
62
- [P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
77
+ [P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
63
78
  };
64
79
 
65
80
  type PayloadRecord = Record<string, unknown>;
66
81
 
67
82
  type AnthropicHeaderCompat = {
68
- forceAdaptiveThinking?: boolean;
69
- supportsEagerToolInputStreaming?: boolean;
83
+ forceAdaptiveThinking?: boolean;
84
+ supportsEagerToolInputStreaming?: boolean;
70
85
  };
71
86
 
72
87
  type Eligibility = {
73
- eligible: boolean;
74
- modelKey: string;
75
- provider?: FastProvider;
76
- serviceTier?: string;
77
- reason?: string;
88
+ eligible: boolean;
89
+ modelKey: string;
90
+ provider?: FastProvider;
91
+ serviceTier?: string;
92
+ reason?: string;
78
93
  };
79
94
 
80
95
  function readConfigFile(path: string): RecursivePartial<FastConfig> {
81
- if (!existsSync(path)) return {};
82
-
83
- try {
84
- const parsed = JSON.parse(readFileSync(path, "utf-8"));
85
- return isPayloadRecord(parsed) ? (parsed as RecursivePartial<FastConfig>) : {};
86
- } catch (error) {
87
- console.error(`Warning: Could not parse ${path}: ${error}`);
88
- return {};
89
- }
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
+ }
90
105
  }
91
106
 
92
107
  function normalizeBoolean(value: unknown, fallback: boolean): boolean {
93
- return typeof value === "boolean" ? value : fallback;
108
+ return typeof value === "boolean" ? value : fallback;
94
109
  }
95
110
 
96
111
  function mergeConfig(base: FastConfig, overrides: RecursivePartial<FastConfig>): FastConfig {
97
- return {
98
- enabled: normalizeBoolean(overrides.enabled, base.enabled),
99
- showStatus: normalizeBoolean(overrides.showStatus, base.showStatus),
100
- };
112
+ return {
113
+ enabled: normalizeBoolean(overrides.enabled, base.enabled),
114
+ showStatus: normalizeBoolean(overrides.showStatus, base.showStatus),
115
+ };
101
116
  }
102
117
 
103
118
  function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
104
- return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
119
+ return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
105
120
  }
106
121
 
107
122
  function findProjectConfigPath(cwd: string): string {
108
- let current = cwd;
109
- while (true) {
110
- const candidate = join(current, CONFIG_DIR_NAME, "fast.json");
111
- if (existsSync(candidate)) return candidate;
112
-
113
- const parent = dirname(current);
114
- if (parent === current) return join(cwd, CONFIG_DIR_NAME, "fast.json");
115
- current = parent;
116
- }
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
+ }
117
132
  }
118
133
 
119
134
  function loadConfig(ctx: ProjectConfigContext): FastConfig {
120
- const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "fast.json"));
121
- const projectConfig = canReadProjectConfig(ctx) ? readConfigFile(findProjectConfigPath(ctx.cwd)) : {};
122
- return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
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);
123
140
  }
124
141
 
125
142
  function isPayloadRecord(payload: unknown): payload is PayloadRecord {
126
- return typeof payload === "object" && payload !== null && !Array.isArray(payload);
127
- }
128
-
129
- function modelKey(ctx: ExtensionContext): string {
130
- const model = ctx.model;
131
- return model ? `${model.provider}/${model.id}` : "no-model";
143
+ return typeof payload === "object" && payload !== null && !Array.isArray(payload);
132
144
  }
133
145
 
134
146
  function isFastEnabled(state: SessionState): boolean {
135
- if (state.override === "on") return true;
136
- if (state.override === "off") return false;
137
- return state.config.enabled;
147
+ if (state.override === "on") return true;
148
+ if (state.override === "off") return false;
149
+ return state.config.enabled;
138
150
  }
139
151
 
140
152
  function describeMode(state: SessionState): string {
141
- if (state.override === "on") return "on (session override)";
142
- if (state.override === "off") return "off (session override)";
143
- return state.config.enabled ? "on (config default)" : "off (config default)";
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)";
144
156
  }
145
157
 
146
158
  function getEligibility(ctx: ExtensionContext): Eligibility {
147
- const model = ctx.model;
148
- if (!model) {
149
- return { eligible: false, modelKey: "no-model", reason: "no model is selected" };
150
- }
151
-
152
- const key = `${model.provider}/${model.id}`;
153
- if (model.provider === ANTHROPIC_PROVIDER_ID) {
154
- if (model.api !== ANTHROPIC_API_ID) {
155
- return {
156
- eligible: false,
157
- modelKey: key,
158
- reason: `current API is ${model.api}, not ${ANTHROPIC_API_ID}`,
159
- };
160
- }
161
-
162
- if (!ANTHROPIC_SUPPORTED_MODELS.has(model.id)) {
163
- return {
164
- eligible: false,
165
- modelKey: key,
166
- reason: "Fast mode is only enabled for Claude Opus 4.8, Claude Opus 5, and Claude Opus 5.5",
167
- };
168
- }
169
-
170
- return { eligible: true, modelKey: key, provider: "anthropic" };
171
- }
172
-
173
- if (model.provider === OPENAI_PROVIDER_ID) {
174
- if (model.api !== OPENAI_API_ID) {
175
- return {
176
- eligible: false,
177
- modelKey: key,
178
- reason: `current API is ${model.api}, not ${OPENAI_API_ID}`,
179
- };
180
- }
181
-
182
- if (!OPENAI_SUPPORTED_MODELS.has(model.id)) {
183
- return {
184
- eligible: false,
185
- modelKey: key,
186
- reason: "Fast mode is not enabled for this OpenAI Codex model",
187
- };
188
- }
189
-
190
- if (!ctx.modelRegistry.isUsingOAuth(model)) {
191
- return {
192
- eligible: false,
193
- modelKey: key,
194
- reason: "ChatGPT OAuth auth is required; API-key auth is intentionally not used",
195
- };
196
- }
197
-
198
- return {
199
- eligible: true,
200
- modelKey: key,
201
- provider: "openai",
202
- serviceTier: OPENAI_FAST_SERVICE_TIER,
203
- };
204
- }
205
-
206
- if (model.provider === OPENAI_API_PROVIDER_ID) {
207
- if (!OPENAI_API_IDS.has(model.api)) {
208
- return {
209
- eligible: false,
210
- modelKey: key,
211
- reason: `current API is ${model.api}, not openai-responses or openai-completions`,
212
- };
213
- }
214
-
215
- if (!OPENAI_API_SUPPORTED_MODELS.has(model.id)) {
216
- return {
217
- eligible: false,
218
- modelKey: key,
219
- reason: "Fast mode is only enabled for GPT-6.1 Sol, GPT-6 Astra, GPT-6 Sol, and GPT-6 Luna on direct OpenAI APIs",
220
- };
221
- }
222
-
223
- return {
224
- eligible: true,
225
- modelKey: key,
226
- provider: "openai",
227
- serviceTier: OPENAI_API_FAST_SERVICE_TIER,
228
- };
229
- }
230
-
231
- return {
232
- eligible: false,
233
- modelKey: key,
234
- reason: `current provider ${model.provider} does not support this extension's Fast mode`,
235
- };
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
+ };
236
316
  }
237
317
 
238
318
  function updateStatus(ctx: ExtensionContext, state: SessionState): void {
239
- if (!ctx.hasUI) return;
240
- if (!state.config.showStatus) {
241
- ctx.ui.setStatus(EXTENSION_ID, undefined);
242
- return;
243
- }
244
-
245
- const eligibility = getEligibility(ctx);
246
- ctx.ui.setStatus(
247
- EXTENSION_ID,
248
- isFastEnabled(state) && eligibility.eligible ? "fast" : undefined,
249
- );
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);
250
333
  }
251
334
 
252
335
  function getStatusMessage(ctx: ExtensionContext, state: SessionState): string {
253
- const enabled = isFastEnabled(state);
254
- const eligibility = getEligibility(ctx);
255
- const active = enabled && eligibility.eligible;
256
- const injected = state.lastInjectedAt
257
- ? ` Last injected for ${state.lastInjectedModel ?? "unknown model"} ${Math.max(0, Math.round((Date.now() - state.lastInjectedAt) / 1000))}s ago.`
258
- : "";
259
-
260
- if (active) {
261
- const wireSetting =
262
- eligibility.provider === "anthropic"
263
- ? `speed=${ANTHROPIC_FAST_SPEED}`
264
- : `service_tier=${eligibility.serviceTier}`;
265
- return `Fast mode is ${describeMode(state)} and active for ${eligibility.modelKey}; requests will use ${wireSetting}.${injected}`;
266
- }
267
-
268
- if (enabled) {
269
- return `Fast mode is ${describeMode(state)}, but inactive for ${eligibility.modelKey}: ${eligibility.reason}.${injected}`;
270
- }
271
-
272
- return `Fast mode is ${describeMode(state)}. Current model: ${eligibility.modelKey}.${injected}`;
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}`;
273
365
  }
274
366
 
275
367
  function splitBetaHeader(value: string | null | undefined): string[] {
276
- return (value ?? "")
277
- .split(",")
278
- .map((part) => part.trim())
279
- .filter(Boolean);
368
+ return (value ?? "")
369
+ .split(",")
370
+ .map((part) => part.trim())
371
+ .filter(Boolean);
280
372
  }
281
373
 
282
374
  async function isAnthropicOAuth(ctx: ExtensionContext): Promise<boolean> {
283
- try {
284
- const resolution = await ctx.modelRegistry.getProviderAuth(ANTHROPIC_PROVIDER_ID);
285
- const apiKey = resolution?.auth.apiKey;
286
- if (typeof apiKey === "string") return apiKey.includes("sk-ant-oat");
287
- } catch {
288
- // Fall back to the synchronous registry snapshot below.
289
- }
290
-
291
- return ctx.model ? ctx.modelRegistry.isUsingOAuth(ctx.model) : false;
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;
292
384
  }
293
385
 
294
386
  function getAnthropicProviderBetas(ctx: ExtensionContext, hasActiveTools: boolean): string[] {
295
- const model = ctx.model;
296
- if (!model) return [];
297
- const compat = model.compat as AnthropicHeaderCompat | undefined;
298
-
299
- const betas: string[] = [];
300
- if (hasActiveTools && compat?.supportsEagerToolInputStreaming === false) {
301
- betas.push(FINE_GRAINED_TOOL_STREAMING_BETA);
302
- }
303
- if (compat?.forceAdaptiveThinking !== true) {
304
- betas.push(INTERLEAVED_THINKING_BETA);
305
- }
306
- return betas;
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;
307
399
  }
308
400
 
309
401
  async function injectAnthropicFastHeader(
310
- headers: Record<string, string | null>,
311
- ctx: ExtensionContext,
312
- state: SessionState,
313
- hasActiveTools: boolean,
402
+ headers: Record<string, string | null>,
403
+ ctx: ExtensionContext,
404
+ state: SessionState,
405
+ hasActiveTools: boolean,
314
406
  ): Promise<void> {
315
- if (!isFastEnabled(state)) return;
316
- const eligibility = getEligibility(ctx);
317
- if (!eligibility.eligible || eligibility.provider !== "anthropic") return;
318
-
319
- const matchingKeys = Object.keys(headers).filter((name) => name.toLowerCase() === "anthropic-beta");
320
- const existing = matchingKeys.flatMap((name) => splitBetaHeader(headers[name]));
321
- const matchingModelEntries = Object.entries(ctx.model?.headers ?? {}).filter(
322
- ([name]) => name.toLowerCase() === "anthropic-beta",
323
- );
324
- const modelBetas = matchingModelEntries.flatMap(([, value]) => splitBetaHeader(value));
325
- const providerBetas = getAnthropicProviderBetas(ctx, hasActiveTools);
326
- const oauthBetas = (await isAnthropicOAuth(ctx)) ? CLAUDE_CODE_OAUTH_BETAS : [];
327
- const next = Array.from(
328
- new Set([...modelBetas, ...existing, ...oauthBetas, ...providerBetas, ANTHROPIC_FAST_BETA]),
329
- );
330
-
331
- for (const key of matchingKeys) delete headers[key];
332
- const outputKey = matchingModelEntries.at(-1)?.[0] ?? "anthropic-beta";
333
- headers[outputKey] = next.join(",");
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 };
334
445
  }
335
446
 
336
447
  function injectFastPayload(
337
- payload: unknown,
338
- ctx: ExtensionContext,
339
- state: SessionState,
448
+ payload: unknown,
449
+ ctx: ExtensionContext,
450
+ state: SessionState,
340
451
  ): PayloadRecord | undefined {
341
- if (!isFastEnabled(state)) return undefined;
342
- const eligibility = getEligibility(ctx);
343
- if (!eligibility.eligible || !eligibility.provider) return undefined;
344
- if (!isPayloadRecord(payload)) return undefined;
345
- if (payload.model !== ctx.model?.id) return undefined;
346
-
347
- if (eligibility.provider === "anthropic") {
348
- if ("speed" in payload) return undefined;
349
- state.lastInjectedAt = Date.now();
350
- state.lastInjectedModel = eligibility.modelKey;
351
- return { ...payload, speed: ANTHROPIC_FAST_SPEED };
352
- }
353
-
354
- if ("service_tier" in payload) return undefined;
355
- if (!eligibility.serviceTier) return undefined;
356
- state.lastInjectedAt = Date.now();
357
- state.lastInjectedModel = eligibility.modelKey;
358
- return { ...payload, service_tier: eligibility.serviceTier };
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");
359
507
  }
360
508
 
361
509
  export default function fastExtension(pi: ExtensionAPI) {
362
- const states = new WeakMap<object, SessionState>();
363
-
364
- function getState(ctx: ExtensionContext): SessionState {
365
- let state = states.get(ctx.sessionManager);
366
- if (!state) {
367
- state = {
368
- config: loadConfig(ctx),
369
- override: "auto",
370
- };
371
- states.set(ctx.sessionManager, state);
372
- }
373
- return state;
374
- }
375
-
376
- pi.on("session_start", (_event, ctx) => {
377
- const state: SessionState = {
378
- config: loadConfig(ctx),
379
- override: "auto",
380
- };
381
- states.set(ctx.sessionManager, state);
382
- updateStatus(ctx, state);
383
- });
384
-
385
- pi.on("model_select", (_event, ctx) => {
386
- updateStatus(ctx, getState(ctx));
387
- });
388
-
389
- pi.on("before_provider_headers", async (event, ctx) => {
390
- const hasActiveTools = typeof pi.getActiveTools === "function" && pi.getActiveTools().length > 0;
391
- await injectAnthropicFastHeader(event.headers, ctx, getState(ctx), hasActiveTools);
392
- });
393
-
394
- pi.on("before_provider_request", (event, ctx) => {
395
- const state = getState(ctx);
396
- const nextPayload = injectFastPayload(event.payload, ctx, state);
397
- updateStatus(ctx, state);
398
- return nextPayload;
399
- });
400
-
401
- pi.registerCommand("fast", {
402
- description: "Toggle Fast mode for supported direct OpenAI, OpenAI Codex, and Anthropic Claude models",
403
- getArgumentCompletions: () => null,
404
- handler: async (args, ctx) => {
405
- const state = getState(ctx);
406
- const action = args.trim();
407
-
408
- if (!action) {
409
- state.override = isFastEnabled(state) ? "off" : "on";
410
- updateStatus(ctx, state);
411
- ctx.ui.notify(getStatusMessage(ctx, state), "info");
412
- return;
413
- }
414
-
415
- ctx.ui.notify("Usage: /fast", "warning");
416
- },
417
- });
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
+ });
418
618
  }