@gajae-code/coding-agent 0.15.5 → 0.15.6
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 +26 -1
- package/dist/types/async/job-manager.d.ts +83 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
- package/dist/types/cli/model-presets-cli.d.ts +9 -0
- package/dist/types/cli/update-cli.d.ts +2 -0
- package/dist/types/commands/auth-gateway.d.ts +3 -0
- package/dist/types/commands/gc.d.ts +13 -0
- package/dist/types/commands/model-presets.d.ts +21 -0
- package/dist/types/config/autorouting-tier-map.d.ts +96 -0
- package/dist/types/config/file-lock.d.ts +49 -2
- package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
- package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +3 -2
- package/dist/types/config/model-preset-registry.d.ts +368 -0
- package/dist/types/config/model-profile-activation.d.ts +39 -0
- package/dist/types/config/model-profiles.d.ts +5 -3
- package/dist/types/config/model-registry.d.ts +11 -5
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
- package/dist/types/coordinator-mcp/projection-scan.d.ts +46 -0
- package/dist/types/extensibility/extensions/types.d.ts +2 -5
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/main.d.ts +3 -1
- package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
- package/dist/types/modes/jobs-observer.d.ts +36 -0
- package/dist/types/sdk/broker/broker.d.ts +3 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
- package/dist/types/sdk/client/discovery.d.ts +1 -0
- package/dist/types/sdk/host/model-pin.d.ts +7 -2
- package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +15 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
- package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +9 -0
- package/dist/types/session/account-inventory.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +19 -7
- package/dist/types/session/fold-coordinator.d.ts +201 -0
- package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
- package/dist/types/session/sdk-run-capability.d.ts +2 -0
- package/dist/types/telemetry/control.d.ts +11 -0
- package/dist/types/telemetry/events.d.ts +20 -0
- package/dist/types/telemetry/index.d.ts +3 -0
- package/dist/types/telemetry/transport.d.ts +22 -0
- package/dist/types/tools/bash-interactive.d.ts +23 -0
- package/dist/types/tools/index.d.ts +6 -5
- package/dist/types/web/search/providers/base.d.ts +9 -4
- package/dist/types/web/search/providers/xai.d.ts +2 -1
- package/dist/types/web/search/types.d.ts +14 -0
- package/package.json +10 -7
- package/scripts/generate-hotkeys-docs.ts +4 -0
- package/scripts/generate-sdk-operation-inventory.ts +3 -3
- package/src/async/job-manager.ts +671 -39
- package/src/cli/auth-broker-cli.ts +40 -12
- package/src/cli/auth-gateway-cli.ts +273 -97
- package/src/cli/config-cli.ts +2 -2
- package/src/cli/fast-help.ts +3 -0
- package/src/cli/model-presets-cli.ts +100 -0
- package/src/cli/update-cli.ts +40 -5
- package/src/cli.ts +6 -2
- package/src/commands/auth-gateway.ts +12 -8
- package/src/commands/gc.ts +19 -0
- package/src/commands/model-presets.ts +24 -0
- package/src/config/autorouting-tier-map.ts +32 -0
- package/src/config/file-lock-gc.ts +9 -4
- package/src/config/file-lock.ts +775 -59
- package/src/config/internal/model-preset-registry-test-state.ts +28 -0
- package/src/config/internal/model-preset-registry-test-support.ts +11 -0
- package/src/config/keybindings.ts +6 -2
- package/src/config/model-preset-registry.ts +2799 -0
- package/src/config/model-profile-activation.ts +115 -28
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +51 -9
- package/src/config/model-registry.ts +1021 -122
- package/src/config/settings-schema.ts +11 -0
- package/src/config/settings.ts +6 -1
- package/src/coordinator-mcp/model-preset.ts +32 -17
- package/src/coordinator-mcp/projection-scan.ts +571 -0
- package/src/coordinator-mcp/server.ts +19 -24
- package/src/coordinator-mcp/session-reaper.ts +9 -3
- package/src/extensibility/extensions/loader.ts +0 -1
- package/src/extensibility/extensions/types.ts +2 -3
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
- package/src/gjc-runtime/empty-delete-gc.ts +301 -0
- package/src/gjc-runtime/gc-render.ts +29 -0
- package/src/gjc-runtime/gc-runtime.ts +174 -11
- package/src/gjc-runtime/session-state-lock.ts +1242 -141
- package/src/gjc-runtime/session-state-sidecar.ts +76 -3
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +9 -8
- package/src/lsp/config.ts +4 -3
- package/src/lsp/path-trust.ts +12 -6
- package/src/main.ts +9 -2
- package/src/modes/acp/acp-agent.ts +11 -5
- package/src/modes/components/jobs-overlay-model.ts +99 -16
- package/src/modes/components/model-selector.ts +158 -18
- package/src/modes/components/status-line/segments.ts +6 -1
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +23 -6
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/jobs-observer.ts +149 -16
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/utils/injected-user-submission.ts +15 -1
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/fold-receipt.md +3 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +17 -2
- package/src/sdk/broker/broker.ts +48 -21
- package/src/sdk/broker/lifecycle.ts +32 -6
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +11 -1
- package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
- package/src/sdk/bus/prompt-reconciliation.ts +28 -4
- package/src/sdk/bus/slack-conversation.ts +5 -1
- package/src/sdk/cli/session-cli.ts +12 -1
- package/src/sdk/client/discovery.ts +3 -1
- package/src/sdk/host/control/dispatch.ts +3 -0
- package/src/sdk/host/model-pin.ts +185 -25
- package/src/sdk/host/sdk-run-capability.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +371 -37
- package/src/sdk/host/session-runtime.ts +364 -52
- package/src/sdk/prompt-deadline-manager.ts +16 -1
- package/src/sdk/protocol/adapter-validation.ts +27 -0
- package/src/sdk/protocol/operation-inventory.generated.json +2 -2
- package/src/sdk/protocol/operation-registry.ts +4 -1
- package/src/sdk/router/session-router.ts +228 -96
- package/src/sdk/session.ts +83 -12
- package/src/session/account-inventory.ts +27 -11
- package/src/session/agent-session.ts +678 -103
- package/src/session/fold-coordinator.ts +433 -0
- package/src/session/sdk-run-capability-internal.ts +15 -0
- package/src/session/sdk-run-capability.ts +2 -0
- package/src/session/session-manager.ts +5 -4
- package/src/setup/provider-presets.json +1 -1
- package/src/slash-commands/helpers/credential-switch.ts +6 -1
- package/src/task/executor.ts +19 -3
- package/src/telemetry/control.ts +26 -0
- package/src/telemetry/events.ts +142 -0
- package/src/telemetry/index.ts +3 -0
- package/src/telemetry/transport.ts +87 -0
- package/src/tools/bash-interactive.ts +182 -85
- package/src/tools/bash.ts +772 -67
- package/src/tools/index.ts +7 -5
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/web/search/provider.ts +120 -11
- package/src/web/search/providers/anthropic.ts +89 -17
- package/src/web/search/providers/base.ts +9 -4
- package/src/web/search/providers/gemini.ts +87 -15
- package/src/web/search/providers/openai-compatible.ts +23 -8
- package/src/web/search/providers/xai.ts +98 -12
- package/src/web/search/types.ts +16 -0
|
@@ -6,7 +6,9 @@ import { clampExplicitThinkingLevelForModel, formatClampedModelSelector } from "
|
|
|
6
6
|
import { validateModelProfileName } from "./model-profile-contract";
|
|
7
7
|
import {
|
|
8
8
|
aggregateModelProfileRequiredProviders,
|
|
9
|
+
deriveModelProfileMappedProviders,
|
|
9
10
|
formatModelProfileDisplayLabel,
|
|
11
|
+
type ModelProfileDefinition,
|
|
10
12
|
PROXY_ROUTABLE_PROVIDER_IDS,
|
|
11
13
|
resolveProfileBindings,
|
|
12
14
|
} from "./model-profiles";
|
|
@@ -537,19 +539,40 @@ function rewriteBindingsProviders(
|
|
|
537
539
|
* preset. Returns undefined when unset or empty. Passwords/labels are never
|
|
538
540
|
* treated as proxy ids here; only lowercase provider ids from settings.
|
|
539
541
|
*/
|
|
540
|
-
function resolveProxyProviderId(settings: Pick<Settings, "get" |
|
|
541
|
-
const
|
|
542
|
-
if (
|
|
543
|
-
|
|
544
|
-
if (!/^[a-z0-9][a-z0-9._-]*$/.test(id)) {
|
|
542
|
+
export function resolveProxyProviderId(settings: Pick<Settings, "get"> | undefined): string | undefined {
|
|
543
|
+
const config = inspectProxyProviderId(settings);
|
|
544
|
+
if (config.status === "unset") return undefined;
|
|
545
|
+
if (config.status === "invalid") {
|
|
545
546
|
throw new Error(
|
|
546
|
-
`modelProfile.proxyProvider must be a lowercase provider id (got "${value
|
|
547
|
+
`modelProfile.proxyProvider must be a lowercase provider id (got "${config.value}"). Configure an OpenAI-compatible proxy with \`gjc setup provider\`, then set its id here.`,
|
|
547
548
|
);
|
|
548
549
|
}
|
|
549
|
-
return id;
|
|
550
|
+
return config.id;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export type ProxyProviderConfig =
|
|
554
|
+
| { status: "unset" }
|
|
555
|
+
| { status: "configured"; id: string }
|
|
556
|
+
| { status: "invalid"; value: string };
|
|
557
|
+
|
|
558
|
+
export function inspectProxyProviderId(settings: Pick<Settings, "get"> | undefined): ProxyProviderConfig {
|
|
559
|
+
if (!settings) return { status: "unset" };
|
|
560
|
+
const value = settings.get("modelProfile.proxyProvider");
|
|
561
|
+
if (typeof value !== "string" || value.trim() === "") return { status: "unset" };
|
|
562
|
+
const id = value.trim().toLowerCase();
|
|
563
|
+
return /^[a-z0-9][a-z0-9._-]*$/.test(id) ? { status: "configured", id } : { status: "invalid", value: value.trim() };
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** Passive surfaces fail closed on malformed settings instead of throwing. */
|
|
567
|
+
export function tryResolveProxyProviderId(settings: Pick<Settings, "get"> | undefined): string | undefined {
|
|
568
|
+
const config = inspectProxyProviderId(settings);
|
|
569
|
+
return config.status === "configured" ? config.id : undefined;
|
|
550
570
|
}
|
|
551
571
|
|
|
552
|
-
|
|
572
|
+
export type ModelProfileProxyMode = "fallback" | "always";
|
|
573
|
+
|
|
574
|
+
export function resolveProxyMode(settings: Pick<Settings, "get"> | undefined): ModelProfileProxyMode {
|
|
575
|
+
if (!settings) return "fallback";
|
|
553
576
|
const value = settings.get("modelProfile.proxyMode");
|
|
554
577
|
if (value === undefined || value === "fallback" || value === "always") return value ?? "fallback";
|
|
555
578
|
throw new Error(`modelProfile.proxyMode must be "fallback" or "always" (got "${String(value)}")`);
|
|
@@ -563,12 +586,35 @@ function resolveProxyMode(settings: Pick<Settings, "get" | "getGlobal" | "getOve
|
|
|
563
586
|
* not retain the upstream provider prefix. Missing or ambiguous matches fail
|
|
564
587
|
* closed rather than leaving an unauthenticated role selector behind.
|
|
565
588
|
*/
|
|
566
|
-
function
|
|
589
|
+
export function getProxyRoutableProviders(profile: ModelProfileDefinition): ReadonlySet<string> {
|
|
590
|
+
if (profile.source === "user") return new Set();
|
|
591
|
+
return profile.source === "registry"
|
|
592
|
+
? new Set([
|
|
593
|
+
...PROXY_ROUTABLE_PROVIDER_IDS,
|
|
594
|
+
...profile.requiredProviders,
|
|
595
|
+
...deriveModelProfileMappedProviders(profile),
|
|
596
|
+
])
|
|
597
|
+
: PROXY_ROUTABLE_PROVIDER_IDS;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Profile sources whose non-default qualified role bindings are part of the
|
|
602
|
+
* activation contract. Proxy rewrites run before role resolution, so a
|
|
603
|
+
* rewritten selector satisfies this prerequisite when its concrete proxy
|
|
604
|
+
* model is present in the effective catalog. Embedded built-ins intentionally
|
|
605
|
+
* retain their legacy tolerance for stale qualified role tails.
|
|
606
|
+
*/
|
|
607
|
+
export function requiresQualifiedModelProfileRoleResolution(profile: Pick<ModelProfileDefinition, "source">): boolean {
|
|
608
|
+
return profile.source === "user" || profile.source === "registry";
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export function rewriteSelectorForProxy(
|
|
567
612
|
selector: string,
|
|
568
613
|
proxyProvider: string,
|
|
569
|
-
proxyMode:
|
|
614
|
+
proxyMode: ModelProfileProxyMode,
|
|
570
615
|
allModels: Model<Api>[],
|
|
571
616
|
directlyAuthenticated: ReadonlySet<string>,
|
|
617
|
+
routableProviders: ReadonlySet<string>,
|
|
572
618
|
): string {
|
|
573
619
|
const suffix = splitSelectorThinkingSuffix(selector);
|
|
574
620
|
const baseSelector = suffix.selector;
|
|
@@ -589,7 +635,7 @@ function rewriteSelectorForProxy(
|
|
|
589
635
|
}
|
|
590
636
|
const directProvider = baseSelector.substring(0, slash);
|
|
591
637
|
if (proxyMode === "fallback" && directlyAuthenticated.has(directProvider)) return selector;
|
|
592
|
-
if (!
|
|
638
|
+
if (!routableProviders.has(directProvider)) return selector;
|
|
593
639
|
if (proxyProvider === directProvider) {
|
|
594
640
|
throw new Error(`Configured proxy "${proxyProvider}" cannot route its own direct selector "${baseSelector}"`);
|
|
595
641
|
}
|
|
@@ -613,9 +659,10 @@ function rewriteSelectorValueForProxy(
|
|
|
613
659
|
proxyMode: "fallback" | "always",
|
|
614
660
|
allModels: Model<Api>[],
|
|
615
661
|
directlyAuthenticated: ReadonlySet<string>,
|
|
662
|
+
routableProviders: ReadonlySet<string>,
|
|
616
663
|
): ModelSelectorValue {
|
|
617
664
|
const selectors = normalizeModelSelectorValue(selectorValue).map(selector =>
|
|
618
|
-
rewriteSelectorForProxy(selector, proxyProvider, proxyMode, allModels, directlyAuthenticated),
|
|
665
|
+
rewriteSelectorForProxy(selector, proxyProvider, proxyMode, allModels, directlyAuthenticated, routableProviders),
|
|
619
666
|
);
|
|
620
667
|
return selectors.length === 1 && typeof selectorValue === "string" ? selectors[0] : selectors;
|
|
621
668
|
}
|
|
@@ -630,6 +677,7 @@ function rewriteBindingsForProxy(
|
|
|
630
677
|
proxyMode: "fallback" | "always",
|
|
631
678
|
allModels: Model<Api>[],
|
|
632
679
|
directlyAuthenticated: ReadonlySet<string>,
|
|
680
|
+
routableProviders: ReadonlySet<string>,
|
|
633
681
|
): {
|
|
634
682
|
defaultSelector?: ModelSelectorValue;
|
|
635
683
|
modelRoles: Record<string, ModelSelectorValue>;
|
|
@@ -643,18 +691,33 @@ function rewriteBindingsForProxy(
|
|
|
643
691
|
proxyMode,
|
|
644
692
|
allModels,
|
|
645
693
|
directlyAuthenticated,
|
|
694
|
+
routableProviders,
|
|
646
695
|
)
|
|
647
696
|
: undefined,
|
|
648
697
|
modelRoles: Object.fromEntries(
|
|
649
698
|
Object.entries(bindings.modelRoles).map(([role, selector]) => [
|
|
650
699
|
role,
|
|
651
|
-
rewriteSelectorValueForProxy(
|
|
700
|
+
rewriteSelectorValueForProxy(
|
|
701
|
+
selector,
|
|
702
|
+
proxyProvider,
|
|
703
|
+
proxyMode,
|
|
704
|
+
allModels,
|
|
705
|
+
directlyAuthenticated,
|
|
706
|
+
routableProviders,
|
|
707
|
+
),
|
|
652
708
|
]),
|
|
653
709
|
),
|
|
654
710
|
agentModelOverrides: Object.fromEntries(
|
|
655
711
|
Object.entries(bindings.agentModelOverrides).map(([role, selector]) => [
|
|
656
712
|
role,
|
|
657
|
-
rewriteSelectorValueForProxy(
|
|
713
|
+
rewriteSelectorValueForProxy(
|
|
714
|
+
selector,
|
|
715
|
+
proxyProvider,
|
|
716
|
+
proxyMode,
|
|
717
|
+
allModels,
|
|
718
|
+
directlyAuthenticated,
|
|
719
|
+
routableProviders,
|
|
720
|
+
),
|
|
658
721
|
]),
|
|
659
722
|
),
|
|
660
723
|
};
|
|
@@ -888,13 +951,25 @@ export async function prepareModelProfileActivation(
|
|
|
888
951
|
const requiredProviders = aggregateModelProfileRequiredProviders(profile.requiredProviders, profile);
|
|
889
952
|
const alternativeGroups = profile.alternativeProviderGroups ?? [];
|
|
890
953
|
const alternativeSet = new Set(alternativeGroups.flat());
|
|
954
|
+
const requiredProviderSet = new Set(requiredProviders);
|
|
955
|
+
const authenticationProbeProviders = new Set<string>([
|
|
956
|
+
...requiredProviders,
|
|
957
|
+
...alternativeSet,
|
|
958
|
+
...deriveModelProfileMappedProviders(profile),
|
|
959
|
+
]);
|
|
891
960
|
|
|
892
961
|
const missingProviders: string[] = [];
|
|
893
962
|
const authenticatedProviders: string[] = [];
|
|
894
|
-
for (const provider of
|
|
895
|
-
|
|
963
|
+
for (const provider of authenticationProbeProviders) {
|
|
964
|
+
let apiKey: string | undefined;
|
|
965
|
+
try {
|
|
966
|
+
apiKey = await options.modelRegistry.getApiKeyForProvider(provider, credentialSessionId);
|
|
967
|
+
} catch (error) {
|
|
968
|
+
if (requiredProviderSet.has(provider) && !alternativeSet.has(provider)) throw error;
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
896
971
|
if (apiKey !== kNoAuth && !isAuthenticated(apiKey)) {
|
|
897
|
-
missingProviders.push(provider);
|
|
972
|
+
if (requiredProviderSet.has(provider)) missingProviders.push(provider);
|
|
898
973
|
} else {
|
|
899
974
|
authenticatedProviders.push(provider);
|
|
900
975
|
}
|
|
@@ -905,8 +980,18 @@ export async function prepareModelProfileActivation(
|
|
|
905
980
|
// A proxy-routable strict provider is satisfied through the configured
|
|
906
981
|
// OpenAI-compatible proxy when that proxy is itself authenticated; otherwise
|
|
907
982
|
// we fail closed pointing at the proxy (or the provider when none is set).
|
|
908
|
-
const proxyProvider = profile.source
|
|
909
|
-
const proxyMode = profile.source
|
|
983
|
+
const proxyProvider = profile.source !== "user" ? resolveProxyProviderId(options.settings) : undefined;
|
|
984
|
+
const proxyMode = profile.source !== "user" ? resolveProxyMode(options.settings) : "fallback";
|
|
985
|
+
const proxyRoutableProviders =
|
|
986
|
+
profile.source === "user"
|
|
987
|
+
? new Set<string>()
|
|
988
|
+
: profile.source === "registry"
|
|
989
|
+
? new Set([
|
|
990
|
+
...PROXY_ROUTABLE_PROVIDER_IDS,
|
|
991
|
+
...profile.requiredProviders,
|
|
992
|
+
...deriveModelProfileMappedProviders(profile),
|
|
993
|
+
])
|
|
994
|
+
: PROXY_ROUTABLE_PROVIDER_IDS;
|
|
910
995
|
if (proxyMode === "always" && proxyProvider === undefined) {
|
|
911
996
|
throw new Error('modelProfile.proxyMode "always" requires modelProfile.proxyProvider');
|
|
912
997
|
}
|
|
@@ -931,13 +1016,13 @@ export async function prepareModelProfileActivation(
|
|
|
931
1016
|
}
|
|
932
1017
|
|
|
933
1018
|
const strictMissing = missingProviders.filter(
|
|
934
|
-
provider => !
|
|
1019
|
+
provider => !proxyRoutableProviders.has(provider) && !alternativeSet.has(provider),
|
|
935
1020
|
);
|
|
936
1021
|
if (strictMissing.length > 0) {
|
|
937
1022
|
throw new ModelProfileCredentialError(profileLabel, strictMissing);
|
|
938
1023
|
}
|
|
939
1024
|
const strictRoutableMissing = missingProviders.filter(
|
|
940
|
-
provider =>
|
|
1025
|
+
provider => proxyRoutableProviders.has(provider) && !alternativeSet.has(provider),
|
|
941
1026
|
);
|
|
942
1027
|
if (strictRoutableMissing.length > 0 && (proxyProvider === undefined || !proxyAuthenticated)) {
|
|
943
1028
|
throw new ModelProfileCredentialError(
|
|
@@ -948,7 +1033,7 @@ export async function prepareModelProfileActivation(
|
|
|
948
1033
|
for (const group of alternativeGroups) {
|
|
949
1034
|
const groupAuthenticated = group.some(provider => authenticatedProviders.includes(provider));
|
|
950
1035
|
if (groupAuthenticated) continue;
|
|
951
|
-
const allRoutable = group.every(provider =>
|
|
1036
|
+
const allRoutable = group.every(provider => proxyRoutableProviders.has(provider));
|
|
952
1037
|
if (allRoutable && proxyAuthenticated) continue;
|
|
953
1038
|
throw new ModelProfileCredentialError(
|
|
954
1039
|
profileLabel,
|
|
@@ -960,20 +1045,22 @@ export async function prepareModelProfileActivation(
|
|
|
960
1045
|
options.modelRegistry.getAvailableForProfileActivation?.() ??
|
|
961
1046
|
options.modelRegistry.getAvailable?.() ??
|
|
962
1047
|
options.modelRegistry.getAll();
|
|
1048
|
+
const roleCatalogModels = options.modelRegistry.getAll();
|
|
963
1049
|
let bindings = resolveProfileBindings(profile);
|
|
964
|
-
if (
|
|
1050
|
+
if (alternativeGroups.length > 0) {
|
|
965
1051
|
bindings = rewriteBindingsProviders(bindings, new Set(authenticatedProviders), alternativeGroups);
|
|
966
1052
|
}
|
|
967
1053
|
// Built-in preset selectors are routed through a configured authenticated
|
|
968
1054
|
// proxy according to the selected mode. This session-scoped rewrite is never
|
|
969
1055
|
// persisted to models.yml.
|
|
970
|
-
if (proxyProvider !== undefined && proxyAuthenticated && profile.source
|
|
1056
|
+
if (proxyProvider !== undefined && proxyAuthenticated && profile.source !== "user") {
|
|
971
1057
|
bindings = rewriteBindingsForProxy(
|
|
972
1058
|
bindings,
|
|
973
1059
|
proxyProvider,
|
|
974
1060
|
proxyMode,
|
|
975
1061
|
availableModels,
|
|
976
1062
|
new Set(authenticatedProviders),
|
|
1063
|
+
proxyRoutableProviders,
|
|
977
1064
|
);
|
|
978
1065
|
}
|
|
979
1066
|
const defaultSelectors = bindings.defaultSelector ? normalizeModelSelectorValue(bindings.defaultSelector) : [];
|
|
@@ -1038,14 +1125,14 @@ export async function prepareModelProfileActivation(
|
|
|
1038
1125
|
][]) {
|
|
1039
1126
|
modelRoles[role] = await resolveAndClampSelectorValue(
|
|
1040
1127
|
selectorValue,
|
|
1041
|
-
|
|
1128
|
+
roleCatalogModels,
|
|
1042
1129
|
{
|
|
1043
1130
|
settings: options.settings as Settings,
|
|
1044
1131
|
modelRegistry: options.modelRegistry as ModelRegistry,
|
|
1045
1132
|
sessionId: options.session.sessionId,
|
|
1046
1133
|
credentialSessionId,
|
|
1047
1134
|
aliasIntent: "preset-equivalent",
|
|
1048
|
-
requireQualifiedResolution: profile
|
|
1135
|
+
requireQualifiedResolution: requiresQualifiedModelProfileRoleResolution(profile),
|
|
1049
1136
|
},
|
|
1050
1137
|
profileLabel,
|
|
1051
1138
|
role,
|
|
@@ -1059,14 +1146,14 @@ export async function prepareModelProfileActivation(
|
|
|
1059
1146
|
][]) {
|
|
1060
1147
|
agentModelOverrides[role] = await resolveAndClampSelectorValue(
|
|
1061
1148
|
selectorValue,
|
|
1062
|
-
|
|
1149
|
+
roleCatalogModels,
|
|
1063
1150
|
{
|
|
1064
1151
|
settings: options.settings as Settings,
|
|
1065
1152
|
modelRegistry: options.modelRegistry as ModelRegistry,
|
|
1066
1153
|
sessionId: options.session.sessionId,
|
|
1067
1154
|
credentialSessionId,
|
|
1068
1155
|
aliasIntent: "preset-equivalent",
|
|
1069
|
-
requireQualifiedResolution: profile
|
|
1156
|
+
requireQualifiedResolution: requiresQualifiedModelProfileRoleResolution(profile),
|
|
1070
1157
|
},
|
|
1071
1158
|
profileLabel,
|
|
1072
1159
|
role,
|
|
@@ -120,7 +120,10 @@ export function projectModelProfileCatalog(
|
|
|
120
120
|
.map(([id, definition]) => ({
|
|
121
121
|
id,
|
|
122
122
|
displayName: formatModelProfileDisplayLabel(definition),
|
|
123
|
-
source:
|
|
123
|
+
source:
|
|
124
|
+
definition.source === "user" || definition.source === "registry"
|
|
125
|
+
? ("configured" as const)
|
|
126
|
+
: ("builtin" as const),
|
|
124
127
|
}))
|
|
125
128
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
126
129
|
}
|
|
@@ -8,6 +8,7 @@ export interface ModelProfileDefinition {
|
|
|
8
8
|
name: string;
|
|
9
9
|
requiredProviders: string[];
|
|
10
10
|
displayName?: string;
|
|
11
|
+
providerGroup?: string;
|
|
11
12
|
/**
|
|
12
13
|
* Optional groups of providers that are interchangeable fallbacks.
|
|
13
14
|
* Each group is an array of provider ids where at least one must be
|
|
@@ -19,7 +20,7 @@ export interface ModelProfileDefinition {
|
|
|
19
20
|
*/
|
|
20
21
|
alternativeProviderGroups?: readonly (readonly string[])[];
|
|
21
22
|
modelMapping: Partial<Record<ModelProfileRole, ModelSelectorValue>>;
|
|
22
|
-
source: "builtin" | "user";
|
|
23
|
+
source: "builtin" | "registry" | "user";
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export interface ResolvedProfileBinding {
|
|
@@ -673,12 +674,14 @@ const PROFILE_RECOMMENDATIONS: Record<string, string> = {
|
|
|
673
674
|
};
|
|
674
675
|
|
|
675
676
|
export function getModelProfilePresentation(
|
|
676
|
-
profile: string | Pick<ModelProfileDefinition, "name" | "displayName">,
|
|
677
|
+
profile: string | Pick<ModelProfileDefinition, "name" | "displayName" | "providerGroup">,
|
|
677
678
|
): ModelProfilePresentation {
|
|
678
679
|
const name = typeof profile === "string" ? profile : profile.name;
|
|
679
680
|
const displayName = typeof profile === "string" ? undefined : profile.displayName;
|
|
680
681
|
const presentation = PROFILE_PRESENTATION[name];
|
|
681
682
|
if (presentation) return presentation;
|
|
683
|
+
if (typeof profile !== "string" && profile.providerGroup)
|
|
684
|
+
return { displayName: formatModelProfileDisplayLabel(profile), providerGroup: profile.providerGroup };
|
|
682
685
|
return { displayName: formatModelProfileDisplayLabel({ name, displayName }), providerGroup: "CUSTOM" };
|
|
683
686
|
}
|
|
684
687
|
|
|
@@ -732,17 +735,56 @@ export function recommendModelProfileForProvider(
|
|
|
732
735
|
return recommended ? profiles.get(recommended) : undefined;
|
|
733
736
|
}
|
|
734
737
|
|
|
735
|
-
export
|
|
738
|
+
export type RegistryModelProfilesInput =
|
|
739
|
+
| ReadonlyMap<string, ModelProfileDefinition>
|
|
740
|
+
| readonly ModelProfileDefinition[]
|
|
741
|
+
| Readonly<Record<string, ModelProfileDefinition>>;
|
|
742
|
+
|
|
743
|
+
function cloneModelSelectorValue(value: ModelSelectorValue): ModelSelectorValue {
|
|
744
|
+
return Array.isArray(value) ? [...value] : value;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function cloneModelProfileDefinition(
|
|
748
|
+
definition: ModelProfileDefinition,
|
|
749
|
+
source: ModelProfileDefinition["source"] = definition.source,
|
|
750
|
+
): ModelProfileDefinition {
|
|
751
|
+
return {
|
|
752
|
+
name: definition.name,
|
|
753
|
+
displayName: definition.displayName,
|
|
754
|
+
providerGroup: definition.providerGroup,
|
|
755
|
+
requiredProviders: [...definition.requiredProviders],
|
|
756
|
+
alternativeProviderGroups: definition.alternativeProviderGroups?.map(group => [...group]),
|
|
757
|
+
modelMapping: Object.fromEntries(
|
|
758
|
+
Object.entries(definition.modelMapping).map(([role, value]) => [role, cloneModelSelectorValue(value)]),
|
|
759
|
+
) as Partial<Record<ModelProfileRole, ModelSelectorValue>>,
|
|
760
|
+
source,
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function registryProfileEntries(
|
|
765
|
+
input: RegistryModelProfilesInput | undefined,
|
|
766
|
+
): Array<[string, ModelProfileDefinition]> {
|
|
767
|
+
if (!input) return [];
|
|
768
|
+
if (input instanceof Map) return [...input.entries()];
|
|
769
|
+
if (Array.isArray(input)) return input.map(definition => [definition.name, definition]);
|
|
770
|
+
return Object.entries(input);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
export function mergeModelProfiles(
|
|
774
|
+
userProfiles?: ModelsConfig["profiles"],
|
|
775
|
+
registryProfiles?: RegistryModelProfilesInput,
|
|
776
|
+
): Map<string, ModelProfileDefinition> {
|
|
736
777
|
const profiles = new Map<string, ModelProfileDefinition>();
|
|
737
778
|
for (const definition of BUILTIN_MODEL_PROFILES) {
|
|
738
|
-
profiles.set(definition.name,
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
});
|
|
779
|
+
profiles.set(definition.name, cloneModelProfileDefinition(definition, "builtin"));
|
|
780
|
+
}
|
|
781
|
+
for (const [name, definition] of registryProfileEntries(registryProfiles)) {
|
|
782
|
+
profiles.set(name, cloneModelProfileDefinition({ ...definition, name }, "registry"));
|
|
743
783
|
}
|
|
744
784
|
for (const [name, definition] of Object.entries(userProfiles ?? {})) {
|
|
745
|
-
const modelMapping =
|
|
785
|
+
const modelMapping = Object.fromEntries(
|
|
786
|
+
Object.entries(definition.model_mapping).map(([role, value]) => [role, cloneModelSelectorValue(value)]),
|
|
787
|
+
) as Partial<Record<ModelProfileRole, ModelSelectorValue>>;
|
|
746
788
|
profiles.set(name, {
|
|
747
789
|
name,
|
|
748
790
|
displayName: definition.display_name,
|