@f5-sales-demo/xcsh 21.49.3 → 21.50.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/xcsh",
4
- "version": "21.49.3",
4
+ "version": "21.50.0",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -67,13 +67,13 @@
67
67
  },
68
68
  "dependencies": {
69
69
  "@agentclientprotocol/sdk": "1.4.0",
70
- "@f5-sales-demo/pi-agent-core": "21.49.3",
71
- "@f5-sales-demo/pi-ai": "21.49.3",
72
- "@f5-sales-demo/pi-natives": "21.49.3",
73
- "@f5-sales-demo/pi-resource-management": "21.49.3",
74
- "@f5-sales-demo/pi-tui": "21.49.3",
75
- "@f5-sales-demo/pi-utils": "21.49.3",
76
- "@f5-sales-demo/xcsh-stats": "21.49.3",
70
+ "@f5-sales-demo/pi-agent-core": "21.50.0",
71
+ "@f5-sales-demo/pi-ai": "21.50.0",
72
+ "@f5-sales-demo/pi-natives": "21.50.0",
73
+ "@f5-sales-demo/pi-resource-management": "21.50.0",
74
+ "@f5-sales-demo/pi-tui": "21.50.0",
75
+ "@f5-sales-demo/pi-utils": "21.50.0",
76
+ "@f5-sales-demo/xcsh-stats": "21.50.0",
77
77
  "@mozilla/readability": "^0.6",
78
78
  "@resvg/resvg-wasm": "2.6.2",
79
79
  "@sinclair/typebox": "0.34.52",
@@ -31,8 +31,8 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
31
31
  process.stdout.write("● Resolving model...\n");
32
32
  const modelRegistry = new ModelRegistry(authStorage, undefined, {
33
33
  getProviderOrder: () => settings.get("modelProviderOrder"),
34
- getOpenAICodexMaxContext: () => settings.get("providers.openaiCodexMaxContext"),
35
- getLiteLLMMaxContext: () => settings.get("providers.litellmMaxContext"),
34
+ getOpenAIContextTier: () => settings.get("providers.openaiContextTier"),
35
+ getLiteLLMContextTier: () => settings.get("providers.litellmContextTier"),
36
36
  });
37
37
  await modelRegistry.refresh();
38
38
  const stagedFilesPromise = (async () => {
@@ -44,8 +44,8 @@ async function runLegacyCommitCommand(args: CommitCommandArgs): Promise<void> {
44
44
  const authStorage = await discoverAuthStorage();
45
45
  const modelRegistry = new ModelRegistry(authStorage, undefined, {
46
46
  getProviderOrder: () => settings.get("modelProviderOrder"),
47
- getOpenAICodexMaxContext: () => settings.get("providers.openaiCodexMaxContext"),
48
- getLiteLLMMaxContext: () => settings.get("providers.litellmMaxContext"),
47
+ getOpenAIContextTier: () => settings.get("providers.openaiContextTier"),
48
+ getLiteLLMContextTier: () => settings.get("providers.litellmContextTier"),
49
49
  });
50
50
  await modelRegistry.refresh();
51
51
 
@@ -20,7 +20,7 @@ import { hardenAgentConfigFileSync, writeAgentConfigFileSync } from "./agent-con
20
20
  import { DEFAULT_MODEL_ROLE } from "./settings-schema";
21
21
 
22
22
  /** Current config schema version. Bump when the generated format changes. */
23
- export const CURRENT_CONFIG_VERSION = 9;
23
+ export const CURRENT_CONFIG_VERSION = 11;
24
24
  const LITELLM_CONFIG_DIR_MODE = 0o700;
25
25
  const LITELLM_MODELS_FILE_MODE = 0o600;
26
26
  const GENERATED_LITELLM_MARKER = "# Auto-generated by xcsh for LiteLLM proxy";
@@ -123,6 +123,7 @@ export function generateModelsYml(baseUrl: string, options?: GenerateModelsYmlOp
123
123
  " - gpt-6-astra",
124
124
  " - gpt-5.6-luna",
125
125
  " - gpt-5.6-sol",
126
+ " - gpt-5.5",
126
127
  " models:",
127
128
  " - id: gpt-6-luna",
128
129
  " name: GPT-6 Luna",
@@ -130,7 +131,8 @@ export function generateModelsYml(baseUrl: string, options?: GenerateModelsYmlOp
130
131
  ` baseUrl: "${responsesBaseUrl}"`,
131
132
  " - id: gpt-5.6-terra",
132
133
  " name: GPT-5.6 Terra",
133
- " api: openai-completions",
134
+ " api: openai-responses",
135
+ ` baseUrl: "${responsesBaseUrl}"`,
134
136
  " - id: gpt-6-sol",
135
137
  " name: GPT-6 Sol",
136
138
  " api: openai-responses",
@@ -141,10 +143,16 @@ export function generateModelsYml(baseUrl: string, options?: GenerateModelsYmlOp
141
143
  ` baseUrl: "${responsesBaseUrl}"`,
142
144
  " - id: gpt-5.6-luna",
143
145
  " name: GPT-5.6 Luna",
144
- " api: openai-completions",
146
+ " api: openai-responses",
147
+ ` baseUrl: "${responsesBaseUrl}"`,
145
148
  " - id: gpt-5.6-sol",
146
149
  " name: GPT-5.6 Sol",
147
- " api: openai-completions",
150
+ " api: openai-responses",
151
+ ` baseUrl: "${responsesBaseUrl}"`,
152
+ " - id: gpt-5.5",
153
+ " name: GPT-5.5",
154
+ " api: openai-responses",
155
+ ` baseUrl: "${responsesBaseUrl}"`,
148
156
  " picker:",
149
157
  " groupId: litellm",
150
158
  " groupLabel: LiteLLM",
@@ -251,6 +259,23 @@ export function generateModelsYml(baseUrl: string, options?: GenerateModelsYmlOp
251
259
  " supportsTemperature: false",
252
260
  " gpt-5.6-luna:",
253
261
  " reasoning: true",
262
+ " input:",
263
+ " - text",
264
+ " - image",
265
+ " contextWindow: 1050000",
266
+ " maxTokens: 128000",
267
+ " compat:",
268
+ " supportsTemperature: false",
269
+ " cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
270
+ " gpt-5.5:",
271
+ " reasoning: true",
272
+ " input:",
273
+ " - text",
274
+ " - image",
275
+ " contextWindow: 1050000",
276
+ " maxTokens: 128000",
277
+ " compat:",
278
+ " supportsTemperature: false",
254
279
  " cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
255
280
  ];
256
281
 
@@ -10,9 +10,16 @@ function compareVersions(left: string, right: string): number {
10
10
  return 0;
11
11
  }
12
12
 
13
- /** Keep historical catalogs available while presenting only current user-facing families. */
14
- const DEFAULT_OPENAI_CODEX_MODEL_IDS = new Set(["gpt-5.6-terra", "gpt-6-astra", "gpt-6-luna", "gpt-6-sol"]);
15
- const CURRENT_LITELLM_OPENAI_MODEL_IDS = new Set(["gpt-6-luna", "gpt-5.6-terra", "gpt-6-sol", "gpt-6-astra"]);
13
+ /** Present the exact model-interaction contract when each provider advertises the model. */
14
+ const CODEX_INTERACTION_MODEL_IDS = new Set([
15
+ "gpt-6-astra",
16
+ "gpt-6-sol",
17
+ "gpt-6-luna",
18
+ "gpt-5.6-sol",
19
+ "gpt-5.6-terra",
20
+ "gpt-5.6-luna",
21
+ "gpt-5.5",
22
+ ]);
16
23
 
17
24
  /** Shared catalog boundary for the TUI and attached human interfaces. */
18
25
  export function filterCurrentBrowserModels(models: readonly Model[]): Model[] {
@@ -39,9 +46,10 @@ export function filterCurrentBrowserModels(models: readonly Model[]): Model[] {
39
46
  return models.filter(model => {
40
47
  const gpt = model.id.match(/^gpt-(\d+(?:\.\d+)?)(?:-|$)/i);
41
48
  if (gpt?.[1]) {
49
+ if (model.provider === "openai-codex" || model.provider === "litellm") {
50
+ return CODEX_INTERACTION_MODEL_IDS.has(model.id.toLowerCase());
51
+ }
42
52
  if (compareVersions(gpt[1], "5.6") < 0) return false;
43
- if (model.provider === "openai-codex") return DEFAULT_OPENAI_CODEX_MODEL_IDS.has(model.id.toLowerCase());
44
- if (model.provider === "litellm") return CURRENT_LITELLM_OPENAI_MODEL_IDS.has(model.id.toLowerCase());
45
53
  }
46
54
  const claude = model.id.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:[.-](\d+))?(?:-|$)/i);
47
55
  if (claude?.[1] && claude[2]) {
@@ -2,7 +2,9 @@ import * as path from "node:path";
2
2
  import {
3
3
  type Api,
4
4
  type AssistantMessageEventStream,
5
+ applyCodexInteractionMetadata,
5
6
  applyGeneratedModelPolicies,
7
+ type CodexContextTier,
6
8
  type Context,
7
9
  createModelManager,
8
10
  DEFAULT_LOCAL_TOKEN,
@@ -23,6 +25,7 @@ import {
23
25
  readModelCache,
24
26
  registerCustomApi,
25
27
  registerOAuthProvider,
28
+ resolveCodexContextBudget,
26
29
  type SimpleStreamOptions,
27
30
  type ThinkingConfig,
28
31
  unregisterCustomApis,
@@ -50,17 +53,14 @@ export type { CanonicalModelIndex, CanonicalModelRecord, CanonicalModelVariant,
50
53
 
51
54
  export const kNoAuth = NO_AUTH_API_KEY;
52
55
 
53
- const OPENAI_CODEX_STANDARD_CONTEXT_WINDOW = 272_000;
54
- const OPENAI_CODEX_MAX_CONTEXT_WINDOW = 1_050_000;
55
- const OPENAI_CODEX_MAX_CONTEXT_MODEL_IDS = new Set(["gpt-6-luna", "gpt-6-sol"]);
56
- const LITELLM_STANDARD_CONTEXT_WINDOW = 272_000;
57
- const LITELLM_MAX_CONTEXT_WINDOW = 1_050_000;
58
- const LITELLM_MAX_CONTEXT_MODEL_IDS = new Set(["gpt-6-luna", "gpt-5.6-terra", "gpt-6-sol", "gpt-6-astra"]);
59
-
60
56
  export function isAuthenticated(apiKey: string | undefined | null): apiKey is string {
61
57
  return Boolean(apiKey) && apiKey !== kNoAuth;
62
58
  }
63
59
 
60
+ function positiveInteger(value: unknown): number | undefined {
61
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : undefined;
62
+ }
63
+
64
64
  export type ModelRole = "default" | "smol" | "slow" | "vision" | "plan" | "designer" | "commit" | "task";
65
65
 
66
66
  export interface ModelRoleInfo {
@@ -878,8 +878,8 @@ export class ModelRegistry {
878
878
  #lastDiscoveryWarnings: Map<string, string> = new Map();
879
879
  #hasProbed = false;
880
880
  #refreshQueue: Promise<void> = Promise.resolve();
881
- #openAICodexMaxContext = false;
882
- #litellmMaxContext = false;
881
+ #openAIContextTier: CodexContextTier = "standard";
882
+ #litellmContextTier: CodexContextTier = "standard";
883
883
 
884
884
  #enqueueRefresh(operation: () => Promise<void>): Promise<void> {
885
885
  const pending = this.#refreshQueue.then(operation);
@@ -901,8 +901,8 @@ export class ModelRegistry {
901
901
  modelsPath?: string,
902
902
  private readonly options: {
903
903
  getProviderOrder?: () => readonly string[];
904
- getOpenAICodexMaxContext?: () => boolean;
905
- getLiteLLMMaxContext?: () => boolean;
904
+ getOpenAIContextTier?: () => CodexContextTier;
905
+ getLiteLLMContextTier?: () => CodexContextTier;
906
906
  } = {},
907
907
  ) {
908
908
  this.#modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
@@ -1055,8 +1055,8 @@ export class ModelRegistry {
1055
1055
  }
1056
1056
 
1057
1057
  #loadModels() {
1058
- this.#openAICodexMaxContext = this.options.getOpenAICodexMaxContext?.() ?? this.#openAICodexMaxContext;
1059
- this.#litellmMaxContext = this.options.getLiteLLMMaxContext?.() ?? this.#litellmMaxContext;
1058
+ this.#openAIContextTier = this.options.getOpenAIContextTier?.() ?? this.#openAIContextTier;
1059
+ this.#litellmContextTier = this.options.getLiteLLMContextTier?.() ?? this.#litellmContextTier;
1060
1060
  // Load custom models from models.json first (to know which providers to override)
1061
1061
  const {
1062
1062
  models: customModels = [],
@@ -1090,42 +1090,39 @@ export class ModelRegistry {
1090
1090
  const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
1091
1091
 
1092
1092
  this.#models = this.#applyProviderModelAllowlists(this.#applyModelOverrides(combined, this.#modelOverrides));
1093
- this.#applyOpenAICodexContextWindow();
1094
- this.#applyLiteLLMContextWindow();
1093
+ this.#applyOpenAIContextTier();
1094
+ this.#applyLiteLLMContextTier();
1095
1095
  this.#rebuildCanonicalIndex();
1096
1096
  }
1097
1097
 
1098
- #applyOpenAICodexContextWindow(): void {
1099
- const contextWindow = this.#openAICodexMaxContext
1100
- ? OPENAI_CODEX_MAX_CONTEXT_WINDOW
1101
- : OPENAI_CODEX_STANDARD_CONTEXT_WINDOW;
1098
+ #applyContextTier(provider: "openai-codex" | "litellm", tier: CodexContextTier): void {
1102
1099
  for (const model of this.#models) {
1103
- if (model.provider === "openai-codex" && OPENAI_CODEX_MAX_CONTEXT_MODEL_IDS.has(model.id)) {
1104
- model.contextWindow = contextWindow;
1105
- }
1100
+ if (model.provider !== provider) continue;
1101
+ Object.assign(model, applyCodexInteractionMetadata(model));
1102
+ const budget = resolveCodexContextBudget(model.id, tier, model.providerContextWindow);
1103
+ if (budget) Object.assign(model, budget);
1106
1104
  }
1107
1105
  }
1108
1106
 
1109
- /** Update GPT-6 subscription context limits without rebuilding the registry. */
1110
- setOpenAICodexMaxContext(enabled: boolean): void {
1111
- this.#openAICodexMaxContext = enabled;
1112
- this.#applyOpenAICodexContextWindow();
1107
+ #applyOpenAIContextTier(): void {
1108
+ this.#applyContextTier("openai-codex", this.#openAIContextTier);
1109
+ }
1110
+
1111
+ /** Update subscription context limits without rebuilding the registry. */
1112
+ setOpenAIContextTier(tier: CodexContextTier): void {
1113
+ this.#openAIContextTier = tier;
1114
+ this.#applyOpenAIContextTier();
1113
1115
  this.#rebuildCanonicalIndex();
1114
1116
  }
1115
1117
 
1116
- #applyLiteLLMContextWindow(): void {
1117
- const contextWindow = this.#litellmMaxContext ? LITELLM_MAX_CONTEXT_WINDOW : LITELLM_STANDARD_CONTEXT_WINDOW;
1118
- for (const model of this.#models) {
1119
- if (model.provider === "litellm" && LITELLM_MAX_CONTEXT_MODEL_IDS.has(model.id)) {
1120
- model.contextWindow = contextWindow;
1121
- }
1122
- }
1118
+ #applyLiteLLMContextTier(): void {
1119
+ this.#applyContextTier("litellm", this.#litellmContextTier);
1123
1120
  }
1124
1121
 
1125
1122
  /** Update current internal OpenAI context limits without changing subscription models. */
1126
- setLiteLLMMaxContext(enabled: boolean): void {
1127
- this.#litellmMaxContext = enabled;
1128
- this.#applyLiteLLMContextWindow();
1123
+ setLiteLLMContextTier(tier: CodexContextTier): void {
1124
+ this.#litellmContextTier = tier;
1125
+ this.#applyLiteLLMContextTier();
1129
1126
  this.#rebuildCanonicalIndex();
1130
1127
  }
1131
1128
 
@@ -1198,6 +1195,13 @@ export class ModelRegistry {
1198
1195
  return merged;
1199
1196
  }
1200
1197
 
1198
+ #filterOverlaysBySuccessfulDiscovery(models: CustomModelOverlay[]): CustomModelOverlay[] {
1199
+ return models.filter(model => {
1200
+ const discovery = this.#providerDiscoveryStates.get(model.provider);
1201
+ return discovery?.status !== "ok" || discovery.models.includes(model.id);
1202
+ });
1203
+ }
1204
+
1201
1205
  #applyProviderModelAllowlists(models: Model<Api>[]): Model<Api>[] {
1202
1206
  return models.filter(model => {
1203
1207
  const allowlist = this.#modelAllowlists.get(model.provider);
@@ -1420,9 +1424,15 @@ export class ModelRegistry {
1420
1424
  ]);
1421
1425
  const discovered = [...configuredDiscovered, ...builtInDiscovered];
1422
1426
  // Replace only catalogs which completed successfully, including an empty response.
1427
+ const successfulDiscoveryProviders = providerFilter
1428
+ ? [...providerFilter].filter(provider => this.#providerDiscoveryStates.get(provider)?.status === "ok")
1429
+ : [...this.#providerDiscoveryStates.values()]
1430
+ .filter(state => state.status === "ok")
1431
+ .map(state => state.provider);
1423
1432
  const refreshed = new Set([
1424
1433
  ...selectedDiscoverableProviders.map(provider => provider.provider),
1425
1434
  ...discovered.map(model => model.provider),
1435
+ ...successfulDiscoveryProviders,
1426
1436
  ]);
1427
1437
  const discoveredModels = this.#applyHardcodedModelPolicies(
1428
1438
  discovered.map(model => {
@@ -1452,12 +1462,18 @@ export class ModelRegistry {
1452
1462
  model => !refreshed.has(model.provider) || this.#providerDiscoveryStates.get(model.provider)?.status !== "ok",
1453
1463
  );
1454
1464
  const resolved = this.#mergeResolvedModels(this.#models, discoveredModels);
1455
- const withConfigModels = this.#mergeCustomModels(resolved, this.#customModelOverlays);
1465
+ const withConfigModels = this.#mergeCustomModels(
1466
+ resolved,
1467
+ this.#filterOverlaysBySuccessfulDiscovery(this.#customModelOverlays),
1468
+ );
1456
1469
  // Merge runtime extension models so they survive online discovery completion
1457
- const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
1470
+ const combined = this.#mergeCustomModels(
1471
+ withConfigModels,
1472
+ this.#filterOverlaysBySuccessfulDiscovery(this.#runtimeModelOverlays),
1473
+ );
1458
1474
  this.#models = this.#applyProviderModelAllowlists(this.#applyModelOverrides(combined, this.#modelOverrides));
1459
- this.#applyOpenAICodexContextWindow();
1460
- this.#applyLiteLLMContextWindow();
1475
+ this.#applyOpenAIContextTier();
1476
+ this.#applyLiteLLMContextTier();
1461
1477
  this.#rebuildCanonicalIndex();
1462
1478
  }
1463
1479
 
@@ -2047,7 +2063,13 @@ export class ModelRegistry {
2047
2063
  throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
2048
2064
  }
2049
2065
  const payload = await response.json();
2050
- const items: Array<{ id: string; contextWindow?: number }> =
2066
+ const items: Array<{
2067
+ id: string;
2068
+ contextWindow?: number;
2069
+ context_window?: unknown;
2070
+ max_input_tokens?: unknown;
2071
+ max_output_tokens?: unknown;
2072
+ }> =
2051
2073
  providerConfig.provider === "vllm"
2052
2074
  ? parseVllmModelsPayload(payload)
2053
2075
  : ((payload as { data?: Array<{ id: string }> }).data ?? []);
@@ -2055,6 +2077,10 @@ export class ModelRegistry {
2055
2077
  for (const item of items) {
2056
2078
  const id = item.id;
2057
2079
  if (!id) continue;
2080
+ const advertisedContextWindow =
2081
+ positiveInteger(item.context_window) ?? positiveInteger(item.max_input_tokens) ?? item.contextWindow;
2082
+ const advertisedOutputLimit = positiveInteger(item.max_output_tokens);
2083
+ const contextWindow = advertisedContextWindow ?? 128000;
2058
2084
  discovered.push(
2059
2085
  enrichModelThinking({
2060
2086
  id,
@@ -2065,11 +2091,13 @@ export class ModelRegistry {
2065
2091
  reasoning: false,
2066
2092
  input: ["text"],
2067
2093
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2068
- contextWindow: item.contextWindow ?? 128000,
2094
+ contextWindow,
2069
2095
  maxTokens:
2070
- item.contextWindow === undefined
2096
+ advertisedOutputLimit ??
2097
+ (advertisedContextWindow === undefined
2071
2098
  ? 8192
2072
- : Math.max(1, Math.min(8192, Math.floor(item.contextWindow / 4))),
2099
+ : Math.max(1, Math.min(8192, Math.floor(advertisedContextWindow / 4)))),
2100
+ providerContextWindow: advertisedContextWindow,
2073
2101
  headers,
2074
2102
  }),
2075
2103
  );
@@ -2150,7 +2178,8 @@ export class ModelRegistry {
2150
2178
  // bundled corrections. Reapply generated-model policy before a cached entry
2151
2179
  // replaces its bundled peer; explicit user overrides still run afterward.
2152
2180
  applyGeneratedModelPolicies(models, { preserveDiscoveredThinking });
2153
- return models.map(model => {
2181
+ return models.map(rawModel => {
2182
+ const model = applyCodexInteractionMetadata(rawModel);
2154
2183
  if (model.id !== "gpt-5.4" || model.provider === "github-copilot") {
2155
2184
  return model;
2156
2185
  }
@@ -1116,6 +1116,20 @@ export function resolveCliModel(options: {
1116
1116
  error: undefined,
1117
1117
  };
1118
1118
  }
1119
+ const thinkingSeparator = pattern.lastIndexOf(":");
1120
+ const exactModelId =
1121
+ thinkingSeparator !== -1 && parseThinkingLevel(pattern.slice(thinkingSeparator + 1))
1122
+ ? pattern.slice(0, thinkingSeparator)
1123
+ : pattern;
1124
+ if (resolveProviderModelReference(provider, exactModelId, allModels)) {
1125
+ return {
1126
+ model: undefined,
1127
+ selector: undefined,
1128
+ thinkingLevel: undefined,
1129
+ warning: undefined,
1130
+ error: `Model "${provider}/${exactModelId}" is unavailable. Configure or sign in to provider "${provider}".`,
1131
+ };
1132
+ }
1119
1133
  }
1120
1134
 
1121
1135
  const candidates = provider ? availableModels.filter(model => model.provider === provider) : availableModels;
@@ -2123,24 +2123,27 @@ export const SETTINGS_SCHEMA = {
2123
2123
  },
2124
2124
  },
2125
2125
 
2126
- "providers.openaiCodexMaxContext": {
2127
- type: "boolean",
2128
- default: false,
2126
+ "providers.openaiContextTier": {
2127
+ type: "enum",
2128
+ values: ["standard", "codex-max", "provider-max"] as const,
2129
+ default: "standard",
2129
2130
  ui: {
2130
2131
  tab: "providers",
2131
- label: "OpenAI Codex Maximum Context",
2132
- description:
2133
- "Use the 1.05M-token context window for GPT-6 Luna and Sol (long-context pricing applies above 272K)",
2132
+ label: "OpenAI Context Tier",
2133
+ description: "Choose the standard, Codex maximum, or provider-advertised context window",
2134
+ submenu: true,
2134
2135
  },
2135
2136
  },
2136
2137
 
2137
- "providers.litellmMaxContext": {
2138
- type: "boolean",
2139
- default: false,
2138
+ "providers.litellmContextTier": {
2139
+ type: "enum",
2140
+ values: ["standard", "codex-max", "provider-max"] as const,
2141
+ default: "standard",
2140
2142
  ui: {
2141
2143
  tab: "providers",
2142
- label: "LiteLLM Maximum Context",
2143
- description: "Use the verified 1.05M-token context window for current internal OpenAI models",
2144
+ label: "LiteLLM Context Tier",
2145
+ description: "Choose the standard, Codex maximum, or live provider-advertised context window",
2146
+ submenu: true,
2144
2147
  },
2145
2148
  },
2146
2149
 
@@ -1,4 +1,4 @@
1
- import type { Model, Usage } from "@f5-sales-demo/pi-ai";
1
+ import { getModelEffectiveContextWindow, type Model, type Usage } from "@f5-sales-demo/pi-ai";
2
2
 
3
3
  export type ContextLoadingMode = "eager" | "progressive";
4
4
 
@@ -182,7 +182,7 @@ export function profileProviderPayload(payload: unknown, model: Model, call: num
182
182
  const bytes = jsonBytes(message);
183
183
  return { role: safeRole(message), bytes, estimatedTokens: estimateContextTokens(bytes) };
184
184
  }),
185
- contextWindow: model.contextWindow,
185
+ contextWindow: getModelEffectiveContextWindow(model),
186
186
  };
187
187
  }
188
188
 
@@ -1,5 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { mkdir, rename, rm, writeFile } from "node:fs/promises";
2
+ import { rmSync } from "node:fs";
3
+ import { mkdir, mkdtemp, rename, rm, writeFile } from "node:fs/promises";
4
+ import os from "node:os";
3
5
  import path from "node:path";
4
6
  import { gunzipSync } from "node:zlib";
5
7
  import { createStore } from "@tobilu/qmd";
@@ -38,6 +40,17 @@ type QmdStore = Awaited<ReturnType<typeof createStore>>;
38
40
  const qmdStores = new Map<string, Promise<QmdStore>>();
39
41
  const verifiedIndexPaths = new Map<string, string>();
40
42
  const indexExtractions = new Map<string, Promise<string>>();
43
+ const runtimeRoots = new Set<string>();
44
+ let runtimeCleanupRegistered = false;
45
+
46
+ function registerRuntimeRoot(root: string): void {
47
+ runtimeRoots.add(root);
48
+ if (runtimeCleanupRegistered) return;
49
+ runtimeCleanupRegistered = true;
50
+ process.once("exit", () => {
51
+ for (const runtimeRoot of runtimeRoots) rmSync(runtimeRoot, { recursive: true, force: true });
52
+ });
53
+ }
41
54
 
42
55
  function indexCacheKey(options: QmdBm25CatalogDiscoveryOptions): string {
43
56
  return `${options.cacheRoot}\u0000${options.prebuiltIndex.fingerprint}\u0000${options.prebuiltIndex.sqliteSha256}`;
@@ -46,7 +59,21 @@ function indexCacheKey(options: QmdBm25CatalogDiscoveryOptions): string {
46
59
  async function getQmdStore(databasePath: string): Promise<QmdStore> {
47
60
  let store = qmdStores.get(databasePath);
48
61
  if (!store) {
49
- store = createStore({ dbPath: databasePath });
62
+ store = (async () => {
63
+ // QMD's FTS query path still needs a writable SQLite runtime. Search a
64
+ // disposable copy so the checksum-pinned cache stays immutable.
65
+ const runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "xcsh-api-catalog-runtime-"));
66
+ const runtimeDatabasePath = path.join(runtimeRoot, "index.sqlite");
67
+ try {
68
+ await Bun.write(runtimeDatabasePath, Bun.file(databasePath));
69
+ const runtimeStore = await createStore({ dbPath: runtimeDatabasePath });
70
+ registerRuntimeRoot(runtimeRoot);
71
+ return runtimeStore;
72
+ } catch (error) {
73
+ await rm(runtimeRoot, { recursive: true, force: true });
74
+ throw error;
75
+ }
76
+ })();
50
77
  qmdStores.set(databasePath, store);
51
78
  }
52
79
  try {