@gajae-code/coding-agent 0.12.21 → 0.13.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 +67 -2
- package/dist/types/cli/args.d.ts +5 -2
- package/dist/types/cli/root-flags.d.ts +246 -0
- package/dist/types/cli.d.ts +11 -19
- package/dist/types/commands/daemon.d.ts +0 -30
- package/dist/types/commands/launch.d.ts +11 -19
- package/dist/types/commands/quick-lane.d.ts +32 -0
- package/dist/types/commands/sdk.d.ts +21 -15
- package/dist/types/config/file-lock-gc.d.ts +2 -0
- package/dist/types/config/model-equivalence.d.ts +20 -0
- package/dist/types/config/model-profile-activation.d.ts +62 -17
- package/dist/types/config/model-profiles.d.ts +10 -0
- package/dist/types/config/model-registry.d.ts +68 -1
- package/dist/types/config/model-resolver.d.ts +15 -4
- package/dist/types/config/models-config-schema.d.ts +7 -2
- package/dist/types/config/provider-selection-policy.d.ts +62 -0
- package/dist/types/config/settings-schema.d.ts +36 -78
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/edit/renderer.d.ts +5 -2
- package/dist/types/edit/streaming.d.ts +13 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
- package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
- package/dist/types/internal-urls/local-protocol.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +0 -2
- package/dist/types/modes/components/provider-order-context.d.ts +45 -0
- package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
- package/dist/types/modes/components/settings-defs.d.ts +5 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/quick-lane/classify.d.ts +40 -0
- package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
- package/dist/types/sdk/broker/broker.d.ts +56 -2
- package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
- package/dist/types/sdk/broker/session-index.d.ts +83 -2
- package/dist/types/sdk/broker/state-version.d.ts +1 -1
- package/dist/types/sdk/bus/notification-service.d.ts +6 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
- package/dist/types/sdk/cli/rows.d.ts +98 -0
- package/dist/types/sdk/cli/session-cli.d.ts +35 -3
- package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
- package/dist/types/sdk/elevation/audit.d.ts +93 -0
- package/dist/types/sdk/elevation/capability.d.ts +3 -0
- package/dist/types/sdk/elevation/digest.d.ts +72 -0
- package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
- package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
- package/dist/types/sdk/elevation/owner.d.ts +48 -0
- package/dist/types/sdk/guides/cache.d.ts +98 -0
- package/dist/types/sdk/guides/catalog.d.ts +156 -0
- package/dist/types/sdk/guides/cli.d.ts +39 -0
- package/dist/types/sdk/guides/manifest.d.ts +66 -0
- package/dist/types/sdk/guides/verify.d.ts +66 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +3 -2
- package/dist/types/sdk/host/query/cursor.d.ts +10 -0
- package/dist/types/sdk/host/query/handlers.d.ts +21 -0
- package/dist/types/sdk/host/session-runtime.d.ts +9 -0
- package/dist/types/sdk/mcp/server.d.ts +2 -2
- package/dist/types/sdk/prompt-status.d.ts +11 -0
- package/dist/types/sdk/session.d.ts +4 -0
- package/dist/types/sdk/transport/index.d.ts +1 -0
- package/dist/types/sdk/transport/relay.d.ts +1 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
- package/dist/types/session/agent-session.d.ts +32 -18
- package/dist/types/session/blob-store.d.ts +13 -7
- package/dist/types/session/fallback-chain-controller.d.ts +15 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
- package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
- package/dist/types/session/session-manager.d.ts +139 -10
- package/dist/types/session/session-storage.d.ts +48 -0
- package/dist/types/setup/provider-onboarding.d.ts +7 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
- package/dist/types/stt/setup.d.ts +2 -0
- package/dist/types/task/executor.d.ts +10 -2
- package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker.d.ts +2 -0
- package/dist/types/tools/browser.d.ts +2 -0
- package/dist/types/tools/image-gen.d.ts +14 -18
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resource-gc.d.ts +2 -0
- package/dist/types/utils/pasted-image-path.d.ts +6 -0
- package/package.json +9 -9
- package/scripts/generate-sdk-operation-inventory.ts +8 -0
- package/scripts/run-test-manifest.ts +61 -21
- package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
- package/src/cli/args.ts +62 -9
- package/src/cli/completion-cli.ts +1 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/root-flags.ts +100 -0
- package/src/cli/setup-cli.ts +6 -2
- package/src/cli.ts +42 -65
- package/src/commands/acp.ts +1 -1
- package/src/commands/daemon.ts +2 -31
- package/src/commands/gc.ts +1 -1
- package/src/commands/launch.ts +8 -129
- package/src/commands/quick-lane.ts +51 -0
- package/src/commands/sdk.ts +200 -26
- package/src/commands/setup.ts +4 -2
- package/src/config/file-lock-gc.ts +4 -1
- package/src/config/model-equivalence.ts +76 -7
- package/src/config/model-profile-activation.ts +1115 -265
- package/src/config/model-profiles.ts +134 -2
- package/src/config/model-registry.ts +352 -50
- package/src/config/model-resolver.ts +289 -38
- package/src/config/models-config-schema.ts +11 -4
- package/src/config/provider-selection-policy.ts +146 -0
- package/src/config/settings-schema.ts +50 -77
- package/src/config/settings.ts +40 -0
- package/src/coordinator-mcp/server.ts +32 -2
- package/src/edit/renderer.ts +175 -223
- package/src/edit/streaming.ts +108 -1
- package/src/export/html/index.ts +57 -7
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/extensions/compact-handler.ts +3 -2
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/gjc-runtime/gc-runtime.ts +379 -20
- package/src/gjc-runtime/launch-worktree.ts +5 -1
- package/src/gjc-runtime/team-runtime.ts +59 -2
- package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
- package/src/internal-urls/docs-index.generated.ts +17 -13
- package/src/internal-urls/local-protocol.ts +1 -0
- package/src/main.ts +40 -3
- package/src/memories/index.ts +4 -2
- package/src/modes/acp/acp-agent.ts +235 -91
- package/src/modes/components/model-selector.ts +206 -42
- package/src/modes/components/provider-order-context.ts +197 -0
- package/src/modes/components/provider-order-editor.ts +441 -0
- package/src/modes/components/settings-defs.ts +21 -1
- package/src/modes/components/settings-selector.ts +45 -2
- package/src/modes/components/tool-execution.ts +47 -15
- package/src/modes/controllers/command-controller.ts +6 -3
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/input-controller.ts +73 -2
- package/src/modes/controllers/selector-controller.ts +260 -153
- package/src/modes/prompt-suggestion-controller.ts +1 -1
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +18 -0
- package/src/prompts/tools/browser.md +1 -0
- package/src/quick-lane/classify.ts +257 -0
- package/src/rlm/index.ts +1 -1
- package/src/runtime/stt-settings-setup.ts +57 -0
- package/src/sdk/acp/adapter.ts +2 -1
- package/src/sdk/broker/broker.ts +780 -12
- package/src/sdk/broker/lifecycle.ts +70 -29
- package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
- package/src/sdk/broker/session-index.ts +463 -48
- package/src/sdk/broker/state-version.ts +13 -9
- package/src/sdk/broker/transport.ts +14 -1
- package/src/sdk/bus/index.ts +34 -13
- package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
- package/src/sdk/bus/notification-service.ts +47 -1
- package/src/sdk/bus/prompt-reconciliation.ts +22 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
- package/src/sdk/bus/telegram-daemon.ts +54 -3
- package/src/sdk/cli/rows.ts +247 -0
- package/src/sdk/cli/session-cli.ts +930 -79
- package/src/sdk/elevation/allowlist.ts +54 -0
- package/src/sdk/elevation/audit.ts +208 -0
- package/src/sdk/elevation/capability.ts +41 -0
- package/src/sdk/elevation/digest.ts +129 -0
- package/src/sdk/elevation/dispatch-receipt.ts +184 -0
- package/src/sdk/elevation/grant-ledger.ts +1142 -0
- package/src/sdk/elevation/owner.ts +109 -0
- package/src/sdk/guides/cache.ts +363 -0
- package/src/sdk/guides/catalog.ts +554 -0
- package/src/sdk/guides/cli.ts +235 -0
- package/src/sdk/guides/manifest.ts +152 -0
- package/src/sdk/guides/verify.ts +178 -0
- package/src/sdk/host/control/dispatch.ts +49 -1
- package/src/sdk/host/control/index.ts +1 -0
- package/src/sdk/host/control/operations.ts +8 -1
- package/src/sdk/host/query/cursor.ts +47 -3
- package/src/sdk/host/query/handlers.ts +155 -2
- package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +391 -1
- package/src/sdk/host/session-runtime.ts +308 -73
- package/src/sdk/host/surface-policy.ts +3 -0
- package/src/sdk/host/websocket-transport.ts +7 -1
- package/src/sdk/mcp/server.ts +88 -15
- package/src/sdk/prompt-status.ts +8 -0
- package/src/sdk/protocol/operation-inventory.generated.json +62 -0
- package/src/sdk/protocol/operation-registry.ts +3 -2
- package/src/sdk/session.ts +87 -39
- package/src/sdk/transport/index.ts +1 -0
- package/src/sdk/transport/relay.ts +3 -0
- package/src/sdk/transport/serve-cli.ts +111 -24
- package/src/secrets/obfuscator.ts +92 -12
- package/src/session/agent-session.ts +322 -93
- package/src/session/blob-store.ts +60 -8
- package/src/session/fallback-chain-controller.ts +35 -0
- package/src/session/internal/managed-session-scope.ts +25 -3
- package/src/session/internal/managed-session-storage.ts +574 -73
- package/src/session/internal/session-memory-sidecar.ts +19 -29
- package/src/session/session-manager.ts +2166 -743
- package/src/session/session-storage.ts +715 -48
- package/src/setup/provider-onboarding.ts +19 -3
- package/src/setup/provider-presets.json +3 -1
- package/src/slash-commands/builtin-registry.ts +85 -3
- package/src/stt/downloader.ts +2 -3
- package/src/stt/setup.ts +29 -4
- package/src/task/executor.ts +22 -18
- package/src/task/index.ts +24 -1
- package/src/tools/archive-reader.ts +11 -2
- package/src/tools/browser/runtime-diagnostics.ts +249 -0
- package/src/tools/browser/tab-protocol.ts +4 -0
- package/src/tools/browser/tab-supervisor.ts +5 -0
- package/src/tools/browser/tab-worker.ts +36 -1
- package/src/tools/browser.ts +7 -0
- package/src/tools/image-gen.ts +110 -166
- package/src/tools/index.ts +2 -0
- package/src/tools/read.ts +1 -1
- package/src/tools/resource-gc.ts +6 -1
- package/src/tools/tool-catalog.generated.ts +5 -1
- package/src/utils/pasted-image-path.ts +93 -0
- package/src/web/search/index.ts +3 -3
|
@@ -14,20 +14,24 @@ import {
|
|
|
14
14
|
truncateToWidth,
|
|
15
15
|
} from "@gajae-code/tui";
|
|
16
16
|
import { sanitizeText } from "@gajae-code/utils";
|
|
17
|
+
import { isModelProfileProviderAvailable } from "../../config/model-profile-contract";
|
|
17
18
|
import {
|
|
18
19
|
getModelProfilePresentation,
|
|
19
20
|
groupModelProfilesForPresetLanding,
|
|
20
21
|
type ModelProfileDefinition,
|
|
22
|
+
resolveProfileBindings,
|
|
21
23
|
} from "../../config/model-profiles";
|
|
22
24
|
import type { GjcModelAssignmentTargetId, ModelRegistry } from "../../config/model-registry";
|
|
23
25
|
import {
|
|
24
26
|
GJC_MODEL_ASSIGNMENT_TARGET_IDS,
|
|
25
27
|
GJC_MODEL_ASSIGNMENT_TARGETS,
|
|
26
28
|
isAuthenticated,
|
|
29
|
+
kNoAuth,
|
|
27
30
|
} from "../../config/model-registry";
|
|
28
31
|
import {
|
|
29
32
|
formatModelSelectorValue,
|
|
30
33
|
resolveConfiguredModelPatterns,
|
|
34
|
+
resolveModelChainWithAuth,
|
|
31
35
|
resolveModelRoleValue,
|
|
32
36
|
type ScopedModelSelection,
|
|
33
37
|
} from "../../config/model-resolver";
|
|
@@ -39,7 +43,6 @@ import type { Settings } from "../../config/settings";
|
|
|
39
43
|
import { type ThemeColor, theme } from "../../modes/theme/theme";
|
|
40
44
|
import { formatModelOnboardingInlineHint } from "../../setup/model-onboarding-guidance";
|
|
41
45
|
import { formatClampedModelSelector, getThinkingLevelMetadata, parseThinkingLevel } from "../../thinking";
|
|
42
|
-
import { getConfiguredImageModel } from "../../tools/image-gen";
|
|
43
46
|
import { getTabBarTheme } from "../shared";
|
|
44
47
|
import { DynamicBorder } from "./dynamic-border";
|
|
45
48
|
|
|
@@ -143,9 +146,6 @@ export type ModelSelectorSelection =
|
|
|
143
146
|
| {
|
|
144
147
|
kind: "deleteProfile";
|
|
145
148
|
profileName: string;
|
|
146
|
-
}
|
|
147
|
-
| {
|
|
148
|
-
kind: "imageGeneration";
|
|
149
149
|
};
|
|
150
150
|
|
|
151
151
|
interface PendingThinkingChoice {
|
|
@@ -211,8 +211,8 @@ interface PresetBrowseRow {
|
|
|
211
211
|
kind: "browse";
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
interface
|
|
215
|
-
kind: "
|
|
214
|
+
interface PresetImageRoleRow {
|
|
215
|
+
kind: "imageRole";
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
type PresetLandingRow =
|
|
@@ -222,7 +222,7 @@ type PresetLandingRow =
|
|
|
222
222
|
| PresetCreateUnavailableRow
|
|
223
223
|
| PresetAlreadySavedRow
|
|
224
224
|
| PresetBrowseRow
|
|
225
|
-
|
|
|
225
|
+
| PresetImageRoleRow;
|
|
226
226
|
|
|
227
227
|
// Stable logical identity for a preset landing row, independent of its current
|
|
228
228
|
// list position. Used to relocate the cursor after the expanded group changes so
|
|
@@ -241,8 +241,8 @@ function presetRowIdentity(row: PresetLandingRow): string {
|
|
|
241
241
|
return "createUnavailable";
|
|
242
242
|
case "alreadySaved":
|
|
243
243
|
return `alreadySaved:${row.profile.name}`;
|
|
244
|
-
case "
|
|
245
|
-
return "
|
|
244
|
+
case "imageRole":
|
|
245
|
+
return "imageRole";
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -383,9 +383,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
383
383
|
#presetScopeMenuOpen: boolean = false;
|
|
384
384
|
#presetScopeIndex: number = 0;
|
|
385
385
|
#providerAuthById = new Map<string, boolean>();
|
|
386
|
+
#bareProfileAuthByName = new Map<string, boolean>();
|
|
386
387
|
#providerAuthPending: boolean = false;
|
|
387
388
|
#presetLoginHint?: string;
|
|
388
389
|
#authSessionId?: string;
|
|
390
|
+
#imageRoleFilter: boolean = false;
|
|
389
391
|
|
|
390
392
|
// Tab state
|
|
391
393
|
#providers: ProviderTabState[] = STATIC_PROVIDER_TABS;
|
|
@@ -515,6 +517,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
515
517
|
settings: this.#settings,
|
|
516
518
|
matchPreferences,
|
|
517
519
|
modelRegistry: this.#modelRegistry,
|
|
520
|
+
aliasIntent: this.#activeModelProfile ? "preset-equivalent" : undefined,
|
|
521
|
+
credentialSessionId: this.#authSessionId,
|
|
518
522
|
});
|
|
519
523
|
return resolved.model !== undefined && modelsAreEqual(resolved.model, this.#currentModel);
|
|
520
524
|
});
|
|
@@ -525,16 +529,28 @@ export class ModelSelectorComponent extends Container {
|
|
|
525
529
|
const allModels = this.#modelRegistry.getAll();
|
|
526
530
|
const matchPreferences = { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() };
|
|
527
531
|
const agentModelOverrides = this.#settings.get("task.agentModelOverrides");
|
|
532
|
+
const activeProfile = this.#activeModelProfile
|
|
533
|
+
? this.#modelRegistry.getModelProfile(this.#activeModelProfile)
|
|
534
|
+
: undefined;
|
|
535
|
+
const activeProfileBindings = activeProfile ? resolveProfileBindings(activeProfile) : undefined;
|
|
528
536
|
for (const role of GJC_MODEL_ASSIGNMENT_TARGET_IDS) {
|
|
529
537
|
const target = GJC_MODEL_ASSIGNMENT_TARGETS[role];
|
|
530
538
|
const roleValue =
|
|
531
539
|
target.settingsPath === "modelRoles" ? this.#settings.getModelRole(role) : agentModelOverrides[role];
|
|
532
540
|
if (!roleValue) continue;
|
|
541
|
+
const profileOwnsRole =
|
|
542
|
+
role === "default"
|
|
543
|
+
? activeProfileBindings?.defaultSelector !== undefined
|
|
544
|
+
: target.settingsPath === "modelRoles"
|
|
545
|
+
? Object.hasOwn(activeProfileBindings?.modelRoles ?? {}, role)
|
|
546
|
+
: Object.hasOwn(activeProfileBindings?.agentModelOverrides ?? {}, role);
|
|
533
547
|
|
|
534
548
|
const resolved = resolveModelRoleValue(roleValue, allModels, {
|
|
535
549
|
settings: this.#settings,
|
|
536
550
|
matchPreferences,
|
|
537
551
|
modelRegistry: this.#modelRegistry,
|
|
552
|
+
aliasIntent: profileOwnsRole ? "preset-equivalent" : undefined,
|
|
553
|
+
credentialSessionId: this.#authSessionId,
|
|
538
554
|
});
|
|
539
555
|
if (resolved.model) {
|
|
540
556
|
roles[role] = {
|
|
@@ -1012,8 +1028,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
1012
1028
|
// Start with all models or filter by provider/canonical view
|
|
1013
1029
|
let baseModels = this.#allModels;
|
|
1014
1030
|
const baseCanonicalModels = this.#canonicalModels;
|
|
1031
|
+
if (this.#imageRoleFilter) {
|
|
1032
|
+
baseModels = baseModels.filter(m => m.model.output?.includes("image") ?? false);
|
|
1033
|
+
}
|
|
1015
1034
|
if (activeProviderId) {
|
|
1016
|
-
baseModels =
|
|
1035
|
+
baseModels = baseModels.filter(m => m.provider === activeProviderId);
|
|
1017
1036
|
}
|
|
1018
1037
|
|
|
1019
1038
|
// Apply fuzzy filter if query is present
|
|
@@ -1104,6 +1123,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
1104
1123
|
settings: this.#settings,
|
|
1105
1124
|
matchPreferences: { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() },
|
|
1106
1125
|
modelRegistry: this.#modelRegistry,
|
|
1126
|
+
aliasIntent: this.#activeModelProfile ? "preset-equivalent" : undefined,
|
|
1127
|
+
credentialSessionId: this.#authSessionId,
|
|
1107
1128
|
});
|
|
1108
1129
|
if (resolved.model) {
|
|
1109
1130
|
resolvedSelectors.push(
|
|
@@ -1187,7 +1208,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1187
1208
|
} else {
|
|
1188
1209
|
rows.push({ kind: "createUnavailable", label: "Select a model before creating a custom preset" });
|
|
1189
1210
|
}
|
|
1190
|
-
rows.push({ kind: "
|
|
1211
|
+
rows.push({ kind: "imageRole" });
|
|
1191
1212
|
rows.push({ kind: "browse" });
|
|
1192
1213
|
return rows;
|
|
1193
1214
|
}
|
|
@@ -1207,15 +1228,74 @@ export class ModelSelectorComponent extends Container {
|
|
|
1207
1228
|
|
|
1208
1229
|
#getMissingProviders(profileOrProfiles: ModelProfileDefinition | ModelProfileDefinition[]): string[] {
|
|
1209
1230
|
const profiles = Array.isArray(profileOrProfiles) ? profileOrProfiles : [profileOrProfiles];
|
|
1210
|
-
const
|
|
1211
|
-
for (const profile of profiles)
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1231
|
+
const missing = new Set<string>();
|
|
1232
|
+
for (const profile of profiles) {
|
|
1233
|
+
const authenticated = new Set(
|
|
1234
|
+
profileRequiredProviders(profile).filter(provider => this.#isProviderAuthenticated(provider) === true),
|
|
1235
|
+
);
|
|
1236
|
+
if (isModelProfileProviderAvailable(profile, authenticated)) continue;
|
|
1237
|
+
const alternativeGroups = profile.alternativeProviderGroups ?? [];
|
|
1238
|
+
const alternativeProviders = new Set(alternativeGroups.flat());
|
|
1239
|
+
for (const provider of profileRequiredProviders(profile)) {
|
|
1240
|
+
if (!alternativeProviders.has(provider) && !authenticated.has(provider)) missing.add(provider);
|
|
1241
|
+
}
|
|
1242
|
+
for (const group of alternativeGroups) {
|
|
1243
|
+
if (!group.some(provider => authenticated.has(provider)))
|
|
1244
|
+
for (const provider of group) missing.add(provider);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return [...missing].sort((a, b) => a.localeCompare(b));
|
|
1215
1248
|
}
|
|
1216
1249
|
|
|
1217
1250
|
#isPresetAuthenticated(profileOrProfiles: ModelProfileDefinition | ModelProfileDefinition[]): boolean {
|
|
1218
|
-
|
|
1251
|
+
const profiles = Array.isArray(profileOrProfiles) ? profileOrProfiles : [profileOrProfiles];
|
|
1252
|
+
return profiles.every(profile => {
|
|
1253
|
+
if (this.#getMissingProviders(profile).length > 0) return false;
|
|
1254
|
+
const bindings = resolveProfileBindings(profile);
|
|
1255
|
+
const values = [
|
|
1256
|
+
...(bindings.defaultSelector ? [bindings.defaultSelector] : []),
|
|
1257
|
+
...Object.values(bindings.modelRoles),
|
|
1258
|
+
...Object.values(bindings.agentModelOverrides),
|
|
1259
|
+
];
|
|
1260
|
+
const bareAssignmentsAvailable = this.#bareProfileAuthByName.get(profile.name) ?? true;
|
|
1261
|
+
return values.every(value =>
|
|
1262
|
+
normalizeModelSelectorValue(value).some(selector => {
|
|
1263
|
+
if (!selector.includes("/")) return bareAssignmentsAvailable;
|
|
1264
|
+
const resolved = resolveModelRoleValue(selector, this.#modelRegistry.getAvailable(), {
|
|
1265
|
+
settings: this.#settings,
|
|
1266
|
+
modelRegistry: this.#modelRegistry,
|
|
1267
|
+
aliasIntent: "preset-equivalent",
|
|
1268
|
+
credentialSessionId: this.#authSessionId,
|
|
1269
|
+
}).model;
|
|
1270
|
+
if (!resolved) return false;
|
|
1271
|
+
if (this.#providerAuthById.get(resolved.provider) === true) return true;
|
|
1272
|
+
const alternativeGroup = profile.alternativeProviderGroups?.find(group =>
|
|
1273
|
+
group.includes(resolved.provider),
|
|
1274
|
+
);
|
|
1275
|
+
return (
|
|
1276
|
+
alternativeGroup?.some(provider => {
|
|
1277
|
+
if (this.#providerAuthById.get(provider) !== true) return false;
|
|
1278
|
+
const providerPrefix = `${resolved.provider}/`;
|
|
1279
|
+
const replacementSelector = selector.startsWith(providerPrefix)
|
|
1280
|
+
? `${provider}/${selector.slice(providerPrefix.length)}`
|
|
1281
|
+
: selector;
|
|
1282
|
+
return (
|
|
1283
|
+
resolveModelRoleValue(
|
|
1284
|
+
replacementSelector,
|
|
1285
|
+
this.#modelRegistry.getAvailable().filter(model => model.provider === provider),
|
|
1286
|
+
{
|
|
1287
|
+
settings: this.#settings,
|
|
1288
|
+
modelRegistry: this.#modelRegistry,
|
|
1289
|
+
aliasIntent: "preset-equivalent",
|
|
1290
|
+
credentialSessionId: this.#authSessionId,
|
|
1291
|
+
},
|
|
1292
|
+
).model !== undefined
|
|
1293
|
+
);
|
|
1294
|
+
}) ?? false
|
|
1295
|
+
);
|
|
1296
|
+
}),
|
|
1297
|
+
);
|
|
1298
|
+
});
|
|
1219
1299
|
}
|
|
1220
1300
|
|
|
1221
1301
|
/**
|
|
@@ -1230,21 +1310,82 @@ export class ModelSelectorComponent extends Container {
|
|
|
1230
1310
|
async #refreshProviderAuth(): Promise<void> {
|
|
1231
1311
|
const providers = new Set<string>();
|
|
1232
1312
|
for (const profiles of this.#getPresetGroups().values()) {
|
|
1233
|
-
for (const profile of profiles)
|
|
1313
|
+
for (const profile of profiles) {
|
|
1234
1314
|
for (const provider of profileRequiredProviders(profile)) providers.add(provider);
|
|
1315
|
+
const bindings = resolveProfileBindings(profile);
|
|
1316
|
+
const values = [
|
|
1317
|
+
...(bindings.defaultSelector ? [bindings.defaultSelector] : []),
|
|
1318
|
+
...Object.values(bindings.modelRoles),
|
|
1319
|
+
...Object.values(bindings.agentModelOverrides),
|
|
1320
|
+
];
|
|
1321
|
+
for (const value of values) {
|
|
1322
|
+
for (const selector of normalizeModelSelectorValue(value)) {
|
|
1323
|
+
const resolved = resolveModelRoleValue(selector, this.#modelRegistry.getAvailable(), {
|
|
1324
|
+
settings: this.#settings,
|
|
1325
|
+
modelRegistry: this.#modelRegistry,
|
|
1326
|
+
aliasIntent: "preset-equivalent",
|
|
1327
|
+
credentialSessionId: this.#authSessionId,
|
|
1328
|
+
}).model;
|
|
1329
|
+
if (resolved) providers.add(resolved.provider);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1235
1333
|
}
|
|
1236
1334
|
this.#providerAuthPending = providers.size > 0;
|
|
1237
1335
|
this.#renderPresetLanding();
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1336
|
+
try {
|
|
1337
|
+
const entries = await Promise.all(
|
|
1338
|
+
[...providers].map(async provider => {
|
|
1339
|
+
try {
|
|
1340
|
+
const apiKey = await this.#modelRegistry.getApiKeyForProvider(provider, this.#authSessionId);
|
|
1341
|
+
return [provider, apiKey === kNoAuth || isAuthenticated(apiKey)] as const;
|
|
1342
|
+
} catch {
|
|
1343
|
+
return [provider, false] as const;
|
|
1344
|
+
}
|
|
1345
|
+
}),
|
|
1346
|
+
);
|
|
1347
|
+
this.#providerAuthById = new Map(entries);
|
|
1348
|
+
const profileAuthEntries = await Promise.all(
|
|
1349
|
+
[...this.#getPresetGroups().values()].flat().map(async profile => {
|
|
1350
|
+
const bindings = resolveProfileBindings(profile);
|
|
1351
|
+
const values = [
|
|
1352
|
+
...(bindings.defaultSelector ? [bindings.defaultSelector] : []),
|
|
1353
|
+
...Object.values(bindings.modelRoles),
|
|
1354
|
+
...Object.values(bindings.agentModelOverrides),
|
|
1355
|
+
];
|
|
1356
|
+
const bareValues = values.filter(value =>
|
|
1357
|
+
normalizeModelSelectorValue(value).some(selector => !selector.includes("/")),
|
|
1358
|
+
);
|
|
1359
|
+
const available = await Promise.all(
|
|
1360
|
+
bareValues.map(async value => {
|
|
1361
|
+
try {
|
|
1362
|
+
const resolution = await resolveModelChainWithAuth(
|
|
1363
|
+
normalizeModelSelectorValue(value),
|
|
1364
|
+
this.#modelRegistry,
|
|
1365
|
+
this.#settings,
|
|
1366
|
+
this.#authSessionId,
|
|
1367
|
+
{
|
|
1368
|
+
managedFallback: true,
|
|
1369
|
+
aliasIntent: "preset-equivalent",
|
|
1370
|
+
canonicalSessionId: null,
|
|
1371
|
+
credentialSessionId: this.#authSessionId,
|
|
1372
|
+
},
|
|
1373
|
+
);
|
|
1374
|
+
return resolution.model !== undefined;
|
|
1375
|
+
} catch {
|
|
1376
|
+
return false;
|
|
1377
|
+
}
|
|
1378
|
+
}),
|
|
1379
|
+
);
|
|
1380
|
+
return [profile.name, available.every(Boolean)] as const;
|
|
1381
|
+
}),
|
|
1382
|
+
);
|
|
1383
|
+
this.#bareProfileAuthByName = new Map(profileAuthEntries);
|
|
1384
|
+
} finally {
|
|
1385
|
+
this.#providerAuthPending = false;
|
|
1386
|
+
this.#renderPresetLanding();
|
|
1387
|
+
this.#tui.requestRender();
|
|
1388
|
+
}
|
|
1248
1389
|
}
|
|
1249
1390
|
|
|
1250
1391
|
#clampPresetCursor(): void {
|
|
@@ -1274,7 +1415,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1274
1415
|
selected.kind === "create" ||
|
|
1275
1416
|
selected.kind === "createUnavailable" ||
|
|
1276
1417
|
selected.kind === "alreadySaved" ||
|
|
1277
|
-
selected.kind === "
|
|
1418
|
+
selected.kind === "imageRole"
|
|
1278
1419
|
)
|
|
1279
1420
|
return;
|
|
1280
1421
|
if (this.#expandedPresetProviderId === selected.groupId) return;
|
|
@@ -1291,7 +1432,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1291
1432
|
selected.kind === "create" ||
|
|
1292
1433
|
selected.kind === "createUnavailable" ||
|
|
1293
1434
|
selected.kind === "alreadySaved" ||
|
|
1294
|
-
selected.kind === "
|
|
1435
|
+
selected.kind === "imageRole"
|
|
1295
1436
|
)
|
|
1296
1437
|
return;
|
|
1297
1438
|
if (this.#expandedPresetProviderId !== selected.groupId) return;
|
|
@@ -1304,7 +1445,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1304
1445
|
this.#searchInput.setValue(value);
|
|
1305
1446
|
}
|
|
1306
1447
|
|
|
1307
|
-
#switchToModelMode(seed?: string): void {
|
|
1448
|
+
#switchToModelMode(seed?: string, options?: { imageRoleFilter?: boolean }): void {
|
|
1308
1449
|
this.#viewMode = "models";
|
|
1309
1450
|
this.#expandedPresetProviderId = undefined;
|
|
1310
1451
|
this.#previewProfileName = undefined;
|
|
@@ -1313,6 +1454,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1313
1454
|
this.#presetLoginHint = undefined;
|
|
1314
1455
|
this.#activeTabIndex = 0;
|
|
1315
1456
|
this.#selectedIndex = 0;
|
|
1457
|
+
this.#imageRoleFilter = options?.imageRoleFilter ?? false;
|
|
1316
1458
|
this.#setSearchInputValue(seed ?? this.#searchInput.getValue());
|
|
1317
1459
|
this.#updateTabBar();
|
|
1318
1460
|
this.#filterModels(this.#searchInput.getValue());
|
|
@@ -1391,13 +1533,14 @@ export class ModelSelectorComponent extends Container {
|
|
|
1391
1533
|
this.#listContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
|
|
1392
1534
|
continue;
|
|
1393
1535
|
}
|
|
1394
|
-
if (row.kind === "
|
|
1395
|
-
const
|
|
1396
|
-
const current =
|
|
1397
|
-
|
|
1398
|
-
?
|
|
1399
|
-
:
|
|
1400
|
-
|
|
1536
|
+
if (row.kind === "imageRole") {
|
|
1537
|
+
const imageRoleValue = this.#settings.getModelRole("image");
|
|
1538
|
+
const current = imageRoleValue
|
|
1539
|
+
? Array.isArray(imageRoleValue)
|
|
1540
|
+
? imageRoleValue[0]
|
|
1541
|
+
: imageRoleValue
|
|
1542
|
+
: "No image model set";
|
|
1543
|
+
const label = `Image Role: ${current}`;
|
|
1401
1544
|
this.#listContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
|
|
1402
1545
|
continue;
|
|
1403
1546
|
}
|
|
@@ -1442,6 +1585,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
1442
1585
|
settings: this.#settings,
|
|
1443
1586
|
matchPreferences: { usageOrder: this.#settings.getStorage()?.getModelUsageOrder() },
|
|
1444
1587
|
modelRegistry: this.#modelRegistry,
|
|
1588
|
+
aliasIntent: "preset-equivalent",
|
|
1589
|
+
credentialSessionId: this.#authSessionId,
|
|
1445
1590
|
});
|
|
1446
1591
|
const label = GJC_MODEL_ASSIGNMENT_TARGETS[role].tag ?? role.toUpperCase();
|
|
1447
1592
|
this.#listContainer.addChild(
|
|
@@ -1920,6 +2065,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
1920
2065
|
this.#renderPresetLanding();
|
|
1921
2066
|
return;
|
|
1922
2067
|
}
|
|
2068
|
+
if (!this.#isPresetAuthenticated(profile)) {
|
|
2069
|
+
this.#presetLoginHint = "No available model matches this preset";
|
|
2070
|
+
this.#renderPresetLanding();
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
1923
2073
|
this.#onSelectCallback({
|
|
1924
2074
|
kind: "profile",
|
|
1925
2075
|
profileName: this.#previewProfileName,
|
|
@@ -1946,8 +2096,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
1946
2096
|
this.#switchToModelMode();
|
|
1947
2097
|
return;
|
|
1948
2098
|
}
|
|
1949
|
-
if (row.kind === "
|
|
1950
|
-
this.#
|
|
2099
|
+
if (row.kind === "imageRole") {
|
|
2100
|
+
this.#switchToModelMode(undefined, { imageRoleFilter: true });
|
|
1951
2101
|
return;
|
|
1952
2102
|
}
|
|
1953
2103
|
if (row.kind === "group") {
|
|
@@ -1956,7 +2106,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
1956
2106
|
// navigable so the user can drill in and pick a usable member.
|
|
1957
2107
|
if (!this.#isPresetGroupUsable(row.profiles)) {
|
|
1958
2108
|
const missing = this.#getMissingProviders(row.profiles);
|
|
1959
|
-
this.#presetLoginHint =
|
|
2109
|
+
this.#presetLoginHint =
|
|
2110
|
+
missing.length > 0
|
|
2111
|
+
? `Run ${missing.map(provider => `/login ${provider}`).join(", ")}`
|
|
2112
|
+
: "No available model matches this preset";
|
|
1960
2113
|
this.#renderPresetLanding();
|
|
1961
2114
|
return;
|
|
1962
2115
|
}
|
|
@@ -1977,6 +2130,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
1977
2130
|
this.#renderPresetLanding();
|
|
1978
2131
|
return;
|
|
1979
2132
|
}
|
|
2133
|
+
if (!isCustomUserProfile(row.profile) && !this.#isPresetAuthenticated(row.profile)) {
|
|
2134
|
+
this.#presetLoginHint = "No available model matches this preset";
|
|
2135
|
+
this.#renderPresetLanding();
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
1980
2138
|
this.#previewProfileName = row.profile.name;
|
|
1981
2139
|
this.#presetLoginHint = undefined;
|
|
1982
2140
|
this.#renderPresetLanding();
|
|
@@ -2173,7 +2331,13 @@ export class ModelSelectorComponent extends Container {
|
|
|
2173
2331
|
return;
|
|
2174
2332
|
}
|
|
2175
2333
|
|
|
2176
|
-
const
|
|
2334
|
+
const currentThinkingLevel = this.#getCurrentRoleThinkingLevel(role);
|
|
2335
|
+
const selectedThinkingLevel =
|
|
2336
|
+
itemThinkingLevel ??
|
|
2337
|
+
(currentThinkingLevel === ThinkingLevel.Inherit ||
|
|
2338
|
+
getSelectableThinkingLevels(item.model).includes(currentThinkingLevel)
|
|
2339
|
+
? currentThinkingLevel
|
|
2340
|
+
: ThinkingLevel.Inherit);
|
|
2177
2341
|
const selectorValue = roles
|
|
2178
2342
|
? formatModelSelectorValue(item.selector, selectedThinkingLevel)
|
|
2179
2343
|
: role === "default"
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live context for the Provider Priority Order editor (`modelProviderOrder`).
|
|
3
|
+
*
|
|
4
|
+
* Owns the subscriptions that keep the editor current: `modelProviderOrder`
|
|
5
|
+
* settings changes and credential-generation changes on the session auth
|
|
6
|
+
* storage. Snapshots are built on demand from the model registry catalog, so
|
|
7
|
+
* the editor needs no catalog event subscription.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ModelRegistry } from "../../config/model-registry";
|
|
11
|
+
import { getProviderAuthHealth } from "../../config/provider-auth-health";
|
|
12
|
+
import type { Settings } from "../../config/settings";
|
|
13
|
+
import type { AuthStorage } from "../../session/auth-storage";
|
|
14
|
+
|
|
15
|
+
/** Normalize a persisted provider id the same way provider selection does: trimmed, lowercased. */
|
|
16
|
+
export function normalizeProviderId(raw: string): string {
|
|
17
|
+
return raw.trim().toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Normalize a persisted provider order the same way provider selection does: trimmed, lowercased, deduplicated, blanks and non-strings dropped. */
|
|
21
|
+
export function normalizeProviderOrder(order: readonly unknown[]): string[] {
|
|
22
|
+
const normalized: string[] = [];
|
|
23
|
+
const seen = new Set<string>();
|
|
24
|
+
for (const raw of order) {
|
|
25
|
+
if (typeof raw !== "string") continue;
|
|
26
|
+
const id = normalizeProviderId(raw);
|
|
27
|
+
if (!id || seen.has(id)) continue;
|
|
28
|
+
seen.add(id);
|
|
29
|
+
normalized.push(id);
|
|
30
|
+
}
|
|
31
|
+
return normalized;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Display label for a provider id, mirroring the model selector tab presentation. */
|
|
35
|
+
export function formatProviderOrderLabel(providerId: string): string {
|
|
36
|
+
return providerId.replace(/[-_]+/g, " ").toUpperCase();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ProviderOrderEntry {
|
|
40
|
+
/** Normalized provider id (trimmed, lowercased). */
|
|
41
|
+
id: string;
|
|
42
|
+
/** Display label for the provider. */
|
|
43
|
+
label: string;
|
|
44
|
+
/** Whether the provider is present in the model registry catalog right now. */
|
|
45
|
+
available: boolean;
|
|
46
|
+
/** Whether the provider has usable auth for this session. */
|
|
47
|
+
authenticated: boolean;
|
|
48
|
+
/** Whether the provider is currently persisted in `modelProviderOrder`. */
|
|
49
|
+
inOrder: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ProviderOrderSnapshot {
|
|
53
|
+
/** Persisted order (normalized, deduplicated). Unavailable entries are retained. */
|
|
54
|
+
order: string[];
|
|
55
|
+
/** Union of registry catalog provider ids and persisted ids. */
|
|
56
|
+
entries: ProviderOrderEntry[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class ProviderOrderContext {
|
|
60
|
+
#registry: ModelRegistry;
|
|
61
|
+
#settings: Settings;
|
|
62
|
+
#authStorage: AuthStorage;
|
|
63
|
+
#onChange: (() => void) | undefined;
|
|
64
|
+
#credentialSessionId: string | undefined;
|
|
65
|
+
#disposed = false;
|
|
66
|
+
#unsubscribeSettings: () => void;
|
|
67
|
+
#unsubscribeAuthGeneration: () => void;
|
|
68
|
+
|
|
69
|
+
constructor(registry: ModelRegistry, settings: Settings, onChange?: () => void, credentialSessionId?: string) {
|
|
70
|
+
this.#registry = registry;
|
|
71
|
+
this.#settings = settings;
|
|
72
|
+
this.#authStorage = registry.authStorage;
|
|
73
|
+
this.#onChange = onChange;
|
|
74
|
+
this.#credentialSessionId = credentialSessionId;
|
|
75
|
+
this.#unsubscribeSettings = settings.onChanged(path => {
|
|
76
|
+
// Only `modelProviderOrder` mutations matter to this editor; the
|
|
77
|
+
// listener takes the single path argument and filters inside.
|
|
78
|
+
if (path !== "modelProviderOrder") return;
|
|
79
|
+
this.#emit();
|
|
80
|
+
});
|
|
81
|
+
this.#unsubscribeAuthGeneration = this.#authStorage.onGenerationChanged(() => this.#emit());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Build the current provider order snapshot from live registry + settings state. */
|
|
85
|
+
snapshot(): ProviderOrderSnapshot {
|
|
86
|
+
const order = this.#readPersistedOrder();
|
|
87
|
+
const catalog = this.#catalogProviders();
|
|
88
|
+
|
|
89
|
+
const seen = new Set<string>();
|
|
90
|
+
const union: string[] = [];
|
|
91
|
+
for (const id of order) {
|
|
92
|
+
seen.add(id);
|
|
93
|
+
union.push(id);
|
|
94
|
+
}
|
|
95
|
+
for (const id of catalog.keys()) {
|
|
96
|
+
if (seen.has(id)) continue;
|
|
97
|
+
seen.add(id);
|
|
98
|
+
union.push(id);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const entries: ProviderOrderEntry[] = union.map(id => ({
|
|
102
|
+
id,
|
|
103
|
+
label: formatProviderOrderLabel(id),
|
|
104
|
+
available: catalog.has(id),
|
|
105
|
+
authenticated: this.#isAuthenticated(catalog.get(id) ?? id),
|
|
106
|
+
inOrder: order.includes(id),
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
return { order, entries };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Persist a new ordered provider list and surface durable write failures. */
|
|
113
|
+
async persistOrder(order: readonly string[]): Promise<void> {
|
|
114
|
+
await this.#persistGlobalOrder(normalizeProviderOrder(order));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Clear the persisted provider order so default resolution applies. */
|
|
118
|
+
async resetOrder(): Promise<void> {
|
|
119
|
+
await this.#persistGlobalOrder(undefined);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
dispose(): void {
|
|
123
|
+
if (this.#disposed) return;
|
|
124
|
+
this.#disposed = true;
|
|
125
|
+
this.#unsubscribeSettings();
|
|
126
|
+
this.#unsubscribeAuthGeneration();
|
|
127
|
+
this.#onChange = undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#readPersistedOrder(): string[] {
|
|
131
|
+
const configured = this.#settings.getGlobal("modelProviderOrder");
|
|
132
|
+
return normalizeProviderOrder(Array.isArray(configured) ? configured : []);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async #persistGlobalOrder(order: readonly string[] | undefined): Promise<void> {
|
|
136
|
+
const previous = this.#settings.getGlobal("modelProviderOrder");
|
|
137
|
+
try {
|
|
138
|
+
if (order === undefined) {
|
|
139
|
+
this.#settings.unset("modelProviderOrder");
|
|
140
|
+
} else {
|
|
141
|
+
this.#settings.set("modelProviderOrder", [...order]);
|
|
142
|
+
}
|
|
143
|
+
await this.#settings.flushOrThrow();
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const rollbackErrors: unknown[] = [];
|
|
146
|
+
try {
|
|
147
|
+
if (previous === undefined) {
|
|
148
|
+
this.#settings.unset("modelProviderOrder");
|
|
149
|
+
} else {
|
|
150
|
+
this.#settings.set("modelProviderOrder", previous);
|
|
151
|
+
}
|
|
152
|
+
} catch (rollbackError) {
|
|
153
|
+
rollbackErrors.push(rollbackError);
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
await this.#settings.flushOrThrow();
|
|
157
|
+
} catch (rollbackError) {
|
|
158
|
+
rollbackErrors.push(rollbackError);
|
|
159
|
+
}
|
|
160
|
+
if (rollbackErrors.length > 0) {
|
|
161
|
+
throw new AggregateError(
|
|
162
|
+
[error, ...rollbackErrors],
|
|
163
|
+
"Provider order save failed and rollback was incomplete",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#catalogProviders(): Map<string, string> {
|
|
171
|
+
const providers = new Map<string, string>();
|
|
172
|
+
for (const model of this.#registry.getAll()) {
|
|
173
|
+
const id = normalizeProviderId(model.provider);
|
|
174
|
+
if (id && !providers.has(id)) providers.set(id, model.provider);
|
|
175
|
+
}
|
|
176
|
+
return providers;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#isAuthenticated(providerId: string): boolean {
|
|
180
|
+
const effectiveCredentialType = (this.#authStorage as Partial<Pick<AuthStorage, "getEffectiveCredentialType">>)
|
|
181
|
+
.getEffectiveCredentialType;
|
|
182
|
+
const health = getProviderAuthHealth(this.#authStorage, providerId);
|
|
183
|
+
if (!effectiveCredentialType)
|
|
184
|
+
return health ? health === "valid" : this.#registry.hasConfiguredProviderAuth(providerId);
|
|
185
|
+
const credentialType = effectiveCredentialType.call(this.#authStorage, providerId, this.#credentialSessionId);
|
|
186
|
+
return (
|
|
187
|
+
(credentialType !== undefined &&
|
|
188
|
+
health !== "invalid" &&
|
|
189
|
+
(this.#authStorage.hasUsableAuth?.(providerId) ?? true)) ||
|
|
190
|
+
this.#registry.isCredentiallessProvider?.(providerId) === true
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#emit(): void {
|
|
195
|
+
this.#onChange?.();
|
|
196
|
+
}
|
|
197
|
+
}
|