@phi-code-admin/phi-code 0.77.2 → 0.77.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.77.3] - 2026-06-13
4
+
5
+ ### Added
6
+
7
+ - **`/context` command to view and set the active model's context window.**
8
+ `/context` shows the current window and whether it is provider-reported,
9
+ inferred, or a manual override. `/context 256k`, `/context 1M`, or
10
+ `/context 200000` set it (applied immediately to the footer and auto-compaction,
11
+ and saved as a per-model override that survives restarts and the background
12
+ refresh). `/context auto` clears the override. Useful when a provider reports no
13
+ window and the inferred value is wrong: the window directly drives when the
14
+ conversation auto-compacts (`compact when tokens > window - reserve`).
15
+
16
+ ### Changed
17
+
18
+ - **Default fallback context window raised from 128k to 256k**, matching most
19
+ current high-end models. Known 128k families (DeepSeek, Llama, GPT-4o, Hy3) are
20
+ pinned explicitly so they are not over-reported. Note the asymmetry: a too-large
21
+ window risks a late compaction and a hard "context exceeded" on a genuinely
22
+ smaller model, so set the real value with `/context` when in doubt.
23
+
3
24
  ## [0.77.2] - 2026-06-13
4
25
 
5
26
  ### Changed
@@ -21,6 +21,7 @@ import {
21
21
  buildOpenCodeGoProviderConfig,
22
22
  getOpenCodeGoModels,
23
23
  } from "./providers/opencode-go.js";
24
+ import { formatWindow, inferContextWindow, parseContextWindow } from "./providers/context-window.js";
24
25
  import { fetchLiveModels, peekCache, resetLiveModelsCache, toPersistedModel } from "./providers/live-models.js";
25
26
 
26
27
  const PROVIDER_DISPLAY: Record<string, string> = {
@@ -190,6 +191,96 @@ export default function modelsExtension(pi: ExtensionAPI) {
190
191
  },
191
192
  });
192
193
 
194
+ pi.registerCommand("context", {
195
+ description:
196
+ "Show or set the active model's context window (e.g. `/context 256k`, `/context 1M`, `/context auto`). Drives when the conversation auto-compacts.",
197
+ handler: async (args, ctx) => {
198
+ const model = ctx.model;
199
+ if (!model) {
200
+ ctx.ui.notify("No active model. Select one with `/model` first.", "warning");
201
+ return;
202
+ }
203
+ const provider = model.provider;
204
+ const modelId = model.id;
205
+ const arg = args.trim();
206
+
207
+ const readOverrideWindow = (): number | undefined => {
208
+ const overrides = store.getProvider(provider)?.modelOverrides as
209
+ | Record<string, { contextWindow?: number }>
210
+ | undefined;
211
+ return overrides?.[modelId]?.contextWindow;
212
+ };
213
+
214
+ const writeOverrides = (overrides: Record<string, unknown>): void => {
215
+ const stored = store.getProvider(provider) ?? {};
216
+ watcher.muteForWrite("models_json_changed");
217
+ store.setKey(provider, stored.apiKey ?? "local", { modelOverrides: overrides });
218
+ };
219
+
220
+ try {
221
+ if (arg === "") {
222
+ const source = readOverrideWindow() !== undefined ? "manual override" : "provider / inferred";
223
+ ctx.ui.notify(
224
+ `**${modelId}** (\`${provider}\`) context window: \`${formatWindow(model.contextWindow)}\` (${source}).\n` +
225
+ "Set the real value with `/context 256k`, `/context 1M`, or `/context 200000`. " +
226
+ "Reset to the detected value with `/context auto`.\n" +
227
+ "This is what determines when the conversation auto-compacts.",
228
+ "info",
229
+ );
230
+ return;
231
+ }
232
+
233
+ if (arg.toLowerCase() === "auto" || arg.toLowerCase() === "reset") {
234
+ const stored = store.getProvider(provider) ?? {};
235
+ const overrides = { ...((stored.modelOverrides as Record<string, unknown>) ?? {}) };
236
+ const entry = overrides[modelId];
237
+ if (entry && typeof entry === "object") {
238
+ const next = { ...(entry as Record<string, unknown>) };
239
+ delete next.contextWindow;
240
+ if (Object.keys(next).length === 0) delete overrides[modelId];
241
+ else overrides[modelId] = next;
242
+ }
243
+ writeOverrides(overrides);
244
+
245
+ // Revert the active model to the persisted/inferred window.
246
+ const persistedModels = (store.getProvider(provider)?.models as
247
+ | Array<{ id?: string; contextWindow?: number }>
248
+ | undefined) ?? [];
249
+ const persisted = persistedModels.find((m) => m?.id === modelId)?.contextWindow;
250
+ const reverted = persisted && persisted > 0 ? persisted : inferContextWindow(modelId, undefined, provider);
251
+ await pi.setModel({ ...model, contextWindow: reverted });
252
+ ctx.ui.notify(`Cleared context override for **${modelId}**. Reverted to \`${formatWindow(reverted)}\`.`, "info");
253
+ return;
254
+ }
255
+
256
+ const value = parseContextWindow(arg);
257
+ if (!value) {
258
+ ctx.ui.notify("Invalid value. Use e.g. `256k`, `1M`, or `200000`.", "warning");
259
+ return;
260
+ }
261
+
262
+ // Immediate effect: the footer and auto-compaction use the new window right away.
263
+ await pi.setModel({ ...model, contextWindow: value });
264
+
265
+ // Persist as a per-model override so it survives restarts and the background
266
+ // refresh (which rewrites `models` but leaves `modelOverrides` untouched).
267
+ const stored = store.getProvider(provider) ?? {};
268
+ const overrides = { ...((stored.modelOverrides as Record<string, unknown>) ?? {}) };
269
+ const existing = (overrides[modelId] as Record<string, unknown> | undefined) ?? {};
270
+ overrides[modelId] = { ...existing, contextWindow: value };
271
+ writeOverrides(overrides);
272
+
273
+ ctx.ui.notify(
274
+ `Context window for **${modelId}** set to \`${formatWindow(value)}\` (saved). ` +
275
+ `Auto-compaction now triggers near ${formatWindow(value)}.`,
276
+ "info",
277
+ );
278
+ } catch (err) {
279
+ ctx.ui.notify(`/context error: ${err instanceof Error ? err.message : String(err)}`, "error");
280
+ }
281
+ },
282
+ });
283
+
193
284
  async function listCommand(target: string | undefined, ctx: { ui: { notify: (m: string, t?: "info" | "warning" | "error") => void } }): Promise<void> {
194
285
  const providers = target ? [target] : store.listProviders();
195
286
  if (providers.length === 0) {
@@ -14,18 +14,26 @@
14
14
  * default-models.json, live-models static specs).
15
15
  */
16
16
 
17
- const DEFAULT_CONTEXT_WINDOW = 128_000;
17
+ // Default when nothing is known. 256k matches the majority of current high-end
18
+ // models. Note the asymmetry: over-reporting risks a hard "context exceeded" on a
19
+ // genuinely smaller model (caught late by the overflow safety net), while
20
+ // under-reporting only compacts a little early. Use `/context` to set the real
21
+ // window for a model whose value is unknown.
22
+ const DEFAULT_CONTEXT_WINDOW = 256_000;
18
23
 
19
24
  export function inferContextWindow(modelId: string, apiValue?: number, providerId?: string): number {
20
25
  if (typeof apiValue === "number" && apiValue > 0) return apiValue;
21
26
 
22
27
  const id = (modelId ?? "").toLowerCase();
28
+ // Large-context families that provider /models endpoints often omit.
23
29
  if (id.includes("qwen") || id.includes("minimax")) return 1_000_000;
24
30
  if (id.includes("gemini")) return id.includes("flash") ? 1_000_000 : 2_000_000;
31
+ if (id.includes("gpt-5")) return 400_000;
25
32
  if (id.includes("kimi")) return 256_000;
26
33
  if (id.includes("glm") || id.includes("mimo")) return 200_000;
27
- if (id.includes("gpt-5")) return 400_000;
28
34
  if (id.includes("claude")) return 200_000;
35
+ // Known 128k families: pin them so the larger default does not over-report them.
36
+ if (id.includes("deepseek") || id.includes("llama") || id.includes("gpt-4") || id.includes("hy3")) return 128_000;
29
37
 
30
38
  // Provider-level hint when the model id is opaque.
31
39
  const provider = (providerId ?? "").toLowerCase();
@@ -33,3 +41,23 @@ export function inferContextWindow(modelId: string, apiValue?: number, providerI
33
41
 
34
42
  return DEFAULT_CONTEXT_WINDOW;
35
43
  }
44
+
45
+ /** Parse a context-window value like "256k", "1M", "1.5m", or "200000". */
46
+ export function parseContextWindow(input: string): number | undefined {
47
+ const m = input.trim().toLowerCase().match(/^(\d+(?:\.\d+)?)\s*([km])?$/);
48
+ if (!m) return undefined;
49
+ const n = Number.parseFloat(m[1]);
50
+ if (!Number.isFinite(n) || n <= 0) return undefined;
51
+ const mult = m[2] === "k" ? 1_000 : m[2] === "m" ? 1_000_000 : 1;
52
+ const value = Math.round(n * mult);
53
+ return value > 0 ? value : undefined;
54
+ }
55
+
56
+ /** Format a token count as a compact "256k" / "1M" label. */
57
+ export function formatWindow(n: number): string {
58
+ if (n >= 1_000_000) {
59
+ const v = n / 1_000_000;
60
+ return `${Number.isInteger(v) ? v : v.toFixed(1)}M`;
61
+ }
62
+ return `${Math.round(n / 1_000)}k`;
63
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.77.2",
3
+ "version": "0.77.3",
4
4
  "description": "Coding agent CLI with persistent memory, sub-agents, intelligent routing, and orchestration",
5
5
  "type": "module",
6
6
  "piConfig": {