@diegopetrucci/pi-extensions 0.1.61 → 0.1.63
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/.pi-fleet-tested-version +1 -1
- package/README.md +3 -2
- package/extensions/agent-workflow-audit/.pi-fleet-tested-version +1 -1
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/.pi-fleet-tested-version +1 -1
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-last-message/.pi-fleet-tested-version +1 -1
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/brrr/.pi-fleet-tested-version +1 -1
- package/extensions/brrr/package.json +1 -1
- package/extensions/claude-fast/.pi-fleet-tested-version +1 -1
- package/extensions/claude-fast/README.md +2 -0
- package/extensions/claude-fast/package.json +1 -1
- package/extensions/code-reviewer/.pi-fleet-tested-version +1 -1
- package/extensions/code-reviewer/README.md +1 -0
- package/extensions/code-reviewer/index.ts +24 -2
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/.pi-fleet-tested-version +1 -1
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/.pi-fleet-tested-version +1 -1
- package/extensions/context-cap/package.json +1 -1
- package/extensions/context-inspector/.pi-fleet-tested-version +1 -1
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/.pi-fleet-tested-version +1 -1
- package/extensions/contrarian/README.md +2 -0
- package/extensions/contrarian/index.ts +34 -8
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/.pi-fleet-tested-version +1 -1
- package/extensions/dirty-repo-guard/package.json +1 -1
- package/extensions/dynamic-context-pruning/.pi-fleet-tested-version +1 -1
- package/extensions/dynamic-context-pruning/README.md +78 -11
- package/extensions/dynamic-context-pruning/index.ts +171 -21
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/.pi-fleet-tested-version +1 -0
- package/extensions/fast/README.md +119 -0
- package/extensions/fast/fast.example.json +4 -0
- package/extensions/fast/index.ts +383 -0
- package/extensions/fast/package.json +39 -0
- package/extensions/git-footer/.pi-fleet-tested-version +1 -1
- package/extensions/git-footer/package.json +1 -1
- package/extensions/gnosis/.pi-fleet-tested-version +1 -1
- package/extensions/gnosis/package.json +1 -1
- package/extensions/illustrations-to-explain-things/.pi-fleet-tested-version +1 -1
- package/extensions/illustrations-to-explain-things/package.json +1 -1
- package/extensions/inline-bash/.pi-fleet-tested-version +1 -1
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/.pi-fleet-tested-version +1 -1
- package/extensions/librarian/README.md +1 -1
- package/extensions/librarian/index.ts +34 -5
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/.pi-fleet-tested-version +1 -1
- package/extensions/minimal-footer/package.json +1 -1
- package/extensions/notify/.pi-fleet-tested-version +1 -1
- package/extensions/notify/package.json +1 -1
- package/extensions/openai-fast/.pi-fleet-tested-version +1 -1
- package/extensions/openai-fast/README.md +2 -0
- package/extensions/openai-fast/package.json +1 -1
- package/extensions/oracle/.pi-fleet-tested-version +1 -1
- package/extensions/oracle/README.md +2 -0
- package/extensions/oracle/index.ts +34 -8
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/.pi-fleet-tested-version +1 -1
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
- package/extensions/quiet-tools/package.json +1 -1
- package/extensions/review/.pi-fleet-tested-version +1 -1
- package/extensions/review/package.json +1 -1
- package/extensions/todo/.pi-fleet-tested-version +1 -1
- package/extensions/todo/package.json +1 -1
- package/extensions/triage-comments/.pi-fleet-tested-version +1 -1
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +5 -6
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
CONFIG_DIR_NAME,
|
|
5
|
+
getAgentDir,
|
|
6
|
+
type ExtensionAPI,
|
|
7
|
+
type ExtensionContext,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
|
|
10
|
+
const EXTENSION_ID = "fast";
|
|
11
|
+
const ANTHROPIC_PROVIDER_ID = "anthropic";
|
|
12
|
+
const ANTHROPIC_API_ID = "anthropic-messages";
|
|
13
|
+
const ANTHROPIC_FAST_SPEED = "fast";
|
|
14
|
+
const ANTHROPIC_FAST_BETA = "fast-mode-2026-02-01";
|
|
15
|
+
const CLAUDE_CODE_OAUTH_BETAS = ["claude-code-20250219", "oauth-2025-04-20"];
|
|
16
|
+
const FINE_GRAINED_TOOL_STREAMING_BETA = "fine-grained-tool-streaming-2025-05-14";
|
|
17
|
+
const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
|
|
18
|
+
const ANTHROPIC_SUPPORTED_MODELS = new Set(["claude-opus-4-6", "claude-opus-4-7", "claude-opus-4-8"]);
|
|
19
|
+
|
|
20
|
+
const OPENAI_PROVIDER_ID = "openai-codex";
|
|
21
|
+
const OPENAI_API_ID = "openai-codex-responses";
|
|
22
|
+
const OPENAI_FAST_SERVICE_TIER = "priority";
|
|
23
|
+
const OPENAI_SUPPORTED_MODELS = new Set([
|
|
24
|
+
"gpt-5.4",
|
|
25
|
+
"gpt-5.5",
|
|
26
|
+
"gpt-5.6-sol",
|
|
27
|
+
"gpt-5.6-terra",
|
|
28
|
+
"gpt-5.6-luna",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const DEFAULT_CONFIG: FastConfig = {
|
|
32
|
+
enabled: false,
|
|
33
|
+
showStatus: true,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type FastOverride = "auto" | "on" | "off";
|
|
37
|
+
type FastProvider = "anthropic" | "openai";
|
|
38
|
+
|
|
39
|
+
type FastConfig = {
|
|
40
|
+
/** Default Fast-mode state when there is no session override. */
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
/** Show a compact `fast` status when Fast mode is active for the current model. */
|
|
43
|
+
showStatus: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type SessionState = {
|
|
47
|
+
config: FastConfig;
|
|
48
|
+
override: FastOverride;
|
|
49
|
+
lastInjectedAt?: number;
|
|
50
|
+
lastInjectedModel?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type ProjectConfigContext = {
|
|
54
|
+
cwd: string;
|
|
55
|
+
isProjectTrusted?: () => boolean;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type RecursivePartial<T> = {
|
|
59
|
+
[P in keyof T]?: T[P] extends object ? RecursivePartial<T[P]> : T[P];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type PayloadRecord = Record<string, unknown>;
|
|
63
|
+
|
|
64
|
+
type AnthropicHeaderCompat = {
|
|
65
|
+
forceAdaptiveThinking?: boolean;
|
|
66
|
+
supportsEagerToolInputStreaming?: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type Eligibility = {
|
|
70
|
+
eligible: boolean;
|
|
71
|
+
modelKey: string;
|
|
72
|
+
provider?: FastProvider;
|
|
73
|
+
reason?: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function readConfigFile(path: string): RecursivePartial<FastConfig> {
|
|
77
|
+
if (!existsSync(path)) return {};
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
81
|
+
return isPayloadRecord(parsed) ? (parsed as RecursivePartial<FastConfig>) : {};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(`Warning: Could not parse ${path}: ${error}`);
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeBoolean(value: unknown, fallback: boolean): boolean {
|
|
89
|
+
return typeof value === "boolean" ? value : fallback;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function mergeConfig(base: FastConfig, overrides: RecursivePartial<FastConfig>): FastConfig {
|
|
93
|
+
return {
|
|
94
|
+
enabled: normalizeBoolean(overrides.enabled, base.enabled),
|
|
95
|
+
showStatus: normalizeBoolean(overrides.showStatus, base.showStatus),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function canReadProjectConfig(ctx: ProjectConfigContext): boolean {
|
|
100
|
+
return typeof ctx.isProjectTrusted === "function" && ctx.isProjectTrusted();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function findProjectConfigPath(cwd: string): string {
|
|
104
|
+
let current = cwd;
|
|
105
|
+
while (true) {
|
|
106
|
+
const candidate = join(current, CONFIG_DIR_NAME, "fast.json");
|
|
107
|
+
if (existsSync(candidate)) return candidate;
|
|
108
|
+
|
|
109
|
+
const parent = dirname(current);
|
|
110
|
+
if (parent === current) return join(cwd, CONFIG_DIR_NAME, "fast.json");
|
|
111
|
+
current = parent;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function loadConfig(ctx: ProjectConfigContext): FastConfig {
|
|
116
|
+
const globalConfig = readConfigFile(join(getAgentDir(), "extensions", "fast.json"));
|
|
117
|
+
const projectConfig = canReadProjectConfig(ctx) ? readConfigFile(findProjectConfigPath(ctx.cwd)) : {};
|
|
118
|
+
return mergeConfig(mergeConfig(DEFAULT_CONFIG, globalConfig), projectConfig);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isPayloadRecord(payload: unknown): payload is PayloadRecord {
|
|
122
|
+
return typeof payload === "object" && payload !== null && !Array.isArray(payload);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function modelKey(ctx: ExtensionContext): string {
|
|
126
|
+
const model = ctx.model;
|
|
127
|
+
return model ? `${model.provider}/${model.id}` : "no-model";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isFastEnabled(state: SessionState): boolean {
|
|
131
|
+
if (state.override === "on") return true;
|
|
132
|
+
if (state.override === "off") return false;
|
|
133
|
+
return state.config.enabled;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function describeMode(state: SessionState): string {
|
|
137
|
+
if (state.override === "on") return "on (session override)";
|
|
138
|
+
if (state.override === "off") return "off (session override)";
|
|
139
|
+
return state.config.enabled ? "on (config default)" : "off (config default)";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getEligibility(ctx: ExtensionContext): Eligibility {
|
|
143
|
+
const model = ctx.model;
|
|
144
|
+
if (!model) {
|
|
145
|
+
return { eligible: false, modelKey: "no-model", reason: "no model is selected" };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const key = `${model.provider}/${model.id}`;
|
|
149
|
+
if (model.provider === ANTHROPIC_PROVIDER_ID) {
|
|
150
|
+
if (model.api !== ANTHROPIC_API_ID) {
|
|
151
|
+
return {
|
|
152
|
+
eligible: false,
|
|
153
|
+
modelKey: key,
|
|
154
|
+
reason: `current API is ${model.api}, not ${ANTHROPIC_API_ID}`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!ANTHROPIC_SUPPORTED_MODELS.has(model.id)) {
|
|
159
|
+
return {
|
|
160
|
+
eligible: false,
|
|
161
|
+
modelKey: key,
|
|
162
|
+
reason: "Fast mode is only enabled for Claude Opus 4.6, 4.7, and 4.8",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { eligible: true, modelKey: key, provider: "anthropic" };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (model.provider === OPENAI_PROVIDER_ID) {
|
|
170
|
+
if (model.api !== OPENAI_API_ID) {
|
|
171
|
+
return {
|
|
172
|
+
eligible: false,
|
|
173
|
+
modelKey: key,
|
|
174
|
+
reason: `current API is ${model.api}, not ${OPENAI_API_ID}`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!OPENAI_SUPPORTED_MODELS.has(model.id)) {
|
|
179
|
+
return {
|
|
180
|
+
eligible: false,
|
|
181
|
+
modelKey: key,
|
|
182
|
+
reason: "Fast mode is not enabled for this OpenAI Codex model",
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!ctx.modelRegistry.isUsingOAuth(model)) {
|
|
187
|
+
return {
|
|
188
|
+
eligible: false,
|
|
189
|
+
modelKey: key,
|
|
190
|
+
reason: "ChatGPT OAuth auth is required; API-key auth is intentionally not used",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { eligible: true, modelKey: key, provider: "openai" };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
eligible: false,
|
|
199
|
+
modelKey: key,
|
|
200
|
+
reason: `current provider ${model.provider} does not support this extension's Fast mode`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function updateStatus(ctx: ExtensionContext, state: SessionState): void {
|
|
205
|
+
if (!ctx.hasUI) return;
|
|
206
|
+
if (!state.config.showStatus) {
|
|
207
|
+
ctx.ui.setStatus(EXTENSION_ID, undefined);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const eligibility = getEligibility(ctx);
|
|
212
|
+
ctx.ui.setStatus(
|
|
213
|
+
EXTENSION_ID,
|
|
214
|
+
isFastEnabled(state) && eligibility.eligible ? "fast" : undefined,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function getStatusMessage(ctx: ExtensionContext, state: SessionState): string {
|
|
219
|
+
const enabled = isFastEnabled(state);
|
|
220
|
+
const eligibility = getEligibility(ctx);
|
|
221
|
+
const active = enabled && eligibility.eligible;
|
|
222
|
+
const injected = state.lastInjectedAt
|
|
223
|
+
? ` Last injected for ${state.lastInjectedModel ?? "unknown model"} ${Math.max(0, Math.round((Date.now() - state.lastInjectedAt) / 1000))}s ago.`
|
|
224
|
+
: "";
|
|
225
|
+
|
|
226
|
+
if (active) {
|
|
227
|
+
const wireSetting =
|
|
228
|
+
eligibility.provider === "anthropic"
|
|
229
|
+
? `speed=${ANTHROPIC_FAST_SPEED}`
|
|
230
|
+
: `service_tier=${OPENAI_FAST_SERVICE_TIER}`;
|
|
231
|
+
return `Fast mode is ${describeMode(state)} and active for ${eligibility.modelKey}; requests will use ${wireSetting}.${injected}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (enabled) {
|
|
235
|
+
return `Fast mode is ${describeMode(state)}, but inactive for ${eligibility.modelKey}: ${eligibility.reason}.${injected}`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return `Fast mode is ${describeMode(state)}. Current model: ${eligibility.modelKey}.${injected}`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function splitBetaHeader(value: string | null | undefined): string[] {
|
|
242
|
+
return (value ?? "")
|
|
243
|
+
.split(",")
|
|
244
|
+
.map((part) => part.trim())
|
|
245
|
+
.filter(Boolean);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function isAnthropicOAuth(ctx: ExtensionContext): Promise<boolean> {
|
|
249
|
+
try {
|
|
250
|
+
const resolution = await ctx.modelRegistry.getProviderAuth(ANTHROPIC_PROVIDER_ID);
|
|
251
|
+
const apiKey = resolution?.auth.apiKey;
|
|
252
|
+
if (typeof apiKey === "string") return apiKey.includes("sk-ant-oat");
|
|
253
|
+
} catch {
|
|
254
|
+
// Fall back to the synchronous registry snapshot below.
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return ctx.model ? ctx.modelRegistry.isUsingOAuth(ctx.model) : false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getAnthropicProviderBetas(ctx: ExtensionContext, hasActiveTools: boolean): string[] {
|
|
261
|
+
const model = ctx.model;
|
|
262
|
+
if (!model) return [];
|
|
263
|
+
const compat = model.compat as AnthropicHeaderCompat | undefined;
|
|
264
|
+
|
|
265
|
+
const betas: string[] = [];
|
|
266
|
+
if (hasActiveTools && compat?.supportsEagerToolInputStreaming === false) {
|
|
267
|
+
betas.push(FINE_GRAINED_TOOL_STREAMING_BETA);
|
|
268
|
+
}
|
|
269
|
+
if (compat?.forceAdaptiveThinking !== true) {
|
|
270
|
+
betas.push(INTERLEAVED_THINKING_BETA);
|
|
271
|
+
}
|
|
272
|
+
return betas;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function injectAnthropicFastHeader(
|
|
276
|
+
headers: Record<string, string | null>,
|
|
277
|
+
ctx: ExtensionContext,
|
|
278
|
+
state: SessionState,
|
|
279
|
+
hasActiveTools: boolean,
|
|
280
|
+
): Promise<void> {
|
|
281
|
+
if (!isFastEnabled(state)) return;
|
|
282
|
+
const eligibility = getEligibility(ctx);
|
|
283
|
+
if (!eligibility.eligible || eligibility.provider !== "anthropic") return;
|
|
284
|
+
|
|
285
|
+
const matchingKeys = Object.keys(headers).filter((name) => name.toLowerCase() === "anthropic-beta");
|
|
286
|
+
const existing = matchingKeys.flatMap((name) => splitBetaHeader(headers[name]));
|
|
287
|
+
const matchingModelEntries = Object.entries(ctx.model?.headers ?? {}).filter(
|
|
288
|
+
([name]) => name.toLowerCase() === "anthropic-beta",
|
|
289
|
+
);
|
|
290
|
+
const modelBetas = matchingModelEntries.flatMap(([, value]) => splitBetaHeader(value));
|
|
291
|
+
const providerBetas = getAnthropicProviderBetas(ctx, hasActiveTools);
|
|
292
|
+
const oauthBetas = (await isAnthropicOAuth(ctx)) ? CLAUDE_CODE_OAUTH_BETAS : [];
|
|
293
|
+
const next = Array.from(
|
|
294
|
+
new Set([...modelBetas, ...existing, ...oauthBetas, ...providerBetas, ANTHROPIC_FAST_BETA]),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
for (const key of matchingKeys) delete headers[key];
|
|
298
|
+
const outputKey = matchingModelEntries.at(-1)?.[0] ?? "anthropic-beta";
|
|
299
|
+
headers[outputKey] = next.join(",");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function injectFastPayload(
|
|
303
|
+
payload: unknown,
|
|
304
|
+
ctx: ExtensionContext,
|
|
305
|
+
state: SessionState,
|
|
306
|
+
): PayloadRecord | undefined {
|
|
307
|
+
if (!isFastEnabled(state)) return undefined;
|
|
308
|
+
const eligibility = getEligibility(ctx);
|
|
309
|
+
if (!eligibility.eligible || !eligibility.provider) return undefined;
|
|
310
|
+
if (!isPayloadRecord(payload)) return undefined;
|
|
311
|
+
if (payload.model !== ctx.model?.id) return undefined;
|
|
312
|
+
|
|
313
|
+
if (eligibility.provider === "anthropic") {
|
|
314
|
+
if ("speed" in payload) return undefined;
|
|
315
|
+
state.lastInjectedAt = Date.now();
|
|
316
|
+
state.lastInjectedModel = eligibility.modelKey;
|
|
317
|
+
return { ...payload, speed: ANTHROPIC_FAST_SPEED };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if ("service_tier" in payload) return undefined;
|
|
321
|
+
state.lastInjectedAt = Date.now();
|
|
322
|
+
state.lastInjectedModel = eligibility.modelKey;
|
|
323
|
+
return { ...payload, service_tier: OPENAI_FAST_SERVICE_TIER };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export default function fastExtension(pi: ExtensionAPI) {
|
|
327
|
+
const states = new WeakMap<object, SessionState>();
|
|
328
|
+
|
|
329
|
+
function getState(ctx: ExtensionContext): SessionState {
|
|
330
|
+
let state = states.get(ctx.sessionManager);
|
|
331
|
+
if (!state) {
|
|
332
|
+
state = {
|
|
333
|
+
config: loadConfig(ctx),
|
|
334
|
+
override: "auto",
|
|
335
|
+
};
|
|
336
|
+
states.set(ctx.sessionManager, state);
|
|
337
|
+
}
|
|
338
|
+
return state;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
pi.on("session_start", (_event, ctx) => {
|
|
342
|
+
const state: SessionState = {
|
|
343
|
+
config: loadConfig(ctx),
|
|
344
|
+
override: "auto",
|
|
345
|
+
};
|
|
346
|
+
states.set(ctx.sessionManager, state);
|
|
347
|
+
updateStatus(ctx, state);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
pi.on("model_select", (_event, ctx) => {
|
|
351
|
+
updateStatus(ctx, getState(ctx));
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
pi.on("before_provider_headers", async (event, ctx) => {
|
|
355
|
+
const hasActiveTools = typeof pi.getActiveTools === "function" && pi.getActiveTools().length > 0;
|
|
356
|
+
await injectAnthropicFastHeader(event.headers, ctx, getState(ctx), hasActiveTools);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
360
|
+
const state = getState(ctx);
|
|
361
|
+
const nextPayload = injectFastPayload(event.payload, ctx, state);
|
|
362
|
+
updateStatus(ctx, state);
|
|
363
|
+
return nextPayload;
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
pi.registerCommand("fast", {
|
|
367
|
+
description: "Toggle Fast mode for supported OpenAI Codex and Anthropic Claude models",
|
|
368
|
+
getArgumentCompletions: () => null,
|
|
369
|
+
handler: async (args, ctx) => {
|
|
370
|
+
const state = getState(ctx);
|
|
371
|
+
const action = args.trim();
|
|
372
|
+
|
|
373
|
+
if (!action) {
|
|
374
|
+
state.override = isFastEnabled(state) ? "off" : "on";
|
|
375
|
+
updateStatus(ctx, state);
|
|
376
|
+
ctx.ui.notify(getStatusMessage(ctx, state), "info");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
ctx.ui.notify("Usage: /fast", "warning");
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@diegopetrucci/pi-fast",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A pi extension that enables Fast mode for supported OpenAI Codex and Anthropic Claude models based on the currently selected model.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi",
|
|
8
|
+
"openai",
|
|
9
|
+
"anthropic",
|
|
10
|
+
"fast"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/diegopetrucci/pi-extensions.git",
|
|
16
|
+
"directory": "extensions/fast"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"index.ts",
|
|
20
|
+
"fast.example.json",
|
|
21
|
+
"README.md",
|
|
22
|
+
".pi-fleet-tested-version"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"pi": {
|
|
28
|
+
"extensions": [
|
|
29
|
+
"index.ts"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
34
|
+
},
|
|
35
|
+
"type": "module",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22.19.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.83.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.83.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.83.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-illustrations-to-explain-things",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "A pi skill for generating clean, absurd Xiaohei-style article illustrations, shot lists, and visual metaphors.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.83.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.83.0
|
|
@@ -4,7 +4,7 @@ A pi GitHub research scout inspired by `pi-librarian`, with a local checkout cac
|
|
|
4
4
|
|
|
5
5
|
When the `librarian` tool runs, it can cache/reuse repository checkouts locally. Use `/librarian-cache off` to force GitHub API/search and temporary fetched files only, or `/librarian-cache on` to re-enable cached local checkouts.
|
|
6
6
|
|
|
7
|
-
The internal librarian subagent uses a fast auto-selected model by default, requests `low` thinking, and prompts its scout to batch independent GitHub probes in parallel. Use `/librarian-config` to set a persistent internal model or thinking-level preference.
|
|
7
|
+
The internal librarian subagent uses a fast auto-selected model by default, requests `low` thinking, and prompts its scout to batch independent GitHub probes in parallel. Automatic candidates and fallbacks stay within the current Pi session's model scope when that scope is non-empty; an explicit tool or `/librarian-config` model remains authoritative. Use `/librarian-config` to set a persistent internal model or thinking-level preference.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -46,6 +46,31 @@ type PiModel = {
|
|
|
46
46
|
thinkingLevelMap?: ThinkingLevelMap;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
type ModelSelectionContext = {
|
|
50
|
+
model?: any;
|
|
51
|
+
modelRegistry: { getAvailable(): any[] | Promise<any[]> };
|
|
52
|
+
scopedModels?: ReadonlyArray<{ model: any; thinkingLevel?: ThinkingLevel }>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function modelKey(model: any): string {
|
|
56
|
+
return `${model.provider}/${model.id}`.toLowerCase();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function hasModelScope(ctx: ModelSelectionContext): boolean {
|
|
60
|
+
return Array.isArray(ctx.scopedModels) && ctx.scopedModels.length > 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isModelInScope(ctx: ModelSelectionContext, model: any): boolean {
|
|
64
|
+
if (!hasModelScope(ctx)) return true;
|
|
65
|
+
const key = modelKey(model);
|
|
66
|
+
return ctx.scopedModels!.some((entry) => modelKey(entry.model) === key);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function getAutoSelectableModels(ctx: ModelSelectionContext): Promise<any[]> {
|
|
70
|
+
const available = await ctx.modelRegistry.getAvailable();
|
|
71
|
+
return hasModelScope(ctx) ? available.filter((model) => isModelInScope(ctx, model)) : available;
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
type CreateAgentSessionOptions = NonNullable<Parameters<typeof createAgentSession>[0]>;
|
|
50
75
|
|
|
51
76
|
function getModelRuntimeOption(ctx: { modelRegistry?: unknown }): Pick<CreateAgentSessionOptions, "modelRuntime"> {
|
|
@@ -677,7 +702,7 @@ function rankPreferredFastLibrarianModel(model: any): number {
|
|
|
677
702
|
}
|
|
678
703
|
|
|
679
704
|
async function findAvailableModel(
|
|
680
|
-
ctx:
|
|
705
|
+
ctx: ModelSelectionContext,
|
|
681
706
|
modelPreference: string,
|
|
682
707
|
): Promise<any | undefined> {
|
|
683
708
|
const available = await ctx.modelRegistry.getAvailable();
|
|
@@ -732,7 +757,7 @@ function withLibrarianFallbackReason(candidate: LibrarianCandidate, previous: Li
|
|
|
732
757
|
// these in order and skip ones that fail with a model-availability error,
|
|
733
758
|
// ultimately falling back to the known-good current session model.
|
|
734
759
|
async function buildLibrarianCandidates(
|
|
735
|
-
ctx:
|
|
760
|
+
ctx: ModelSelectionContext,
|
|
736
761
|
modelPreference: string | undefined,
|
|
737
762
|
thinkingLevel: ThinkingLevel,
|
|
738
763
|
): Promise<LibrarianCandidate[]> {
|
|
@@ -762,7 +787,7 @@ async function buildLibrarianCandidates(
|
|
|
762
787
|
}
|
|
763
788
|
}
|
|
764
789
|
|
|
765
|
-
const available = await ctx
|
|
790
|
+
const available = await getAutoSelectableModels(ctx);
|
|
766
791
|
const preferred = available.filter(isPreferredFastLibrarianModel);
|
|
767
792
|
const ranked = preferred.length > 0
|
|
768
793
|
? [...preferred].sort((a, b) => rankPreferredFastLibrarianModel(a) - rankPreferredFastLibrarianModel(b))
|
|
@@ -785,7 +810,7 @@ async function buildLibrarianCandidates(
|
|
|
785
810
|
});
|
|
786
811
|
|
|
787
812
|
// Final fallback: the active session model is known to be servable.
|
|
788
|
-
if (ctx.model) {
|
|
813
|
+
if (ctx.model && isModelInScope(ctx, ctx.model)) {
|
|
789
814
|
push(ctx.model, {
|
|
790
815
|
modelRef: modelRef(ctx.model),
|
|
791
816
|
provider: ctx.model.provider,
|
|
@@ -797,7 +822,11 @@ async function buildLibrarianCandidates(
|
|
|
797
822
|
}
|
|
798
823
|
|
|
799
824
|
if (candidates.length === 0) {
|
|
800
|
-
throw new Error(
|
|
825
|
+
throw new Error(
|
|
826
|
+
hasModelScope(ctx)
|
|
827
|
+
? "No authenticated models are available for Librarian in the current session model scope. Adjust the scope, log in, or configure an API key."
|
|
828
|
+
: "No authenticated models are available for Librarian. Log in or configure an API key first.",
|
|
829
|
+
);
|
|
801
830
|
}
|
|
802
831
|
|
|
803
832
|
return candidates;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.83.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.83.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.83.0
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A pi extension that enables OpenAI Codex Fast mode for ChatGPT-auth GPT-5.4, GPT-5.5, and GPT-5.6 Codex variants (`gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`).
|
|
4
4
|
|
|
5
|
+
This package is standalone-only and is not auto-loaded by the `@diegopetrucci/pi-extensions` collection package. The collection uses the unified [`fast`](../fast) extension instead.
|
|
6
|
+
|
|
5
7
|
When active, the extension injects this into eligible OpenAI Codex request payloads:
|
|
6
8
|
|
|
7
9
|
```json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-openai-fast",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "A pi extension that enables OpenAI Codex Fast mode for ChatGPT-auth GPT-5.4, GPT-5.5, and GPT-5.6 Codex variants by injecting the priority service tier.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.83.0
|
|
@@ -106,6 +106,8 @@ Tool-call parameters still win over these defaults. `auto` clears the configured
|
|
|
106
106
|
- `thinkingLevel` - optional reasoning/thinking override; falls back to the `/oracle thinking` default, then built-in defaults
|
|
107
107
|
- `cwd` - optional working directory override
|
|
108
108
|
|
|
109
|
+
When the current Pi session has a non-empty model scope, Oracle limits automatic model selection and automatic fallbacks to that scope. An explicit tool or `/oracle model` override remains authoritative even when it is outside the session scope.
|
|
110
|
+
|
|
109
111
|
## Notes
|
|
110
112
|
|
|
111
113
|
- The oracle is intentionally **read-only by default**.
|