@gajae-code/coding-agent 0.4.4 → 0.5.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 +83 -0
- package/dist/types/cli/fast-help.d.ts +1 -0
- package/dist/types/cli/setup-cli.d.ts +2 -0
- package/dist/types/commands/harness.d.ts +6 -0
- package/dist/types/commands/setup.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -2
- package/dist/types/config/model-profiles.d.ts +7 -0
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +35 -0
- package/dist/types/config/settings-schema.d.ts +4 -3
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/server.d.ts +8 -2
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +3 -0
- package/dist/types/harness-control-plane/finalize.d.ts +5 -0
- package/dist/types/harness-control-plane/owner.d.ts +1 -1
- package/dist/types/harness-control-plane/phase-rollup.d.ts +23 -0
- package/dist/types/harness-control-plane/receipt-ingest.d.ts +19 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
- package/dist/types/harness-control-plane/receipts.d.ts +46 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +6 -1
- package/dist/types/harness-control-plane/types.d.ts +13 -1
- package/dist/types/hindsight/mental-models.d.ts +5 -5
- package/dist/types/main.d.ts +2 -2
- package/dist/types/modes/components/model-selector.d.ts +1 -12
- package/dist/types/modes/rpc/rpc-client.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-types.d.ts +4 -1
- package/dist/types/modes/utils/abort-message.d.ts +4 -0
- package/dist/types/sdk.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/blob-store.d.ts +20 -1
- package/dist/types/session/session-manager.d.ts +32 -6
- package/dist/types/session/streaming-output.d.ts +3 -2
- package/dist/types/session/tool-choice-queue.d.ts +6 -0
- package/dist/types/setup/hermes-setup.d.ts +7 -0
- package/dist/types/task/fork-context-advisory.d.ts +13 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/roi-reconciliation.d.ts +27 -0
- package/dist/types/task/types.d.ts +17 -0
- package/dist/types/thinking-metadata.d.ts +16 -0
- package/dist/types/thinking.d.ts +3 -12
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resolve.d.ts +0 -10
- package/dist/types/utils/tool-choice.d.ts +14 -1
- package/package.json +8 -7
- package/scripts/build-binary.ts +4 -0
- package/src/cli/fast-help.ts +80 -0
- package/src/cli/setup-cli.ts +12 -3
- package/src/cli.ts +112 -17
- package/src/commands/coordinator.ts +44 -1
- package/src/commands/harness.ts +128 -11
- package/src/commands/launch.ts +2 -2
- package/src/commands/mcp-serve.ts +3 -2
- package/src/commands/session.ts +3 -1
- package/src/commands/setup.ts +4 -0
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +255 -56
- package/src/config/model-resolver.ts +9 -6
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/server.ts +427 -193
- package/src/cursor.ts +46 -4
- package/src/defaults/gjc/skills/team/SKILL.md +3 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -2
- package/src/export/html/index.ts +13 -9
- package/src/gjc-runtime/launch-worktree.ts +12 -1
- package/src/gjc-runtime/session-state-sidecar.ts +38 -0
- package/src/gjc-runtime/team-runtime.ts +33 -7
- package/src/gjc-runtime/tmux-common.ts +15 -0
- package/src/gjc-runtime/tmux-sessions.ts +19 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +505 -41
- package/src/gjc-runtime/workflow-manifest.generated.json +27 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -1
- package/src/harness-control-plane/finalize.ts +39 -5
- package/src/harness-control-plane/owner.ts +87 -28
- package/src/harness-control-plane/phase-rollup.ts +96 -0
- package/src/harness-control-plane/receipt-ingest.ts +127 -0
- package/src/harness-control-plane/receipt-spool.ts +128 -0
- package/src/harness-control-plane/receipts.ts +229 -1
- package/src/harness-control-plane/rpc-adapter.ts +8 -0
- package/src/harness-control-plane/state-machine.ts +27 -6
- package/src/harness-control-plane/storage.ts +23 -0
- package/src/harness-control-plane/types.ts +33 -1
- package/src/hindsight/mental-models.ts +17 -16
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/main.ts +7 -3
- package/src/modes/components/assistant-message.ts +26 -14
- package/src/modes/components/diff.ts +97 -0
- package/src/modes/components/model-selector.ts +353 -181
- package/src/modes/components/status-line.ts +6 -6
- package/src/modes/components/tool-execution.ts +30 -13
- package/src/modes/controllers/event-controller.ts +5 -4
- package/src/modes/controllers/selector-controller.ts +33 -42
- package/src/modes/interactive-mode.ts +4 -5
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +3 -2
- package/src/modes/rpc/rpc-mode.ts +44 -14
- package/src/modes/rpc/rpc-types.ts +5 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +10 -5
- package/src/modes/shared/agent-wire/command-validation.ts +11 -0
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/utils/abort-message.ts +41 -0
- package/src/modes/utils/context-usage.ts +15 -8
- package/src/modes/utils/ui-helpers.ts +5 -6
- package/src/sdk.ts +38 -6
- package/src/secrets/obfuscator.ts +102 -27
- package/src/session/agent-session.ts +121 -25
- package/src/session/blob-store.ts +89 -3
- package/src/session/session-manager.ts +328 -57
- package/src/session/streaming-output.ts +185 -122
- package/src/session/tool-choice-queue.ts +23 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +3 -2
- package/src/setup/hermes-setup.ts +63 -8
- package/src/task/executor.ts +69 -6
- package/src/task/fork-context-advisory.ts +99 -0
- package/src/task/index.ts +31 -2
- package/src/task/receipt.ts +7 -0
- package/src/task/render.ts +21 -1
- package/src/task/roi-reconciliation.ts +90 -0
- package/src/task/types.ts +15 -0
- package/src/thinking-metadata.ts +51 -0
- package/src/thinking.ts +26 -46
- package/src/tools/bash.ts +1 -1
- package/src/tools/index.ts +4 -2
- package/src/tools/resolve.ts +93 -18
- package/src/tools/subagent-render.ts +10 -1
- package/src/utils/edit-mode.ts +1 -1
- package/src/utils/title-generator.ts +16 -2
- package/src/utils/tool-choice.ts +45 -16
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
2
|
-
import {
|
|
2
|
+
import { getSupportedEfforts, type Model, modelsAreEqual } from "@gajae-code/ai";
|
|
3
3
|
import {
|
|
4
4
|
Container,
|
|
5
5
|
fuzzyFilter,
|
|
@@ -12,9 +12,17 @@ import {
|
|
|
12
12
|
Text,
|
|
13
13
|
type TUI,
|
|
14
14
|
} from "@gajae-code/tui";
|
|
15
|
-
import
|
|
15
|
+
import {
|
|
16
|
+
getModelProfilePresentation,
|
|
17
|
+
groupModelProfilesForPresetLanding,
|
|
18
|
+
type ModelProfileDefinition,
|
|
19
|
+
} from "../../config/model-profiles";
|
|
16
20
|
import type { GjcModelAssignmentTargetId, ModelRegistry } from "../../config/model-registry";
|
|
17
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
GJC_MODEL_ASSIGNMENT_TARGET_IDS,
|
|
23
|
+
GJC_MODEL_ASSIGNMENT_TARGETS,
|
|
24
|
+
isAuthenticated,
|
|
25
|
+
} from "../../config/model-registry";
|
|
18
26
|
import {
|
|
19
27
|
formatModelSelectorValue,
|
|
20
28
|
resolveModelRoleValue,
|
|
@@ -23,7 +31,7 @@ import {
|
|
|
23
31
|
import type { Settings } from "../../config/settings";
|
|
24
32
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
25
33
|
import { formatModelOnboardingInlineHint } from "../../setup/model-onboarding-guidance";
|
|
26
|
-
import { getThinkingLevelMetadata, parseThinkingLevel } from "../../thinking";
|
|
34
|
+
import { formatClampedModelSelector, getThinkingLevelMetadata, parseThinkingLevel } from "../../thinking";
|
|
27
35
|
import { getTabBarTheme } from "../shared";
|
|
28
36
|
import { DynamicBorder } from "./dynamic-border";
|
|
29
37
|
|
|
@@ -75,12 +83,6 @@ interface CanonicalModelItem {
|
|
|
75
83
|
explicitThinkingLevel?: boolean;
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
interface ProfileItem {
|
|
79
|
-
kind: "profile";
|
|
80
|
-
name: string;
|
|
81
|
-
profile: ModelProfileDefinition;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
86
|
type ScopedModelItem = ScopedModelSelection;
|
|
85
87
|
|
|
86
88
|
interface RoleAssignment {
|
|
@@ -88,13 +90,6 @@ interface RoleAssignment {
|
|
|
88
90
|
thinkingLevel: ThinkingLevel;
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
export interface ModelAssignmentPreset {
|
|
92
|
-
id: "openai-codex";
|
|
93
|
-
label: string;
|
|
94
|
-
description: string;
|
|
95
|
-
assignments: Partial<Record<GjcModelAssignmentTargetId, ThinkingLevel>>;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
93
|
export type ModelSelectorSelection =
|
|
99
94
|
| {
|
|
100
95
|
kind: "assignment";
|
|
@@ -103,13 +98,6 @@ export type ModelSelectorSelection =
|
|
|
103
98
|
thinkingLevel?: ThinkingLevel;
|
|
104
99
|
selector?: string;
|
|
105
100
|
}
|
|
106
|
-
| {
|
|
107
|
-
kind: "preset";
|
|
108
|
-
model: Model;
|
|
109
|
-
selector: string;
|
|
110
|
-
preset: ModelAssignmentPreset;
|
|
111
|
-
assignments: Record<GjcModelAssignmentTargetId, ThinkingLevel>;
|
|
112
|
-
}
|
|
113
101
|
| {
|
|
114
102
|
kind: "profile";
|
|
115
103
|
profileName: string;
|
|
@@ -137,18 +125,6 @@ const STATIC_PROVIDER_TABS: ProviderTabState[] = [
|
|
|
137
125
|
{ id: ALL_TAB, label: ALL_TAB },
|
|
138
126
|
{ id: CANONICAL_TAB, label: CANONICAL_TAB },
|
|
139
127
|
];
|
|
140
|
-
const OPENAI_CODE_PROFILE_PRESET: ModelAssignmentPreset = {
|
|
141
|
-
id: "openai-codex",
|
|
142
|
-
label: "Apply OpenAI Codex role preset",
|
|
143
|
-
description: "Default medium, Executor low, Architect xhigh, Planner medium, Critic high",
|
|
144
|
-
assignments: {
|
|
145
|
-
default: ThinkingLevel.Medium,
|
|
146
|
-
executor: ThinkingLevel.Low,
|
|
147
|
-
architect: ThinkingLevel.XHigh,
|
|
148
|
-
planner: ThinkingLevel.Medium,
|
|
149
|
-
critic: ThinkingLevel.High,
|
|
150
|
-
},
|
|
151
|
-
};
|
|
152
128
|
|
|
153
129
|
function formatProviderTabLabel(providerId: string): string {
|
|
154
130
|
return providerId.replace(/[-_]+/g, " ").toUpperCase();
|
|
@@ -157,6 +133,43 @@ function formatProviderTabLabel(providerId: string): string {
|
|
|
157
133
|
function createProviderTab(providerId: string): ProviderTabState {
|
|
158
134
|
return { id: providerId, label: formatProviderTabLabel(providerId), providerId };
|
|
159
135
|
}
|
|
136
|
+
|
|
137
|
+
type ModelSelectorViewMode = "presets" | "models";
|
|
138
|
+
|
|
139
|
+
interface PresetGroupRow {
|
|
140
|
+
kind: "group";
|
|
141
|
+
groupId: string;
|
|
142
|
+
profiles: ModelProfileDefinition[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface PresetProfileRow {
|
|
146
|
+
kind: "profile";
|
|
147
|
+
groupId: string;
|
|
148
|
+
profile: ModelProfileDefinition;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface PresetBrowseRow {
|
|
152
|
+
kind: "browse";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
type PresetLandingRow = PresetGroupRow | PresetProfileRow | PresetBrowseRow;
|
|
156
|
+
|
|
157
|
+
const PROFILE_ROLE_PREVIEW_ORDER: GjcModelAssignmentTargetId[] = [
|
|
158
|
+
"default",
|
|
159
|
+
"executor",
|
|
160
|
+
"planner",
|
|
161
|
+
"critic",
|
|
162
|
+
"architect",
|
|
163
|
+
];
|
|
164
|
+
const PRESET_SCOPE_LABELS = ["Apply for this session", "Set as default"];
|
|
165
|
+
|
|
166
|
+
function isPrintableCharacter(keyData: string): boolean {
|
|
167
|
+
return keyData.length === 1 && keyData >= " " && keyData !== "\x7f";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function profileRequiredProviders(profile: ModelProfileDefinition): string[] {
|
|
171
|
+
return [...new Set(profile.requiredProviders)].sort((a, b) => a.localeCompare(b));
|
|
172
|
+
}
|
|
160
173
|
/**
|
|
161
174
|
* Component that renders a canonical model selector with provider tabs.
|
|
162
175
|
* - Tab/Arrow Left/Right: Switch between provider tabs
|
|
@@ -173,7 +186,6 @@ export class ModelSelectorComponent extends Container {
|
|
|
173
186
|
#filteredModels: ModelItem[] = [];
|
|
174
187
|
#canonicalModels: CanonicalModelItem[] = [];
|
|
175
188
|
#filteredCanonicalModels: CanonicalModelItem[] = [];
|
|
176
|
-
#profileItems: ProfileItem[] = [];
|
|
177
189
|
#selectedIndex: number = 0;
|
|
178
190
|
#roles = {} as Record<string, RoleAssignment | undefined>;
|
|
179
191
|
#settings = null as unknown as Settings;
|
|
@@ -189,6 +201,18 @@ export class ModelSelectorComponent extends Container {
|
|
|
189
201
|
#pendingThinkingChoice?: PendingThinkingChoice;
|
|
190
202
|
#selectedThinkingIndex: number = 0;
|
|
191
203
|
|
|
204
|
+
// Preset landing state
|
|
205
|
+
#viewMode: ModelSelectorViewMode = "presets";
|
|
206
|
+
#presetCursor: number = 0;
|
|
207
|
+
#expandedPresetProviderId?: string;
|
|
208
|
+
#previewProfileName?: string;
|
|
209
|
+
#presetScopeMenuOpen: boolean = false;
|
|
210
|
+
#presetScopeIndex: number = 0;
|
|
211
|
+
#providerAuthById = new Map<string, boolean>();
|
|
212
|
+
#providerAuthPending: boolean = false;
|
|
213
|
+
#presetLoginHint?: string;
|
|
214
|
+
#authSessionId?: string;
|
|
215
|
+
|
|
192
216
|
// Tab state
|
|
193
217
|
#providers: ProviderTabState[] = STATIC_PROVIDER_TABS;
|
|
194
218
|
#activeTabIndex: number = 0;
|
|
@@ -201,7 +225,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
201
225
|
scopedModels: ReadonlyArray<ScopedModelItem>,
|
|
202
226
|
onSelect: RoleSelectCallback,
|
|
203
227
|
onCancel: () => void,
|
|
204
|
-
options?: { temporaryOnly?: boolean; initialSearchInput?: string },
|
|
228
|
+
options?: { temporaryOnly?: boolean; initialSearchInput?: string; sessionId?: string },
|
|
205
229
|
) {
|
|
206
230
|
super();
|
|
207
231
|
|
|
@@ -212,7 +236,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
212
236
|
this.#onSelectCallback = onSelect;
|
|
213
237
|
this.#onCancelCallback = onCancel;
|
|
214
238
|
this.#temporaryOnly = options?.temporaryOnly ?? false;
|
|
239
|
+
this.#authSessionId = options?.sessionId;
|
|
215
240
|
const initialSearchInput = options?.initialSearchInput;
|
|
241
|
+
this.#viewMode = this.#temporaryOnly || initialSearchInput || scopedModels.length > 0 ? "models" : "presets";
|
|
216
242
|
|
|
217
243
|
// Load current role assignments from settings
|
|
218
244
|
this.#loadRoleModels();
|
|
@@ -240,13 +266,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
240
266
|
}
|
|
241
267
|
this.#searchInput.onSubmit = () => {
|
|
242
268
|
const selectedItem = this.#getSelectedItem();
|
|
243
|
-
if (selectedItem)
|
|
244
|
-
if (selectedItem.kind === "profile") {
|
|
245
|
-
this.#beginProfileActionMenu(selectedItem);
|
|
246
|
-
} else {
|
|
247
|
-
this.#beginActionMenuOrSelect(selectedItem);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
269
|
+
if (selectedItem) this.#beginActionMenuOrSelect(selectedItem);
|
|
250
270
|
};
|
|
251
271
|
this.addChild(this.#searchInput);
|
|
252
272
|
|
|
@@ -264,14 +284,23 @@ export class ModelSelectorComponent extends Container {
|
|
|
264
284
|
// Load models and do initial render
|
|
265
285
|
this.#loadModels().then(() => {
|
|
266
286
|
this.#buildProviderTabs();
|
|
267
|
-
this.#
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
this.#
|
|
287
|
+
if (this.#viewMode === "presets" && (this.#modelRegistry.getModelProfiles?.().size ?? 0) === 0) {
|
|
288
|
+
this.#viewMode = "models";
|
|
289
|
+
}
|
|
290
|
+
if (this.#viewMode === "presets") {
|
|
291
|
+
this.#updatePresetExpansion();
|
|
292
|
+
void this.#refreshProviderAuth();
|
|
293
|
+
this.#renderPresetLanding();
|
|
273
294
|
} else {
|
|
274
|
-
this.#
|
|
295
|
+
this.#updateTabBar();
|
|
296
|
+
// Always apply the current search query — the user may have typed
|
|
297
|
+
// while models were loading asynchronously.
|
|
298
|
+
const currentQuery = this.#searchInput.getValue();
|
|
299
|
+
if (currentQuery) {
|
|
300
|
+
this.#filterModels(currentQuery);
|
|
301
|
+
} else {
|
|
302
|
+
this.#updateList();
|
|
303
|
+
}
|
|
275
304
|
}
|
|
276
305
|
// Request re-render after models are loaded
|
|
277
306
|
this.#tui.requestRender();
|
|
@@ -482,18 +511,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
482
511
|
|
|
483
512
|
this.#sortModels(models);
|
|
484
513
|
this.#sortCanonicalModels(canonicalModels);
|
|
485
|
-
const profiles = this.#modelRegistry.getModelProfiles?.() ?? new Map();
|
|
486
|
-
const profileItems = this.#temporaryOnly
|
|
487
|
-
? []
|
|
488
|
-
: [...profiles.values()]
|
|
489
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
490
|
-
.map(profile => ({ kind: "profile" as const, name: profile.name, profile }));
|
|
491
|
-
|
|
492
514
|
this.#allModels = models;
|
|
493
515
|
this.#filteredModels = models;
|
|
494
516
|
this.#canonicalModels = canonicalModels;
|
|
495
517
|
this.#filteredCanonicalModels = canonicalModels;
|
|
496
|
-
this.#profileItems = profileItems;
|
|
497
518
|
this.#selectedIndex = Math.min(this.#selectedIndex, Math.max(0, models.length - 1));
|
|
498
519
|
}
|
|
499
520
|
|
|
@@ -642,6 +663,164 @@ export class ModelSelectorComponent extends Container {
|
|
|
642
663
|
return `${ageMinutes}m ago`;
|
|
643
664
|
}
|
|
644
665
|
|
|
666
|
+
#getPresetGroups(): Map<string, ModelProfileDefinition[]> {
|
|
667
|
+
return groupModelProfilesForPresetLanding(this.#modelRegistry.getModelProfiles?.() ?? new Map());
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
#getPresetRows(): PresetLandingRow[] {
|
|
671
|
+
const rows: PresetLandingRow[] = [];
|
|
672
|
+
for (const [groupId, profiles] of this.#getPresetGroups()) {
|
|
673
|
+
rows.push({ kind: "group", groupId, profiles });
|
|
674
|
+
if (this.#expandedPresetProviderId === groupId) {
|
|
675
|
+
for (const profile of profiles) rows.push({ kind: "profile", groupId, profile });
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
rows.push({ kind: "browse" });
|
|
679
|
+
return rows;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
#getSelectedPresetRow(): PresetLandingRow | undefined {
|
|
683
|
+
return this.#getPresetRows()[this.#presetCursor];
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
#getProfileByName(name: string | undefined): ModelProfileDefinition | undefined {
|
|
687
|
+
if (!name) return undefined;
|
|
688
|
+
return this.#modelRegistry.getModelProfile?.(name) ?? this.#modelRegistry.getModelProfiles?.().get(name);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
#isProviderAuthenticated(providerId: string): boolean | undefined {
|
|
692
|
+
return this.#providerAuthById.get(providerId);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
#getMissingProviders(profileOrProfiles: ModelProfileDefinition | ModelProfileDefinition[]): string[] {
|
|
696
|
+
const profiles = Array.isArray(profileOrProfiles) ? profileOrProfiles : [profileOrProfiles];
|
|
697
|
+
const providers = new Set<string>();
|
|
698
|
+
for (const profile of profiles) for (const provider of profileRequiredProviders(profile)) providers.add(provider);
|
|
699
|
+
return [...providers]
|
|
700
|
+
.filter(provider => this.#isProviderAuthenticated(provider) !== true)
|
|
701
|
+
.sort((a, b) => a.localeCompare(b));
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
#isPresetAuthenticated(profileOrProfiles: ModelProfileDefinition | ModelProfileDefinition[]): boolean {
|
|
705
|
+
return this.#getMissingProviders(profileOrProfiles).length === 0;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
async #refreshProviderAuth(): Promise<void> {
|
|
709
|
+
const providers = new Set<string>();
|
|
710
|
+
for (const profiles of this.#getPresetGroups().values()) {
|
|
711
|
+
for (const profile of profiles)
|
|
712
|
+
for (const provider of profileRequiredProviders(profile)) providers.add(provider);
|
|
713
|
+
}
|
|
714
|
+
this.#providerAuthPending = providers.size > 0;
|
|
715
|
+
this.#renderPresetLanding();
|
|
716
|
+
const entries = await Promise.all(
|
|
717
|
+
[...providers].map(async provider => {
|
|
718
|
+
const apiKey = await this.#modelRegistry.getApiKeyForProvider(provider, this.#authSessionId);
|
|
719
|
+
return [provider, isAuthenticated(apiKey)] as const;
|
|
720
|
+
}),
|
|
721
|
+
);
|
|
722
|
+
this.#providerAuthById = new Map(entries);
|
|
723
|
+
this.#providerAuthPending = false;
|
|
724
|
+
this.#renderPresetLanding();
|
|
725
|
+
this.#tui.requestRender();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
#updatePresetExpansion(): void {
|
|
729
|
+
const selected = this.#getSelectedPresetRow();
|
|
730
|
+
if (selected?.kind === "group") this.#expandedPresetProviderId = selected.groupId;
|
|
731
|
+
if (selected?.kind === "profile") this.#expandedPresetProviderId = selected.groupId;
|
|
732
|
+
const rows = this.#getPresetRows();
|
|
733
|
+
this.#presetCursor = Math.min(this.#presetCursor, Math.max(0, rows.length - 1));
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
#switchToModelMode(seed?: string): void {
|
|
737
|
+
this.#viewMode = "models";
|
|
738
|
+
this.#expandedPresetProviderId = undefined;
|
|
739
|
+
this.#previewProfileName = undefined;
|
|
740
|
+
this.#presetScopeMenuOpen = false;
|
|
741
|
+
this.#presetScopeIndex = 0;
|
|
742
|
+
this.#presetLoginHint = undefined;
|
|
743
|
+
this.#activeTabIndex = 0;
|
|
744
|
+
this.#selectedIndex = 0;
|
|
745
|
+
this.#searchInput.setValue(seed ?? this.#searchInput.getValue());
|
|
746
|
+
this.#updateTabBar();
|
|
747
|
+
this.#filterModels(this.#searchInput.getValue());
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
#renderPresetLanding(): void {
|
|
751
|
+
this.#headerContainer.clear();
|
|
752
|
+
this.#tabBar = null;
|
|
753
|
+
this.#listContainer.clear();
|
|
754
|
+
this.#headerContainer.addChild(new Text(theme.fg("accent", "Model presets"), 0, 0));
|
|
755
|
+
const rows = this.#getPresetRows();
|
|
756
|
+
for (let i = 0; i < rows.length; i++) {
|
|
757
|
+
const row = rows[i];
|
|
758
|
+
const selected = i === this.#presetCursor;
|
|
759
|
+
const prefix = selected ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
760
|
+
if (row.kind === "browse") {
|
|
761
|
+
const label = "Browse all models";
|
|
762
|
+
this.#listContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (row.kind === "group") {
|
|
766
|
+
const authenticated = this.#isPresetAuthenticated(row.profiles);
|
|
767
|
+
const mark = this.#providerAuthPending ? "…" : authenticated ? "✓" : "✗";
|
|
768
|
+
const label = `${mark} ${row.groupId}`;
|
|
769
|
+
const renderedLabel = selected ? theme.fg("accent", label) : authenticated ? label : theme.fg("dim", label);
|
|
770
|
+
this.#listContainer.addChild(new Text(`${prefix}${renderedLabel}`, 0, 0));
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
const presentation = getModelProfilePresentation(row.profile.name);
|
|
774
|
+
const authenticated = this.#isPresetAuthenticated(row.profile);
|
|
775
|
+
const mark = this.#providerAuthPending ? "…" : authenticated ? "✓" : "✗";
|
|
776
|
+
const label = ` ${mark} ${presentation.displayName}`;
|
|
777
|
+
const renderedLabel = selected ? theme.fg("accent", label) : authenticated ? label : theme.fg("dim", label);
|
|
778
|
+
this.#listContainer.addChild(new Text(`${prefix}${renderedLabel}`, 0, 0));
|
|
779
|
+
}
|
|
780
|
+
if (this.#presetLoginHint) {
|
|
781
|
+
this.#listContainer.addChild(new Spacer(1));
|
|
782
|
+
this.#listContainer.addChild(new Text(theme.fg("warning", ` ${this.#presetLoginHint}`), 0, 0));
|
|
783
|
+
}
|
|
784
|
+
const previewProfile = this.#getProfileByName(this.#previewProfileName);
|
|
785
|
+
if (previewProfile) this.#renderPresetPreview(previewProfile);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
#renderPresetPreview(profile: ModelProfileDefinition): void {
|
|
789
|
+
this.#listContainer.addChild(new Spacer(1));
|
|
790
|
+
this.#listContainer.addChild(
|
|
791
|
+
new Text(
|
|
792
|
+
theme.fg("muted", ` Preset preview: ${getModelProfilePresentation(profile.name).displayName}`),
|
|
793
|
+
0,
|
|
794
|
+
0,
|
|
795
|
+
),
|
|
796
|
+
);
|
|
797
|
+
for (const role of PROFILE_ROLE_PREVIEW_ORDER) {
|
|
798
|
+
const selector = profile.modelMapping[role];
|
|
799
|
+
if (!selector) continue;
|
|
800
|
+
const resolved = resolveModelRoleValue(selector, this.#modelRegistry.getAll(), {
|
|
801
|
+
settings: this.#settings,
|
|
802
|
+
matchPreferences: { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() },
|
|
803
|
+
modelRegistry: this.#modelRegistry,
|
|
804
|
+
});
|
|
805
|
+
const label = GJC_MODEL_ASSIGNMENT_TARGETS[role].tag ?? role.toUpperCase();
|
|
806
|
+
this.#listContainer.addChild(
|
|
807
|
+
new Text(` ${label}: ${formatClampedModelSelector(selector, resolved.model)}`, 0, 0),
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
this.#listContainer.addChild(new Spacer(1));
|
|
811
|
+
if (this.#presetScopeMenuOpen) {
|
|
812
|
+
for (let i = 0; i < PRESET_SCOPE_LABELS.length; i++) {
|
|
813
|
+
const label = PRESET_SCOPE_LABELS[i] ?? "";
|
|
814
|
+
const prefix = i === this.#presetScopeIndex ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
815
|
+
this.#listContainer.addChild(
|
|
816
|
+
new Text(`${prefix}${i === this.#presetScopeIndex ? theme.fg("accent", label) : label}`, 0, 0),
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
} else {
|
|
820
|
+
this.#listContainer.addChild(new Text(theme.fg("muted", " Press Enter to apply this preset"), 0, 0));
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
645
824
|
#formatDiscoveryErrorHint(error: string | undefined): string | undefined {
|
|
646
825
|
if (!error) {
|
|
647
826
|
return undefined;
|
|
@@ -688,17 +867,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
688
867
|
}
|
|
689
868
|
}
|
|
690
869
|
|
|
691
|
-
#getVisibleProfiles(): ProfileItem[] {
|
|
692
|
-
return !this.#temporaryOnly && !this.#isCanonicalTab() && this.#getActiveTabId() === ALL_TAB
|
|
693
|
-
? this.#profileItems
|
|
694
|
-
: [];
|
|
695
|
-
}
|
|
696
|
-
|
|
697
870
|
#updateList(): void {
|
|
698
871
|
this.#listContainer.clear();
|
|
699
872
|
const isCanonicalTab = this.#isCanonicalTab();
|
|
700
|
-
const
|
|
701
|
-
const modelSelectedIndex = Math.max(0, this.#selectedIndex - visibleProfiles.length);
|
|
873
|
+
const modelSelectedIndex = this.#selectedIndex;
|
|
702
874
|
const visibleItems = isCanonicalTab ? this.#filteredCanonicalModels : this.#filteredModels;
|
|
703
875
|
|
|
704
876
|
const maxVisible = 10;
|
|
@@ -710,18 +882,6 @@ export class ModelSelectorComponent extends Container {
|
|
|
710
882
|
|
|
711
883
|
const showProvider = this.#getActiveTabId() === ALL_TAB;
|
|
712
884
|
|
|
713
|
-
if (visibleProfiles.length > 0) {
|
|
714
|
-
this.#listContainer.addChild(new Text(theme.fg("muted", "Profiles"), 0, 0));
|
|
715
|
-
for (let i = 0; i < visibleProfiles.length; i++) {
|
|
716
|
-
const profile = visibleProfiles[i];
|
|
717
|
-
if (!profile) continue;
|
|
718
|
-
const isSelected = i === this.#selectedIndex;
|
|
719
|
-
const prefix = isSelected ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
720
|
-
const label = isSelected ? theme.fg("accent", profile.name) : profile.name;
|
|
721
|
-
this.#listContainer.addChild(new Text(`${prefix}${label}`, 0, 0));
|
|
722
|
-
}
|
|
723
|
-
this.#listContainer.addChild(new Spacer(1));
|
|
724
|
-
}
|
|
725
885
|
// Show visible slice of filtered models
|
|
726
886
|
for (let i = startIndex; i < endIndex; i++) {
|
|
727
887
|
const item = visibleItems[i];
|
|
@@ -729,7 +889,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
729
889
|
const canonicalItem = isCanonicalTab ? (item as CanonicalModelItem) : undefined;
|
|
730
890
|
const providerItem = isCanonicalTab ? undefined : (item as ModelItem);
|
|
731
891
|
|
|
732
|
-
const isSelected = i
|
|
892
|
+
const isSelected = i === this.#selectedIndex;
|
|
733
893
|
|
|
734
894
|
// Build role badges (inverted: color as background, black text)
|
|
735
895
|
const roleBadgeTokens: string[] = [];
|
|
@@ -786,7 +946,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
786
946
|
for (const line of errorLines) {
|
|
787
947
|
this.#listContainer.addChild(new Text(theme.fg("error", line), 0, 0));
|
|
788
948
|
}
|
|
789
|
-
} else if (visibleItems.length === 0
|
|
949
|
+
} else if (visibleItems.length === 0) {
|
|
790
950
|
const statusMessage = this.#getProviderEmptyStateMessage();
|
|
791
951
|
this.#listContainer.addChild(
|
|
792
952
|
new Text(
|
|
@@ -795,11 +955,6 @@ export class ModelSelectorComponent extends Container {
|
|
|
795
955
|
0,
|
|
796
956
|
),
|
|
797
957
|
);
|
|
798
|
-
} else if (this.#selectedIndex < visibleProfiles.length) {
|
|
799
|
-
const selectedProfile = visibleProfiles[this.#selectedIndex];
|
|
800
|
-
if (selectedProfile && this.#pendingActionItem) {
|
|
801
|
-
this.#renderProfileActionMenu(selectedProfile);
|
|
802
|
-
}
|
|
803
958
|
} else {
|
|
804
959
|
const selected = visibleItems[modelSelectedIndex];
|
|
805
960
|
if (!selected) {
|
|
@@ -828,9 +983,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
828
983
|
for (let i = 0; i < actionCount; i++) {
|
|
829
984
|
const prefix = i === this.#selectedActionIndex ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
830
985
|
const role = GJC_MODEL_ASSIGNMENT_TARGET_IDS[i];
|
|
831
|
-
const label = role
|
|
832
|
-
? `Set as ${GJC_MODEL_ASSIGNMENT_TARGETS[role].tag ?? role.toUpperCase()} (${GJC_MODEL_ASSIGNMENT_TARGETS[role].name})`
|
|
833
|
-
: `${OPENAI_CODE_PROFILE_PRESET.label} (${OPENAI_CODE_PROFILE_PRESET.description})`;
|
|
986
|
+
const label = `Set as ${GJC_MODEL_ASSIGNMENT_TARGETS[role].tag ?? role.toUpperCase()} (${GJC_MODEL_ASSIGNMENT_TARGETS[role].name})`;
|
|
834
987
|
this.#listContainer.addChild(
|
|
835
988
|
new Text(`${prefix}${i === this.#selectedActionIndex ? theme.fg("accent", label) : label}`, 0, 0),
|
|
836
989
|
);
|
|
@@ -855,32 +1008,17 @@ export class ModelSelectorComponent extends Container {
|
|
|
855
1008
|
}
|
|
856
1009
|
}
|
|
857
1010
|
|
|
858
|
-
#renderProfileActionMenu(profile: ProfileItem): void {
|
|
859
|
-
this.#listContainer.addChild(new Spacer(1));
|
|
860
|
-
this.#listContainer.addChild(new Text(theme.fg("muted", ` Action for profile: ${profile.name}`), 0, 0));
|
|
861
|
-
this.#listContainer.addChild(new Spacer(1));
|
|
862
|
-
const labels = ["Apply for this session", "Set as default"];
|
|
863
|
-
for (let i = 0; i < labels.length; i++) {
|
|
864
|
-
const label = labels[i] ?? "";
|
|
865
|
-
const prefix = i === this.#selectedActionIndex ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
866
|
-
this.#listContainer.addChild(
|
|
867
|
-
new Text(`${prefix}${i === this.#selectedActionIndex ? theme.fg("accent", label) : label}`, 0, 0),
|
|
868
|
-
);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
|
|
872
1011
|
#getCurrentRoleThinkingLevel(role: string): ThinkingLevel {
|
|
873
1012
|
return this.#roles[role]?.thinkingLevel ?? ThinkingLevel.Inherit;
|
|
874
1013
|
}
|
|
875
|
-
#getActionCount(
|
|
876
|
-
return GJC_MODEL_ASSIGNMENT_TARGET_IDS.length
|
|
1014
|
+
#getActionCount(_model: Model): number {
|
|
1015
|
+
return GJC_MODEL_ASSIGNMENT_TARGET_IDS.length;
|
|
877
1016
|
}
|
|
878
1017
|
|
|
879
|
-
#getSelectedItem(): ModelItem | CanonicalModelItem |
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
return this.#isCanonicalTab() ? this.#filteredCanonicalModels[modelIndex] : this.#filteredModels[modelIndex];
|
|
1018
|
+
#getSelectedItem(): ModelItem | CanonicalModelItem | undefined {
|
|
1019
|
+
return this.#isCanonicalTab()
|
|
1020
|
+
? this.#filteredCanonicalModels[this.#selectedIndex]
|
|
1021
|
+
: this.#filteredModels[this.#selectedIndex];
|
|
884
1022
|
}
|
|
885
1023
|
|
|
886
1024
|
handleInput(keyData: string): void {
|
|
@@ -893,6 +1031,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
893
1031
|
return;
|
|
894
1032
|
}
|
|
895
1033
|
|
|
1034
|
+
if (this.#viewMode === "presets") {
|
|
1035
|
+
this.#handlePresetLandingInput(keyData);
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
896
1039
|
// Tab bar navigation
|
|
897
1040
|
if (this.#tabBar?.handleInput(keyData)) {
|
|
898
1041
|
return;
|
|
@@ -900,9 +1043,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
900
1043
|
|
|
901
1044
|
// Up arrow - navigate list (wrap to bottom when at top)
|
|
902
1045
|
if (matchesKey(keyData, "up")) {
|
|
903
|
-
const itemCount =
|
|
904
|
-
this.#getVisibleProfiles().length +
|
|
905
|
-
(this.#isCanonicalTab() ? this.#filteredCanonicalModels.length : this.#filteredModels.length);
|
|
1046
|
+
const itemCount = this.#isCanonicalTab() ? this.#filteredCanonicalModels.length : this.#filteredModels.length;
|
|
906
1047
|
if (itemCount === 0) return;
|
|
907
1048
|
this.#selectedIndex = this.#selectedIndex === 0 ? itemCount - 1 : this.#selectedIndex - 1;
|
|
908
1049
|
this.#updateList();
|
|
@@ -911,9 +1052,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
911
1052
|
|
|
912
1053
|
// Down arrow - navigate list (wrap to top when at bottom)
|
|
913
1054
|
if (matchesKey(keyData, "down")) {
|
|
914
|
-
const itemCount =
|
|
915
|
-
this.#getVisibleProfiles().length +
|
|
916
|
-
(this.#isCanonicalTab() ? this.#filteredCanonicalModels.length : this.#filteredModels.length);
|
|
1055
|
+
const itemCount = this.#isCanonicalTab() ? this.#filteredCanonicalModels.length : this.#filteredModels.length;
|
|
917
1056
|
if (itemCount === 0) return;
|
|
918
1057
|
this.#selectedIndex = this.#selectedIndex === itemCount - 1 ? 0 : this.#selectedIndex + 1;
|
|
919
1058
|
this.#updateList();
|
|
@@ -924,13 +1063,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
924
1063
|
// existing non-persistent quick-switch behavior.
|
|
925
1064
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
926
1065
|
const selectedItem = this.#getSelectedItem();
|
|
927
|
-
if (selectedItem)
|
|
928
|
-
if (selectedItem.kind === "profile") {
|
|
929
|
-
this.#beginProfileActionMenu(selectedItem);
|
|
930
|
-
} else {
|
|
931
|
-
this.#beginActionMenuOrSelect(selectedItem);
|
|
932
|
-
}
|
|
933
|
-
}
|
|
1066
|
+
if (selectedItem) this.#beginActionMenuOrSelect(selectedItem);
|
|
934
1067
|
return;
|
|
935
1068
|
}
|
|
936
1069
|
|
|
@@ -944,10 +1077,99 @@ export class ModelSelectorComponent extends Container {
|
|
|
944
1077
|
this.#searchInput.handleInput(keyData);
|
|
945
1078
|
this.#filterModels(this.#searchInput.getValue());
|
|
946
1079
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1080
|
+
|
|
1081
|
+
#handlePresetLandingInput(keyData: string): void {
|
|
1082
|
+
if (isPrintableCharacter(keyData)) {
|
|
1083
|
+
this.#switchToModelMode(keyData);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
if (matchesKey(keyData, "up")) {
|
|
1087
|
+
const rows = this.#getPresetRows();
|
|
1088
|
+
if (rows.length === 0) return;
|
|
1089
|
+
if (this.#presetScopeMenuOpen) {
|
|
1090
|
+
this.#presetScopeIndex =
|
|
1091
|
+
this.#presetScopeIndex === 0 ? PRESET_SCOPE_LABELS.length - 1 : this.#presetScopeIndex - 1;
|
|
1092
|
+
} else {
|
|
1093
|
+
this.#presetCursor = this.#presetCursor === 0 ? rows.length - 1 : this.#presetCursor - 1;
|
|
1094
|
+
this.#previewProfileName = undefined;
|
|
1095
|
+
this.#presetLoginHint = undefined;
|
|
1096
|
+
this.#updatePresetExpansion();
|
|
1097
|
+
}
|
|
1098
|
+
this.#renderPresetLanding();
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
if (matchesKey(keyData, "down")) {
|
|
1102
|
+
const rows = this.#getPresetRows();
|
|
1103
|
+
if (rows.length === 0) return;
|
|
1104
|
+
if (this.#presetScopeMenuOpen) {
|
|
1105
|
+
this.#presetScopeIndex = (this.#presetScopeIndex + 1) % PRESET_SCOPE_LABELS.length;
|
|
1106
|
+
} else {
|
|
1107
|
+
this.#presetCursor = (this.#presetCursor + 1) % rows.length;
|
|
1108
|
+
this.#previewProfileName = undefined;
|
|
1109
|
+
this.#presetLoginHint = undefined;
|
|
1110
|
+
this.#updatePresetExpansion();
|
|
1111
|
+
}
|
|
1112
|
+
this.#renderPresetLanding();
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
1116
|
+
this.#handlePresetEnter();
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
if (getKeybindings().matches(keyData, "tui.select.cancel")) {
|
|
1120
|
+
if (this.#presetScopeMenuOpen) {
|
|
1121
|
+
this.#presetScopeMenuOpen = false;
|
|
1122
|
+
this.#renderPresetLanding();
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
if (this.#previewProfileName) {
|
|
1126
|
+
this.#previewProfileName = undefined;
|
|
1127
|
+
this.#renderPresetLanding();
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
if (this.#expandedPresetProviderId) {
|
|
1131
|
+
this.#expandedPresetProviderId = undefined;
|
|
1132
|
+
this.#presetCursor = Math.min(this.#presetCursor, Math.max(0, this.#getPresetRows().length - 1));
|
|
1133
|
+
this.#renderPresetLanding();
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
this.#onCancelCallback();
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
#handlePresetEnter(): void {
|
|
1141
|
+
if (this.#presetScopeMenuOpen && this.#previewProfileName) {
|
|
1142
|
+
this.#onSelectCallback({
|
|
1143
|
+
kind: "profile",
|
|
1144
|
+
profileName: this.#previewProfileName,
|
|
1145
|
+
setDefault: this.#presetScopeIndex === 1,
|
|
1146
|
+
});
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (this.#previewProfileName) {
|
|
1150
|
+
this.#presetScopeMenuOpen = true;
|
|
1151
|
+
this.#presetScopeIndex = 0;
|
|
1152
|
+
this.#renderPresetLanding();
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const row = this.#getSelectedPresetRow();
|
|
1156
|
+
if (!row) return;
|
|
1157
|
+
if (row.kind === "browse") {
|
|
1158
|
+
this.#switchToModelMode();
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
const missing =
|
|
1162
|
+
row.kind === "group" ? this.#getMissingProviders(row.profiles) : this.#getMissingProviders(row.profile);
|
|
1163
|
+
if (missing.length > 0) {
|
|
1164
|
+
this.#presetLoginHint = `Run ${missing.map(provider => `/login ${provider}`).join(", ")}`;
|
|
1165
|
+
this.#renderPresetLanding();
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (row.kind === "profile") {
|
|
1169
|
+
this.#previewProfileName = row.profile.name;
|
|
1170
|
+
this.#presetLoginHint = undefined;
|
|
1171
|
+
this.#renderPresetLanding();
|
|
1172
|
+
}
|
|
951
1173
|
}
|
|
952
1174
|
|
|
953
1175
|
#beginActionMenuOrSelect(item: ModelItem | CanonicalModelItem): void {
|
|
@@ -963,7 +1185,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
963
1185
|
#handleActionMenuInput(keyData: string): void {
|
|
964
1186
|
const item = this.#pendingActionItem;
|
|
965
1187
|
if (!item) return;
|
|
966
|
-
const actionCount =
|
|
1188
|
+
const actionCount = this.#getActionCount(item.model);
|
|
967
1189
|
if (matchesKey(keyData, "up")) {
|
|
968
1190
|
this.#selectedActionIndex = this.#selectedActionIndex === 0 ? actionCount - 1 : this.#selectedActionIndex - 1;
|
|
969
1191
|
this.#updateList();
|
|
@@ -976,21 +1198,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
976
1198
|
}
|
|
977
1199
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
978
1200
|
this.#pendingActionItem = undefined;
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
this.#onSelectCallback({
|
|
982
|
-
kind: "profile",
|
|
983
|
-
profileName: profile.name,
|
|
984
|
-
setDefault: this.#selectedActionIndex === 1,
|
|
985
|
-
});
|
|
986
|
-
} else {
|
|
987
|
-
const role = GJC_MODEL_ASSIGNMENT_TARGET_IDS[this.#selectedActionIndex];
|
|
988
|
-
if (role) {
|
|
989
|
-
this.#handleSelect(item, role);
|
|
990
|
-
} else {
|
|
991
|
-
this.#handlePresetSelect(item, OPENAI_CODE_PROFILE_PRESET);
|
|
992
|
-
}
|
|
993
|
-
}
|
|
1201
|
+
const role = GJC_MODEL_ASSIGNMENT_TARGET_IDS[this.#selectedActionIndex];
|
|
1202
|
+
if (role) this.#handleSelect(item, role);
|
|
994
1203
|
return;
|
|
995
1204
|
}
|
|
996
1205
|
if (getKeybindings().matches(keyData, "tui.select.cancel")) {
|
|
@@ -1029,18 +1238,6 @@ export class ModelSelectorComponent extends Container {
|
|
|
1029
1238
|
this.#updateList();
|
|
1030
1239
|
}
|
|
1031
1240
|
}
|
|
1032
|
-
#handlePresetSelect(item: ModelItem | CanonicalModelItem, preset: ModelAssignmentPreset): void {
|
|
1033
|
-
const selectorValue = item.selector;
|
|
1034
|
-
const assignments = resolvePresetAssignments(item.model, preset);
|
|
1035
|
-
for (const [role, thinkingLevel] of Object.entries(assignments) as [
|
|
1036
|
-
GjcModelAssignmentTargetId,
|
|
1037
|
-
ThinkingLevel,
|
|
1038
|
-
][]) {
|
|
1039
|
-
this.#roles[role] = { model: item.model, thinkingLevel };
|
|
1040
|
-
}
|
|
1041
|
-
this.#onSelectCallback({ kind: "preset", model: item.model, selector: selectorValue, preset, assignments });
|
|
1042
|
-
this.#updateList();
|
|
1043
|
-
}
|
|
1044
1241
|
|
|
1045
1242
|
#handleSelect(
|
|
1046
1243
|
item: ModelItem | CanonicalModelItem,
|
|
@@ -1100,31 +1297,6 @@ function requiresExplicitThinkingChoice(model: Model): boolean {
|
|
|
1100
1297
|
return model.reasoning === true && (model.provider === "openai" || model.provider === "openai-codex");
|
|
1101
1298
|
}
|
|
1102
1299
|
|
|
1103
|
-
function supportsOpenAICodexPreset(model: Model): boolean {
|
|
1104
|
-
return model.provider === "openai-codex" && model.reasoning === true;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
function resolvePresetAssignments(
|
|
1108
|
-
model: Model,
|
|
1109
|
-
preset: ModelAssignmentPreset,
|
|
1110
|
-
): Record<GjcModelAssignmentTargetId, ThinkingLevel> {
|
|
1111
|
-
const resolved = {} as Record<GjcModelAssignmentTargetId, ThinkingLevel>;
|
|
1112
|
-
for (const [role, requestedLevel] of Object.entries(preset.assignments) as [
|
|
1113
|
-
GjcModelAssignmentTargetId,
|
|
1114
|
-
ThinkingLevel,
|
|
1115
|
-
][]) {
|
|
1116
|
-
const clampedLevel =
|
|
1117
|
-
requestedLevel === ThinkingLevel.Off || requestedLevel === ThinkingLevel.Inherit
|
|
1118
|
-
? requestedLevel
|
|
1119
|
-
: clampThinkingLevelForModel(model, requestedLevel);
|
|
1120
|
-
if (!clampedLevel) {
|
|
1121
|
-
throw new Error(`Model ${model.provider}/${model.id} does not support ${requestedLevel} reasoning`);
|
|
1122
|
-
}
|
|
1123
|
-
resolved[role] = clampedLevel;
|
|
1124
|
-
}
|
|
1125
|
-
return resolved;
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
1300
|
function getSelectableThinkingLevels(model: Model): ThinkingLevel[] {
|
|
1129
1301
|
const levels: ThinkingLevel[] = [ThinkingLevel.Off];
|
|
1130
1302
|
let efforts: readonly string[];
|