@f5-sales-demo/xcsh 21.44.0 → 21.45.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/CHANGELOG.md +12 -0
- package/package.json +8 -8
- package/src/cli/plugin-cli.ts +102 -36
- package/src/commands/plugin.ts +4 -0
- package/src/commit/agentic/index.ts +2 -0
- package/src/commit/pipeline.ts +2 -0
- package/src/config/auto-config.ts +110 -9
- package/src/config/model-catalog.ts +2 -0
- package/src/config/model-registry.ts +25 -0
- package/src/config/settings-schema.ts +17 -6
- package/src/extensibility/extensions/runner.ts +2 -1
- package/src/extensibility/plugins/marketplace/builtin-marketplace-snapshot.ts +1 -1
- package/src/extensibility/plugins/marketplace/builtin-marketplace.provenance.json +3 -3
- package/src/extensibility/plugins/marketplace/manager.ts +44 -18
- package/src/internal-urls/api-catalog-qmd-index.generated.ts +2 -2
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +1 -0
- package/src/modes/components/request-user-input.ts +14 -2
- package/src/modes/controllers/context-command-controller.ts +25 -1
- package/src/modes/controllers/login-model.ts +12 -12
- package/src/modes/controllers/selector-controller.ts +5 -0
- package/src/modes/interactive-mode.ts +1 -1
- package/src/routing/presets.ts +3 -3
- package/src/sdk.ts +1 -0
- package/src/slash-commands/builtin-registry.ts +39 -8
- package/src/slash-commands/marketplace-install-parser.ts +38 -3
- package/src/slash-commands/plugin-reviewed-actions.ts +4 -3
- package/src/task/executor.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
8
|
|
|
9
|
+
- Refreshed generated internal LiteLLM configuration with seven current GPT-6/Claude models,
|
|
10
|
+
route-accurate Responses and native Messages transports, hidden legacy session resolution, and an
|
|
11
|
+
independent maximum-context setting ([#4279](https://github.com/f5-sales-demo/xcsh/issues/4279)).
|
|
9
12
|
- Added current ChatGPT subscription roles for GPT-6 Luna/Sol, Claude Opus 5.5 selection, and an
|
|
10
13
|
opt-in Providers setting for the GPT-6 1.05M-token context window. The default remains 272K to
|
|
11
14
|
make long-context pricing explicit; LiteLLM retains its existing catalog
|
|
@@ -57,6 +60,15 @@
|
|
|
57
60
|
|
|
58
61
|
### Fixed
|
|
59
62
|
|
|
63
|
+
- Published KVM 3.0.4 through the built-in marketplace snapshot with immutable source provenance,
|
|
64
|
+
so fresh xcsh installations discover the qualified plugin without a development catalog
|
|
65
|
+
([#4321](https://github.com/f5-sales-demo/xcsh/issues/4321),
|
|
66
|
+
[#4323](https://github.com/f5-sales-demo/xcsh/issues/4323)).
|
|
67
|
+
- Made Platform plugin setup offer sorted activation commands for saved session contexts,
|
|
68
|
+
resolved unique short plugin names during reviewed uninstall while rejecting ambiguity, and
|
|
69
|
+
directed slash-command health checks to the supported `xcsh plugin doctor` CLI. Plugin
|
|
70
|
+
integration status and setup can now select a saved session context explicitly with `--context`
|
|
71
|
+
([#4306](https://github.com/f5-sales-demo/xcsh/issues/4306)).
|
|
60
72
|
- Accepted fully qualified marketplace plugin identifiers in `plugin setup`, matching install,
|
|
61
73
|
upgrade, uninstall, and doctor behavior ([#4288](https://github.com/f5-sales-demo/xcsh/issues/4288)).
|
|
62
74
|
- Added the exact `/plugin setup <plugin>` next action after interactive installation of plugins
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "21.
|
|
4
|
+
"version": "21.45.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",
|
|
@@ -64,13 +64,13 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@agentclientprotocol/sdk": "1.4.0",
|
|
67
|
-
"@f5-sales-demo/pi-agent-core": "21.
|
|
68
|
-
"@f5-sales-demo/pi-ai": "21.
|
|
69
|
-
"@f5-sales-demo/pi-natives": "21.
|
|
70
|
-
"@f5-sales-demo/pi-resource-management": "21.
|
|
71
|
-
"@f5-sales-demo/pi-tui": "21.
|
|
72
|
-
"@f5-sales-demo/pi-utils": "21.
|
|
73
|
-
"@f5-sales-demo/xcsh-stats": "21.
|
|
67
|
+
"@f5-sales-demo/pi-agent-core": "21.45.0",
|
|
68
|
+
"@f5-sales-demo/pi-ai": "21.45.0",
|
|
69
|
+
"@f5-sales-demo/pi-natives": "21.45.0",
|
|
70
|
+
"@f5-sales-demo/pi-resource-management": "21.45.0",
|
|
71
|
+
"@f5-sales-demo/pi-tui": "21.45.0",
|
|
72
|
+
"@f5-sales-demo/pi-utils": "21.45.0",
|
|
73
|
+
"@f5-sales-demo/xcsh-stats": "21.45.0",
|
|
74
74
|
"@mozilla/readability": "^0.6",
|
|
75
75
|
"@sinclair/typebox": "0.34.52",
|
|
76
76
|
"@tobilu/qmd": "2.8.3",
|
package/src/cli/plugin-cli.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
9
9
|
import { APP_NAME, getProjectDir } from "@f5-sales-demo/pi-utils";
|
|
10
10
|
import { CliUsageError } from "@f5-sales-demo/pi-utils/cli";
|
|
11
11
|
import chalk from "chalk";
|
|
12
|
+
import { Settings } from "../config/settings";
|
|
12
13
|
import { preloadPluginRoots, resolveOrDefaultProjectRegistryPath } from "../discovery/helpers";
|
|
13
14
|
import { discoverAndLoadExtensions } from "../extensibility/extensions";
|
|
14
15
|
import { PluginManager, parseSettingValue, validateSetting } from "../extensibility/plugins";
|
|
@@ -30,6 +31,16 @@ import {
|
|
|
30
31
|
import type { IntegrationHandle } from "../integrations/types";
|
|
31
32
|
import { theme } from "../modes/theme/theme";
|
|
32
33
|
import { personProfileService } from "../person-profile/service";
|
|
34
|
+
import { ContextError, ContextService, type XCSHContext } from "../services/xcsh-context";
|
|
35
|
+
import {
|
|
36
|
+
deriveTenantFromUrl,
|
|
37
|
+
isInjectableContextEnvKey,
|
|
38
|
+
XCSH_API_TOKEN,
|
|
39
|
+
XCSH_API_URL,
|
|
40
|
+
XCSH_CONTEXT_NAME,
|
|
41
|
+
XCSH_NAMESPACE,
|
|
42
|
+
XCSH_TENANT,
|
|
43
|
+
} from "../services/xcsh-env";
|
|
33
44
|
|
|
34
45
|
// =============================================================================
|
|
35
46
|
// Types
|
|
@@ -64,6 +75,7 @@ export interface PluginCommandArgs {
|
|
|
64
75
|
disable?: string;
|
|
65
76
|
set?: string;
|
|
66
77
|
scope?: "user" | "project";
|
|
78
|
+
context?: string;
|
|
67
79
|
};
|
|
68
80
|
}
|
|
69
81
|
|
|
@@ -145,6 +157,11 @@ export function parsePluginArgs(args: string[]): PluginCommandArgs | undefined {
|
|
|
145
157
|
// --scope with no value following
|
|
146
158
|
console.error(chalk.red(`--scope requires a value: "user" or "project".`));
|
|
147
159
|
process.exit(1);
|
|
160
|
+
} else if (arg === "--context" && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
161
|
+
result.flags.context = args[++i];
|
|
162
|
+
} else if (arg === "--context") {
|
|
163
|
+
console.error(chalk.red("--context requires a saved context name."));
|
|
164
|
+
process.exit(1);
|
|
148
165
|
} else if (!arg.startsWith("-")) {
|
|
149
166
|
result.args.push(arg);
|
|
150
167
|
}
|
|
@@ -166,6 +183,8 @@ export { classifyInstallTarget } from "./classify-install-target";
|
|
|
166
183
|
*/
|
|
167
184
|
export async function runPluginCommand(cmd: PluginCommandArgs): Promise<void> {
|
|
168
185
|
const manager = new PluginManager();
|
|
186
|
+
if (cmd.flags.context && cmd.action !== "status" && cmd.action !== "setup")
|
|
187
|
+
throw new CliUsageError("plugin --context is supported only for integration status or setup");
|
|
169
188
|
|
|
170
189
|
switch (cmd.action) {
|
|
171
190
|
case "install":
|
|
@@ -243,34 +262,78 @@ export function selectSetupIntegration(
|
|
|
243
262
|
return setupMatches[0];
|
|
244
263
|
}
|
|
245
264
|
|
|
246
|
-
|
|
247
|
-
|
|
265
|
+
function contextEnvironment(context: XCSHContext): Record<string, string> {
|
|
266
|
+
const environment: Record<string, string> = {
|
|
267
|
+
[XCSH_API_URL]: context.apiUrl,
|
|
268
|
+
[XCSH_API_TOKEN]: context.apiToken,
|
|
269
|
+
[XCSH_NAMESPACE]: context.defaultNamespace,
|
|
270
|
+
[XCSH_CONTEXT_NAME]: context.name,
|
|
271
|
+
};
|
|
272
|
+
const tenant = deriveTenantFromUrl(context.apiUrl);
|
|
273
|
+
if (tenant) environment[XCSH_TENANT] = tenant;
|
|
274
|
+
for (const [name, value] of Object.entries(context.env ?? {})) {
|
|
275
|
+
if (isInjectableContextEnvKey(name) && value) environment[name] = value;
|
|
276
|
+
}
|
|
277
|
+
return environment;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function withPluginContext<T>(name: string | undefined, run: () => Promise<T>): Promise<T> {
|
|
281
|
+
if (!name) return run();
|
|
282
|
+
let context: XCSHContext;
|
|
283
|
+
try {
|
|
284
|
+
await Settings.init({ cwd: getProjectDir() });
|
|
285
|
+
const service = await ContextService.getOrInit(undefined, getProjectDir());
|
|
286
|
+
const review = service.prepareActivation(name);
|
|
287
|
+
context = await service.activate(name, { revision: review.revision, beforeCommit: () => {} });
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if (error instanceof ContextError) throw new CliUsageError(error.message);
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
const previous = new Map<string, string | undefined>();
|
|
293
|
+
for (const [key, value] of Object.entries(contextEnvironment(context))) {
|
|
294
|
+
previous.set(key, process.env[key]);
|
|
295
|
+
if (!process.env[key]) process.env[key] = value;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
return await run();
|
|
299
|
+
} finally {
|
|
300
|
+
for (const [key, value] of previous) {
|
|
301
|
+
if (value === undefined) delete process.env[key];
|
|
302
|
+
else process.env[key] = value;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function handleIntegrationStatus(args: string[], flags: { json?: boolean; context?: string }): Promise<void> {
|
|
308
|
+
if (args.length > 1) throw new Error(`Usage: ${APP_NAME} plugin status [plugin] [--context <name>] [--json]`);
|
|
248
309
|
const handles = await loadIntegrationHandles();
|
|
249
310
|
const selected = args[0] ? handles.filter(handle => matchesIntegration(handle, args[0])) : handles;
|
|
250
311
|
if (args[0] && !selected.length) throw new Error(`No enabled integration is registered for ${args[0]}`);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (flags.json) {
|
|
261
|
-
process.stdout.write(`${JSON.stringify({ integrations: statuses })}\n`);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (!statuses.length) {
|
|
265
|
-
process.stdout.write("No enabled plugin integrations are registered.\n");
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
for (const status of statuses) {
|
|
269
|
-
process.stdout.write(
|
|
270
|
-
`${status.plugin ?? status.id}: ${status.state}${status.reason ? ` (${status.reason})` : ""}\n`,
|
|
312
|
+
await withPluginContext(flags.context, async () => {
|
|
313
|
+
const statuses = await Promise.all(
|
|
314
|
+
selected.map(async handle => {
|
|
315
|
+
const { value: _, ...status } = await handle.get();
|
|
316
|
+
return {
|
|
317
|
+
...status,
|
|
318
|
+
nextAction: status.state === "setup_required" ? describeIntegrationSetupNextAction(handle) : undefined,
|
|
319
|
+
};
|
|
320
|
+
}),
|
|
271
321
|
);
|
|
272
|
-
if (
|
|
273
|
-
|
|
322
|
+
if (flags.json) {
|
|
323
|
+
process.stdout.write(`${JSON.stringify({ integrations: statuses })}\n`);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (!statuses.length) {
|
|
327
|
+
process.stdout.write("No enabled plugin integrations are registered.\n");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
for (const status of statuses) {
|
|
331
|
+
process.stdout.write(
|
|
332
|
+
`${status.plugin ?? status.id}: ${status.state}${status.reason ? ` (${status.reason})` : ""}\n`,
|
|
333
|
+
);
|
|
334
|
+
if (status.nextAction) process.stdout.write(` next: ${status.nextAction}\n`);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
274
337
|
}
|
|
275
338
|
|
|
276
339
|
export async function reviewAndExecuteIntegrationSetup(handle: IntegrationHandle<unknown>) {
|
|
@@ -295,20 +358,23 @@ export async function reviewAndExecuteIntegrationSetup(handle: IntegrationHandle
|
|
|
295
358
|
return result;
|
|
296
359
|
}
|
|
297
360
|
|
|
298
|
-
async function handleIntegrationSetup(args: string[], flags: { json?: boolean }): Promise<void> {
|
|
361
|
+
async function handleIntegrationSetup(args: string[], flags: { json?: boolean; context?: string }): Promise<void> {
|
|
299
362
|
if (flags.json) throw new CliUsageError("plugin setup is human-only and does not accept --json");
|
|
300
|
-
if (args.length !== 1) throw new Error(`Usage: ${APP_NAME} plugin setup <plugin
|
|
363
|
+
if (args.length !== 1) throw new Error(`Usage: ${APP_NAME} plugin setup <plugin> [--context <name>]`);
|
|
301
364
|
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error("Plugin setup requires an interactive terminal");
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
365
|
+
const handles = await loadIntegrationHandles();
|
|
366
|
+
await withPluginContext(flags.context, async () => {
|
|
367
|
+
const handle = selectSetupIntegration(handles, args[0]);
|
|
368
|
+
const current = await handle.get();
|
|
369
|
+
if (current.state === "ready") {
|
|
370
|
+
process.stdout.write(`${handle.plugin ?? handle.id}: ready (setup is not required)\n`);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const result = await reviewAndExecuteIntegrationSetup(handle);
|
|
374
|
+
process.stdout.write(
|
|
375
|
+
`${handle.plugin ?? handle.id}: ${result.state}${result.reason ? ` (${result.reason})` : ""}\n`,
|
|
376
|
+
);
|
|
377
|
+
});
|
|
312
378
|
}
|
|
313
379
|
|
|
314
380
|
async function reportMarketplaceInstallation(
|
package/src/commands/plugin.ts
CHANGED
|
@@ -51,6 +51,9 @@ export default class Plugin extends Command {
|
|
|
51
51
|
description: 'Install scope: "user" (default) or "project"',
|
|
52
52
|
options: ["user", "project"],
|
|
53
53
|
}),
|
|
54
|
+
context: Flags.string({
|
|
55
|
+
description: "Use a saved context for integration status or setup",
|
|
56
|
+
}),
|
|
54
57
|
};
|
|
55
58
|
|
|
56
59
|
async run(): Promise<void> {
|
|
@@ -71,6 +74,7 @@ export default class Plugin extends Command {
|
|
|
71
74
|
disable: flags.disable,
|
|
72
75
|
set: flags.set,
|
|
73
76
|
scope: flags.scope as "user" | "project" | undefined,
|
|
77
|
+
context: flags.context,
|
|
74
78
|
},
|
|
75
79
|
};
|
|
76
80
|
|
|
@@ -31,6 +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
36
|
});
|
|
35
37
|
await modelRegistry.refresh();
|
|
36
38
|
const stagedFilesPromise = (async () => {
|
package/src/commit/pipeline.ts
CHANGED
|
@@ -44,6 +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
49
|
});
|
|
48
50
|
await modelRegistry.refresh();
|
|
49
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 =
|
|
23
|
+
export const CURRENT_CONFIG_VERSION = 9;
|
|
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";
|
|
@@ -64,7 +64,7 @@ export function generateModelsYml(baseUrl: string, options?: GenerateModelsYmlOp
|
|
|
64
64
|
// LiteLLM's OpenAI-compatible chat route can live behind a custom API prefix
|
|
65
65
|
// (for example /api/v1). Astra with tools and reasoning requires the
|
|
66
66
|
// Responses API, which LiteLLM exposes on its dedicated OpenAI route.
|
|
67
|
-
const
|
|
67
|
+
const responsesBaseUrl = `${baseUrl.replace(/\/+$/, "")}/openai/v1`;
|
|
68
68
|
// When a literal API key is provided, double-quote it to handle YAML-significant
|
|
69
69
|
// characters (: ! # { } [ ] ' " etc.). Escape backslashes and double quotes first.
|
|
70
70
|
const apiKeyValue = options?.apiKeyLiteral
|
|
@@ -81,14 +81,35 @@ export function generateModelsYml(baseUrl: string, options?: GenerateModelsYmlOp
|
|
|
81
81
|
" anthropic:",
|
|
82
82
|
` baseUrl: "${baseUrl}/anthropic"`,
|
|
83
83
|
` apiKey: ${apiKeyValue}`,
|
|
84
|
+
" api: anthropic-messages",
|
|
84
85
|
" modelAllowlist:",
|
|
85
|
-
" - claude-opus-5",
|
|
86
|
-
" - claude-sonnet-5",
|
|
87
86
|
" - claude-haiku-4-5",
|
|
87
|
+
" - claude-sonnet-5",
|
|
88
|
+
" - claude-opus-5-5",
|
|
89
|
+
" - claude-opus-5",
|
|
88
90
|
" picker:",
|
|
89
91
|
" groupId: litellm",
|
|
90
92
|
" groupLabel: LiteLLM",
|
|
91
93
|
" sectionLabel: Anthropic",
|
|
94
|
+
" modelOverrides:",
|
|
95
|
+
" claude-haiku-4-5:",
|
|
96
|
+
" cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
|
|
97
|
+
" contextWindow: 200000",
|
|
98
|
+
" maxTokens: 64000",
|
|
99
|
+
" compat: { supportsToolChoice: false }",
|
|
100
|
+
" claude-sonnet-5:",
|
|
101
|
+
" cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
|
|
102
|
+
" contextWindow: 1000000",
|
|
103
|
+
" maxTokens: 128000",
|
|
104
|
+
" compat: { supportsToolChoice: false }",
|
|
105
|
+
" claude-opus-5-5:",
|
|
106
|
+
" cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
|
|
107
|
+
" contextWindow: 1000000",
|
|
108
|
+
" maxTokens: 128000",
|
|
109
|
+
" compat: { supportsToolChoice: false }",
|
|
110
|
+
" claude-opus-5:",
|
|
111
|
+
" cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
|
|
112
|
+
" compat: { supportsToolChoice: false }",
|
|
92
113
|
" litellm:",
|
|
93
114
|
` baseUrl: "${baseUrl}${apiBase}"`,
|
|
94
115
|
` apiKey: ${apiKeyValue}`,
|
|
@@ -96,25 +117,105 @@ export function generateModelsYml(baseUrl: string, options?: GenerateModelsYmlOp
|
|
|
96
117
|
" discovery:",
|
|
97
118
|
" type: openai-compat",
|
|
98
119
|
" modelAllowlist:",
|
|
99
|
-
" - gpt-6-
|
|
100
|
-
" - gpt-5.6-sol",
|
|
120
|
+
" - gpt-6-luna",
|
|
101
121
|
" - gpt-5.6-terra",
|
|
122
|
+
" - gpt-6-sol",
|
|
123
|
+
" - gpt-6-astra",
|
|
102
124
|
" - gpt-5.6-luna",
|
|
125
|
+
" - gpt-5.6-sol",
|
|
103
126
|
" models:",
|
|
127
|
+
" - id: gpt-6-luna",
|
|
128
|
+
" name: GPT-6 Luna",
|
|
129
|
+
" api: openai-responses",
|
|
130
|
+
` baseUrl: "${responsesBaseUrl}"`,
|
|
131
|
+
" - id: gpt-5.6-terra",
|
|
132
|
+
" name: GPT-5.6 Terra",
|
|
133
|
+
" api: openai-completions",
|
|
134
|
+
" - id: gpt-6-sol",
|
|
135
|
+
" name: GPT-6 Sol",
|
|
136
|
+
" api: openai-responses",
|
|
137
|
+
` baseUrl: "${responsesBaseUrl}"`,
|
|
104
138
|
" - id: gpt-6-astra",
|
|
105
139
|
" name: GPT-6 Astra",
|
|
106
140
|
" api: openai-responses",
|
|
107
|
-
` baseUrl: "${
|
|
141
|
+
` baseUrl: "${responsesBaseUrl}"`,
|
|
142
|
+
" - id: gpt-5.6-luna",
|
|
143
|
+
" name: GPT-5.6 Luna",
|
|
144
|
+
" api: openai-completions",
|
|
145
|
+
" - id: gpt-5.6-sol",
|
|
146
|
+
" name: GPT-5.6 Sol",
|
|
147
|
+
" api: openai-completions",
|
|
108
148
|
" picker:",
|
|
109
149
|
" groupId: litellm",
|
|
110
150
|
" groupLabel: LiteLLM",
|
|
111
151
|
" sectionLabel: OpenAI",
|
|
112
152
|
" modelOverrides:",
|
|
153
|
+
" gpt-6-luna:",
|
|
154
|
+
" reasoning: true",
|
|
155
|
+
" input:",
|
|
156
|
+
" - text",
|
|
157
|
+
" - image",
|
|
158
|
+
" cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
|
|
159
|
+
" thinking:",
|
|
160
|
+
" mode: effort",
|
|
161
|
+
" defaultLevel: medium",
|
|
162
|
+
" supportedLevels:",
|
|
163
|
+
" - { effort: none, description: No reasoning }",
|
|
164
|
+
" - { effort: low, description: Light reasoning }",
|
|
165
|
+
" - { effort: medium, description: Balanced reasoning }",
|
|
166
|
+
" - { effort: high, description: Deep reasoning }",
|
|
167
|
+
" - { effort: xhigh, description: Very deep reasoning }",
|
|
168
|
+
" - { effort: max, description: Maximum reasoning }",
|
|
169
|
+
" contextWindow: 1050000",
|
|
170
|
+
" maxTokens: 128000",
|
|
171
|
+
" compat:",
|
|
172
|
+
" supportsTemperature: false",
|
|
173
|
+
" gpt-5.6-terra:",
|
|
174
|
+
" reasoning: true",
|
|
175
|
+
" input:",
|
|
176
|
+
" - text",
|
|
177
|
+
" - image",
|
|
178
|
+
" cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
|
|
179
|
+
" thinking:",
|
|
180
|
+
" mode: effort",
|
|
181
|
+
" defaultLevel: medium",
|
|
182
|
+
" supportedLevels:",
|
|
183
|
+
" - { effort: none, description: No reasoning }",
|
|
184
|
+
" - { effort: low, description: Light reasoning }",
|
|
185
|
+
" - { effort: medium, description: Balanced reasoning }",
|
|
186
|
+
" - { effort: high, description: Deep reasoning }",
|
|
187
|
+
" - { effort: xhigh, description: Very deep reasoning }",
|
|
188
|
+
" - { effort: max, description: Maximum reasoning }",
|
|
189
|
+
" contextWindow: 1050000",
|
|
190
|
+
" maxTokens: 128000",
|
|
191
|
+
" compat:",
|
|
192
|
+
" supportsTemperature: false",
|
|
193
|
+
" gpt-6-sol:",
|
|
194
|
+
" reasoning: true",
|
|
195
|
+
" input:",
|
|
196
|
+
" - text",
|
|
197
|
+
" - image",
|
|
198
|
+
" cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
|
|
199
|
+
" thinking:",
|
|
200
|
+
" mode: effort",
|
|
201
|
+
" defaultLevel: medium",
|
|
202
|
+
" supportedLevels:",
|
|
203
|
+
" - { effort: none, description: No reasoning }",
|
|
204
|
+
" - { effort: low, description: Light reasoning }",
|
|
205
|
+
" - { effort: medium, description: Balanced reasoning }",
|
|
206
|
+
" - { effort: high, description: Deep reasoning }",
|
|
207
|
+
" - { effort: xhigh, description: Very deep reasoning }",
|
|
208
|
+
" - { effort: max, description: Maximum reasoning }",
|
|
209
|
+
" contextWindow: 1050000",
|
|
210
|
+
" maxTokens: 128000",
|
|
211
|
+
" compat:",
|
|
212
|
+
" supportsTemperature: false",
|
|
113
213
|
" gpt-6-astra:",
|
|
114
214
|
" reasoning: true",
|
|
115
215
|
" input:",
|
|
116
216
|
" - text",
|
|
117
217
|
" - image",
|
|
218
|
+
" cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
|
|
118
219
|
" thinking:",
|
|
119
220
|
" mode: effort",
|
|
120
221
|
" defaultLevel: medium",
|
|
@@ -133,6 +234,7 @@ export function generateModelsYml(baseUrl: string, options?: GenerateModelsYmlOp
|
|
|
133
234
|
" input:",
|
|
134
235
|
" - text",
|
|
135
236
|
" - image",
|
|
237
|
+
" cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
|
|
136
238
|
" thinking:",
|
|
137
239
|
" mode: effort",
|
|
138
240
|
" defaultLevel: medium",
|
|
@@ -147,10 +249,9 @@ export function generateModelsYml(baseUrl: string, options?: GenerateModelsYmlOp
|
|
|
147
249
|
" maxTokens: 128000",
|
|
148
250
|
" compat:",
|
|
149
251
|
" supportsTemperature: false",
|
|
150
|
-
" gpt-5.6-terra:",
|
|
151
|
-
" reasoning: true",
|
|
152
252
|
" gpt-5.6-luna:",
|
|
153
253
|
" reasoning: true",
|
|
254
|
+
" cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }",
|
|
154
255
|
];
|
|
155
256
|
|
|
156
257
|
lines.push("");
|
|
@@ -12,6 +12,7 @@ function compareVersions(left: string, right: string): number {
|
|
|
12
12
|
|
|
13
13
|
/** Keep historical catalogs available while presenting only current user-facing families. */
|
|
14
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"]);
|
|
15
16
|
|
|
16
17
|
/** Shared catalog boundary for the TUI and attached human interfaces. */
|
|
17
18
|
export function filterCurrentBrowserModels(models: readonly Model[]): Model[] {
|
|
@@ -40,6 +41,7 @@ export function filterCurrentBrowserModels(models: readonly Model[]): Model[] {
|
|
|
40
41
|
if (gpt?.[1]) {
|
|
41
42
|
if (compareVersions(gpt[1], "5.6") < 0) return false;
|
|
42
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());
|
|
43
45
|
}
|
|
44
46
|
const claude = model.id.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:[.-](\d+))?(?:-|$)/i);
|
|
45
47
|
if (claude?.[1] && claude[2]) {
|
|
@@ -53,6 +53,9 @@ export const kNoAuth = NO_AUTH_API_KEY;
|
|
|
53
53
|
const OPENAI_CODEX_STANDARD_CONTEXT_WINDOW = 272_000;
|
|
54
54
|
const OPENAI_CODEX_MAX_CONTEXT_WINDOW = 1_050_000;
|
|
55
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"]);
|
|
56
59
|
|
|
57
60
|
export function isAuthenticated(apiKey: string | undefined | null): apiKey is string {
|
|
58
61
|
return Boolean(apiKey) && apiKey !== kNoAuth;
|
|
@@ -876,6 +879,7 @@ export class ModelRegistry {
|
|
|
876
879
|
#hasProbed = false;
|
|
877
880
|
#refreshQueue: Promise<void> = Promise.resolve();
|
|
878
881
|
#openAICodexMaxContext = false;
|
|
882
|
+
#litellmMaxContext = false;
|
|
879
883
|
|
|
880
884
|
#enqueueRefresh(operation: () => Promise<void>): Promise<void> {
|
|
881
885
|
const pending = this.#refreshQueue.then(operation);
|
|
@@ -898,6 +902,7 @@ export class ModelRegistry {
|
|
|
898
902
|
private readonly options: {
|
|
899
903
|
getProviderOrder?: () => readonly string[];
|
|
900
904
|
getOpenAICodexMaxContext?: () => boolean;
|
|
905
|
+
getLiteLLMMaxContext?: () => boolean;
|
|
901
906
|
} = {},
|
|
902
907
|
) {
|
|
903
908
|
this.#modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
|
|
@@ -1051,6 +1056,7 @@ export class ModelRegistry {
|
|
|
1051
1056
|
|
|
1052
1057
|
#loadModels() {
|
|
1053
1058
|
this.#openAICodexMaxContext = this.options.getOpenAICodexMaxContext?.() ?? this.#openAICodexMaxContext;
|
|
1059
|
+
this.#litellmMaxContext = this.options.getLiteLLMMaxContext?.() ?? this.#litellmMaxContext;
|
|
1054
1060
|
// Load custom models from models.json first (to know which providers to override)
|
|
1055
1061
|
const {
|
|
1056
1062
|
models: customModels = [],
|
|
@@ -1085,6 +1091,7 @@ export class ModelRegistry {
|
|
|
1085
1091
|
|
|
1086
1092
|
this.#models = this.#applyProviderModelAllowlists(this.#applyModelOverrides(combined, this.#modelOverrides));
|
|
1087
1093
|
this.#applyOpenAICodexContextWindow();
|
|
1094
|
+
this.#applyLiteLLMContextWindow();
|
|
1088
1095
|
this.#rebuildCanonicalIndex();
|
|
1089
1096
|
}
|
|
1090
1097
|
|
|
@@ -1106,6 +1113,22 @@ export class ModelRegistry {
|
|
|
1106
1113
|
this.#rebuildCanonicalIndex();
|
|
1107
1114
|
}
|
|
1108
1115
|
|
|
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
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/** Update current internal OpenAI context limits without changing subscription models. */
|
|
1126
|
+
setLiteLLMMaxContext(enabled: boolean): void {
|
|
1127
|
+
this.#litellmMaxContext = enabled;
|
|
1128
|
+
this.#applyLiteLLMContextWindow();
|
|
1129
|
+
this.#rebuildCanonicalIndex();
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1109
1132
|
/** Load built-in models, applying provider-level overrides only.
|
|
1110
1133
|
* Per-model overrides are applied later by #applyModelOverrides. */
|
|
1111
1134
|
#loadBuiltInModels(overrides: Map<string, ProviderOverride>): Model<Api>[] {
|
|
@@ -1433,6 +1456,8 @@ export class ModelRegistry {
|
|
|
1433
1456
|
// Merge runtime extension models so they survive online discovery completion
|
|
1434
1457
|
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
|
|
1435
1458
|
this.#models = this.#applyProviderModelAllowlists(this.#applyModelOverrides(combined, this.#modelOverrides));
|
|
1459
|
+
this.#applyOpenAICodexContextWindow();
|
|
1460
|
+
this.#applyLiteLLMContextWindow();
|
|
1436
1461
|
this.#rebuildCanonicalIndex();
|
|
1437
1462
|
}
|
|
1438
1463
|
|
|
@@ -161,21 +161,22 @@ const DEFAULT_CYCLE_ORDER: string[] = ["smol", "default", "slow"];
|
|
|
161
161
|
/**
|
|
162
162
|
* Binary-baked default model role. Ships in the binary so a fresh install needs
|
|
163
163
|
* NO `~/.xcsh/agent/config.yml` — `/login` only supplies the (PII) proxy URL + key.
|
|
164
|
-
* GPT-5.6
|
|
164
|
+
* GPT-5.6 Terra Medium is the current general-purpose default through the OpenAI
|
|
165
165
|
* Chat Completions route on LiteLLM. Keep the effort explicit in the role so returning
|
|
166
166
|
* from a lower-effort role cannot inherit that role's effort.
|
|
167
167
|
*/
|
|
168
|
-
export const DEFAULT_MODEL_ROLE = "litellm/gpt-5.6-
|
|
168
|
+
export const DEFAULT_MODEL_ROLE = "litellm/gpt-5.6-terra:medium";
|
|
169
169
|
/** Fast role for lightweight work (commit messages, titles, memory summaries). */
|
|
170
|
-
const SMOL_MODEL_ROLE = "litellm/gpt-
|
|
170
|
+
const SMOL_MODEL_ROLE = "litellm/gpt-6-luna:low";
|
|
171
171
|
/**
|
|
172
|
-
* Baked role map
|
|
173
|
-
* work
|
|
172
|
+
* Baked role map for the current internal LiteLLM catalog. Sol handles slow and
|
|
173
|
+
* planning work, while Luna and Terra cover lightweight and default work.
|
|
174
174
|
*/
|
|
175
175
|
const DEFAULT_MODEL_ROLES: Record<string, string> = {
|
|
176
176
|
default: DEFAULT_MODEL_ROLE,
|
|
177
177
|
smol: SMOL_MODEL_ROLE,
|
|
178
|
-
slow:
|
|
178
|
+
slow: "litellm/gpt-6-sol:high",
|
|
179
|
+
plan: "litellm/gpt-6-sol:high",
|
|
179
180
|
};
|
|
180
181
|
const EMPTY_MODEL_TAGS_RECORD: ModelTagsSettings = {};
|
|
181
182
|
export const DEFAULT_BASH_INTERCEPTOR_RULES: BashInterceptorRule[] = [
|
|
@@ -2123,6 +2124,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
2123
2124
|
},
|
|
2124
2125
|
},
|
|
2125
2126
|
|
|
2127
|
+
"providers.litellmMaxContext": {
|
|
2128
|
+
type: "boolean",
|
|
2129
|
+
default: false,
|
|
2130
|
+
ui: {
|
|
2131
|
+
tab: "providers",
|
|
2132
|
+
label: "LiteLLM Maximum Context",
|
|
2133
|
+
description: "Use the verified 1.05M-token context window for current internal OpenAI models",
|
|
2134
|
+
},
|
|
2135
|
+
},
|
|
2136
|
+
|
|
2126
2137
|
"providers.parallelFetch": {
|
|
2127
2138
|
type: "boolean",
|
|
2128
2139
|
default: true,
|
|
@@ -256,7 +256,7 @@ export class ExtensionRunner {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
/** Replace every registration surface after marketplace state changes. */
|
|
259
|
-
reloadExtensions(extensions: readonly Extension[], runtime: ExtensionRuntime): void {
|
|
259
|
+
async reloadExtensions(extensions: readonly Extension[], runtime: ExtensionRuntime): Promise<void> {
|
|
260
260
|
const activeOwners = new Set(extensions.map(extension => extension.resolvedPath));
|
|
261
261
|
for (const extension of this.extensions) {
|
|
262
262
|
if (activeOwners.has(extension.resolvedPath)) continue;
|
|
@@ -269,6 +269,7 @@ export class ExtensionRunner {
|
|
|
269
269
|
if (this.#initialization) {
|
|
270
270
|
const { actions, contextActions, commandContextActions, uiContext } = this.#initialization;
|
|
271
271
|
this.initialize(actions, contextActions, commandContextActions, uiContext);
|
|
272
|
+
await this.emit({ type: "session_start" });
|
|
272
273
|
}
|
|
273
274
|
}
|
|
274
275
|
|