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