@f5-sales-demo/xcsh 19.103.4 → 19.104.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 +10 -0
- package/package.json +8 -8
- package/src/config/auto-config.ts +12 -1
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/litellm-model-selector.ts +40 -0
- package/src/modes/controllers/login-model.ts +46 -12
- package/src/modes/controllers/selector-controller.ts +88 -20
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [19.104.0] - 2026-07-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added a post-login LiteLLM model chooser for GPT-5.6 Sol and Claude Opus 5 with High reasoning; authenticated users can switch later with `/model`
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Restricted LiteLLM credential configuration files to owner-only permissions
|
|
14
|
+
|
|
5
15
|
## [19.51.5] - 2026-06-26
|
|
6
16
|
|
|
7
17
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "19.
|
|
4
|
+
"version": "19.104.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",
|
|
@@ -57,13 +57,13 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
59
59
|
"@mozilla/readability": "^0.6",
|
|
60
|
-
"@f5-sales-demo/xcsh-stats": "19.
|
|
61
|
-
"@f5-sales-demo/pi-agent-core": "19.
|
|
62
|
-
"@f5-sales-demo/pi-ai": "19.
|
|
63
|
-
"@f5-sales-demo/pi-natives": "19.
|
|
64
|
-
"@f5-sales-demo/pi-resource-management": "19.
|
|
65
|
-
"@f5-sales-demo/pi-tui": "19.
|
|
66
|
-
"@f5-sales-demo/pi-utils": "19.
|
|
60
|
+
"@f5-sales-demo/xcsh-stats": "19.104.0",
|
|
61
|
+
"@f5-sales-demo/pi-agent-core": "19.104.0",
|
|
62
|
+
"@f5-sales-demo/pi-ai": "19.104.0",
|
|
63
|
+
"@f5-sales-demo/pi-natives": "19.104.0",
|
|
64
|
+
"@f5-sales-demo/pi-resource-management": "19.104.0",
|
|
65
|
+
"@f5-sales-demo/pi-tui": "19.104.0",
|
|
66
|
+
"@f5-sales-demo/pi-utils": "19.104.0",
|
|
67
67
|
"@sinclair/typebox": "^0.34",
|
|
68
68
|
"@xterm/headless": "^6.0",
|
|
69
69
|
"ajv": "^8.20",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
17
|
import * as path from "node:path";
|
|
18
|
-
import { $env, logger, readProviderFromModelsYml } from "@f5-sales-demo/pi-utils";
|
|
18
|
+
import { $env, isEnoent, logger, readProviderFromModelsYml } from "@f5-sales-demo/pi-utils";
|
|
19
19
|
import { DEFAULT_MODEL_ROLE } from "./settings-schema";
|
|
20
20
|
|
|
21
21
|
/** Current config schema version. Bump when the generated format changes. */
|
|
@@ -81,6 +81,17 @@ export function generateModelsYml(baseUrl: string, options?: GenerateModelsYmlOp
|
|
|
81
81
|
return lines.join("\n");
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/** Persist models.yml with owner-only permissions because it may contain a literal proxy credential. */
|
|
85
|
+
export async function writeLiteLLMModelsYml(filePath: string, content: string): Promise<void> {
|
|
86
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
87
|
+
try {
|
|
88
|
+
await fs.promises.chmod(filePath, 0o600);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (!isEnoent(error)) throw error;
|
|
91
|
+
}
|
|
92
|
+
await fs.promises.writeFile(filePath, content, { encoding: "utf-8", mode: 0o600 });
|
|
93
|
+
}
|
|
94
|
+
|
|
84
95
|
export interface LiteLLMConfig {
|
|
85
96
|
baseUrl: string;
|
|
86
97
|
apiKey: string;
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.104.0",
|
|
21
|
+
"commit": "f07ee3a993f88e57cd1b44eb586f1ea527b63e27",
|
|
22
|
+
"shortCommit": "f07ee3a",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-07-31T13:
|
|
26
|
-
"buildDate": "2026-07-
|
|
24
|
+
"tag": "v19.104.0",
|
|
25
|
+
"commitDate": "2026-07-31T13:40:17Z",
|
|
26
|
+
"buildDate": "2026-07-31T14:12:51.834Z",
|
|
27
27
|
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5-sales-demo/xcsh",
|
|
30
30
|
"repoSlug": "f5-sales-demo/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/f07ee3a993f88e57cd1b44eb586f1ea527b63e27",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.104.0"
|
|
33
33
|
};
|
|
@@ -15,6 +15,7 @@ export * from "./hook-input";
|
|
|
15
15
|
export * from "./hook-message";
|
|
16
16
|
export * from "./hook-selector";
|
|
17
17
|
export * from "./keybinding-hints";
|
|
18
|
+
export * from "./litellm-model-selector";
|
|
18
19
|
export * from "./login-dialog";
|
|
19
20
|
export * from "./model-selector";
|
|
20
21
|
export * from "./oauth-selector";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Container, type SelectItem, SelectList, Spacer, Text } from "@f5-sales-demo/pi-tui";
|
|
2
|
+
import type { LiteLLMLoginModelChoice } from "../controllers/login-model";
|
|
3
|
+
import { getSelectListTheme } from "../theme/theme";
|
|
4
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
5
|
+
|
|
6
|
+
export class LiteLLMModelSelectorComponent extends Container {
|
|
7
|
+
#selectList: SelectList;
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
choices: readonly LiteLLMLoginModelChoice[],
|
|
11
|
+
onSelect: (choice: LiteLLMLoginModelChoice) => void,
|
|
12
|
+
onCancel: () => void,
|
|
13
|
+
) {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
const items: SelectItem[] = choices.map(choice => ({
|
|
17
|
+
value: `${choice.provider}/${choice.modelId}`,
|
|
18
|
+
label: choice.label,
|
|
19
|
+
description: choice.description,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
this.addChild(new DynamicBorder());
|
|
23
|
+
this.addChild(new Spacer(1));
|
|
24
|
+
this.addChild(new Text("Select your default model:", 1, 0));
|
|
25
|
+
this.addChild(new Spacer(1));
|
|
26
|
+
this.#selectList = new SelectList(items, items.length, getSelectListTheme());
|
|
27
|
+
this.#selectList.onSelect = item => {
|
|
28
|
+
const choice = choices.find(candidate => `${candidate.provider}/${candidate.modelId}` === item.value);
|
|
29
|
+
if (choice) onSelect(choice);
|
|
30
|
+
};
|
|
31
|
+
this.#selectList.onCancel = onCancel;
|
|
32
|
+
this.addChild(this.#selectList);
|
|
33
|
+
this.addChild(new Spacer(1));
|
|
34
|
+
this.addChild(new DynamicBorder());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getSelectList(): SelectList {
|
|
38
|
+
return this.#selectList;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,32 +1,66 @@
|
|
|
1
|
+
import { ThinkingLevel } from "@f5-sales-demo/pi-agent-core";
|
|
1
2
|
import type { Model } from "@f5-sales-demo/pi-ai";
|
|
2
3
|
|
|
4
|
+
export interface LiteLLMLoginModelChoice {
|
|
5
|
+
label: string;
|
|
6
|
+
description: string;
|
|
7
|
+
provider: "anthropic" | "litellm";
|
|
8
|
+
modelId: "claude-opus-5" | "gpt-5.6-sol";
|
|
9
|
+
thinkingLevel: ThinkingLevel;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const LITELLM_LOGIN_MODEL_CHOICES: readonly LiteLLMLoginModelChoice[] = [
|
|
13
|
+
{
|
|
14
|
+
label: "GPT-5.6 Sol",
|
|
15
|
+
description: "OpenAI-compatible model with high reasoning",
|
|
16
|
+
provider: "litellm",
|
|
17
|
+
modelId: "gpt-5.6-sol",
|
|
18
|
+
thinkingLevel: ThinkingLevel.High,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: "Claude Opus 5",
|
|
22
|
+
description: "Anthropic Messages model with high reasoning",
|
|
23
|
+
provider: "anthropic",
|
|
24
|
+
modelId: "claude-opus-5",
|
|
25
|
+
thinkingLevel: ThinkingLevel.High,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export function getAvailableLiteLLMLoginModelChoices(availableModelIds: readonly string[]): LiteLLMLoginModelChoice[] {
|
|
30
|
+
const available = new Set(availableModelIds);
|
|
31
|
+
return LITELLM_LOGIN_MODEL_CHOICES.filter(choice => available.has(choice.modelId));
|
|
32
|
+
}
|
|
33
|
+
|
|
3
34
|
/**
|
|
4
35
|
* Minimal session surface needed to apply a model after a successful login.
|
|
5
36
|
* Kept structural so the login flow can call it without pulling in the full
|
|
6
37
|
* AgentSession type, and so it stays trivially unit-testable.
|
|
7
38
|
*/
|
|
8
39
|
interface ModelApplicableSession {
|
|
9
|
-
model: Model | undefined;
|
|
10
40
|
modelRegistry: { getAll(): Model[] };
|
|
11
|
-
setModel(model: Model, role: "default", options
|
|
41
|
+
setModel(model: Model, role: "default", options: { selector: string; thinkingLevel: ThinkingLevel }): Promise<void>;
|
|
42
|
+
setThinkingLevel(level: ThinkingLevel): void;
|
|
12
43
|
}
|
|
13
44
|
|
|
14
45
|
/**
|
|
15
|
-
* After a successful `/login`,
|
|
16
|
-
*
|
|
17
|
-
* has no model yet, so we never override a model the user already chose.
|
|
46
|
+
* After a successful `/login`, apply the model the user explicitly selected as
|
|
47
|
+
* the active and persisted default model, including its thinking level.
|
|
18
48
|
*
|
|
19
|
-
* Returns true when
|
|
20
|
-
* from the provider's /models list; resolving it here (post-registry-refresh) is
|
|
21
|
-
* what lets the LLM readiness gate lift without a manual `/model` step.
|
|
49
|
+
* Returns true when the exact provider/model pair resolves after registry refresh.
|
|
22
50
|
*/
|
|
23
51
|
export async function applyModelAfterLogin(
|
|
24
52
|
session: ModelApplicableSession,
|
|
25
|
-
|
|
53
|
+
choice: LiteLLMLoginModelChoice,
|
|
26
54
|
): Promise<boolean> {
|
|
27
|
-
|
|
28
|
-
|
|
55
|
+
const resolved = session.modelRegistry
|
|
56
|
+
.getAll()
|
|
57
|
+
.find(model => model.provider === choice.provider && model.id === choice.modelId);
|
|
29
58
|
if (!resolved) return false;
|
|
30
|
-
|
|
59
|
+
const selector = `${choice.provider}/${choice.modelId}`;
|
|
60
|
+
await session.setModel(resolved, "default", {
|
|
61
|
+
selector,
|
|
62
|
+
thinkingLevel: choice.thinkingLevel,
|
|
63
|
+
});
|
|
64
|
+
session.setThinkingLevel(choice.thinkingLevel);
|
|
31
65
|
return true;
|
|
32
66
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
healConfigYmlModelRoles,
|
|
14
14
|
probeLiteLLMConnection,
|
|
15
15
|
readLiteLLMConfig,
|
|
16
|
+
writeLiteLLMModelsYml,
|
|
16
17
|
} from "../../config/auto-config";
|
|
17
18
|
import { getRoleInfo } from "../../config/model-registry";
|
|
18
19
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
@@ -46,6 +47,7 @@ import { AssistantMessageComponent } from "../components/assistant-message";
|
|
|
46
47
|
import { ExtensionDashboard } from "../components/extensions";
|
|
47
48
|
import { GutterBlock } from "../components/gutter-block";
|
|
48
49
|
import { HistorySearchComponent } from "../components/history-search";
|
|
50
|
+
import { LiteLLMModelSelectorComponent } from "../components/litellm-model-selector";
|
|
49
51
|
import { ModelSelectorComponent } from "../components/model-selector";
|
|
50
52
|
import { OAuthSelectorComponent } from "../components/oauth-selector";
|
|
51
53
|
import { PluginSelectorComponent } from "../components/plugin-selector";
|
|
@@ -58,7 +60,7 @@ import { ToolExecutionComponent } from "../components/tool-execution";
|
|
|
58
60
|
import { TreeSelectorComponent } from "../components/tree-selector";
|
|
59
61
|
import { UserMessageSelectorComponent } from "../components/user-message-selector";
|
|
60
62
|
import type { SessionObserverRegistry } from "../session-observer-registry";
|
|
61
|
-
import { applyModelAfterLogin } from "./login-model";
|
|
63
|
+
import { applyModelAfterLogin, getAvailableLiteLLMLoginModelChoices, LITELLM_LOGIN_MODEL_CHOICES } from "./login-model";
|
|
62
64
|
|
|
63
65
|
const CALLBACK_SERVER_PROVIDERS = new Set<OAuthProvider>([
|
|
64
66
|
"anthropic",
|
|
@@ -472,6 +474,69 @@ export class SelectorController {
|
|
|
472
474
|
});
|
|
473
475
|
}
|
|
474
476
|
|
|
477
|
+
async #showLiteLLMLoginModelSelector(availableModelIds: readonly string[]): Promise<boolean> {
|
|
478
|
+
const choices = getAvailableLiteLLMLoginModelChoices(availableModelIds);
|
|
479
|
+
const available = new Set(choices.map(choice => choice.modelId));
|
|
480
|
+
const unavailable = LITELLM_LOGIN_MODEL_CHOICES.filter(choice => !available.has(choice.modelId));
|
|
481
|
+
|
|
482
|
+
if (unavailable.length > 0) {
|
|
483
|
+
this.ctx.chatContainer.addChild(
|
|
484
|
+
new Text(
|
|
485
|
+
theme.fg(
|
|
486
|
+
"warning",
|
|
487
|
+
`Unavailable from this proxy: ${unavailable.map(choice => choice.label).join(", ")}`,
|
|
488
|
+
),
|
|
489
|
+
1,
|
|
490
|
+
0,
|
|
491
|
+
),
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (choices.length === 0) {
|
|
496
|
+
this.ctx.showError("LiteLLM login succeeded, but neither GPT-5.6 Sol nor Claude Opus 5 is available.");
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const { promise, resolve } = Promise.withResolvers<boolean>();
|
|
501
|
+
let applying = false;
|
|
502
|
+
this.showSelector(done => {
|
|
503
|
+
const selector = new LiteLLMModelSelectorComponent(
|
|
504
|
+
choices,
|
|
505
|
+
choice => {
|
|
506
|
+
if (applying) return;
|
|
507
|
+
applying = true;
|
|
508
|
+
void (async () => {
|
|
509
|
+
try {
|
|
510
|
+
const applied = await applyModelAfterLogin(this.ctx.session, choice);
|
|
511
|
+
if (!applied) {
|
|
512
|
+
this.ctx.showError(`Model unavailable after refresh: ${choice.provider}/${choice.modelId}`);
|
|
513
|
+
applying = false;
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
this.ctx.statusLine.invalidate();
|
|
517
|
+
this.ctx.updateEditorBorderColor();
|
|
518
|
+
done();
|
|
519
|
+
resolve(true);
|
|
520
|
+
this.ctx.ui.requestRender();
|
|
521
|
+
} catch (error) {
|
|
522
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
523
|
+
applying = false;
|
|
524
|
+
}
|
|
525
|
+
})();
|
|
526
|
+
},
|
|
527
|
+
() => {
|
|
528
|
+
if (applying) return;
|
|
529
|
+
done();
|
|
530
|
+
resolve(false);
|
|
531
|
+
this.ctx.ui.requestRender();
|
|
532
|
+
},
|
|
533
|
+
);
|
|
534
|
+
return { component: selector, focus: selector.getSelectList() };
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
return promise;
|
|
538
|
+
}
|
|
539
|
+
|
|
475
540
|
async showPluginSelector(mode: "install" | "uninstall" = "install"): Promise<void> {
|
|
476
541
|
const mgr = new MarketplaceManager({
|
|
477
542
|
marketplacesRegistryPath: getMarketplacesRegistryPath(),
|
|
@@ -927,8 +992,7 @@ export class SelectorController {
|
|
|
927
992
|
apiBasePath: probe.apiBasePath,
|
|
928
993
|
apiKeyLiteral: result.apiKey,
|
|
929
994
|
});
|
|
930
|
-
|
|
931
|
-
fs.writeFileSync(modelsPath, yml);
|
|
995
|
+
await writeLiteLLMModelsYml(modelsPath, yml);
|
|
932
996
|
|
|
933
997
|
// Create/heal config.yml for model defaults
|
|
934
998
|
const configPath = path.join(path.dirname(modelsPath), "config.yml");
|
|
@@ -940,11 +1004,24 @@ export class SelectorController {
|
|
|
940
1004
|
// Force online refresh so the registry re-probes the new proxy
|
|
941
1005
|
// instead of serving stale data from the in-process SQLite cache.
|
|
942
1006
|
await this.ctx.session.modelRegistry.refresh("online");
|
|
1007
|
+
const modelSelected = await this.#showLiteLLMLoginModelSelector(probe.models);
|
|
943
1008
|
|
|
944
1009
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
945
1010
|
this.ctx.chatContainer.addChild(
|
|
946
1011
|
new Text(theme.fg("success", `LiteLLM configuration saved to ${modelsPath}`), 1, 0),
|
|
947
1012
|
);
|
|
1013
|
+
this.ctx.chatContainer.addChild(
|
|
1014
|
+
new Text(
|
|
1015
|
+
theme.fg(
|
|
1016
|
+
modelSelected ? "dim" : "warning",
|
|
1017
|
+
modelSelected
|
|
1018
|
+
? "Use /model to switch models without logging in again."
|
|
1019
|
+
: "No model selected. Use /model to choose one without logging in again.",
|
|
1020
|
+
),
|
|
1021
|
+
1,
|
|
1022
|
+
0,
|
|
1023
|
+
),
|
|
1024
|
+
);
|
|
948
1025
|
this.ctx.ui.requestRender();
|
|
949
1026
|
} catch (error: unknown) {
|
|
950
1027
|
this.ctx.showError(`LiteLLM login failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1143,29 +1220,17 @@ export class SelectorController {
|
|
|
1143
1220
|
|
|
1144
1221
|
// Auto-retry once on network errors
|
|
1145
1222
|
if (!probe.reachable && probe.error && !/\b(401|403|Unauthorized|Forbidden)\b/i.test(probe.error)) {
|
|
1146
|
-
await
|
|
1223
|
+
await Bun.sleep(1000);
|
|
1147
1224
|
probe = await probeLiteLLMConnection(baseUrl, apiKey);
|
|
1148
1225
|
}
|
|
1149
1226
|
|
|
1150
1227
|
if (probe.reachable) {
|
|
1151
|
-
// Auto-select best model
|
|
1152
|
-
const preferenceOrder = ["claude-opus", "claude-sonnet", "claude", "gpt-4", "gpt"];
|
|
1153
|
-
let selectedModel: string | undefined;
|
|
1154
|
-
for (const pref of preferenceOrder) {
|
|
1155
|
-
selectedModel = probe.models.find(m => m.toLowerCase().includes(pref));
|
|
1156
|
-
if (selectedModel) break;
|
|
1157
|
-
}
|
|
1158
|
-
if (!selectedModel && probe.models.length > 0) {
|
|
1159
|
-
selectedModel = probe.models[0];
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
1228
|
// Save config
|
|
1163
1229
|
const yml = generateModelsYml(baseUrl, {
|
|
1164
1230
|
apiBasePath: probe.apiBasePath,
|
|
1165
1231
|
apiKeyLiteral: apiKey,
|
|
1166
1232
|
});
|
|
1167
|
-
|
|
1168
|
-
fs.writeFileSync(modelsPath, yml, { mode: 0o600 });
|
|
1233
|
+
await writeLiteLLMModelsYml(modelsPath, yml);
|
|
1169
1234
|
|
|
1170
1235
|
const configPath = path.join(path.dirname(modelsPath), "config.yml");
|
|
1171
1236
|
if (!fs.existsSync(configPath)) {
|
|
@@ -1174,10 +1239,13 @@ export class SelectorController {
|
|
|
1174
1239
|
healConfigYmlModelRoles(configPath);
|
|
1175
1240
|
|
|
1176
1241
|
await this.ctx.session.modelRegistry.refresh("online");
|
|
1177
|
-
|
|
1178
|
-
// readiness gate lifts without requiring a manual /model selection.
|
|
1179
|
-
await applyModelAfterLogin(this.ctx.session, selectedModel);
|
|
1242
|
+
const modelSelected = await this.#showLiteLLMLoginModelSelector(probe.models);
|
|
1180
1243
|
await this.ctx.refreshWelcomeAfterLogin();
|
|
1244
|
+
this.ctx.showStatus(
|
|
1245
|
+
modelSelected
|
|
1246
|
+
? "LiteLLM configured. Use /model to switch models without logging in again."
|
|
1247
|
+
: "LiteLLM configured. Use /model to select a model.",
|
|
1248
|
+
);
|
|
1181
1249
|
probeSuccess = true;
|
|
1182
1250
|
} else {
|
|
1183
1251
|
const errorMsg = probe.error ?? "connection failed";
|