@f5-sales-demo/xcsh 21.49.4 → 22.0.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.
Files changed (142) hide show
  1. package/README.md +0 -3
  2. package/package.json +8 -8
  3. package/src/browser/headless-bridge.ts +0 -2
  4. package/src/browser/host-profiles.ts +1 -1
  5. package/src/capability/extension.ts +2 -4
  6. package/src/capability/types.ts +6 -6
  7. package/src/cli/args.ts +0 -11
  8. package/src/cli/flag-spec.ts +0 -2
  9. package/src/commit/agentic/agent.ts +0 -1
  10. package/src/commit/agentic/index.ts +2 -2
  11. package/src/commit/pipeline.ts +2 -2
  12. package/src/config/auto-config.ts +29 -4
  13. package/src/config/model-catalog.ts +13 -5
  14. package/src/config/model-registry.ts +74 -45
  15. package/src/config/model-resolver.ts +14 -0
  16. package/src/config/settings-schema.ts +14 -66
  17. package/src/context/profile.ts +2 -2
  18. package/src/cursor.ts +5 -26
  19. package/src/discovery/builtin.ts +1 -130
  20. package/src/discovery/claude-plugins.ts +1 -88
  21. package/src/discovery/claude.ts +0 -80
  22. package/src/discovery/codex.ts +11 -148
  23. package/src/discovery/cursor.ts +1 -84
  24. package/src/discovery/gemini.ts +0 -89
  25. package/src/discovery/helpers.ts +1 -1
  26. package/src/discovery/index.ts +0 -4
  27. package/src/discovery/opencode.ts +1 -106
  28. package/src/discovery/windsurf.ts +1 -82
  29. package/src/extensibility/custom-tools/types.ts +0 -4
  30. package/src/extensibility/extensions/types.ts +0 -4
  31. package/src/extensibility/plugins/marketplace/builtin-marketplace-snapshot.ts +1 -1
  32. package/src/extensibility/plugins/marketplace/builtin-marketplace.provenance.json +1 -1
  33. package/src/extensibility/plugins/marketplace/types.ts +0 -1
  34. package/src/internal-urls/api-catalog-discovery.ts +32 -4
  35. package/src/internal-urls/api-catalog-qmd-index.generated.ts +2 -2
  36. package/src/internal-urls/build-info.generated.ts +8 -8
  37. package/src/internal-urls/docs-index.generated.ts +14 -16
  38. package/src/internal-urls/index.ts +1 -2
  39. package/src/internal-urls/router.ts +1 -1
  40. package/src/internal-urls/types.ts +2 -2
  41. package/src/lifecycle/native-acceptance-driver.ts +0 -1
  42. package/src/lifecycle/native-acceptance.ts +1 -2
  43. package/src/locales/ar.json +1 -65
  44. package/src/locales/de.json +1 -65
  45. package/src/locales/en.json +1 -99
  46. package/src/locales/es.json +1 -65
  47. package/src/locales/fr.json +1 -65
  48. package/src/locales/hi.json +1 -65
  49. package/src/locales/it.json +1 -65
  50. package/src/locales/ja.json +1 -65
  51. package/src/locales/ko.json +1 -65
  52. package/src/locales/pt-br.json +1 -65
  53. package/src/locales/th.json +1 -65
  54. package/src/locales/zh-cn.json +1 -65
  55. package/src/locales/zh-tw.json +1 -65
  56. package/src/main.ts +5 -22
  57. package/src/memories/index.ts +3 -3
  58. package/src/modes/acp/acp-agent.ts +14 -146
  59. package/src/modes/components/agent-dashboard.ts +0 -1
  60. package/src/modes/components/extensions/extension-dashboard.ts +1 -2
  61. package/src/modes/components/extensions/state-manager.ts +0 -46
  62. package/src/modes/components/extensions/types.ts +0 -1
  63. package/src/modes/components/footer.ts +3 -1
  64. package/src/modes/components/model-selector.ts +1 -0
  65. package/src/modes/components/settings-defs.ts +1 -2
  66. package/src/modes/components/status-line.ts +2 -2
  67. package/src/modes/controllers/command-controller.ts +0 -14
  68. package/src/modes/controllers/selector-controller.ts +4 -12
  69. package/src/modes/interactive-mode.ts +3 -20
  70. package/src/modes/theme/theme.ts +0 -6
  71. package/src/modes/types.ts +0 -6
  72. package/src/prompts/internal-urls/about.md +1 -1
  73. package/src/prompts/system/progressive-system-prompt.md +1 -1
  74. package/src/prompts/system/system-prompt.md +0 -9
  75. package/src/prompts/tools/search-tool-bm25-progressive.md +3 -6
  76. package/src/qmd-smoke.ts +189 -24
  77. package/src/remote-control/host.ts +1 -1
  78. package/src/remote-control/interactions.ts +1 -1
  79. package/src/remote-control/managed-sessions.ts +1 -1
  80. package/src/remote-control/protocol-methods.json +0 -10
  81. package/src/remote-control/router.ts +1 -2
  82. package/src/remote-control/session.ts +4 -4
  83. package/src/remote-control/voice-timeline.ts +2 -2
  84. package/src/sdk.ts +12 -254
  85. package/src/session/active-model.ts +2 -2
  86. package/src/session/agent-session.ts +35 -330
  87. package/src/session/compaction/branch-summarization.ts +2 -2
  88. package/src/session/compaction/compaction.ts +21 -4
  89. package/src/session/session-manager.ts +0 -40
  90. package/src/slash-commands/builtin-registry.ts +3 -85
  91. package/src/system-prompt.ts +0 -9
  92. package/src/task/executor.ts +2 -118
  93. package/src/task/index.ts +0 -4
  94. package/src/thinking.ts +5 -0
  95. package/src/{mcp → tools}/discoverable-tool-metadata.ts +14 -84
  96. package/src/tools/index.ts +7 -28
  97. package/src/tools/path-utils.ts +2 -10
  98. package/src/tools/read.ts +2 -2
  99. package/src/tools/search-tool-bm25.ts +28 -72
  100. package/src/tools/vim.ts +1 -1
  101. package/src/web/search/index.ts +2 -5
  102. package/src/web/search/provider.ts +0 -3
  103. package/src/web/search/providers/exa.ts +4 -84
  104. package/src/web/search/types.ts +0 -2
  105. package/src/capability/mcp.ts +0 -74
  106. package/src/config/mcp-schema.json +0 -230
  107. package/src/discovery/mcp-json.ts +0 -171
  108. package/src/discovery/vscode.ts +0 -105
  109. package/src/exa/factory.ts +0 -61
  110. package/src/exa/index.ts +0 -27
  111. package/src/exa/mcp-client.ts +0 -293
  112. package/src/exa/render.ts +0 -232
  113. package/src/exa/researcher.ts +0 -47
  114. package/src/exa/search.ts +0 -86
  115. package/src/exa/types.ts +0 -166
  116. package/src/exa/websets.ts +0 -247
  117. package/src/internal-urls/mcp-protocol.ts +0 -156
  118. package/src/mcp/client.ts +0 -482
  119. package/src/mcp/config-writer.ts +0 -225
  120. package/src/mcp/config.ts +0 -365
  121. package/src/mcp/index.ts +0 -29
  122. package/src/mcp/json-rpc.ts +0 -84
  123. package/src/mcp/loader.ts +0 -124
  124. package/src/mcp/manager.ts +0 -1173
  125. package/src/mcp/oauth-discovery.ts +0 -349
  126. package/src/mcp/oauth-flow.ts +0 -398
  127. package/src/mcp/policy.ts +0 -24
  128. package/src/mcp/render.ts +0 -161
  129. package/src/mcp/runtime-controller.ts +0 -95
  130. package/src/mcp/smithery-auth.ts +0 -105
  131. package/src/mcp/smithery-connect.ts +0 -145
  132. package/src/mcp/smithery-registry.ts +0 -477
  133. package/src/mcp/tool-bridge.ts +0 -449
  134. package/src/mcp/tool-cache.ts +0 -117
  135. package/src/mcp/transports/http.ts +0 -475
  136. package/src/mcp/transports/index.ts +0 -6
  137. package/src/mcp/transports/stdio.ts +0 -325
  138. package/src/mcp/types.ts +0 -428
  139. package/src/modes/components/mcp-add-wizard.ts +0 -1346
  140. package/src/modes/controllers/mcp-command-controller.ts +0 -2502
  141. package/src/prompts/tools/search-tool-bm25.md +0 -38
  142. package/src/web/search/providers/zai.ts +0 -311
package/README.md CHANGED
@@ -10,9 +10,6 @@ For installation, setup, provider configuration, model roles, slash commands, an
10
10
  Package-specific references:
11
11
 
12
12
  - [CHANGELOG](./CHANGELOG.md)
13
- - [MCP configuration guide](../../docs/mcp-config.md)
14
- - [MCP runtime lifecycle](../../docs/mcp-runtime-lifecycle.md)
15
- - [MCP server/tool authoring](../../docs/mcp-server-tool-authoring.md)
16
13
  - [Remote-control implementation](./src/remote-control/IMPLEMENTATION.md)
17
14
  - [Remote-control acceptance](./src/remote-control/ACCEPTANCE.md)
18
15
  - [CONTRIBUTING](../../CONTRIBUTING.md)
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.4",
4
+ "version": "22.0.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.4",
71
- "@f5-sales-demo/pi-ai": "21.49.4",
72
- "@f5-sales-demo/pi-natives": "21.49.4",
73
- "@f5-sales-demo/pi-resource-management": "21.49.4",
74
- "@f5-sales-demo/pi-tui": "21.49.4",
75
- "@f5-sales-demo/pi-utils": "21.49.4",
76
- "@f5-sales-demo/xcsh-stats": "21.49.4",
70
+ "@f5-sales-demo/pi-agent-core": "22.0.0",
71
+ "@f5-sales-demo/pi-ai": "22.0.0",
72
+ "@f5-sales-demo/pi-natives": "22.0.0",
73
+ "@f5-sales-demo/pi-resource-management": "22.0.0",
74
+ "@f5-sales-demo/pi-tui": "22.0.0",
75
+ "@f5-sales-demo/pi-utils": "22.0.0",
76
+ "@f5-sales-demo/xcsh-stats": "22.0.0",
77
77
  "@mozilla/readability": "^0.6",
78
78
  "@resvg/resvg-wasm": "2.6.2",
79
79
  "@sinclair/typebox": "0.34.52",
@@ -180,7 +180,6 @@ export async function startHeadlessChatBridge(
180
180
  hasUI: false,
181
181
  toolNames: [...new Set([...BROWSER_TOOL_NAMES, ...EXTENSION_AGENT_TOOL_NAMES])],
182
182
  customTools: createExtensionBridgeTools(bridge),
183
- enableMCP: false,
184
183
  enableLsp: false,
185
184
  disableExtensionDiscovery: true,
186
185
  ...(process.env.XCSH_BENCH_EXTENSION
@@ -201,7 +200,6 @@ export async function startHeadlessChatBridge(
201
200
  sessionManager: SessionManager.inMemory(cwd),
202
201
  toolNames: [...OFFICE_TOOL_NAMES],
203
202
  customTools: [],
204
- enableMCP: false,
205
203
  enableLsp: false,
206
204
  disableExtensionDiscovery: true,
207
205
  bundledExtensions: ["sandbox-guard"],
@@ -102,7 +102,7 @@ SAFETY — NEVER DO THESE:
102
102
  const OFFICE_NATIVE_TOOLS_NOTE = `
103
103
  NATIVE TOOLS: Beyond the document host tools, you have xcsh's full local toolset — \`bash\` (run shell commands, including CLIs like \`az\`, \`gh\`, \`terraform\`, \`git\` when installed and authenticated), file tools (\`read\`/\`write\`/\`edit\`), and \`grep\` — plus any skills available in this workspace. Reach for them when the task genuinely needs them (pull live data with a CLI, read a local file the user points you at). Prefer the document host tools for document work. Your file tools and shell are confined to the folder xcsh was launched from.
104
104
 
105
- This pane runs NO MCP servers and exposes NO plugin-provided TOOLS — the tools listed above (plus the document host tools) are everything you can call. To use a cloud or SCM CLI, invoke it directly with \`bash\` (e.g. \`az account show\`, \`gh repo view\`) rather than hunting for an MCP server that would provide it. The user sees your narration, so don't describe a missing MCP tool as a failure — just use the CLI.
105
+ This pane exposes no plugin-provided tools — the tools listed above (plus the document host tools) are everything you can call. To use a cloud or SCM CLI, invoke it directly with \`bash\` (e.g. \`az account show\`, \`gh repo view\`).
106
106
 
107
107
  Plugin RESOURCES are a different thing and they ARE available to you: an installed plugin's skills, slash commands, schemas, templates and engines. Read them through \`xcsh://plugin/<name>\` (summary), \`xcsh://plugin/<name>/<key>\` (a declared resource such as \`schema\`), or \`xcsh://plugin/<name>/file/<path>\` (any file in the plugin), and run a plugin's engine with \`bash\` (e.g. \`bun xcsh://plugin/<name>/file/engine/cli.ts <command>\`). When a plugin declares a deterministic engine, use it — never recompute by hand what the engine computes.
108
108
 
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Extensions Capability
3
3
  *
4
- * Gemini-style extensions that provide MCP servers, tools, and context.
4
+ * Gemini-style extensions that provide tools and context.
5
5
  */
6
6
  import { defineCapability } from ".";
7
- import type { MCPServer } from "./mcp";
8
7
  import type { SourceMeta } from "./types";
9
8
 
10
9
  /**
@@ -13,7 +12,6 @@ import type { SourceMeta } from "./types";
13
12
  export interface ExtensionManifest {
14
13
  name?: string;
15
14
  description?: string;
16
- mcpServers?: Record<string, Omit<MCPServer, "name" | "_source">>;
17
15
  tools?: unknown[];
18
16
  context?: unknown;
19
17
  }
@@ -37,7 +35,7 @@ export interface Extension {
37
35
  export const extensionCapability = defineCapability<Extension>({
38
36
  id: "extensions",
39
37
  displayName: "Extensions",
40
- description: "Gemini-style extensions providing MCP servers, tools, and context",
38
+ description: "Gemini-style extensions providing tools and context",
41
39
  key: ext => ext.name,
42
40
  validate: ext => {
43
41
  if (!ext.name) return "Missing extension name";
@@ -2,8 +2,8 @@
2
2
  * Core types for the capability-based config discovery system.
3
3
  *
4
4
  * This architecture inverts control: instead of callers knowing about paths like
5
- * `.xcsh`, `.codex`, `.gemini`, they simply ask for `load("mcps")` and get back
6
- * a unified array of MCP servers.
5
+ * `.xcsh`, `.codex`, `.gemini`, they simply ask for a capability and get back
6
+ * a unified array of items.
7
7
  */
8
8
 
9
9
  /**
@@ -33,7 +33,7 @@ export interface LoadResult<T> {
33
33
  * A provider that can load items for a capability.
34
34
  */
35
35
  export interface Provider<T> {
36
- /** Unique provider ID (e.g., "xcsh", "mcp-json", "agents-md") */
36
+ /** Unique provider ID (e.g., "xcsh", "codex", "agents-md") */
37
37
  id: string;
38
38
 
39
39
  /** Human-readable name for UI display (e.g., "xcsh", "OpenAI Codex") */
@@ -47,7 +47,7 @@ export interface Provider<T> {
47
47
  * Suggested ranges:
48
48
  * 100+ : Primary providers (xcsh, pi)
49
49
  * 50-99: Tool-specific providers (claude, codex, gemini)
50
- * 1-49 : Shared standards (mcp-json, agents-md)
50
+ * 1-49 : Shared standards (agents-md)
51
51
  */
52
52
  priority: number;
53
53
 
@@ -110,10 +110,10 @@ export interface CapabilityResult<T> {
110
110
  * Definition of a capability.
111
111
  */
112
112
  export interface Capability<T> {
113
- /** Capability ID (e.g., "mcps", "skills", "context-files") */
113
+ /** Capability ID (e.g., "skills", "context-files") */
114
114
  id: string;
115
115
 
116
- /** Human-readable name for UI display (e.g., "MCP Servers", "Skills") */
116
+ /** Human-readable name for UI display (e.g., "Skills", "Context Files") */
117
117
  displayName: string;
118
118
 
119
119
  /** Short description for settings/status UI */
package/src/cli/args.ts CHANGED
@@ -3,7 +3,6 @@
3
3
  */
4
4
  import { type Effort, THINKING_EFFORTS } from "@f5-sales-demo/pi-ai";
5
5
  import { APP_NAME, CONFIG_DIR_NAME, logger } from "@f5-sales-demo/pi-utils";
6
- import { CliUsageError } from "@f5-sales-demo/pi-utils/cli";
7
6
  import chalk from "chalk";
8
7
  import { parseEffort } from "../thinking";
9
8
  import {
@@ -50,8 +49,6 @@ export interface Args {
50
49
  models?: string[];
51
50
  tools?: string[];
52
51
  noTools?: boolean;
53
- mcp?: boolean;
54
- noMcp?: boolean;
55
52
  noLsp?: boolean;
56
53
  noPty?: boolean;
57
54
  hooks?: string[];
@@ -161,12 +158,6 @@ const APPLY: Record<LaunchFlagName, (result: Args, value: string | true) => void
161
158
  "no-tools": r => {
162
159
  r.noTools = true;
163
160
  },
164
- mcp: r => {
165
- r.mcp = true;
166
- },
167
- "no-mcp": r => {
168
- r.noMcp = true;
169
- },
170
161
  "no-lsp": r => {
171
162
  r.noLsp = true;
172
163
  },
@@ -404,8 +395,6 @@ export function parseArgs(args: string[], extensionFlags?: ExtensionFlagRegistry
404
395
  result.unrecognizedFlags.push({ token, name: name ?? token.replace(/^-+/, "") });
405
396
  }
406
397
 
407
- if (result.mcp && result.noMcp) throw new CliUsageError("--mcp cannot be combined with --no-mcp");
408
- if (result.mcp && result.noTools) throw new CliUsageError("--mcp cannot be combined with --no-tools");
409
398
  return result;
410
399
  }
411
400
 
@@ -79,8 +79,6 @@ export const LAUNCH_FLAGS = defineFlags({
79
79
  "provider-session-id": { arity: "value", description: "Resume a provider-side session", hidden: true },
80
80
  models: { arity: "value", description: "Comma-separated model patterns for Ctrl+P cycling" },
81
81
  "no-tools": { arity: "boolean", description: "Disable all built-in tools" },
82
- mcp: { arity: "boolean", description: "Enable MCP servers for this run" },
83
- "no-mcp": { arity: "boolean", description: "Disable MCP server discovery and tools" },
84
82
  "no-lsp": { arity: "boolean", description: "Disable LSP tools, formatting, and diagnostics" },
85
83
  "no-pty": { arity: "boolean", description: "Disable PTY-based interactive bash execution" },
86
84
  tools: { arity: "value", description: "Comma-separated list of tools to enable (default: all)" },
@@ -63,7 +63,6 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
63
63
  systemPrompt,
64
64
  customTools: tools,
65
65
  enableLsp: false,
66
- enableMCP: false,
67
66
  hasUI: false,
68
67
  spawns,
69
68
  toolNames: ["__none__"],
@@ -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;