@gajae-code/coding-agent 0.15.5 → 0.16.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 +85 -1
- package/dist/types/async/job-manager.d.ts +83 -0
- package/dist/types/cli/args.d.ts +2 -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/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +15 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/auth-gateway.d.ts +3 -0
- package/dist/types/commands/gc.d.ts +13 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/model-presets.d.ts +21 -0
- package/dist/types/config/autorouting-tier-map.d.ts +120 -0
- package/dist/types/config/file-lock.d.ts +54 -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 +17 -5
- package/dist/types/config/settings-schema.d.ts +19 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
- package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
- package/dist/types/crash/record-loader.d.ts +15 -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/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.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 +72 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/main.d.ts +3 -1
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/jobs-observer.d.ts +36 -0
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +80 -1
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/state-version.d.ts +4 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/index.d.ts +6 -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/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +4 -3
- package/dist/types/sdk/cli/session-cli.d.ts +18 -2
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/client/discovery.d.ts +1 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/host.d.ts +10 -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 +67 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +2 -0
- 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 +11 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/session/account-inventory.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +24 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- 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/session/session-manager.d.ts +65 -0
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -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/bash.d.ts +4 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -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 +9 -3
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +671 -39
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- 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 +4 -0
- package/src/cli/model-presets-cli.ts +100 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +117 -16
- package/src/cli.ts +6 -2
- package/src/commands/auth-gateway.ts +12 -8
- package/src/commands/gc.ts +19 -0
- package/src/commands/launch.ts +86 -18
- package/src/commands/model-presets.ts +24 -0
- package/src/commands/sdk.ts +113 -21
- package/src/config/autorouting-tier-map.ts +44 -0
- package/src/config/file-lock-gc.ts +9 -4
- package/src/config/file-lock.ts +890 -99
- 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 +2795 -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 +56 -14
- package/src/config/model-registry.ts +1044 -122
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +19 -1
- package/src/config/settings.ts +11 -1
- package/src/coordinator-mcp/model-preset.ts +32 -17
- package/src/coordinator-mcp/projection-scan.ts +592 -0
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +111 -42
- package/src/coordinator-mcp/session-reaper.ts +9 -3
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- 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/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +1321 -147
- package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/lsp/config.ts +4 -3
- package/src/lsp/path-trust.ts +12 -6
- package/src/main.ts +66 -4
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +616 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/jobs-overlay-model.ts +99 -16
- package/src/modes/components/model-selector.ts +158 -18
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +29 -10
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +51 -6
- package/src/modes/controllers/selector-controller.ts +191 -96
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/jobs-observer.ts +149 -16
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/injected-user-submission.ts +15 -1
- package/src/prompts/system/master-mode.md +28 -0
- 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 +1671 -34
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +5 -0
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +217 -46
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +548 -0
- package/src/sdk/broker/state-version.ts +26 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +46 -5
- package/src/sdk/bus/index.ts +51 -4
- 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/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +11 -8
- package/src/sdk/cli/session-cli.ts +276 -6
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/client/discovery.ts +3 -1
- package/src/sdk/host/control/dispatch.ts +101 -37
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/host.ts +27 -2
- 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 +581 -43
- package/src/sdk/host/session-runtime.ts +655 -101
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +10 -2
- 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 +53 -2
- package/src/sdk/protocol/operation-registry.ts +17 -4
- package/src/sdk/router/session-router.ts +294 -154
- package/src/sdk/session.ts +440 -167
- package/src/sdk/startup-capability.ts +1 -0
- package/src/session/account-inventory.ts +27 -11
- package/src/session/agent-session.ts +1292 -180
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +433 -0
- package/src/session/internal/managed-session-storage.ts +153 -78
- 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 +342 -28
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- 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 +1268 -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 +1028 -109
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +11 -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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
5
|
import {
|
|
5
6
|
type Api,
|
|
6
7
|
type AssistantMessageEventStream,
|
|
@@ -55,8 +56,8 @@ function normalizeVllmApiKey(provider: string, apiKey: string | undefined): stri
|
|
|
55
56
|
|
|
56
57
|
import { registerOAuthProvider, unregisterOAuthProviders } from "@gajae-code/ai/utils/oauth";
|
|
57
58
|
import type { OAuthCredentials, OAuthLoginCallbacks } from "@gajae-code/ai/utils/oauth/types";
|
|
58
|
-
import { $pickCredentialEnv, $rotatingCredentialEnv, isRecord, logger } from "@gajae-code/utils";
|
|
59
|
-
import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
|
|
59
|
+
import { $pickCredentialEnv, $rotatingCredentialEnv, getAgentDir, isRecord, logger } from "@gajae-code/utils";
|
|
60
|
+
import { parseModelString, resolveProviderModelReference, splitSelectorThinkingSuffix } from "../config/model-resolver";
|
|
60
61
|
import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
|
|
61
62
|
import {
|
|
62
63
|
type ActiveProviderDescriptor,
|
|
@@ -65,10 +66,15 @@ import {
|
|
|
65
66
|
} from "../sdk/providers";
|
|
66
67
|
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
67
68
|
import type { ActiveSearchModelContext, WebSearchMode } from "../web/search/types";
|
|
68
|
-
import {
|
|
69
|
+
import { ConfigError, ConfigFile } from "./config-file";
|
|
69
70
|
import { isAuthenticated, kNoAuth } from "./model-auth";
|
|
70
71
|
import { type ConfiguredModelBindings, ModelBindingsApplier } from "./model-bindings-applier";
|
|
71
72
|
import { ModelDiscoveryManager, type ProviderDiscoveryState } from "./model-discovery-manager";
|
|
73
|
+
import {
|
|
74
|
+
loadAcceptedModelPresetProfiles,
|
|
75
|
+
type ModelPresetRegistryDependencies,
|
|
76
|
+
refreshModelPresetRegistryInBackground,
|
|
77
|
+
} from "./model-preset-registry";
|
|
72
78
|
|
|
73
79
|
export type { ProviderDiscoveryState, ProviderDiscoveryStatus } from "./model-discovery-manager";
|
|
74
80
|
|
|
@@ -86,6 +92,7 @@ import {
|
|
|
86
92
|
type ModelProfileDefinition,
|
|
87
93
|
mergeModelProfiles,
|
|
88
94
|
} from "./model-profiles";
|
|
95
|
+
import { normalizeModelSelectorValue } from "./model-selector-value";
|
|
89
96
|
import {
|
|
90
97
|
GJC_MODEL_ASSIGNMENT_TARGET_IDS,
|
|
91
98
|
type ModelOverride,
|
|
@@ -111,6 +118,7 @@ export type { CanonicalModelIndex, CanonicalModelRecord, CanonicalModelVariant,
|
|
|
111
118
|
export { isAuthenticated, kNoAuth };
|
|
112
119
|
|
|
113
120
|
const MAX_SESSION_CANONICAL_VARIANTS = 64;
|
|
121
|
+
const GENERATED_AUTH_HEADER = Symbol("generated-auth-header");
|
|
114
122
|
function firstPositiveDiscoveryNumber(...values: readonly unknown[]): number | undefined {
|
|
115
123
|
for (const value of values) {
|
|
116
124
|
const number =
|
|
@@ -134,15 +142,18 @@ function stripUrlQuery(value: string): string {
|
|
|
134
142
|
return value.slice(0, queryStart) + (fragmentStart < 0 ? "" : value.slice(fragmentStart));
|
|
135
143
|
}
|
|
136
144
|
|
|
137
|
-
function envAvailabilityFingerprint(): string {
|
|
138
|
-
|
|
139
|
-
.filter(
|
|
140
|
-
|
|
145
|
+
function envAvailabilityFingerprint(configuredNames?: ReadonlySet<string>): string {
|
|
146
|
+
const names = new Set(
|
|
147
|
+
Object.keys(process.env).filter(
|
|
148
|
+
name =>
|
|
141
149
|
/(?:_API_KEY|_OAUTH_TOKEN|_ACCESS_TOKEN)$/.test(name) ||
|
|
142
150
|
/^(?:GH_TOKEN|GITHUB_TOKEN|HF_TOKEN|COPILOT_GITHUB_TOKEN)$/.test(name),
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
for (const name of configuredNames ?? []) names.add(name);
|
|
154
|
+
return [...names]
|
|
155
|
+
.sort((left, right) => left.localeCompare(right))
|
|
156
|
+
.map(name => `${name}=${process.env[name] ?? ""}`)
|
|
146
157
|
.join("\u0000");
|
|
147
158
|
}
|
|
148
159
|
|
|
@@ -576,6 +587,7 @@ export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsCon
|
|
|
576
587
|
|
|
577
588
|
/** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */
|
|
578
589
|
interface ProviderOverride {
|
|
590
|
+
api?: Api;
|
|
579
591
|
baseUrl?: string;
|
|
580
592
|
headers?: Record<string, string>;
|
|
581
593
|
apiKey?: string;
|
|
@@ -584,6 +596,82 @@ interface ProviderOverride {
|
|
|
584
596
|
transport?: Model<Api>["transport"];
|
|
585
597
|
requestTransform?: ModelRequestTransform;
|
|
586
598
|
cacheRetention?: CacheRetention;
|
|
599
|
+
isOAuth?: boolean;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function hasMatchingRegistryTransport(left: Model<Api>, right: Model<Api> | ProviderOverride): boolean {
|
|
603
|
+
return (
|
|
604
|
+
left.baseUrl === right.baseUrl &&
|
|
605
|
+
isDeepStrictEqual(left.headers, right.headers) &&
|
|
606
|
+
left.transport === right.transport &&
|
|
607
|
+
isDeepStrictEqual(left.requestTransform, right.requestTransform) &&
|
|
608
|
+
left.cacheRetention === right.cacheRetention &&
|
|
609
|
+
left.isOAuth === right.isOAuth
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function commonRegistryTransportCompat(models: readonly Model<Api>[]): Model<Api>["compat"] | undefined {
|
|
614
|
+
if (models.length === 0) return undefined;
|
|
615
|
+
const records = models.map(model => model.compat as Record<string, unknown> | undefined);
|
|
616
|
+
const keys = new Set(records.flatMap(record => (record ? Object.keys(record) : [])));
|
|
617
|
+
const common: Record<string, unknown> = {};
|
|
618
|
+
for (const key of keys) {
|
|
619
|
+
const value = records[0]?.[key];
|
|
620
|
+
if (records.every(record => record !== undefined && isDeepStrictEqual(record[key], value))) common[key] = value;
|
|
621
|
+
}
|
|
622
|
+
return Object.keys(common).length > 0 ? (common as Model<Api>["compat"]) : undefined;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function registryModelMetadataWithoutApiSpecificFields(model: Model<Api>): Partial<Model<Api>> {
|
|
626
|
+
const metadata: Partial<Model<Api>> = {
|
|
627
|
+
name: model.name,
|
|
628
|
+
cost: model.cost,
|
|
629
|
+
contextWindow: model.contextWindow,
|
|
630
|
+
maxTokens: model.maxTokens,
|
|
631
|
+
};
|
|
632
|
+
if (model.longContextPricing !== undefined) metadata.longContextPricing = model.longContextPricing;
|
|
633
|
+
if (model.premiumMultiplier !== undefined) metadata.premiumMultiplier = model.premiumMultiplier;
|
|
634
|
+
if (model.priority !== undefined) metadata.priority = model.priority;
|
|
635
|
+
if (model.contextPromotionTarget !== undefined) metadata.contextPromotionTarget = model.contextPromotionTarget;
|
|
636
|
+
return metadata;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function registrySelectorResolvesToModel(selector: string, models: readonly Model<Api>[]): boolean {
|
|
640
|
+
if (models.some(model => model.id === selector || `${model.provider}/${model.id}` === selector)) return true;
|
|
641
|
+
const suffix = splitSelectorThinkingSuffix(selector);
|
|
642
|
+
const baseSelector = suffix.thinkingLevel === undefined ? selector : suffix.selector;
|
|
643
|
+
const parsed = parseModelString(baseSelector);
|
|
644
|
+
if (parsed) return models.some(model => model.provider === parsed.provider && model.id === parsed.id);
|
|
645
|
+
return models.some(model => model.id === baseSelector || model.id.endsWith(`/${baseSelector}`));
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function filterMaterializedRegistryProfiles(
|
|
649
|
+
profiles: ReadonlyMap<string, ModelProfileDefinition>,
|
|
650
|
+
models: readonly Model<Api>[],
|
|
651
|
+
dynamicProviders: ReadonlySet<string>,
|
|
652
|
+
): Map<string, ModelProfileDefinition> {
|
|
653
|
+
const filtered = new Map<string, ModelProfileDefinition>();
|
|
654
|
+
for (const [name, profile] of profiles) {
|
|
655
|
+
if (
|
|
656
|
+
profile.source === "registry" &&
|
|
657
|
+
Object.values(profile.modelMapping).some(selectorValue => {
|
|
658
|
+
const selectors = normalizeModelSelectorValue(selectorValue);
|
|
659
|
+
return (
|
|
660
|
+
selectors.length > 0 &&
|
|
661
|
+
!selectors.some(selector => {
|
|
662
|
+
if (registrySelectorResolvesToModel(selector, models)) return true;
|
|
663
|
+
const suffix = splitSelectorThinkingSuffix(selector);
|
|
664
|
+
const parsed = parseModelString(suffix.thinkingLevel ? suffix.selector : selector);
|
|
665
|
+
return parsed !== undefined && dynamicProviders.has(parsed.provider);
|
|
666
|
+
})
|
|
667
|
+
);
|
|
668
|
+
})
|
|
669
|
+
) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
filtered.set(name, profile);
|
|
673
|
+
}
|
|
674
|
+
return filtered;
|
|
587
675
|
}
|
|
588
676
|
|
|
589
677
|
const PROVIDER_BASE_URL_ENV_ALIASES: Record<string, readonly string[]> = {
|
|
@@ -665,7 +753,7 @@ export function mergeDiscoveredModel<TApi extends Api>(
|
|
|
665
753
|
return {
|
|
666
754
|
...model,
|
|
667
755
|
baseUrl: providerOverride?.baseUrl ?? model.baseUrl ?? existing.baseUrl,
|
|
668
|
-
headers: existing.headers
|
|
756
|
+
headers: mergeCaseInsensitiveHeaders(existing.headers, model.headers),
|
|
669
757
|
requestTransform: mergeRequestTransform(
|
|
670
758
|
mergeRequestTransform(existing.requestTransform, model.requestTransform),
|
|
671
759
|
providerOverride?.requestTransform,
|
|
@@ -677,7 +765,7 @@ export function mergeDiscoveredModel<TApi extends Api>(
|
|
|
677
765
|
return {
|
|
678
766
|
...model,
|
|
679
767
|
baseUrl: providerOverride.baseUrl ?? model.baseUrl,
|
|
680
|
-
headers:
|
|
768
|
+
headers: mergeCaseInsensitiveHeaders(model.headers, providerOverride.headers),
|
|
681
769
|
...(providerOverride.transport !== undefined ? { transport: providerOverride.transport } : {}),
|
|
682
770
|
requestTransform: mergeRequestTransform(model.requestTransform, providerOverride.requestTransform),
|
|
683
771
|
};
|
|
@@ -906,7 +994,11 @@ function resolveOAuthAccountIdForAccessToken(
|
|
|
906
994
|
authStorage: AuthStorage,
|
|
907
995
|
provider: string,
|
|
908
996
|
accessToken: string,
|
|
997
|
+
owner?: object,
|
|
909
998
|
): string | undefined {
|
|
999
|
+
if (authStorage.getEffectiveCredentialType(provider, undefined, owner ? { owner } : undefined) !== "oauth") {
|
|
1000
|
+
return undefined;
|
|
1001
|
+
}
|
|
910
1002
|
const oauthCredentials = getOAuthCredentialsForProvider(authStorage, provider);
|
|
911
1003
|
const matchingCredential = oauthCredentials.find(credential => credential.access === accessToken);
|
|
912
1004
|
if (matchingCredential) {
|
|
@@ -1020,6 +1112,15 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
|
|
|
1020
1112
|
}
|
|
1021
1113
|
if (override.headers) {
|
|
1022
1114
|
result.headers = { ...model.headers, ...override.headers };
|
|
1115
|
+
const explicitAuthKey = Object.keys(override.headers).find(key => key.toLowerCase() === "authorization");
|
|
1116
|
+
if (explicitAuthKey !== undefined) {
|
|
1117
|
+
const explicitAuthValue = override.headers[explicitAuthKey];
|
|
1118
|
+
deleteHeaderCaseInsensitive(result.headers, "Authorization");
|
|
1119
|
+
result.headers[explicitAuthKey] = explicitAuthValue;
|
|
1120
|
+
delete (result.headers as Record<string, string> & { [GENERATED_AUTH_HEADER]?: boolean })[
|
|
1121
|
+
GENERATED_AUTH_HEADER
|
|
1122
|
+
];
|
|
1123
|
+
}
|
|
1023
1124
|
}
|
|
1024
1125
|
result.compat = mergeCompat(model.compat, override.compat);
|
|
1025
1126
|
return enrichModelThinking(result);
|
|
@@ -1091,6 +1192,7 @@ type CustomModelOverlay = {
|
|
|
1091
1192
|
wireModelId?: string;
|
|
1092
1193
|
requestTransform?: ModelRequestTransform;
|
|
1093
1194
|
cacheRetention?: CacheRetention;
|
|
1195
|
+
transport?: Model<Api>["transport"];
|
|
1094
1196
|
isOAuth?: boolean;
|
|
1095
1197
|
};
|
|
1096
1198
|
|
|
@@ -1100,7 +1202,36 @@ function mergeCustomModelHeaders(
|
|
|
1100
1202
|
authHeader: boolean | undefined,
|
|
1101
1203
|
apiKeyConfig: string | undefined,
|
|
1102
1204
|
): Record<string, string> | undefined {
|
|
1103
|
-
|
|
1205
|
+
const merged = mergeCaseInsensitiveHeaders(providerHeaders, modelHeaders);
|
|
1206
|
+
return mergeAuthHeader(merged, authHeader, apiKeyConfig);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
function mergeCaseInsensitiveHeaders(
|
|
1210
|
+
baseHeaders: Record<string, string> | undefined,
|
|
1211
|
+
overrideHeaders: Record<string, string> | undefined,
|
|
1212
|
+
): (Record<string, string> & { [GENERATED_AUTH_HEADER]?: string }) | undefined {
|
|
1213
|
+
const merged: Record<string, string> = {};
|
|
1214
|
+
for (const [key, value] of Object.entries(baseHeaders ?? {})) {
|
|
1215
|
+
if (key.toLowerCase() === "authorization") deleteHeaderCaseInsensitive(merged, "Authorization");
|
|
1216
|
+
merged[key] = value;
|
|
1217
|
+
}
|
|
1218
|
+
for (const [key, value] of Object.entries(overrideHeaders ?? {})) {
|
|
1219
|
+
if (key.toLowerCase() === "authorization") deleteHeaderCaseInsensitive(merged, "Authorization");
|
|
1220
|
+
merged[key] = value;
|
|
1221
|
+
}
|
|
1222
|
+
const baseGenerated = (baseHeaders as (Record<string, string> & { [GENERATED_AUTH_HEADER]?: string }) | undefined)?.[
|
|
1223
|
+
GENERATED_AUTH_HEADER
|
|
1224
|
+
];
|
|
1225
|
+
const overrideGenerated = (
|
|
1226
|
+
overrideHeaders as (Record<string, string> & { [GENERATED_AUTH_HEADER]?: string }) | undefined
|
|
1227
|
+
)?.[GENERATED_AUTH_HEADER];
|
|
1228
|
+
const survivingGenerated =
|
|
1229
|
+
headerValue(overrideHeaders, "Authorization") !== undefined ? overrideGenerated : baseGenerated;
|
|
1230
|
+
if (survivingGenerated !== undefined && headerValue(merged, "Authorization") === survivingGenerated) {
|
|
1231
|
+
(merged as Record<string, string> & { [GENERATED_AUTH_HEADER]?: string })[GENERATED_AUTH_HEADER] =
|
|
1232
|
+
survivingGenerated;
|
|
1233
|
+
}
|
|
1234
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
1104
1235
|
}
|
|
1105
1236
|
|
|
1106
1237
|
function mergeAuthHeader(
|
|
@@ -1109,11 +1240,41 @@ function mergeAuthHeader(
|
|
|
1109
1240
|
apiKeyConfig: string | undefined,
|
|
1110
1241
|
): Record<string, string> | undefined {
|
|
1111
1242
|
const nextHeaders = headers && Object.keys(headers).length > 0 ? { ...headers } : undefined;
|
|
1112
|
-
if (!authHeader || !apiKeyConfig) {
|
|
1243
|
+
if (!authHeader || !apiKeyConfig || headerValue(nextHeaders, "Authorization") !== undefined) {
|
|
1113
1244
|
return nextHeaders;
|
|
1114
1245
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1246
|
+
return apiKeyConfig
|
|
1247
|
+
? ({
|
|
1248
|
+
...nextHeaders,
|
|
1249
|
+
Authorization: `Bearer ${apiKeyConfig}`,
|
|
1250
|
+
[GENERATED_AUTH_HEADER]: `Bearer ${apiKeyConfig}`,
|
|
1251
|
+
} as Record<string, string>)
|
|
1252
|
+
: nextHeaders;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
function headerValue(headers: Record<string, string> | undefined, name: string): string | undefined {
|
|
1256
|
+
const lowerName = name.toLowerCase();
|
|
1257
|
+
const key = Object.keys(headers ?? {}).find(candidate => candidate.toLowerCase() === lowerName);
|
|
1258
|
+
return key === undefined ? undefined : headers?.[key];
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
function deleteHeaderCaseInsensitive(headers: Record<string, string>, name: string): void {
|
|
1262
|
+
const lowerName = name.toLowerCase();
|
|
1263
|
+
for (const key of Object.keys(headers)) {
|
|
1264
|
+
if (key.toLowerCase() === lowerName) delete headers[key];
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function ownsOnlyGeneratedAuthorization(headers: Record<string, string>, generated: string | undefined): boolean {
|
|
1269
|
+
if (generated === undefined) return false;
|
|
1270
|
+
const entries = Object.entries(headers).filter(([key]) => key.toLowerCase() === "authorization");
|
|
1271
|
+
return entries.length === 1 && entries[0]![1] === generated;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
function deleteAuthorizationValue(headers: Record<string, string>, value: string): void {
|
|
1275
|
+
for (const key of Object.keys(headers)) {
|
|
1276
|
+
if (key.toLowerCase() === "authorization" && headers[key] === value) delete headers[key];
|
|
1277
|
+
}
|
|
1117
1278
|
}
|
|
1118
1279
|
|
|
1119
1280
|
/**
|
|
@@ -1143,6 +1304,7 @@ function buildCustomModelOverlay(
|
|
|
1143
1304
|
providerRequestTransform: ModelRequestTransform | undefined,
|
|
1144
1305
|
providerAuth: ProviderAuthMode | undefined,
|
|
1145
1306
|
providerCacheRetention: CacheRetention | undefined,
|
|
1307
|
+
providerTransport: Model<Api>["transport"] | undefined,
|
|
1146
1308
|
modelDef: CustomModelDefinitionLike,
|
|
1147
1309
|
): CustomModelOverlay | undefined {
|
|
1148
1310
|
const api = modelDef.api ?? providerApi;
|
|
@@ -1171,6 +1333,7 @@ function buildCustomModelOverlay(
|
|
|
1171
1333
|
contextPromotionTarget: modelDef.contextPromotionTarget,
|
|
1172
1334
|
premiumMultiplier: modelDef.premiumMultiplier,
|
|
1173
1335
|
cacheRetention: modelDef.cacheRetention ?? providerCacheRetention,
|
|
1336
|
+
transport: providerTransport,
|
|
1174
1337
|
isOAuth: resolveCustomModelIsOAuth(api, providerAuth),
|
|
1175
1338
|
};
|
|
1176
1339
|
}
|
|
@@ -1306,6 +1469,7 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
|
|
|
1306
1469
|
wireModelId: resolvedModel.wireModelId,
|
|
1307
1470
|
requestTransform: resolvedModel.requestTransform,
|
|
1308
1471
|
cacheRetention: resolvedModel.cacheRetention ?? reference?.cacheRetention,
|
|
1472
|
+
transport: resolvedModel.transport,
|
|
1309
1473
|
premiumMultiplier: resolvedModel.premiumMultiplier,
|
|
1310
1474
|
isOAuth: resolvedModel.isOAuth,
|
|
1311
1475
|
} as Model<Api>);
|
|
@@ -1366,6 +1530,8 @@ interface ConfiguredDiscoveryResult {
|
|
|
1366
1530
|
clearPublishedModelIds?: readonly string[];
|
|
1367
1531
|
}
|
|
1368
1532
|
|
|
1533
|
+
type ProviderRefreshFence = { providerId: string; generation: number } | { generations: ReadonlyMap<string, number> };
|
|
1534
|
+
|
|
1369
1535
|
/**
|
|
1370
1536
|
* Model registry - loads and manages models, resolves API keys via AuthStorage.
|
|
1371
1537
|
*/
|
|
@@ -1384,6 +1550,7 @@ export class ModelRegistry {
|
|
|
1384
1550
|
#sessionCanonicalVariants = new Map<string, string>();
|
|
1385
1551
|
#customProviderApiKeys: Map<string, string> = new Map();
|
|
1386
1552
|
#customProviderApiKeyEnvNames: Map<string, string> = new Map();
|
|
1553
|
+
#customProviderAuthHeaders: Map<string, boolean> = new Map();
|
|
1387
1554
|
#providerWebSearchModes: Map<string, WebSearchMode> = new Map();
|
|
1388
1555
|
#keylessProviders: Set<string> = new Set();
|
|
1389
1556
|
#optionalAuthProviders: Set<string> = new Set();
|
|
@@ -1420,21 +1587,42 @@ export class ModelRegistry {
|
|
|
1420
1587
|
#configError: ConfigError | undefined = undefined;
|
|
1421
1588
|
#modelsConfigFile: ConfigFile<ModelsConfig>;
|
|
1422
1589
|
#settings: Pick<Settings, "get" | "getGlobal">;
|
|
1590
|
+
readonly #authStorageConfigOwner: object = {};
|
|
1591
|
+
#disposeAuthStorageFallbackResolver: (() => void) | undefined;
|
|
1423
1592
|
#lastStaticLoadMtime: number | null = null;
|
|
1424
1593
|
#lastStaticLoadEnvironmentFingerprint: string | undefined;
|
|
1594
|
+
#lastModelPresetRegistryFingerprint: string | undefined;
|
|
1595
|
+
#loadedModelPresetRegistryManifestSha256: string | undefined;
|
|
1596
|
+
#modelPresetRegistryAgentDir: string;
|
|
1597
|
+
#modelPresetRegistryDependencies: Omit<ModelPresetRegistryDependencies, "agentDir">;
|
|
1598
|
+
#cancelModelPresetRegistryRefresh: (() => Promise<void>) | undefined;
|
|
1599
|
+
#disposePromise: Promise<void> | undefined;
|
|
1600
|
+
#unsubscribeAuthGeneration: (() => void) | undefined;
|
|
1425
1601
|
#staticModelsLoaded = false;
|
|
1426
1602
|
#lastDisabledProviderKey: string | undefined;
|
|
1427
1603
|
#registeredProviderSources: Set<string> = new Set();
|
|
1428
1604
|
#cacheDbPath?: string;
|
|
1429
1605
|
#suppressedSelectors: Map<string, number> = new Map();
|
|
1430
1606
|
#backgroundRefresh?: Promise<void>;
|
|
1607
|
+
#catalogMutationTail: Promise<void> = Promise.resolve();
|
|
1608
|
+
#pendingCatalogMutations = 0;
|
|
1609
|
+
#catalogRefreshGeneration = 0;
|
|
1610
|
+
#providerRefreshGenerations = new Map<string, number>();
|
|
1611
|
+
#disposed = false;
|
|
1431
1612
|
// Runtime extension model overlays — persist across refresh() cycles so that
|
|
1432
1613
|
// models registered by extensions survive the model selector's offline reload.
|
|
1433
1614
|
#runtimeModelOverlays: CustomModelOverlay[] = [];
|
|
1434
1615
|
#runtimeProviderApiKeys: Map<string, string> = new Map();
|
|
1616
|
+
#runtimeProviderResolvedApiKeys: Map<string, string> = new Map();
|
|
1617
|
+
#runtimeProviderCredentialInstalled: Set<string> = new Set();
|
|
1618
|
+
#runtimeProviderApiKeyEnvNames: Map<string, string> = new Map();
|
|
1435
1619
|
#runtimeProviderOverrides: Map<string, ProviderOverride> = new Map();
|
|
1620
|
+
#runtimeProviderAuthHeaders: Map<string, boolean> = new Map();
|
|
1621
|
+
#generatedAuthHeaderProviders: Set<string> = new Set();
|
|
1622
|
+
#generatedAuthHeaders: WeakMap<Model<Api>, { authorization?: string; apiKey?: string }> = new WeakMap();
|
|
1436
1623
|
#runtimeProvidersBySource: Map<string, Set<string>> = new Map();
|
|
1437
1624
|
#runtimeProviderSourceByName: Map<string, string> = new Map();
|
|
1625
|
+
#registryModelKeys: Set<string> = new Set();
|
|
1438
1626
|
#rebuildPending: boolean = false;
|
|
1439
1627
|
#rebuildSuspended: number = 0;
|
|
1440
1628
|
#configuredApiKeyEnvNames: Set<string> = new Set();
|
|
@@ -1448,18 +1636,77 @@ export class ModelRegistry {
|
|
|
1448
1636
|
readonly authStorage: AuthStorage,
|
|
1449
1637
|
modelsPath?: string,
|
|
1450
1638
|
registrySettings?: Pick<Settings, "get" | "getGlobal">,
|
|
1639
|
+
modelPresetRegistryDependencies: ModelPresetRegistryDependencies = {},
|
|
1451
1640
|
) {
|
|
1452
1641
|
this.#settings = registrySettings ?? settings;
|
|
1642
|
+
const configuredAgentDir = path.resolve(modelPresetRegistryDependencies.agentDir ?? getAgentDir());
|
|
1453
1643
|
this.#modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
|
|
1644
|
+
this.#modelPresetRegistryAgentDir = modelPresetRegistryDependencies.agentDir
|
|
1645
|
+
? configuredAgentDir
|
|
1646
|
+
: modelsPath && path.isAbsolute(modelsPath)
|
|
1647
|
+
? path.dirname(modelsPath)
|
|
1648
|
+
: configuredAgentDir;
|
|
1649
|
+
const { agentDir: _agentDir, ...registryDependencies } = modelPresetRegistryDependencies;
|
|
1650
|
+
this.#modelPresetRegistryDependencies = registryDependencies;
|
|
1454
1651
|
this.#cacheDbPath = modelsPath ? path.join(path.dirname(modelsPath), "models.db") : undefined;
|
|
1455
1652
|
// Set up fallback resolver for custom provider API keys
|
|
1456
|
-
this.authStorage.setFallbackResolver(provider => {
|
|
1653
|
+
this.#disposeAuthStorageFallbackResolver = this.authStorage.setFallbackResolver(provider => {
|
|
1457
1654
|
const keyConfig = this.#customProviderApiKeys.get(provider);
|
|
1458
1655
|
return keyConfig;
|
|
1459
|
-
});
|
|
1460
|
-
this.authStorage.onGenerationChanged(() => this.#invalidateAvailableModels());
|
|
1656
|
+
}, this.#authStorageConfigOwner);
|
|
1657
|
+
this.#unsubscribeAuthGeneration = this.authStorage.onGenerationChanged(() => this.#invalidateAvailableModels());
|
|
1461
1658
|
// Load models synchronously in constructor
|
|
1462
1659
|
this.#loadModels();
|
|
1660
|
+
this.#cancelModelPresetRegistryRefresh = refreshModelPresetRegistryInBackground(
|
|
1661
|
+
{
|
|
1662
|
+
...this.#modelPresetRegistryDependencies,
|
|
1663
|
+
agentDir: this.#modelPresetRegistryAgentDir,
|
|
1664
|
+
knownManifestSha256: this.#loadedModelPresetRegistryManifestSha256,
|
|
1665
|
+
},
|
|
1666
|
+
() => {
|
|
1667
|
+
void this.#enqueueCatalogMutation(() => {
|
|
1668
|
+
if (this.#disposed) return;
|
|
1669
|
+
this.#reloadStaticModels();
|
|
1670
|
+
this.#modelBindingsApplier.apply();
|
|
1671
|
+
this.#notifyCatalogChanged();
|
|
1672
|
+
}).catch(() => undefined);
|
|
1673
|
+
},
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
dispose(): Promise<void> {
|
|
1678
|
+
if (this.#disposePromise) return this.#disposePromise;
|
|
1679
|
+
this.#disposed = true;
|
|
1680
|
+
this.#catalogRefreshGeneration++;
|
|
1681
|
+
const awaitRefreshDisposal = this.#cancelModelPresetRegistryRefresh?.() ?? Promise.resolve();
|
|
1682
|
+
this.#cancelModelPresetRegistryRefresh = undefined;
|
|
1683
|
+
this.#unsubscribeAuthGeneration?.();
|
|
1684
|
+
this.#unsubscribeAuthGeneration = undefined;
|
|
1685
|
+
this.authStorage.clearConfigApiKeys(this.#authStorageConfigOwner);
|
|
1686
|
+
this.#disposeAuthStorageFallbackResolver?.();
|
|
1687
|
+
this.#disposeAuthStorageFallbackResolver = undefined;
|
|
1688
|
+
this.#catalogChangeListeners.clear();
|
|
1689
|
+
this.#disposePromise = awaitRefreshDisposal;
|
|
1690
|
+
return this.#disposePromise;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
#enqueueCatalogMutation(operation: () => void | Promise<void>): Promise<void> {
|
|
1694
|
+
this.#pendingCatalogMutations++;
|
|
1695
|
+
let run: Promise<void>;
|
|
1696
|
+
if (this.#pendingCatalogMutations === 1) {
|
|
1697
|
+
try {
|
|
1698
|
+
run = Promise.resolve(operation());
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
run = Promise.reject(error);
|
|
1701
|
+
}
|
|
1702
|
+
} else {
|
|
1703
|
+
run = this.#catalogMutationTail.then(operation);
|
|
1704
|
+
}
|
|
1705
|
+
const completion = run.finally(() => {
|
|
1706
|
+
this.#pendingCatalogMutations--;
|
|
1707
|
+
});
|
|
1708
|
+
this.#catalogMutationTail = completion.catch(() => undefined);
|
|
1709
|
+
return completion;
|
|
1463
1710
|
}
|
|
1464
1711
|
|
|
1465
1712
|
onCatalogChanged(listener: () => void): () => void {
|
|
@@ -1471,22 +1718,34 @@ export class ModelRegistry {
|
|
|
1471
1718
|
|
|
1472
1719
|
/** Replace the read-only settings snapshot used by profile-scoped resolution. */
|
|
1473
1720
|
setScopedSettings(settingsReader: Pick<Settings, "get" | "getGlobal">): void {
|
|
1721
|
+
this.#catalogRefreshGeneration++;
|
|
1474
1722
|
this.#settings = settingsReader;
|
|
1723
|
+
this.#staticModelsLoaded = false;
|
|
1724
|
+
this.#reloadStaticModels();
|
|
1725
|
+
this.#rebuildCanonicalIndex();
|
|
1475
1726
|
}
|
|
1476
1727
|
|
|
1477
1728
|
/**
|
|
1478
|
-
* Reload models from disk (
|
|
1729
|
+
* Reload models from disk (embedded + accepted registry + custom from models.yml).
|
|
1479
1730
|
*/
|
|
1480
1731
|
async refresh(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
|
|
1481
|
-
this.#
|
|
1482
|
-
|
|
1483
|
-
this.#
|
|
1484
|
-
this.#
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
this.#
|
|
1489
|
-
|
|
1732
|
+
if (this.#disposed) return;
|
|
1733
|
+
await this.#enqueueCatalogMutation(async () => {
|
|
1734
|
+
if (this.#disposed) return;
|
|
1735
|
+
const refreshGeneration = ++this.#catalogRefreshGeneration;
|
|
1736
|
+
const providerRefreshFence: ProviderRefreshFence = {
|
|
1737
|
+
generations: new Map(this.#providerRefreshGenerations),
|
|
1738
|
+
};
|
|
1739
|
+
this.#suspendRebuild();
|
|
1740
|
+
try {
|
|
1741
|
+
this.#reloadStaticModels();
|
|
1742
|
+
this.#suppressedSelectors.clear();
|
|
1743
|
+
await this.#refreshRuntimeDiscoveries(strategy, undefined, refreshGeneration, providerRefreshFence);
|
|
1744
|
+
if (refreshGeneration === this.#catalogRefreshGeneration) this.#modelBindingsApplier.apply();
|
|
1745
|
+
} finally {
|
|
1746
|
+
this.#resumeRebuild();
|
|
1747
|
+
}
|
|
1748
|
+
});
|
|
1490
1749
|
}
|
|
1491
1750
|
|
|
1492
1751
|
refreshInBackground(strategy: ModelRefreshStrategy = "online-if-uncached"): void {
|
|
@@ -1508,19 +1767,34 @@ export class ModelRegistry {
|
|
|
1508
1767
|
}
|
|
1509
1768
|
|
|
1510
1769
|
async refreshProvider(providerId: string, strategy: ModelRefreshStrategy = "online"): Promise<void> {
|
|
1511
|
-
this.#
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1770
|
+
if (this.#disposed) return;
|
|
1771
|
+
const providerRefreshGeneration = (this.#providerRefreshGenerations.get(providerId) ?? 0) + 1;
|
|
1772
|
+
this.#providerRefreshGenerations.set(providerId, providerRefreshGeneration);
|
|
1773
|
+
await this.#enqueueCatalogMutation(async () => {
|
|
1774
|
+
if (this.#disposed) return;
|
|
1775
|
+
const refreshGeneration = this.#catalogRefreshGeneration;
|
|
1776
|
+
this.#suspendRebuild();
|
|
1777
|
+
try {
|
|
1778
|
+
this.#reloadStaticModels();
|
|
1779
|
+
for (const selector of this.#suppressedSelectors.keys()) {
|
|
1780
|
+
if (selector.startsWith(`${providerId}/`)) {
|
|
1781
|
+
this.#suppressedSelectors.delete(selector);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
await this.#refreshRuntimeDiscoveries(strategy, new Set([providerId]), refreshGeneration, {
|
|
1785
|
+
providerId,
|
|
1786
|
+
generation: providerRefreshGeneration,
|
|
1787
|
+
});
|
|
1788
|
+
if (
|
|
1789
|
+
refreshGeneration === this.#catalogRefreshGeneration &&
|
|
1790
|
+
this.#providerRefreshGenerations.get(providerId) === providerRefreshGeneration
|
|
1791
|
+
) {
|
|
1792
|
+
this.#modelBindingsApplier.apply();
|
|
1517
1793
|
}
|
|
1794
|
+
} finally {
|
|
1795
|
+
this.#resumeRebuild();
|
|
1518
1796
|
}
|
|
1519
|
-
|
|
1520
|
-
this.#modelBindingsApplier.apply();
|
|
1521
|
-
} finally {
|
|
1522
|
-
this.#resumeRebuild();
|
|
1523
|
-
}
|
|
1797
|
+
});
|
|
1524
1798
|
}
|
|
1525
1799
|
|
|
1526
1800
|
admitCachedProviderForStoredLiteralCredential(providerId: string, selector: AuthCredentialSelector): boolean {
|
|
@@ -1564,7 +1838,11 @@ export class ModelRegistry {
|
|
|
1564
1838
|
this.#discoveryManager.providerIds().has(providerId) ||
|
|
1565
1839
|
PROVIDER_DESCRIPTORS.some(descriptor => descriptor.providerId === providerId);
|
|
1566
1840
|
if (!supportsDiscovery) return undefined;
|
|
1567
|
-
const authEvidence = this.authStorage.getStoredLiteralApiKeyEvidenceGeneration(
|
|
1841
|
+
const authEvidence = this.authStorage.getStoredLiteralApiKeyEvidenceGeneration(
|
|
1842
|
+
providerId,
|
|
1843
|
+
selector,
|
|
1844
|
+
this.#authStorageConfigOwner,
|
|
1845
|
+
);
|
|
1568
1846
|
if (!authEvidence) return undefined;
|
|
1569
1847
|
return [
|
|
1570
1848
|
...this.#loadCachedStandardProviderModels(providerId, authEvidence),
|
|
@@ -1608,11 +1886,22 @@ export class ModelRegistry {
|
|
|
1608
1886
|
const currentMtime = this.#modelsConfigFile.getMtimeMs();
|
|
1609
1887
|
const disabledProviderKey = [...getDisabledProviderIdsFromSettings(this.#settings)].sort().join("\u0000");
|
|
1610
1888
|
const environmentFingerprint = this.#getStaticLoadEnvironmentFingerprint();
|
|
1889
|
+
const acceptedPresets = loadAcceptedModelPresetProfiles(
|
|
1890
|
+
this.#modelPresetRegistryAgentDir,
|
|
1891
|
+
this.#modelPresetRegistryDependencies,
|
|
1892
|
+
);
|
|
1893
|
+
const modelPresetRegistryFingerprint = JSON.stringify({
|
|
1894
|
+
revision: acceptedPresets.revision,
|
|
1895
|
+
manifestSha256: acceptedPresets.manifestSha256,
|
|
1896
|
+
disabled: acceptedPresets.disabled,
|
|
1897
|
+
error: acceptedPresets.error,
|
|
1898
|
+
});
|
|
1611
1899
|
if (
|
|
1612
1900
|
this.#staticModelsLoaded &&
|
|
1613
1901
|
currentMtime === this.#lastStaticLoadMtime &&
|
|
1614
1902
|
disabledProviderKey === this.#lastDisabledProviderKey &&
|
|
1615
|
-
environmentFingerprint === this.#lastStaticLoadEnvironmentFingerprint
|
|
1903
|
+
environmentFingerprint === this.#lastStaticLoadEnvironmentFingerprint &&
|
|
1904
|
+
modelPresetRegistryFingerprint === this.#lastModelPresetRegistryFingerprint
|
|
1616
1905
|
) {
|
|
1617
1906
|
// models.json and settings-derived implicit provider state are unchanged.
|
|
1618
1907
|
return;
|
|
@@ -1620,6 +1909,7 @@ export class ModelRegistry {
|
|
|
1620
1909
|
this.#modelsConfigFile.invalidate();
|
|
1621
1910
|
this.#customProviderApiKeys.clear();
|
|
1622
1911
|
this.#customProviderApiKeyEnvNames.clear();
|
|
1912
|
+
this.#customProviderAuthHeaders.clear();
|
|
1623
1913
|
this.#providerWebSearchModes.clear();
|
|
1624
1914
|
this.#keylessProviders.clear();
|
|
1625
1915
|
this.#optionalAuthProviders.clear();
|
|
@@ -1631,7 +1921,7 @@ export class ModelRegistry {
|
|
|
1631
1921
|
// Drop config-sourced apiKeys from AuthStorage before reload; entries
|
|
1632
1922
|
// removed from models.yml must actually disappear from the resolver, not
|
|
1633
1923
|
// linger from the previous parse. The post-load setters below repopulate.
|
|
1634
|
-
this.authStorage.clearConfigApiKeys();
|
|
1924
|
+
this.authStorage.clearConfigApiKeys(this.#authStorageConfigOwner);
|
|
1635
1925
|
// Runtime provider keys are reapplied after #loadModels so they retain
|
|
1636
1926
|
// registration-time precedence over colliding static provider keys.
|
|
1637
1927
|
this.#providerOverrides.clear();
|
|
@@ -1641,10 +1931,70 @@ export class ModelRegistry {
|
|
|
1641
1931
|
this.#configError = undefined;
|
|
1642
1932
|
this.#loadModels();
|
|
1643
1933
|
for (const [provider, apiKeyConfig] of this.#runtimeProviderApiKeys) {
|
|
1644
|
-
const resolved =
|
|
1934
|
+
const resolved = this.#runtimeProviderApiKeyEnvNames.has(provider)
|
|
1935
|
+
? $rotatingCredentialEnv(this.#runtimeProviderApiKeyEnvNames.get(provider)!)
|
|
1936
|
+
: resolveApiKeyConfig(apiKeyConfig);
|
|
1937
|
+
if (!resolved) {
|
|
1938
|
+
this.#runtimeProviderCredentialInstalled.delete(provider);
|
|
1939
|
+
const authHeader = this.#runtimeProviderAuthHeaders.get(provider);
|
|
1940
|
+
if (authHeader === true) {
|
|
1941
|
+
this.#runtimeModelOverlays = this.#runtimeModelOverlays.map(overlay => {
|
|
1942
|
+
if (overlay.provider !== provider) return overlay;
|
|
1943
|
+
const headers = { ...(overlay.headers ?? {}) } as Record<string, string> & {
|
|
1944
|
+
[GENERATED_AUTH_HEADER]?: string;
|
|
1945
|
+
};
|
|
1946
|
+
const generated = headers[GENERATED_AUTH_HEADER];
|
|
1947
|
+
if (typeof generated === "string" && headers.Authorization === generated)
|
|
1948
|
+
delete headers.Authorization;
|
|
1949
|
+
delete headers[GENERATED_AUTH_HEADER];
|
|
1950
|
+
return { ...overlay, headers };
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
const override = this.#runtimeProviderOverrides.get(provider);
|
|
1954
|
+
if (override) this.#runtimeProviderOverrides.set(provider, { ...override, apiKey: "" });
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
this.#customProviderApiKeys.set(provider, resolved);
|
|
1958
|
+
this.#runtimeProviderResolvedApiKeys.set(provider, resolved);
|
|
1959
|
+
this.#runtimeProviderCredentialInstalled.add(provider);
|
|
1960
|
+
this.authStorage.setConfigApiKey(provider, resolved, { owner: this.#authStorageConfigOwner });
|
|
1961
|
+
const override = this.#runtimeProviderOverrides.get(provider);
|
|
1962
|
+
if (override) this.#runtimeProviderOverrides.set(provider, { ...override, apiKey: resolved });
|
|
1963
|
+
const authHeader = this.#runtimeProviderAuthHeaders.get(provider);
|
|
1964
|
+
if (authHeader === true) {
|
|
1965
|
+
this.#runtimeModelOverlays = this.#runtimeModelOverlays.map(overlay => {
|
|
1966
|
+
if (overlay.provider !== provider) return overlay;
|
|
1967
|
+
const headers = { ...(overlay.headers ?? {}) } as Record<string, string> & {
|
|
1968
|
+
[GENERATED_AUTH_HEADER]?: string;
|
|
1969
|
+
};
|
|
1970
|
+
const generated = headers[GENERATED_AUTH_HEADER];
|
|
1971
|
+
const hadAuthorization = headerValue(headers, "Authorization") !== undefined;
|
|
1972
|
+
const ownsAuthorization = ownsOnlyGeneratedAuthorization(headers, generated);
|
|
1973
|
+
if (ownsAuthorization) deleteHeaderCaseInsensitive(headers, "Authorization");
|
|
1974
|
+
if (resolved && (ownsAuthorization || !hadAuthorization))
|
|
1975
|
+
return {
|
|
1976
|
+
...overlay,
|
|
1977
|
+
headers: {
|
|
1978
|
+
...headers,
|
|
1979
|
+
Authorization: `Bearer ${resolved}`,
|
|
1980
|
+
[GENERATED_AUTH_HEADER]: `Bearer ${resolved}`,
|
|
1981
|
+
},
|
|
1982
|
+
};
|
|
1983
|
+
delete headers[GENERATED_AUTH_HEADER];
|
|
1984
|
+
return { ...overlay, headers };
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
this.#loadModels();
|
|
1989
|
+
for (const [provider, apiKeyConfig] of this.#runtimeProviderApiKeys) {
|
|
1990
|
+
const resolved = this.#runtimeProviderApiKeyEnvNames.has(provider)
|
|
1991
|
+
? $rotatingCredentialEnv(this.#runtimeProviderApiKeyEnvNames.get(provider)!)
|
|
1992
|
+
: resolveApiKeyConfig(apiKeyConfig);
|
|
1645
1993
|
if (!resolved) continue;
|
|
1646
1994
|
this.#customProviderApiKeys.set(provider, resolved);
|
|
1647
|
-
this.
|
|
1995
|
+
this.#runtimeProviderResolvedApiKeys.set(provider, resolved);
|
|
1996
|
+
this.#runtimeProviderCredentialInstalled.add(provider);
|
|
1997
|
+
this.authStorage.setConfigApiKey(provider, resolved, { owner: this.#authStorageConfigOwner });
|
|
1648
1998
|
}
|
|
1649
1999
|
this.#lastDisabledProviderKey = disabledProviderKey;
|
|
1650
2000
|
}
|
|
@@ -1670,7 +2020,6 @@ export class ModelRegistry {
|
|
|
1670
2020
|
profiles,
|
|
1671
2021
|
error: configError,
|
|
1672
2022
|
} = this.#loadCustomModels();
|
|
1673
|
-
this.#configError = configError;
|
|
1674
2023
|
this.#keylessProviders = keylessProviders;
|
|
1675
2024
|
this.#discoveryManager.setProviders(discoverableProviders);
|
|
1676
2025
|
this.#configuredProviderIds = new Set(configuredProviders);
|
|
@@ -1681,25 +2030,59 @@ export class ModelRegistry {
|
|
|
1681
2030
|
this.#codexContextWindowOverrides = this.#collectCodexContextWindowOverrides();
|
|
1682
2031
|
this.#equivalenceConfig = equivalence;
|
|
1683
2032
|
this.#modelBindingsApplier.setBindings(modelBindings);
|
|
1684
|
-
|
|
2033
|
+
const acceptedPresets = loadAcceptedModelPresetProfiles(
|
|
2034
|
+
this.#modelPresetRegistryAgentDir,
|
|
2035
|
+
this.#modelPresetRegistryDependencies,
|
|
2036
|
+
);
|
|
2037
|
+
const acceptedRegistryError = acceptedPresets.error
|
|
2038
|
+
? new ConfigError("model-preset-registry", undefined, {
|
|
2039
|
+
err: new Error(acceptedPresets.error),
|
|
2040
|
+
stage: "Registry",
|
|
2041
|
+
})
|
|
2042
|
+
: undefined;
|
|
2043
|
+
this.#configError = configError ?? acceptedRegistryError;
|
|
1685
2044
|
|
|
1686
2045
|
this.#addImplicitDiscoverableProviders(configuredProviders);
|
|
1687
2046
|
const builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
|
|
2047
|
+
const registryModels = this.#applyHardcodedModelPolicies(acceptedPresets.presets);
|
|
2048
|
+
const acceptedRegistryModelKeys = new Set(registryModels.map(model => `${model.provider}\u0000${model.id}`));
|
|
1688
2049
|
const cachedStandardModels = this.#applyHardcodedModelPolicies(this.#loadCachedStandardProviderModels());
|
|
1689
2050
|
const cachedDiscoveries = this.#applyHardcodedModelPolicies(this.#loadCachedDiscoverableModels());
|
|
1690
|
-
const
|
|
2051
|
+
const resolvedProviderCatalog = this.#mergeResolvedModels(
|
|
1691
2052
|
this.#mergeResolvedModels(builtInModels, cachedStandardModels),
|
|
1692
2053
|
cachedDiscoveries,
|
|
1693
2054
|
);
|
|
2055
|
+
const resolvedDefaults = this.#mergeRegistryModelMetadata(resolvedProviderCatalog, registryModels, overrides);
|
|
2056
|
+
const materializedRegistryModelKeys = new Set(
|
|
2057
|
+
resolvedDefaults
|
|
2058
|
+
.map(model => `${model.provider}\u0000${model.id}`)
|
|
2059
|
+
.filter(key => acceptedRegistryModelKeys.has(key)),
|
|
2060
|
+
);
|
|
2061
|
+
this.#registryModelKeys = materializedRegistryModelKeys;
|
|
1694
2062
|
const withConfigModels = this.#mergeCustomModels(resolvedDefaults, this.#customModelOverlays);
|
|
1695
2063
|
// Merge runtime extension models so they survive refresh() cycles
|
|
1696
2064
|
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
|
|
1697
2065
|
const withModelOverrides = this.#applyModelOverrides(combined, this.#modelOverrides);
|
|
1698
2066
|
this.#models = this.#finalizeModels(this.#applyRuntimeProviderOverrides(withModelOverrides));
|
|
2067
|
+
this.#modelProfiles = mergeModelProfiles(
|
|
2068
|
+
profiles,
|
|
2069
|
+
filterMaterializedRegistryProfiles(
|
|
2070
|
+
acceptedPresets.profiles,
|
|
2071
|
+
this.#models,
|
|
2072
|
+
new Set(acceptedPresets.dynamicProviders),
|
|
2073
|
+
),
|
|
2074
|
+
);
|
|
1699
2075
|
this.#rebuildProviderActivity();
|
|
1700
2076
|
this.#rebuildCanonicalIndex();
|
|
1701
2077
|
this.#lastStaticLoadMtime = this.#modelsConfigFile.getMtimeMs();
|
|
1702
2078
|
this.#lastStaticLoadEnvironmentFingerprint = this.#getStaticLoadEnvironmentFingerprint();
|
|
2079
|
+
this.#lastModelPresetRegistryFingerprint = JSON.stringify({
|
|
2080
|
+
revision: acceptedPresets.revision,
|
|
2081
|
+
manifestSha256: acceptedPresets.manifestSha256,
|
|
2082
|
+
disabled: acceptedPresets.disabled,
|
|
2083
|
+
error: acceptedPresets.error,
|
|
2084
|
+
});
|
|
2085
|
+
this.#loadedModelPresetRegistryManifestSha256 = acceptedPresets.manifestSha256;
|
|
1703
2086
|
this.#staticModelsLoaded = true;
|
|
1704
2087
|
}
|
|
1705
2088
|
|
|
@@ -1716,6 +2099,10 @@ export class ModelRegistry {
|
|
|
1716
2099
|
}
|
|
1717
2100
|
for (const overlay of [...this.#customModelOverlays, ...this.#runtimeModelOverlays])
|
|
1718
2101
|
addStaticModel(overlay.provider, overlay.id);
|
|
2102
|
+
for (const model of this.#models) {
|
|
2103
|
+
if (this.#registryModelKeys.has(`${model.provider}\u0000${model.id}`))
|
|
2104
|
+
addStaticModel(model.provider, model.id);
|
|
2105
|
+
}
|
|
1719
2106
|
|
|
1720
2107
|
const runtimeProviderIds = new Set(this.#runtimeProviderSourceByName.keys());
|
|
1721
2108
|
const providerIds = new Set<string>([
|
|
@@ -1794,6 +2181,86 @@ export class ModelRegistry {
|
|
|
1794
2181
|
return merged;
|
|
1795
2182
|
}
|
|
1796
2183
|
|
|
2184
|
+
#mergeRegistryModelMetadata(
|
|
2185
|
+
baseModels: Model<Api>[],
|
|
2186
|
+
registryModels: Model<Api>[],
|
|
2187
|
+
providerOverrides: ReadonlyMap<string, ProviderOverride>,
|
|
2188
|
+
): Model<Api>[] {
|
|
2189
|
+
const merged = [...baseModels];
|
|
2190
|
+
const indexByKey = new Map(merged.map((model, index) => [`${model.provider}\u0000${model.id}`, index]));
|
|
2191
|
+
for (const registryModel of registryModels) {
|
|
2192
|
+
const key = `${registryModel.provider}\u0000${registryModel.id}`;
|
|
2193
|
+
const existingIndex = indexByKey.get(key);
|
|
2194
|
+
const explicitTransport = this.#resolveProviderOverride(registryModel.provider, providerOverrides);
|
|
2195
|
+
if (existingIndex === undefined) {
|
|
2196
|
+
const transportTemplates = merged.filter(
|
|
2197
|
+
model => model.provider === registryModel.provider && model.api === registryModel.api,
|
|
2198
|
+
);
|
|
2199
|
+
const transportTemplate = transportTemplates[0];
|
|
2200
|
+
const explicitTransportMatches =
|
|
2201
|
+
explicitTransport?.api === registryModel.api && explicitTransport.baseUrl !== undefined;
|
|
2202
|
+
const templatesAgree =
|
|
2203
|
+
transportTemplate !== undefined &&
|
|
2204
|
+
transportTemplates.every(template => hasMatchingRegistryTransport(transportTemplate, template));
|
|
2205
|
+
if (!explicitTransportMatches && transportTemplates.length > 0 && !templatesAgree) continue;
|
|
2206
|
+
if (!transportTemplate && !explicitTransportMatches) continue;
|
|
2207
|
+
const transportSource = explicitTransportMatches ? explicitTransport : transportTemplate!;
|
|
2208
|
+
const inheritedCompat = explicitTransportMatches
|
|
2209
|
+
? explicitTransport?.compat
|
|
2210
|
+
: commonRegistryTransportCompat(transportTemplates);
|
|
2211
|
+
const hydratedRegistryModel = {
|
|
2212
|
+
...registryModel,
|
|
2213
|
+
baseUrl: transportSource.baseUrl!,
|
|
2214
|
+
headers: transportSource.headers,
|
|
2215
|
+
transport: transportSource.transport,
|
|
2216
|
+
requestTransform: transportSource.requestTransform,
|
|
2217
|
+
cacheRetention: transportSource.cacheRetention,
|
|
2218
|
+
isOAuth: transportSource.isOAuth,
|
|
2219
|
+
compat:
|
|
2220
|
+
inheritedCompat || registryModel.compat || explicitTransport?.compat
|
|
2221
|
+
? { ...inheritedCompat, ...registryModel.compat, ...explicitTransport?.compat }
|
|
2222
|
+
: undefined,
|
|
2223
|
+
};
|
|
2224
|
+
merged.push(
|
|
2225
|
+
explicitTransportMatches
|
|
2226
|
+
? this.#applyProviderTransportOverride(hydratedRegistryModel, explicitTransport!)
|
|
2227
|
+
: hydratedRegistryModel,
|
|
2228
|
+
);
|
|
2229
|
+
indexByKey.set(key, merged.length - 1);
|
|
2230
|
+
continue;
|
|
2231
|
+
}
|
|
2232
|
+
const existing = merged[existingIndex]!;
|
|
2233
|
+
const effectiveApi = explicitTransport?.api ?? existing.api;
|
|
2234
|
+
const registryApiMatchesEffectiveTransport = registryModel.api === effectiveApi;
|
|
2235
|
+
const registryMetadata = registryApiMatchesEffectiveTransport
|
|
2236
|
+
? registryModel
|
|
2237
|
+
: registryModelMetadataWithoutApiSpecificFields(registryModel);
|
|
2238
|
+
merged[existingIndex] = {
|
|
2239
|
+
...existing,
|
|
2240
|
+
...registryMetadata,
|
|
2241
|
+
api: explicitTransport?.api ?? existing.api,
|
|
2242
|
+
baseUrl: existing.baseUrl,
|
|
2243
|
+
headers: existing.headers,
|
|
2244
|
+
transport: existing.transport,
|
|
2245
|
+
requestTransform: existing.requestTransform,
|
|
2246
|
+
cacheRetention: existing.cacheRetention,
|
|
2247
|
+
isOAuth: explicitTransport?.isOAuth ?? existing.isOAuth,
|
|
2248
|
+
wireModelId: existing.wireModelId,
|
|
2249
|
+
compat:
|
|
2250
|
+
existing.compat ||
|
|
2251
|
+
(registryApiMatchesEffectiveTransport ? registryModel.compat : undefined) ||
|
|
2252
|
+
explicitTransport?.compat
|
|
2253
|
+
? {
|
|
2254
|
+
...existing.compat,
|
|
2255
|
+
...(registryApiMatchesEffectiveTransport ? registryModel.compat : undefined),
|
|
2256
|
+
...explicitTransport?.compat,
|
|
2257
|
+
}
|
|
2258
|
+
: undefined,
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
return merged;
|
|
2262
|
+
}
|
|
2263
|
+
|
|
1797
2264
|
/** Merge custom models with built-in, replacing by provider+id match */
|
|
1798
2265
|
#mergeCustomModels(builtInModels: Model<Api>[], customModels: CustomModelOverlay[]): Model<Api>[] {
|
|
1799
2266
|
const merged = [...builtInModels];
|
|
@@ -1828,6 +2295,7 @@ export class ModelRegistry {
|
|
|
1828
2295
|
// re-merge bundled transport metadata here.
|
|
1829
2296
|
headers: customModel.headers,
|
|
1830
2297
|
compat: customModel.compat,
|
|
2298
|
+
transport: customModel.transport,
|
|
1831
2299
|
contextPromotionTarget: customModel.contextPromotionTarget ?? existingModel.contextPromotionTarget,
|
|
1832
2300
|
wireModelId: customModel.wireModelId,
|
|
1833
2301
|
requestTransform: customModel.requestTransform,
|
|
@@ -2104,6 +2572,11 @@ export class ModelRegistry {
|
|
|
2104
2572
|
const configuredProviders = new Set(Object.keys(value.providers ?? {}));
|
|
2105
2573
|
|
|
2106
2574
|
for (const [providerName, providerConfig] of providerEntries) {
|
|
2575
|
+
const authMode = (providerConfig.auth ?? "apiKey") as ProviderAuthMode;
|
|
2576
|
+
const isOAuth = resolveCustomModelIsOAuth(
|
|
2577
|
+
(providerConfig.api as Api | undefined) ?? "openai-completions",
|
|
2578
|
+
providerConfig.auth as ProviderAuthMode | undefined,
|
|
2579
|
+
);
|
|
2107
2580
|
if (providerConfig.apiKeyEnv) {
|
|
2108
2581
|
this.#configuredApiKeyEnvNames.add(providerConfig.apiKeyEnv);
|
|
2109
2582
|
}
|
|
@@ -2113,12 +2586,16 @@ export class ModelRegistry {
|
|
|
2113
2586
|
if (providerConfig.openaiCompat?.apiKey)
|
|
2114
2587
|
this.#configuredApiKeyEnvNames.add(providerConfig.openaiCompat.apiKey);
|
|
2115
2588
|
if (providerConfig.webSearch) this.#providerWebSearchModes.set(providerName, providerConfig.webSearch);
|
|
2116
|
-
const providerApiKeyConfig = providerConfig.apiKey
|
|
2589
|
+
const providerApiKeyConfig = providerConfig.apiKey
|
|
2590
|
+
? resolveApiKeyConfig(providerConfig.apiKey)
|
|
2591
|
+
: resolveApiKeyEnvConfig(providerConfig.apiKeyEnv);
|
|
2117
2592
|
const localOpenAICompat = providerConfig.openaiCompat;
|
|
2118
2593
|
const rotatingApiKeyEnv = providerConfig.apiKey
|
|
2119
2594
|
? undefined
|
|
2120
2595
|
: (providerConfig.apiKeyEnv ?? (localOpenAICompat?.apiKey ? undefined : localOpenAICompat?.apiKeyEnv));
|
|
2121
2596
|
if (rotatingApiKeyEnv) this.#customProviderApiKeyEnvNames.set(providerName, rotatingApiKeyEnv);
|
|
2597
|
+
if (providerConfig.authHeader !== undefined)
|
|
2598
|
+
this.#customProviderAuthHeaders.set(providerName, providerConfig.authHeader);
|
|
2122
2599
|
const localOpenAICompatApiKeyConfig = localOpenAICompat
|
|
2123
2600
|
? localOpenAICompat.apiKey
|
|
2124
2601
|
? resolveApiKeyConfig(localOpenAICompat.apiKey)
|
|
@@ -2132,8 +2609,10 @@ export class ModelRegistry {
|
|
|
2132
2609
|
? resolveApiKeyEnvConfig(localOpenAICompat.apiKeyEnv)
|
|
2133
2610
|
: undefined;
|
|
2134
2611
|
overrides.set(providerName, {
|
|
2612
|
+
api: "openai-completions",
|
|
2135
2613
|
baseUrl: localOpenAICompatBaseUrl,
|
|
2136
2614
|
apiKey: localOpenAICompatApiKeyConfig,
|
|
2615
|
+
isOAuth,
|
|
2137
2616
|
compat: {
|
|
2138
2617
|
supportsStore: false,
|
|
2139
2618
|
supportsDeveloperRole: false,
|
|
@@ -2156,6 +2635,7 @@ export class ModelRegistry {
|
|
|
2156
2635
|
this.#customProviderApiKeys.set(providerName, localCompatResolvedKey);
|
|
2157
2636
|
this.authStorage.setConfigApiKey(providerName, localCompatResolvedKey, {
|
|
2158
2637
|
envSourced: !localOpenAICompat.apiKey,
|
|
2638
|
+
owner: this.#authStorageConfigOwner,
|
|
2159
2639
|
});
|
|
2160
2640
|
} else {
|
|
2161
2641
|
keylessProviders.add(providerName);
|
|
@@ -2177,6 +2657,7 @@ export class ModelRegistry {
|
|
|
2177
2657
|
) {
|
|
2178
2658
|
const disableStrictCompat = providerConfig.disableStrictTools ? { disableStrictTools: true } : undefined;
|
|
2179
2659
|
overrides.set(providerName, {
|
|
2660
|
+
api: providerConfig.api as Api | undefined,
|
|
2180
2661
|
baseUrl: providerConfig.baseUrl,
|
|
2181
2662
|
headers: providerConfig.headers,
|
|
2182
2663
|
apiKey: providerApiKeyConfig,
|
|
@@ -2185,10 +2666,10 @@ export class ModelRegistry {
|
|
|
2185
2666
|
transport: providerConfig.transport,
|
|
2186
2667
|
requestTransform: providerConfig.requestTransform,
|
|
2187
2668
|
cacheRetention: providerConfig.cacheRetention,
|
|
2669
|
+
isOAuth,
|
|
2188
2670
|
});
|
|
2189
2671
|
}
|
|
2190
2672
|
|
|
2191
|
-
const authMode = (providerConfig.auth ?? "apiKey") as ProviderAuthMode;
|
|
2192
2673
|
if (authMode === "none") {
|
|
2193
2674
|
keylessProviders.add(providerName);
|
|
2194
2675
|
}
|
|
@@ -2219,7 +2700,10 @@ export class ModelRegistry {
|
|
|
2219
2700
|
: undefined;
|
|
2220
2701
|
if (resolved) this.#customProviderApiKeys.set(providerName, resolved);
|
|
2221
2702
|
if (resolved) {
|
|
2222
|
-
this.authStorage.setConfigApiKey(providerName, resolved, {
|
|
2703
|
+
this.authStorage.setConfigApiKey(providerName, resolved, {
|
|
2704
|
+
envSourced: !providerConfig.apiKey,
|
|
2705
|
+
owner: this.#authStorageConfigOwner,
|
|
2706
|
+
});
|
|
2223
2707
|
}
|
|
2224
2708
|
}
|
|
2225
2709
|
|
|
@@ -2275,7 +2759,7 @@ export class ModelRegistry {
|
|
|
2275
2759
|
);
|
|
2276
2760
|
}
|
|
2277
2761
|
const current = loaded.value ?? this.#modelsConfigFile.createDefault();
|
|
2278
|
-
if (
|
|
2762
|
+
if (current.profiles?.[normalizedName] !== undefined) {
|
|
2279
2763
|
throw new Error(`Custom model profile already exists: ${normalizedName}. Choose a unique preset id.`);
|
|
2280
2764
|
}
|
|
2281
2765
|
const next: ModelsConfig = {
|
|
@@ -2401,6 +2885,8 @@ export class ModelRegistry {
|
|
|
2401
2885
|
async #refreshRuntimeDiscoveries(
|
|
2402
2886
|
strategy: ModelRefreshStrategy,
|
|
2403
2887
|
providerFilter?: ReadonlySet<string>,
|
|
2888
|
+
refreshGeneration = this.#catalogRefreshGeneration,
|
|
2889
|
+
providerRefresh?: ProviderRefreshFence,
|
|
2404
2890
|
): Promise<void> {
|
|
2405
2891
|
const disabledProviders = getDisabledProviderIdsFromSettings(this.#settings);
|
|
2406
2892
|
const selectedDiscoverableProviders = (
|
|
@@ -2412,12 +2898,22 @@ export class ModelRegistry {
|
|
|
2412
2898
|
selectedDiscoverableProviders.length === 0
|
|
2413
2899
|
? Promise.resolve([] as ConfiguredDiscoveryResult[])
|
|
2414
2900
|
: Promise.all(
|
|
2415
|
-
selectedDiscoverableProviders.map(provider =>
|
|
2901
|
+
selectedDiscoverableProviders.map(provider =>
|
|
2902
|
+
this.#discoverProviderModels(provider, strategy, providerRefresh),
|
|
2903
|
+
),
|
|
2416
2904
|
);
|
|
2417
2905
|
const [configuredDiscoveryResults, builtInDiscovered] = await Promise.all([
|
|
2418
2906
|
configuredDiscoveriesPromise,
|
|
2419
|
-
this.#discoverBuiltInProviderModels(strategy, providerFilter),
|
|
2907
|
+
this.#discoverBuiltInProviderModels(strategy, providerFilter, providerRefresh),
|
|
2420
2908
|
]);
|
|
2909
|
+
if (
|
|
2910
|
+
refreshGeneration !== this.#catalogRefreshGeneration ||
|
|
2911
|
+
(providerRefresh !== undefined &&
|
|
2912
|
+
"providerId" in providerRefresh &&
|
|
2913
|
+
this.#providerRefreshGenerations.get(providerRefresh.providerId) !== providerRefresh.generation)
|
|
2914
|
+
) {
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2421
2917
|
const currentConfiguredDiscoveryResults = configuredDiscoveryResults.map(result => {
|
|
2422
2918
|
const providerConfig = selectedDiscoverableProviders.find(provider => provider.provider === result.provider);
|
|
2423
2919
|
const current =
|
|
@@ -2584,6 +3080,7 @@ export class ModelRegistry {
|
|
|
2584
3080
|
async #discoverProviderModels(
|
|
2585
3081
|
providerConfig: DiscoveryProviderConfig,
|
|
2586
3082
|
strategy: ModelRefreshStrategy,
|
|
3083
|
+
providerRefresh?: ProviderRefreshFence,
|
|
2587
3084
|
): Promise<ConfiguredDiscoveryResult> {
|
|
2588
3085
|
const provider = providerConfig.provider;
|
|
2589
3086
|
const preflightEpoch = this.#optionalAuthPreflightEpoch;
|
|
@@ -2598,7 +3095,8 @@ export class ModelRegistry {
|
|
|
2598
3095
|
let preflightCompleted = false;
|
|
2599
3096
|
const optionalAuth = this.#optionalAuthProviders.has(provider);
|
|
2600
3097
|
const shouldPreflightAuth = optionalAuth
|
|
2601
|
-
? this.authStorage.has(provider) ||
|
|
3098
|
+
? this.authStorage.has(provider) ||
|
|
3099
|
+
this.authStorage.hasAuth(provider, undefined, { owner: this.#authStorageConfigOwner })
|
|
2602
3100
|
: !this.#isCredentiallessProvider(provider);
|
|
2603
3101
|
let preflightAuthConfigurationGeneration = this.authStorage.getProviderConfigurationGeneration(provider);
|
|
2604
3102
|
let preflightOAuthRefreshGeneration = this.authStorage.getProviderOAuthRefreshGeneration(provider);
|
|
@@ -2639,7 +3137,11 @@ export class ModelRegistry {
|
|
|
2639
3137
|
preflightCompleted = true;
|
|
2640
3138
|
if (!preflightFailed && optionalAuth && isCurrentPreflight()) {
|
|
2641
3139
|
this.#providerEvidenceApiKeys.set(provider, apiKey);
|
|
2642
|
-
const authGeneration = this.authStorage.getProviderEvidenceGeneration(
|
|
3140
|
+
const authGeneration = this.authStorage.getProviderEvidenceGeneration(
|
|
3141
|
+
provider,
|
|
3142
|
+
apiKey,
|
|
3143
|
+
this.#authStorageConfigOwner,
|
|
3144
|
+
);
|
|
2643
3145
|
if (apiKey === undefined) this.#credentiallessAuthFallbackProviders.set(provider, authGeneration);
|
|
2644
3146
|
else this.#credentiallessAuthFallbackProviders.delete(provider);
|
|
2645
3147
|
}
|
|
@@ -2688,6 +3190,14 @@ export class ModelRegistry {
|
|
|
2688
3190
|
const isCurrentEndpoint = () =>
|
|
2689
3191
|
endpoint ===
|
|
2690
3192
|
this.#normalizeDiscoveryEvidenceEndpoint(this.#effectiveDiscoveryProviderConfig(providerConfig).baseUrl ?? "");
|
|
3193
|
+
const isCurrentProviderRefresh = () =>
|
|
3194
|
+
providerRefresh === undefined ||
|
|
3195
|
+
("providerId" in providerRefresh
|
|
3196
|
+
? strategy !== "online-if-uncached" ||
|
|
3197
|
+
(providerRefresh.providerId === provider &&
|
|
3198
|
+
this.#providerRefreshGenerations.get(provider) === providerRefresh.generation)
|
|
3199
|
+
: (this.#providerRefreshGenerations.get(provider) ?? 0) ===
|
|
3200
|
+
(providerRefresh.generations.get(provider) ?? 0));
|
|
2691
3201
|
const evidence = this.#configuredDiscoveryEvidence.get(provider);
|
|
2692
3202
|
const cacheDynamicModelProvenance = fingerprintConfiguredDiscoveryRequestShape(
|
|
2693
3203
|
effectiveProviderConfig,
|
|
@@ -2736,7 +3246,7 @@ export class ModelRegistry {
|
|
|
2736
3246
|
),
|
|
2737
3247
|
getEvidenceGeneration: provider => this.#getProviderEvidenceGeneration(provider.provider, preflightApiKey),
|
|
2738
3248
|
cacheDynamicModelProvenance: cacheLookupProvenance,
|
|
2739
|
-
canPublishCache: isCurrentEndpoint,
|
|
3249
|
+
canPublishCache: () => isCurrentEndpoint() && isCurrentProviderRefresh(),
|
|
2740
3250
|
});
|
|
2741
3251
|
const authGeneration =
|
|
2742
3252
|
mergeInput.authGeneration ??
|
|
@@ -2792,7 +3302,7 @@ export class ModelRegistry {
|
|
|
2792
3302
|
return {
|
|
2793
3303
|
...providerConfig,
|
|
2794
3304
|
baseUrl: effectiveBaseUrl,
|
|
2795
|
-
headers:
|
|
3305
|
+
headers: mergeCaseInsensitiveHeaders(providerConfig.headers, override?.headers),
|
|
2796
3306
|
compat: override?.compat ? mergeCompat(providerConfig.compat, override.compat) : providerConfig.compat,
|
|
2797
3307
|
requestTransform: mergeRequestTransform(providerConfig.requestTransform, override?.requestTransform),
|
|
2798
3308
|
cacheRetention: override?.cacheRetention ?? providerConfig.cacheRetention,
|
|
@@ -2822,6 +3332,7 @@ export class ModelRegistry {
|
|
|
2822
3332
|
async #discoverBuiltInProviderModels(
|
|
2823
3333
|
strategy: ModelRefreshStrategy,
|
|
2824
3334
|
providerFilter?: ReadonlySet<string>,
|
|
3335
|
+
providerRefresh?: ProviderRefreshFence,
|
|
2825
3336
|
): Promise<Model<Api>[]> {
|
|
2826
3337
|
// Skip providers already handled by configured discovery (e.g. user-configured ollama with discovery.type)
|
|
2827
3338
|
const configuredDiscoveryProviders = new Set(this.#discoveryManager.providers.map(p => p.provider));
|
|
@@ -2839,7 +3350,7 @@ export class ModelRegistry {
|
|
|
2839
3350
|
return [];
|
|
2840
3351
|
}
|
|
2841
3352
|
const discoveries = await Promise.all(
|
|
2842
|
-
managerOptions.map(entry => this.#discoverWithModelManager(entry, strategy)),
|
|
3353
|
+
managerOptions.map(entry => this.#discoverWithModelManager(entry, strategy, providerRefresh)),
|
|
2843
3354
|
);
|
|
2844
3355
|
return discoveries.flat();
|
|
2845
3356
|
}
|
|
@@ -2874,7 +3385,12 @@ export class ModelRegistry {
|
|
|
2874
3385
|
providerId: "openai-codex",
|
|
2875
3386
|
resolveKey: value => value,
|
|
2876
3387
|
createOptions: accessToken => {
|
|
2877
|
-
const accountId = resolveOAuthAccountIdForAccessToken(
|
|
3388
|
+
const accountId = resolveOAuthAccountIdForAccessToken(
|
|
3389
|
+
this.authStorage,
|
|
3390
|
+
"openai-codex",
|
|
3391
|
+
accessToken,
|
|
3392
|
+
this.#authStorageConfigOwner,
|
|
3393
|
+
);
|
|
2878
3394
|
return openaiCodexModelManagerOptions({
|
|
2879
3395
|
accessToken,
|
|
2880
3396
|
accountId,
|
|
@@ -2967,6 +3483,7 @@ export class ModelRegistry {
|
|
|
2967
3483
|
async #discoverWithModelManager(
|
|
2968
3484
|
{ options, authGeneration, apiKey, endpoint, endpointContainsUserinfo }: ModelManagerDiscoveryOptions,
|
|
2969
3485
|
strategy: ModelRefreshStrategy,
|
|
3486
|
+
providerRefresh?: ProviderRefreshFence,
|
|
2970
3487
|
): Promise<Model<Api>[]> {
|
|
2971
3488
|
const generation = (this.#descriptorDiscoveryGenerations.get(options.providerId) ?? 0) + 1;
|
|
2972
3489
|
this.#descriptorDiscoveryGenerations.set(options.providerId, generation);
|
|
@@ -2987,18 +3504,34 @@ export class ModelRegistry {
|
|
|
2987
3504
|
// stale fallback, and online-if-uncached reuse all fail closed.
|
|
2988
3505
|
const cacheDynamicModelProvenance =
|
|
2989
3506
|
reusableCacheProvenance ?? `gajae:non-cacheable-endpoint:${crypto.randomUUID()}`;
|
|
2990
|
-
const
|
|
3507
|
+
const isCurrentDiscoveryContext = () =>
|
|
2991
3508
|
(this.#descriptorDiscoveryGenerations.get(options.providerId) ?? 0) === generation &&
|
|
2992
3509
|
this.#getProviderEvidenceGeneration(options.providerId, apiKey) === authGeneration &&
|
|
2993
3510
|
(endpoint ===
|
|
2994
3511
|
this.#normalizeDiscoveryEvidenceEndpoint(this.#getProviderBaseUrlForDiscovery(options.providerId) ?? "") ||
|
|
2995
3512
|
(canUseCredentialDerivedXiaomiEndpoint && credentialDerivedEndpoint !== undefined));
|
|
3513
|
+
const isCurrentProviderRefresh = () =>
|
|
3514
|
+
providerRefresh === undefined ||
|
|
3515
|
+
("providerId" in providerRefresh
|
|
3516
|
+
? providerRefresh.providerId === options.providerId &&
|
|
3517
|
+
this.#providerRefreshGenerations.get(options.providerId) === providerRefresh.generation
|
|
3518
|
+
: (this.#providerRefreshGenerations.get(options.providerId) ?? 0) ===
|
|
3519
|
+
(providerRefresh.generations.get(options.providerId) ?? 0));
|
|
3520
|
+
const isCurrentDiscovery = () =>
|
|
3521
|
+
isCurrentDiscoveryContext() &&
|
|
3522
|
+
(providerRefresh === undefined || "generations" in providerRefresh || isCurrentProviderRefresh());
|
|
3523
|
+
const canPublishCache = () =>
|
|
3524
|
+
isCurrentDiscoveryContext() &&
|
|
3525
|
+
(providerRefresh === undefined ||
|
|
3526
|
+
("providerId" in providerRefresh
|
|
3527
|
+
? strategy !== "online-if-uncached" || isCurrentDiscovery()
|
|
3528
|
+
: isCurrentProviderRefresh()));
|
|
2996
3529
|
try {
|
|
2997
3530
|
const manager = createModelManager({
|
|
2998
3531
|
...options,
|
|
2999
3532
|
cacheDbPath: this.#cacheDbPath,
|
|
3000
3533
|
cacheDynamicModelProvenance,
|
|
3001
|
-
canPublishCache
|
|
3534
|
+
canPublishCache,
|
|
3002
3535
|
...(options.fetchDynamicModels
|
|
3003
3536
|
? {
|
|
3004
3537
|
fetchDynamicModels: async () => {
|
|
@@ -3159,8 +3692,15 @@ export class ModelRegistry {
|
|
|
3159
3692
|
discoveryApiKey ??
|
|
3160
3693
|
(this.#isCredentiallessProvider(providerConfig.provider)
|
|
3161
3694
|
? kNoAuth
|
|
3162
|
-
: await this.authStorage.getApiKey(providerConfig.provider
|
|
3163
|
-
|
|
3695
|
+
: await this.authStorage.getApiKey(providerConfig.provider, undefined, {
|
|
3696
|
+
owner: this.#authStorageConfigOwner,
|
|
3697
|
+
}));
|
|
3698
|
+
if (
|
|
3699
|
+
apiKey &&
|
|
3700
|
+
apiKey !== DEFAULT_LOCAL_TOKEN &&
|
|
3701
|
+
apiKey !== kNoAuth &&
|
|
3702
|
+
headerValue(headers, "Authorization") === undefined
|
|
3703
|
+
) {
|
|
3164
3704
|
headers.Authorization = `Bearer ${apiKey}`;
|
|
3165
3705
|
}
|
|
3166
3706
|
const response = await fetch(tagsUrl, {
|
|
@@ -3239,8 +3779,15 @@ export class ModelRegistry {
|
|
|
3239
3779
|
discoveryApiKey ??
|
|
3240
3780
|
(this.#isCredentiallessProvider(providerConfig.provider)
|
|
3241
3781
|
? kNoAuth
|
|
3242
|
-
: await this.authStorage.getApiKey(providerConfig.provider
|
|
3243
|
-
|
|
3782
|
+
: await this.authStorage.getApiKey(providerConfig.provider, undefined, {
|
|
3783
|
+
owner: this.#authStorageConfigOwner,
|
|
3784
|
+
}));
|
|
3785
|
+
if (
|
|
3786
|
+
apiKey &&
|
|
3787
|
+
apiKey !== DEFAULT_LOCAL_TOKEN &&
|
|
3788
|
+
apiKey !== kNoAuth &&
|
|
3789
|
+
headerValue(requestHeaders, "Authorization") === undefined
|
|
3790
|
+
) {
|
|
3244
3791
|
requestHeaders.Authorization = `Bearer ${apiKey}`;
|
|
3245
3792
|
}
|
|
3246
3793
|
|
|
@@ -3361,12 +3908,16 @@ export class ModelRegistry {
|
|
|
3361
3908
|
discoveryApiKey ??
|
|
3362
3909
|
(this.#isCredentiallessProvider(providerConfig.provider)
|
|
3363
3910
|
? kNoAuth
|
|
3364
|
-
: await this.authStorage.getApiKey(providerConfig.provider, undefined, {
|
|
3911
|
+
: await this.authStorage.getApiKey(providerConfig.provider, undefined, {
|
|
3912
|
+
baseUrl,
|
|
3913
|
+
owner: this.#authStorageConfigOwner,
|
|
3914
|
+
}));
|
|
3365
3915
|
if (
|
|
3366
3916
|
apiKey &&
|
|
3367
3917
|
apiKey !== DEFAULT_LOCAL_TOKEN &&
|
|
3368
3918
|
!isVllmNoAuthToken(providerConfig.provider, apiKey) &&
|
|
3369
|
-
apiKey !== kNoAuth
|
|
3919
|
+
apiKey !== kNoAuth &&
|
|
3920
|
+
headerValue(requestHeaders, "Authorization") === undefined
|
|
3370
3921
|
) {
|
|
3371
3922
|
requestHeaders.Authorization = `Bearer ${apiKey}`;
|
|
3372
3923
|
}
|
|
@@ -3508,7 +4059,11 @@ export class ModelRegistry {
|
|
|
3508
4059
|
try {
|
|
3509
4060
|
fallbackMatchesCurrentEvidence =
|
|
3510
4061
|
fallbackEvidenceGeneration ===
|
|
3511
|
-
this.authStorage.getProviderEvidenceGeneration(
|
|
4062
|
+
this.authStorage.getProviderEvidenceGeneration(
|
|
4063
|
+
provider,
|
|
4064
|
+
this.#providerEvidenceApiKeys.get(provider),
|
|
4065
|
+
this.#authStorageConfigOwner,
|
|
4066
|
+
);
|
|
3512
4067
|
} catch {
|
|
3513
4068
|
// AuthStorage may be unavailable while a registry is being torn down.
|
|
3514
4069
|
}
|
|
@@ -3516,7 +4071,8 @@ export class ModelRegistry {
|
|
|
3516
4071
|
return (
|
|
3517
4072
|
this.#keylessProviders.has(provider) &&
|
|
3518
4073
|
(!this.#optionalAuthProviders.has(provider) ||
|
|
3519
|
-
(!this.authStorage.hasAuth(provider
|
|
4074
|
+
(!this.authStorage.hasAuth(provider, undefined, { owner: this.#authStorageConfigOwner }) &&
|
|
4075
|
+
!this.authStorage.has(provider)) ||
|
|
3520
4076
|
fallbackMatchesCurrentEvidence)
|
|
3521
4077
|
);
|
|
3522
4078
|
}
|
|
@@ -3527,6 +4083,7 @@ export class ModelRegistry {
|
|
|
3527
4083
|
return this.authStorage.getProviderEvidenceGeneration(
|
|
3528
4084
|
provider,
|
|
3529
4085
|
resolvedApiKey ?? this.#providerEvidenceApiKeys.get(provider),
|
|
4086
|
+
this.#authStorageConfigOwner,
|
|
3530
4087
|
);
|
|
3531
4088
|
}
|
|
3532
4089
|
#normalizeOpenAIModelsListBaseUrl(baseUrl?: string): string {
|
|
@@ -3564,7 +4121,7 @@ export class ModelRegistry {
|
|
|
3564
4121
|
|
|
3565
4122
|
#resolveProviderOverride(
|
|
3566
4123
|
provider: string,
|
|
3567
|
-
overrides:
|
|
4124
|
+
overrides: ReadonlyMap<string, ProviderOverride> = this.#providerOverrides,
|
|
3568
4125
|
): ProviderOverride | undefined {
|
|
3569
4126
|
const explicitOverride = overrides.get(provider);
|
|
3570
4127
|
if (explicitOverride?.baseUrl) {
|
|
@@ -3591,24 +4148,19 @@ export class ModelRegistry {
|
|
|
3591
4148
|
|
|
3592
4149
|
#mergeProviderOverride(baseOverride: ProviderOverride | undefined, override: ProviderOverride): ProviderOverride {
|
|
3593
4150
|
return {
|
|
4151
|
+
api: override.api ?? baseOverride?.api,
|
|
3594
4152
|
baseUrl: override.baseUrl ?? baseOverride?.baseUrl,
|
|
3595
4153
|
apiKey: override.apiKey ?? baseOverride?.apiKey,
|
|
3596
4154
|
authHeader: override.authHeader ?? baseOverride?.authHeader,
|
|
3597
|
-
headers:
|
|
4155
|
+
headers: mergeCaseInsensitiveHeaders(baseOverride?.headers, override.headers),
|
|
3598
4156
|
compat: override.compat ? mergeCompat(baseOverride?.compat, override.compat) : baseOverride?.compat,
|
|
3599
4157
|
transport: override.transport ?? baseOverride?.transport,
|
|
3600
4158
|
requestTransform: mergeRequestTransform(baseOverride?.requestTransform, override.requestTransform),
|
|
3601
4159
|
cacheRetention: override.cacheRetention ?? baseOverride?.cacheRetention,
|
|
4160
|
+
isOAuth: override.isOAuth ?? baseOverride?.isOAuth,
|
|
3602
4161
|
};
|
|
3603
4162
|
}
|
|
3604
|
-
#applyProviderTransportOverride<
|
|
3605
|
-
T extends {
|
|
3606
|
-
baseUrl?: string;
|
|
3607
|
-
headers?: Record<string, string>;
|
|
3608
|
-
compat?: Model<Api>["compat"];
|
|
3609
|
-
cacheRetention?: CacheRetention;
|
|
3610
|
-
},
|
|
3611
|
-
>(
|
|
4163
|
+
#applyProviderTransportOverride<T extends Model<Api>>(
|
|
3612
4164
|
entry: T,
|
|
3613
4165
|
override: Pick<
|
|
3614
4166
|
ProviderOverride,
|
|
@@ -3622,12 +4174,39 @@ export class ModelRegistry {
|
|
|
3622
4174
|
| "cacheRetention"
|
|
3623
4175
|
>,
|
|
3624
4176
|
): T {
|
|
3625
|
-
const
|
|
3626
|
-
override.headers ? { ...entry.headers, ...override.headers } : entry.headers
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
4177
|
+
const overrideHeaders = (
|
|
4178
|
+
override.headers ? { ...entry.headers, ...override.headers } : { ...entry.headers }
|
|
4179
|
+
) as Record<string, string> & { [GENERATED_AUTH_HEADER]?: string };
|
|
4180
|
+
const explicitAuthKey = override.headers
|
|
4181
|
+
? Object.keys(override.headers).find(key => key.toLowerCase() === "authorization")
|
|
4182
|
+
: undefined;
|
|
4183
|
+
if (explicitAuthKey !== undefined) {
|
|
4184
|
+
const explicitAuthValue = override.headers![explicitAuthKey]!;
|
|
4185
|
+
deleteHeaderCaseInsensitive(overrideHeaders, "Authorization");
|
|
4186
|
+
overrideHeaders[explicitAuthKey] = explicitAuthValue;
|
|
4187
|
+
delete (overrideHeaders as Record<string, string> & { [GENERATED_AUTH_HEADER]?: string })[
|
|
4188
|
+
GENERATED_AUTH_HEADER
|
|
4189
|
+
];
|
|
4190
|
+
}
|
|
4191
|
+
const existingMarker = (overrideHeaders as Record<string, string> & { [GENERATED_AUTH_HEADER]?: string })[
|
|
4192
|
+
GENERATED_AUTH_HEADER
|
|
4193
|
+
];
|
|
4194
|
+
const managesAuth = override.authHeader !== undefined || override.apiKey !== undefined;
|
|
4195
|
+
const canInject =
|
|
4196
|
+
managesAuth &&
|
|
4197
|
+
override.authHeader === true &&
|
|
4198
|
+
Boolean(override.apiKey) &&
|
|
4199
|
+
(headerValue(overrideHeaders, "Authorization") === undefined ||
|
|
4200
|
+
ownsOnlyGeneratedAuthorization(overrideHeaders, existingMarker));
|
|
4201
|
+
if (canInject) {
|
|
4202
|
+
deleteHeaderCaseInsensitive(overrideHeaders, "Authorization");
|
|
4203
|
+
delete overrideHeaders[GENERATED_AUTH_HEADER];
|
|
4204
|
+
} else if (managesAuth && existingMarker !== undefined) {
|
|
4205
|
+
deleteAuthorizationValue(overrideHeaders, existingMarker);
|
|
4206
|
+
delete overrideHeaders[GENERATED_AUTH_HEADER];
|
|
4207
|
+
}
|
|
4208
|
+
const headers = mergeAuthHeader(overrideHeaders, canInject, canInject ? override.apiKey : undefined);
|
|
4209
|
+
const result = {
|
|
3631
4210
|
...entry,
|
|
3632
4211
|
compat: mergeProviderCompat(entry.compat, override.compat),
|
|
3633
4212
|
baseUrl: override.baseUrl ?? entry.baseUrl,
|
|
@@ -3641,6 +4220,21 @@ export class ModelRegistry {
|
|
|
3641
4220
|
),
|
|
3642
4221
|
cacheRetention: entry.cacheRetention ?? override.cacheRetention,
|
|
3643
4222
|
};
|
|
4223
|
+
if (!canInject && explicitAuthKey !== undefined) {
|
|
4224
|
+
delete (result.headers as Record<string, string> & { [GENERATED_AUTH_HEADER]?: string })[
|
|
4225
|
+
GENERATED_AUTH_HEADER
|
|
4226
|
+
];
|
|
4227
|
+
}
|
|
4228
|
+
const generatedHeader = (
|
|
4229
|
+
result.headers as (Record<string, string> & { [GENERATED_AUTH_HEADER]?: string }) | undefined
|
|
4230
|
+
)?.[GENERATED_AUTH_HEADER];
|
|
4231
|
+
if (generatedHeader !== undefined && result.headers?.Authorization === generatedHeader) {
|
|
4232
|
+
this.#generatedAuthHeaders.set(result, { authorization: result.headers.Authorization });
|
|
4233
|
+
} else if (explicitAuthKey === undefined) {
|
|
4234
|
+
const generated = this.#generatedAuthHeaders.get(entry);
|
|
4235
|
+
if (generated) this.#generatedAuthHeaders.set(result, generated);
|
|
4236
|
+
}
|
|
4237
|
+
return result;
|
|
3644
4238
|
}
|
|
3645
4239
|
#applyRuntimeProviderOverride(model: Model<Api>, override: ProviderOverride): Model<Api> {
|
|
3646
4240
|
const withTransportOverride = this.#applyProviderTransportOverride(
|
|
@@ -3653,26 +4247,53 @@ export class ModelRegistry {
|
|
|
3653
4247
|
? withTransportOverride
|
|
3654
4248
|
: { ...withTransportOverride, ...(sanitizedBaseUrl === undefined ? {} : { baseUrl: sanitizedBaseUrl }) };
|
|
3655
4249
|
const modelCompat = this.#modelOverrides.get(model.provider.toLowerCase())?.get(model.id.toLowerCase())?.compat;
|
|
3656
|
-
|
|
4250
|
+
const result = enrichModelThinking(
|
|
3657
4251
|
modelCompat
|
|
3658
4252
|
? { ...sanitizedTransport, compat: mergeCompat(sanitizedTransport.compat, modelCompat) }
|
|
3659
4253
|
: sanitizedTransport,
|
|
3660
4254
|
);
|
|
4255
|
+
const generated = this.#generatedAuthHeaders.get(sanitizedTransport);
|
|
4256
|
+
if (generated) this.#generatedAuthHeaders.set(result, generated);
|
|
4257
|
+
return result;
|
|
3661
4258
|
}
|
|
3662
4259
|
#applyRuntimeProviderOverrides(models: Model<Api>[]): Model<Api>[] {
|
|
3663
4260
|
if (this.#runtimeProviderOverrides.size === 0) return models;
|
|
3664
4261
|
return models.map(model => {
|
|
3665
4262
|
const override = this.#runtimeProviderOverrides.get(model.provider);
|
|
3666
4263
|
if (!override) return model;
|
|
3667
|
-
|
|
4264
|
+
const runtimeCredentialRevoked =
|
|
4265
|
+
this.#runtimeProviderApiKeys.has(model.provider) &&
|
|
4266
|
+
!this.#runtimeProviderCredentialInstalled.has(model.provider);
|
|
4267
|
+
return this.#applyRuntimeProviderOverride(
|
|
4268
|
+
model,
|
|
4269
|
+
runtimeCredentialRevoked ? { ...override, apiKey: undefined, authHeader: undefined } : override,
|
|
4270
|
+
);
|
|
3668
4271
|
});
|
|
3669
4272
|
}
|
|
3670
4273
|
#finalizeModels(models: Model<Api>[]): Model<Api>[] {
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
4274
|
+
const finalized = models.map(model => enrichModelThinking({ ...this.#restoreDeclaredThinking(model) }));
|
|
4275
|
+
const result = applyFinalCodexGpt56ContextCap(finalized, undefined, this.#codexContextWindowOverrides);
|
|
4276
|
+
for (let index = 0; index < result.length; index++) {
|
|
4277
|
+
if (
|
|
4278
|
+
(result[index]?.headers as (Record<string, string> & { [GENERATED_AUTH_HEADER]?: string }) | undefined)?.[
|
|
4279
|
+
GENERATED_AUTH_HEADER
|
|
4280
|
+
]
|
|
4281
|
+
) {
|
|
4282
|
+
const authorization = result[index]?.headers?.Authorization;
|
|
4283
|
+
const marker = (result[index]?.headers as Record<string, string> & { [GENERATED_AUTH_HEADER]?: string })[
|
|
4284
|
+
GENERATED_AUTH_HEADER
|
|
4285
|
+
];
|
|
4286
|
+
if (authorization && marker === authorization)
|
|
4287
|
+
this.#generatedAuthHeaders.set(result[index]!, { authorization });
|
|
4288
|
+
else
|
|
4289
|
+
delete (result[index]?.headers as Record<string, string> & { [GENERATED_AUTH_HEADER]?: string })[
|
|
4290
|
+
GENERATED_AUTH_HEADER
|
|
4291
|
+
];
|
|
4292
|
+
}
|
|
4293
|
+
const generated = this.#generatedAuthHeaders.get(models[index]!);
|
|
4294
|
+
if (generated) this.#generatedAuthHeaders.set(result[index]!, generated);
|
|
4295
|
+
}
|
|
4296
|
+
return result;
|
|
3676
4297
|
}
|
|
3677
4298
|
#restoreDeclaredThinking(model: Model<Api>): Model<Api> {
|
|
3678
4299
|
const overrideThinking = this.#modelOverrides
|
|
@@ -3811,7 +4432,10 @@ export class ModelRegistry {
|
|
|
3811
4432
|
: undefined;
|
|
3812
4433
|
if (resolved) this.#customProviderApiKeys.set(providerName, resolved);
|
|
3813
4434
|
if (resolved) {
|
|
3814
|
-
this.authStorage.setConfigApiKey(providerName, resolved, {
|
|
4435
|
+
this.authStorage.setConfigApiKey(providerName, resolved, {
|
|
4436
|
+
envSourced: !providerConfig.apiKey,
|
|
4437
|
+
owner: this.#authStorageConfigOwner,
|
|
4438
|
+
});
|
|
3815
4439
|
}
|
|
3816
4440
|
}
|
|
3817
4441
|
for (const modelDef of modelDefs) {
|
|
@@ -3833,6 +4457,7 @@ export class ModelRegistry {
|
|
|
3833
4457
|
providerConfig.requestTransform,
|
|
3834
4458
|
(providerConfig.auth as ProviderAuthMode | undefined) ?? undefined,
|
|
3835
4459
|
providerConfig.cacheRetention,
|
|
4460
|
+
providerConfig.transport,
|
|
3836
4461
|
modelDef as CustomModelDefinitionLike,
|
|
3837
4462
|
);
|
|
3838
4463
|
if (!model) continue;
|
|
@@ -3843,8 +4468,8 @@ export class ModelRegistry {
|
|
|
3843
4468
|
}
|
|
3844
4469
|
|
|
3845
4470
|
/**
|
|
3846
|
-
* Get all models (
|
|
3847
|
-
* If models.
|
|
4471
|
+
* Get all models (embedded + accepted registry + custom).
|
|
4472
|
+
* If models.yml had errors, returns embedded plus accepted registry models.
|
|
3848
4473
|
*/
|
|
3849
4474
|
getAll(): Model<Api>[] {
|
|
3850
4475
|
return this.#models;
|
|
@@ -3859,7 +4484,8 @@ export class ModelRegistry {
|
|
|
3859
4484
|
const disabled = disabledProviders ?? getDisabledProviderIdsFromSettings(this.#settings);
|
|
3860
4485
|
return (
|
|
3861
4486
|
!disabled.has(model.provider) &&
|
|
3862
|
-
(this.#keylessProviders.has(model.provider) ||
|
|
4487
|
+
(this.#keylessProviders.has(model.provider) ||
|
|
4488
|
+
this.authStorage.hasAuth(model.provider, undefined, { owner: this.#authStorageConfigOwner }))
|
|
3863
4489
|
);
|
|
3864
4490
|
}
|
|
3865
4491
|
/** Per-query precomputed variant filter inputs; plan fields are authoritative when provided. */
|
|
@@ -3913,7 +4539,9 @@ export class ModelRegistry {
|
|
|
3913
4539
|
* 6. Unknown (no credential surface)
|
|
3914
4540
|
*/
|
|
3915
4541
|
#effectiveProviderAuth(provider: string, sessionId?: string): EffectiveProviderAuth {
|
|
3916
|
-
const credentialType = this.authStorage.getEffectiveCredentialType(provider, sessionId
|
|
4542
|
+
const credentialType = this.authStorage.getEffectiveCredentialType(provider, sessionId, {
|
|
4543
|
+
owner: this.#authStorageConfigOwner,
|
|
4544
|
+
});
|
|
3917
4545
|
if (credentialType === "api_key") return "key";
|
|
3918
4546
|
if (credentialType === "oauth") return "oauth";
|
|
3919
4547
|
if (this.#keylessProviders.has(provider)) return "keyless";
|
|
@@ -3962,6 +4590,22 @@ export class ModelRegistry {
|
|
|
3962
4590
|
autoroutingProviderOrder(): readonly string[] {
|
|
3963
4591
|
return projectCatalogProviderOrder(getConfiguredProviderOrderFromSettings(), this.#models);
|
|
3964
4592
|
}
|
|
4593
|
+
|
|
4594
|
+
/**
|
|
4595
|
+
* Provider priority for automatic model selection. Explicit provider order
|
|
4596
|
+
* wins first, followed by omitted OAuth providers, then other providers;
|
|
4597
|
+
* catalog order breaks ties within each policy band.
|
|
4598
|
+
*/
|
|
4599
|
+
automaticProviderOrder(sessionId?: string): readonly string[] {
|
|
4600
|
+
const policy = this.#buildProviderSelectionPolicy(sessionId);
|
|
4601
|
+
return [...policy.orderedProviders()].sort((left, right) => {
|
|
4602
|
+
const rankDifference = policy.rank(left) - policy.rank(right);
|
|
4603
|
+
return rankDifference !== 0
|
|
4604
|
+
? rankDifference
|
|
4605
|
+
: policy.providerCatalogIndex(left) - policy.providerCatalogIndex(right);
|
|
4606
|
+
});
|
|
4607
|
+
}
|
|
4608
|
+
|
|
3965
4609
|
#providerRankMap(policy: ProviderSelectionPolicy): Map<string, number> {
|
|
3966
4610
|
const providerRank = new Map<string, number>();
|
|
3967
4611
|
for (const provider of policy.orderedProviders()) {
|
|
@@ -4263,9 +4907,10 @@ export class ModelRegistry {
|
|
|
4263
4907
|
* This is a fast check that doesn't refresh OAuth tokens.
|
|
4264
4908
|
*/
|
|
4265
4909
|
getAvailable(): Model<Api>[] {
|
|
4910
|
+
this.#synchronizeEnvironmentCredentials();
|
|
4266
4911
|
const disabledProviders = getDisabledProviderIdsFromSettings(this.#settings);
|
|
4267
4912
|
const disabledProviderKey = [...disabledProviders].sort().join("\u0000");
|
|
4268
|
-
const envFingerprint = envAvailabilityFingerprint();
|
|
4913
|
+
const envFingerprint = envAvailabilityFingerprint(this.#configuredApiKeyEnvNames);
|
|
4269
4914
|
if (
|
|
4270
4915
|
this.#availableModelsCache &&
|
|
4271
4916
|
this.#availableModelsDisabledProviders === disabledProviderKey &&
|
|
@@ -4279,6 +4924,19 @@ export class ModelRegistry {
|
|
|
4279
4924
|
return this.#availableModelsCache;
|
|
4280
4925
|
}
|
|
4281
4926
|
|
|
4927
|
+
#synchronizeEnvironmentCredentials(): void {
|
|
4928
|
+
// Runtime registrations own a provider while their credential is present.
|
|
4929
|
+
// Refresh them first so a missing runtime env key can hand ownership back to
|
|
4930
|
+
// the static config before the static apiKeyEnv pass runs.
|
|
4931
|
+
for (const provider of this.#runtimeProviderApiKeyEnvNames.keys()) {
|
|
4932
|
+
this.#refreshRotatingConfigApiKey(provider);
|
|
4933
|
+
}
|
|
4934
|
+
for (const provider of this.#customProviderApiKeyEnvNames.keys()) {
|
|
4935
|
+
if (this.#runtimeProviderCredentialInstalled.has(provider)) continue;
|
|
4936
|
+
this.#refreshRotatingConfigApiKey(provider, true);
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4282
4940
|
/**
|
|
4283
4941
|
* Get authenticated models, excluding bundled entries that a fresh provider
|
|
4284
4942
|
* catalog has positively shown to be unavailable. Bundled entries remain
|
|
@@ -4348,7 +5006,10 @@ export class ModelRegistry {
|
|
|
4348
5006
|
|
|
4349
5007
|
#activeConnectionKind(model: Model<Api>): ActiveProviderDescriptor["connectionKind"] | undefined {
|
|
4350
5008
|
const evidence = this.#providerActivity.get(model.provider);
|
|
4351
|
-
if (
|
|
5009
|
+
if (
|
|
5010
|
+
!this.#isCredentiallessProvider(model.provider) &&
|
|
5011
|
+
this.authStorage.hasUsableAuth(model.provider, { owner: this.#authStorageConfigOwner })
|
|
5012
|
+
) {
|
|
4352
5013
|
if (!evidence) return undefined;
|
|
4353
5014
|
const discoveryOnly =
|
|
4354
5015
|
!evidence.staticConfigured &&
|
|
@@ -4423,14 +5084,20 @@ export class ModelRegistry {
|
|
|
4423
5084
|
* as providers with stored credentials. See issue #993.
|
|
4424
5085
|
*/
|
|
4425
5086
|
hasConfiguredAuth(model: Model<Api>): boolean {
|
|
4426
|
-
return
|
|
5087
|
+
return (
|
|
5088
|
+
this.#keylessProviders.has(model.provider) ||
|
|
5089
|
+
this.authStorage.hasAuth(model.provider, undefined, { owner: this.#authStorageConfigOwner })
|
|
5090
|
+
);
|
|
4427
5091
|
}
|
|
4428
5092
|
|
|
4429
5093
|
/**
|
|
4430
5094
|
* Check whether auth is configured for a provider.
|
|
4431
5095
|
*/
|
|
4432
5096
|
hasConfiguredProviderAuth(provider: string): boolean {
|
|
4433
|
-
return
|
|
5097
|
+
return (
|
|
5098
|
+
this.#keylessProviders.has(provider) ||
|
|
5099
|
+
this.authStorage.hasAuth(provider, undefined, { owner: this.#authStorageConfigOwner })
|
|
5100
|
+
);
|
|
4434
5101
|
}
|
|
4435
5102
|
|
|
4436
5103
|
isCredentiallessProvider(provider: string): boolean {
|
|
@@ -4465,11 +5132,24 @@ export class ModelRegistry {
|
|
|
4465
5132
|
);
|
|
4466
5133
|
}
|
|
4467
5134
|
|
|
5135
|
+
/** Opaque owner token for registry-scoped AuthStorage reads. */
|
|
5136
|
+
getAuthStorageOwner(): object {
|
|
5137
|
+
return this.#authStorageConfigOwner;
|
|
5138
|
+
}
|
|
5139
|
+
|
|
4468
5140
|
getProviderWebSearchMode(provider: string): WebSearchMode | undefined {
|
|
4469
5141
|
return this.#providerWebSearchModes.get(provider);
|
|
4470
5142
|
}
|
|
4471
5143
|
|
|
4472
5144
|
getActiveSearchModelContext(model: Model<Api>): ActiveSearchModelContext {
|
|
5145
|
+
const provider = model.provider.toLowerCase();
|
|
5146
|
+
const ownerAuthOverride =
|
|
5147
|
+
this.authStorage.hasConfigApiKey(model.provider, this.#authStorageConfigOwner) ||
|
|
5148
|
+
this.#providerOverrides.has(provider) ||
|
|
5149
|
+
this.#runtimeProviderOverrides.has(provider) ||
|
|
5150
|
+
this.#modelOverrides.get(provider)?.has(model.id.toLowerCase()) === true ||
|
|
5151
|
+
this.#customProviderAuthHeaders.has(provider) ||
|
|
5152
|
+
this.#runtimeProviderAuthHeaders.has(provider);
|
|
4473
5153
|
return {
|
|
4474
5154
|
provider: model.provider,
|
|
4475
5155
|
modelId: model.id,
|
|
@@ -4477,6 +5157,15 @@ export class ModelRegistry {
|
|
|
4477
5157
|
api: model.api,
|
|
4478
5158
|
baseUrl: model.baseUrl,
|
|
4479
5159
|
headers: model.headers,
|
|
5160
|
+
ownerAuthOverride,
|
|
5161
|
+
resolveCredentials: async ({ sessionId, signal }) => {
|
|
5162
|
+
// Resolve against the current registry model so rotating apiKeyEnv
|
|
5163
|
+
// credentials and generated Authorization headers are synchronized
|
|
5164
|
+
// immediately before native search sends its request.
|
|
5165
|
+
const currentModel = this.find(model.provider, model.id) ?? model;
|
|
5166
|
+
const apiKey = await this.getApiKey(currentModel, sessionId, { signal });
|
|
5167
|
+
return { apiKey, headers: currentModel.headers };
|
|
5168
|
+
},
|
|
4480
5169
|
webSearch: this.getProviderWebSearchMode(model.provider),
|
|
4481
5170
|
};
|
|
4482
5171
|
}
|
|
@@ -4507,15 +5196,18 @@ export class ModelRegistry {
|
|
|
4507
5196
|
} = {},
|
|
4508
5197
|
): Promise<string | undefined> {
|
|
4509
5198
|
this.#refreshRotatingConfigApiKey(model.provider);
|
|
4510
|
-
|
|
5199
|
+
const apiKey = await this.#getApiKeyOrNoAuth(model.provider, () =>
|
|
4511
5200
|
this.authStorage.getApiKey(model.provider, sessionId, {
|
|
4512
5201
|
baseUrl: model.baseUrl,
|
|
4513
5202
|
modelId: model.id,
|
|
4514
5203
|
credentialSelector: options.credentialSelector,
|
|
4515
5204
|
preferredCredentialSelector: options.preferredCredentialSelector,
|
|
5205
|
+
owner: this.#authStorageConfigOwner,
|
|
4516
5206
|
signal: options.signal,
|
|
4517
5207
|
}),
|
|
4518
5208
|
);
|
|
5209
|
+
this.#applyEffectiveAuthHeader(model, apiKey);
|
|
5210
|
+
return apiKey;
|
|
4519
5211
|
}
|
|
4520
5212
|
|
|
4521
5213
|
/**
|
|
@@ -4532,28 +5224,143 @@ export class ModelRegistry {
|
|
|
4532
5224
|
} = {},
|
|
4533
5225
|
): Promise<string | undefined> {
|
|
4534
5226
|
this.#refreshRotatingConfigApiKey(provider);
|
|
4535
|
-
|
|
5227
|
+
const apiKey = await this.#getApiKeyOrNoAuth(provider, () =>
|
|
4536
5228
|
this.authStorage.getApiKey(provider, sessionId, {
|
|
4537
5229
|
baseUrl,
|
|
4538
5230
|
credentialSelector: options.credentialSelector,
|
|
4539
5231
|
preferredCredentialSelector: options.preferredCredentialSelector,
|
|
5232
|
+
owner: this.#authStorageConfigOwner,
|
|
4540
5233
|
signal: options.signal,
|
|
4541
5234
|
}),
|
|
4542
5235
|
);
|
|
5236
|
+
if (
|
|
5237
|
+
this.#runtimeProviderAuthHeaders.get(provider) === true ||
|
|
5238
|
+
this.#customProviderAuthHeaders.get(provider) === true
|
|
5239
|
+
) {
|
|
5240
|
+
for (const model of this.#models) {
|
|
5241
|
+
if (model.provider === provider) this.#applyEffectiveAuthHeader(model, apiKey);
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5244
|
+
return apiKey;
|
|
4543
5245
|
}
|
|
4544
5246
|
|
|
4545
|
-
#refreshRotatingConfigApiKey(provider: string): void {
|
|
4546
|
-
const
|
|
5247
|
+
#refreshRotatingConfigApiKey(provider: string, forceStatic = false): void {
|
|
5248
|
+
const runtimeOwned = !forceStatic && this.#runtimeProviderApiKeys.has(provider);
|
|
5249
|
+
const envName = runtimeOwned
|
|
5250
|
+
? this.#runtimeProviderApiKeyEnvNames.get(provider)
|
|
5251
|
+
: this.#customProviderApiKeyEnvNames.get(provider);
|
|
4547
5252
|
if (!envName) return;
|
|
4548
5253
|
const resolved = $rotatingCredentialEnv(envName);
|
|
4549
|
-
|
|
5254
|
+
const previous = runtimeOwned
|
|
5255
|
+
? this.#runtimeProviderResolvedApiKeys.get(provider)
|
|
5256
|
+
: this.#customProviderApiKeys.get(provider);
|
|
5257
|
+
if (resolved === previous && (!runtimeOwned || this.#runtimeProviderCredentialInstalled.has(provider))) return;
|
|
4550
5258
|
if (resolved === undefined) {
|
|
5259
|
+
if (runtimeOwned) {
|
|
5260
|
+
if (!this.#runtimeProviderCredentialInstalled.has(provider)) return;
|
|
5261
|
+
this.#runtimeProviderCredentialInstalled.delete(provider);
|
|
5262
|
+
this.#staticModelsLoaded = false;
|
|
5263
|
+
this.#reloadStaticModels();
|
|
5264
|
+
return;
|
|
5265
|
+
}
|
|
4551
5266
|
this.#customProviderApiKeys.delete(provider);
|
|
4552
|
-
this.authStorage.removeConfigApiKey(provider);
|
|
5267
|
+
this.authStorage.removeConfigApiKey(provider, this.#authStorageConfigOwner);
|
|
5268
|
+
} else {
|
|
5269
|
+
this.#customProviderApiKeys.set(provider, resolved);
|
|
5270
|
+
if (runtimeOwned) {
|
|
5271
|
+
this.#runtimeProviderResolvedApiKeys.set(provider, resolved);
|
|
5272
|
+
this.#runtimeProviderCredentialInstalled.add(provider);
|
|
5273
|
+
}
|
|
5274
|
+
if (runtimeOwned) {
|
|
5275
|
+
this.authStorage.setConfigApiKey(provider, resolved, { owner: this.#authStorageConfigOwner });
|
|
5276
|
+
} else {
|
|
5277
|
+
this.authStorage.setConfigApiKey(provider, resolved, {
|
|
5278
|
+
envSourced: true,
|
|
5279
|
+
owner: this.#authStorageConfigOwner,
|
|
5280
|
+
});
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
const authHeader =
|
|
5284
|
+
this.#runtimeProviderAuthHeaders.get(provider) ?? this.#customProviderAuthHeaders.get(provider);
|
|
5285
|
+
const runtimeOverride = this.#runtimeProviderOverrides.get(provider);
|
|
5286
|
+
if (runtimeOverride) this.#runtimeProviderOverrides.set(provider, { ...runtimeOverride, apiKey: resolved ?? "" });
|
|
5287
|
+
this.#runtimeModelOverlays = this.#runtimeModelOverlays.map(overlay => {
|
|
5288
|
+
if (overlay.provider !== provider) return overlay;
|
|
5289
|
+
const headers = { ...(overlay.headers ?? {}) } as Record<string, string> & {
|
|
5290
|
+
[GENERATED_AUTH_HEADER]?: string;
|
|
5291
|
+
};
|
|
5292
|
+
const generated = headers[GENERATED_AUTH_HEADER];
|
|
5293
|
+
const hadAuthorization = headerValue(headers, "Authorization") !== undefined;
|
|
5294
|
+
const ownsAuthorization = ownsOnlyGeneratedAuthorization(headers, generated);
|
|
5295
|
+
if (ownsAuthorization) deleteHeaderCaseInsensitive(headers, "Authorization");
|
|
5296
|
+
else if (typeof generated === "string") deleteAuthorizationValue(headers, generated);
|
|
5297
|
+
delete headers[GENERATED_AUTH_HEADER];
|
|
5298
|
+
if (authHeader === true && resolved && (ownsAuthorization || !hadAuthorization)) {
|
|
5299
|
+
headers.Authorization = `Bearer ${resolved}`;
|
|
5300
|
+
headers[GENERATED_AUTH_HEADER] = `Bearer ${resolved}`;
|
|
5301
|
+
}
|
|
5302
|
+
return { ...overlay, headers };
|
|
5303
|
+
});
|
|
5304
|
+
if (authHeader === true) this.#generatedAuthHeaderProviders.add(provider);
|
|
5305
|
+
if (authHeader !== true && !this.#generatedAuthHeaderProviders.has(provider)) return;
|
|
5306
|
+
for (const model of this.#models) {
|
|
5307
|
+
if (model.provider !== provider) continue;
|
|
5308
|
+
const headers = { ...(model.headers ?? {}) } as Record<string, string> & {
|
|
5309
|
+
[GENERATED_AUTH_HEADER]?: string;
|
|
5310
|
+
};
|
|
5311
|
+
const generated =
|
|
5312
|
+
this.#generatedAuthHeaders.get(model) ??
|
|
5313
|
+
(typeof headers[GENERATED_AUTH_HEADER] === "string"
|
|
5314
|
+
? { authorization: headers[GENERATED_AUTH_HEADER] }
|
|
5315
|
+
: undefined);
|
|
5316
|
+
const hadAuthorization = headerValue(headers, "Authorization") !== undefined;
|
|
5317
|
+
const ownsAuthorization = ownsOnlyGeneratedAuthorization(headers, generated?.authorization);
|
|
5318
|
+
if (ownsAuthorization) deleteHeaderCaseInsensitive(headers, "Authorization");
|
|
5319
|
+
else if (generated?.authorization) deleteAuthorizationValue(headers, generated.authorization);
|
|
5320
|
+
if (generated?.apiKey === headers["X-Api-Key"]) delete headers["X-Api-Key"];
|
|
5321
|
+
delete headers[GENERATED_AUTH_HEADER];
|
|
5322
|
+
model.headers =
|
|
5323
|
+
authHeader === true && resolved && (ownsAuthorization || !hadAuthorization)
|
|
5324
|
+
? { ...headers, Authorization: `Bearer ${resolved}`, [GENERATED_AUTH_HEADER]: `Bearer ${resolved}` }
|
|
5325
|
+
: headers;
|
|
5326
|
+
if (authHeader === true && resolved && (ownsAuthorization || !hadAuthorization))
|
|
5327
|
+
this.#generatedAuthHeaders.set(model, { authorization: model.headers.Authorization });
|
|
5328
|
+
}
|
|
5329
|
+
this.#rebuildCanonicalIndex();
|
|
5330
|
+
}
|
|
5331
|
+
|
|
5332
|
+
#applyEffectiveAuthHeader(model: Model<Api>, apiKey: string | undefined): void {
|
|
5333
|
+
const runtimeCredentialRevoked =
|
|
5334
|
+
this.#runtimeProviderApiKeys.has(model.provider) &&
|
|
5335
|
+
!this.#runtimeProviderCredentialInstalled.has(model.provider);
|
|
5336
|
+
const authHeader = runtimeCredentialRevoked
|
|
5337
|
+
? this.#customProviderAuthHeaders.get(model.provider)
|
|
5338
|
+
: (this.#runtimeProviderAuthHeaders.get(model.provider) ??
|
|
5339
|
+
this.#customProviderAuthHeaders.get(model.provider));
|
|
5340
|
+
if (authHeader === true) this.#generatedAuthHeaderProviders.add(model.provider);
|
|
5341
|
+
const headers = { ...(model.headers ?? {}) } as Record<string, string> & {
|
|
5342
|
+
[GENERATED_AUTH_HEADER]?: string;
|
|
5343
|
+
};
|
|
5344
|
+
const generated =
|
|
5345
|
+
this.#generatedAuthHeaders.get(model) ??
|
|
5346
|
+
(typeof headers[GENERATED_AUTH_HEADER] === "string"
|
|
5347
|
+
? { authorization: headers[GENERATED_AUTH_HEADER] }
|
|
5348
|
+
: undefined);
|
|
5349
|
+
const hadAuthorization = headerValue(headers, "Authorization") !== undefined;
|
|
5350
|
+
const ownsAuthorization = ownsOnlyGeneratedAuthorization(headers, generated?.authorization);
|
|
5351
|
+
if (authHeader !== true && generated === undefined && !this.#generatedAuthHeaderProviders.has(model.provider))
|
|
4553
5352
|
return;
|
|
5353
|
+
if (ownsAuthorization) deleteHeaderCaseInsensitive(headers, "Authorization");
|
|
5354
|
+
else if (generated?.authorization) deleteAuthorizationValue(headers, generated.authorization);
|
|
5355
|
+
if (generated?.apiKey === headers["X-Api-Key"]) delete headers["X-Api-Key"];
|
|
5356
|
+
delete headers[GENERATED_AUTH_HEADER];
|
|
5357
|
+
model.headers =
|
|
5358
|
+
authHeader === true && apiKey && apiKey !== kNoAuth && (ownsAuthorization || !hadAuthorization)
|
|
5359
|
+
? { ...headers, Authorization: `Bearer ${apiKey}`, [GENERATED_AUTH_HEADER]: `Bearer ${apiKey}` }
|
|
5360
|
+
: headers;
|
|
5361
|
+
if (authHeader === true && apiKey && apiKey !== kNoAuth && (ownsAuthorization || !hadAuthorization)) {
|
|
5362
|
+
this.#generatedAuthHeaders.set(model, { authorization: model.headers.Authorization });
|
|
4554
5363
|
}
|
|
4555
|
-
this.#customProviderApiKeys.set(provider, resolved);
|
|
4556
|
-
this.authStorage.setConfigApiKey(provider, resolved, { envSourced: true });
|
|
4557
5364
|
}
|
|
4558
5365
|
|
|
4559
5366
|
async #peekApiKeyForProvider(
|
|
@@ -4569,16 +5376,21 @@ export class ModelRegistry {
|
|
|
4569
5376
|
return kNoAuth;
|
|
4570
5377
|
}
|
|
4571
5378
|
try {
|
|
4572
|
-
this.authStorage.getProviderEvidenceGeneration(provider);
|
|
5379
|
+
this.authStorage.getProviderEvidenceGeneration(provider, undefined, this.#authStorageConfigOwner);
|
|
4573
5380
|
} catch {
|
|
4574
5381
|
return undefined;
|
|
4575
5382
|
}
|
|
4576
5383
|
if (options.refreshOAuth && this.authStorage.hasOAuth(provider)) {
|
|
4577
|
-
return this.authStorage.getApiKey(provider, undefined, {
|
|
5384
|
+
return this.authStorage.getApiKey(provider, undefined, {
|
|
5385
|
+
baseUrl: options.baseUrl,
|
|
5386
|
+
owner: this.#authStorageConfigOwner,
|
|
5387
|
+
});
|
|
4578
5388
|
}
|
|
4579
5389
|
return options.ignoreCredentiallessFallback
|
|
4580
|
-
? this.authStorage.peekApiKey(provider)
|
|
4581
|
-
: this.#getApiKeyOrNoAuth(provider, () =>
|
|
5390
|
+
? this.authStorage.peekApiKey(provider, { owner: this.#authStorageConfigOwner })
|
|
5391
|
+
: this.#getApiKeyOrNoAuth(provider, () =>
|
|
5392
|
+
this.authStorage.peekApiKey(provider, { owner: this.#authStorageConfigOwner }),
|
|
5393
|
+
);
|
|
4582
5394
|
}
|
|
4583
5395
|
|
|
4584
5396
|
/**
|
|
@@ -4604,9 +5416,13 @@ export class ModelRegistry {
|
|
|
4604
5416
|
|
|
4605
5417
|
#clearRuntimeProviderState(providerName: string): void {
|
|
4606
5418
|
this.#runtimeProviderApiKeys.delete(providerName);
|
|
5419
|
+
this.#runtimeProviderResolvedApiKeys.delete(providerName);
|
|
5420
|
+
this.#runtimeProviderCredentialInstalled.delete(providerName);
|
|
5421
|
+
this.#runtimeProviderApiKeyEnvNames.delete(providerName);
|
|
4607
5422
|
this.#runtimeProviderOverrides.delete(providerName);
|
|
5423
|
+
this.#runtimeProviderAuthHeaders.delete(providerName);
|
|
4608
5424
|
this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(overlay => overlay.provider !== providerName);
|
|
4609
|
-
this.authStorage.removeConfigApiKey(providerName);
|
|
5425
|
+
this.authStorage.removeConfigApiKey(providerName, this.#authStorageConfigOwner);
|
|
4610
5426
|
this.#clearDescriptorDiscoveryEvidence(providerName);
|
|
4611
5427
|
}
|
|
4612
5428
|
|
|
@@ -4692,7 +5508,6 @@ export class ModelRegistry {
|
|
|
4692
5508
|
sourceId,
|
|
4693
5509
|
});
|
|
4694
5510
|
}
|
|
4695
|
-
|
|
4696
5511
|
let sourceHandoff = false;
|
|
4697
5512
|
if (sourceId) {
|
|
4698
5513
|
this.#registeredProviderSources.add(sourceId);
|
|
@@ -4715,14 +5530,89 @@ export class ModelRegistry {
|
|
|
4715
5530
|
this.#lastStaticLoadMtime = null;
|
|
4716
5531
|
this.#staticModelsLoaded = false;
|
|
4717
5532
|
this.#reloadStaticModels();
|
|
5533
|
+
if (config.authHeader !== undefined) this.#runtimeProviderAuthHeaders.set(providerName, config.authHeader);
|
|
5534
|
+
}
|
|
5535
|
+
if (config.authHeader !== undefined) {
|
|
5536
|
+
if (config.authHeader === false) {
|
|
5537
|
+
this.#runtimeModelOverlays = this.#runtimeModelOverlays.map(overlay => {
|
|
5538
|
+
if (overlay.provider !== providerName) return overlay;
|
|
5539
|
+
const headers = { ...(overlay.headers ?? {}) } as Record<string, string> & {
|
|
5540
|
+
[GENERATED_AUTH_HEADER]?: string;
|
|
5541
|
+
};
|
|
5542
|
+
const generated = headers[GENERATED_AUTH_HEADER];
|
|
5543
|
+
if (typeof generated === "string" && headers.Authorization === generated) delete headers.Authorization;
|
|
5544
|
+
delete headers[GENERATED_AUTH_HEADER];
|
|
5545
|
+
return { ...overlay, headers };
|
|
5546
|
+
});
|
|
5547
|
+
}
|
|
5548
|
+
this.#runtimeProviderAuthHeaders.set(providerName, config.authHeader);
|
|
5549
|
+
if (config.authHeader === true) this.#generatedAuthHeaderProviders.add(providerName);
|
|
4718
5550
|
}
|
|
4719
5551
|
|
|
4720
5552
|
if (config.apiKey) {
|
|
4721
|
-
|
|
5553
|
+
const resolved = resolveApiKeyConfig(config.apiKey);
|
|
5554
|
+
if (!resolved) return;
|
|
5555
|
+
if (Bun.env[config.apiKey] !== undefined) this.#runtimeProviderApiKeyEnvNames.set(providerName, config.apiKey);
|
|
5556
|
+
else this.#runtimeProviderApiKeyEnvNames.delete(providerName);
|
|
5557
|
+
this.#customProviderApiKeys.set(providerName, resolved);
|
|
4722
5558
|
// Persist runtime API keys so they survive #reloadStaticModels() cycles
|
|
4723
5559
|
this.#runtimeProviderApiKeys.set(providerName, config.apiKey);
|
|
4724
|
-
|
|
4725
|
-
|
|
5560
|
+
this.#runtimeProviderResolvedApiKeys.set(providerName, resolved);
|
|
5561
|
+
this.#runtimeProviderCredentialInstalled.add(providerName);
|
|
5562
|
+
if (config.authHeader !== undefined) this.#runtimeProviderAuthHeaders.set(providerName, config.authHeader);
|
|
5563
|
+
this.authStorage.setConfigApiKey(providerName, resolved, { owner: this.#authStorageConfigOwner });
|
|
5564
|
+
}
|
|
5565
|
+
if (config.oauth && !config.apiKey && this.#runtimeProviderApiKeys.has(providerName)) {
|
|
5566
|
+
const previousApiKey = this.#runtimeProviderResolvedApiKeys.get(providerName);
|
|
5567
|
+
this.#runtimeProviderApiKeys.delete(providerName);
|
|
5568
|
+
this.#runtimeProviderResolvedApiKeys.delete(providerName);
|
|
5569
|
+
this.#runtimeProviderCredentialInstalled.delete(providerName);
|
|
5570
|
+
this.#runtimeProviderApiKeyEnvNames.delete(providerName);
|
|
5571
|
+
this.#runtimeProviderAuthHeaders.delete(providerName);
|
|
5572
|
+
this.#customProviderApiKeys.delete(providerName);
|
|
5573
|
+
this.authStorage.removeConfigApiKey(providerName, this.#authStorageConfigOwner);
|
|
5574
|
+
this.#runtimeModelOverlays = this.#runtimeModelOverlays.map(overlay => {
|
|
5575
|
+
if (overlay.provider !== providerName) return overlay;
|
|
5576
|
+
const headers = { ...(overlay.headers ?? {}) } as Record<string, string> & {
|
|
5577
|
+
[GENERATED_AUTH_HEADER]?: string;
|
|
5578
|
+
};
|
|
5579
|
+
const generated = headers[GENERATED_AUTH_HEADER];
|
|
5580
|
+
for (const key of Object.keys(headers)) {
|
|
5581
|
+
if (key.toLowerCase() !== "authorization") continue;
|
|
5582
|
+
const value = headers[key];
|
|
5583
|
+
if (
|
|
5584
|
+
(typeof generated === "string" && value === generated) ||
|
|
5585
|
+
(previousApiKey !== undefined && value === `Bearer ${previousApiKey}`) ||
|
|
5586
|
+
(previousApiKey === undefined && value.startsWith("Bearer "))
|
|
5587
|
+
)
|
|
5588
|
+
delete headers[key];
|
|
5589
|
+
}
|
|
5590
|
+
delete headers[GENERATED_AUTH_HEADER];
|
|
5591
|
+
return { ...overlay, headers };
|
|
5592
|
+
});
|
|
5593
|
+
const previousOverride = this.#runtimeProviderOverrides.get(providerName);
|
|
5594
|
+
if (previousOverride) {
|
|
5595
|
+
const headers = { ...(previousOverride.headers ?? {}) };
|
|
5596
|
+
for (const key of Object.keys(headers)) {
|
|
5597
|
+
if (key.toLowerCase() !== "authorization") continue;
|
|
5598
|
+
const value = headers[key];
|
|
5599
|
+
if (
|
|
5600
|
+
(previousApiKey !== undefined && value === `Bearer ${previousApiKey}`) ||
|
|
5601
|
+
(previousApiKey === undefined && value.startsWith("Bearer "))
|
|
5602
|
+
)
|
|
5603
|
+
delete headers[key];
|
|
5604
|
+
}
|
|
5605
|
+
this.#runtimeProviderOverrides.set(providerName, {
|
|
5606
|
+
...previousOverride,
|
|
5607
|
+
apiKey: "",
|
|
5608
|
+
authHeader: undefined,
|
|
5609
|
+
isOAuth: true,
|
|
5610
|
+
headers,
|
|
5611
|
+
});
|
|
5612
|
+
}
|
|
5613
|
+
this.#staticModelsLoaded = false;
|
|
5614
|
+
this.#reloadStaticModels();
|
|
5615
|
+
if (config.authHeader !== undefined) this.#runtimeProviderAuthHeaders.set(providerName, config.authHeader);
|
|
4726
5616
|
}
|
|
4727
5617
|
|
|
4728
5618
|
if (config.models && config.models.length > 0) {
|
|
@@ -4734,12 +5624,13 @@ export class ModelRegistry {
|
|
|
4734
5624
|
config.baseUrl!,
|
|
4735
5625
|
config.api,
|
|
4736
5626
|
config.headers,
|
|
4737
|
-
config.apiKey,
|
|
5627
|
+
config.apiKey ? resolveApiKeyConfig(config.apiKey) : undefined,
|
|
4738
5628
|
config.authHeader,
|
|
4739
5629
|
config.compat,
|
|
4740
5630
|
config.requestTransform,
|
|
4741
5631
|
undefined,
|
|
4742
5632
|
undefined,
|
|
5633
|
+
config.transport,
|
|
4743
5634
|
modelDef as CustomModelDefinitionLike,
|
|
4744
5635
|
);
|
|
4745
5636
|
if (!overlay) {
|
|
@@ -4756,6 +5647,29 @@ export class ModelRegistry {
|
|
|
4756
5647
|
for (const overlay of newOverlays) {
|
|
4757
5648
|
nextModels.push(finalizeCustomModel(overlay, { useDefaults: true }));
|
|
4758
5649
|
}
|
|
5650
|
+
if (
|
|
5651
|
+
config.baseUrl ||
|
|
5652
|
+
config.headers ||
|
|
5653
|
+
config.apiKey ||
|
|
5654
|
+
config.authHeader !== undefined ||
|
|
5655
|
+
config.compat !== undefined ||
|
|
5656
|
+
config.requestTransform !== undefined ||
|
|
5657
|
+
config.transport !== undefined
|
|
5658
|
+
) {
|
|
5659
|
+
this.#runtimeProviderOverrides.set(
|
|
5660
|
+
providerName,
|
|
5661
|
+
this.#mergeProviderOverride(this.#runtimeProviderOverrides.get(providerName), {
|
|
5662
|
+
api: config.api,
|
|
5663
|
+
baseUrl: this.#runtimeProviderOverrides.get(providerName)?.baseUrl ?? config.baseUrl,
|
|
5664
|
+
headers: config.headers,
|
|
5665
|
+
apiKey: config.apiKey ? resolveApiKeyConfig(config.apiKey) : undefined,
|
|
5666
|
+
authHeader: config.authHeader,
|
|
5667
|
+
compat: config.compat,
|
|
5668
|
+
requestTransform: config.requestTransform,
|
|
5669
|
+
transport: config.transport,
|
|
5670
|
+
}),
|
|
5671
|
+
);
|
|
5672
|
+
}
|
|
4759
5673
|
const runtimeTransportOverride = this.#runtimeProviderOverrides.get(providerName);
|
|
4760
5674
|
const withRuntimeTransportOverride = runtimeTransportOverride
|
|
4761
5675
|
? nextModels.map(model => {
|
|
@@ -4766,7 +5680,9 @@ export class ModelRegistry {
|
|
|
4766
5680
|
const withModelOverrides = this.#applyProviderModelOverrides(providerName, withRuntimeTransportOverride);
|
|
4767
5681
|
|
|
4768
5682
|
if (config.oauth?.modifyModels) {
|
|
4769
|
-
const credential = this.authStorage.getOAuthCredential(providerName
|
|
5683
|
+
const credential = this.authStorage.getOAuthCredential(providerName, undefined, {
|
|
5684
|
+
owner: this.#authStorageConfigOwner,
|
|
5685
|
+
});
|
|
4770
5686
|
if (credential) {
|
|
4771
5687
|
this.#models = this.#finalizeModels(config.oauth.modifyModels(withModelOverrides, credential));
|
|
4772
5688
|
this.#rebuildCanonicalIndex();
|
|
@@ -4791,9 +5707,10 @@ export class ModelRegistry {
|
|
|
4791
5707
|
config.transport !== undefined
|
|
4792
5708
|
) {
|
|
4793
5709
|
const transportOverride = {
|
|
5710
|
+
api: config.api,
|
|
4794
5711
|
baseUrl: config.baseUrl,
|
|
4795
5712
|
headers: config.headers,
|
|
4796
|
-
apiKey: config.apiKey,
|
|
5713
|
+
apiKey: config.apiKey ? resolveApiKeyConfig(config.apiKey) : undefined,
|
|
4797
5714
|
authHeader: config.authHeader,
|
|
4798
5715
|
compat: config.compat,
|
|
4799
5716
|
requestTransform: config.requestTransform,
|
|
@@ -4808,6 +5725,11 @@ export class ModelRegistry {
|
|
|
4808
5725
|
if (m.provider !== providerName) return m;
|
|
4809
5726
|
return this.#applyRuntimeProviderOverride(m, transportOverride);
|
|
4810
5727
|
});
|
|
5728
|
+
if (config.authHeader === false) {
|
|
5729
|
+
for (const model of this.#models) {
|
|
5730
|
+
if (model.provider === providerName) this.#applyEffectiveAuthHeader(model, undefined);
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
4811
5733
|
this.#rebuildCanonicalIndex();
|
|
4812
5734
|
this.#rebuildProviderActivity();
|
|
4813
5735
|
}
|