@oh-my-pi/pi-coding-agent 14.0.5 → 14.1.1
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 +120 -0
- package/package.json +8 -8
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +43 -10
- package/src/async/support.ts +5 -0
- package/src/cli/list-models.ts +96 -57
- package/src/commit/agentic/tools/analyze-file.ts +1 -2
- package/src/commit/model-selection.ts +16 -13
- package/src/config/mcp-schema.json +1 -1
- package/src/config/model-equivalence.ts +675 -0
- package/src/config/model-registry.ts +242 -45
- package/src/config/model-resolver.ts +282 -65
- package/src/config/settings-schema.ts +27 -3
- package/src/config/settings.ts +1 -1
- package/src/cursor.ts +64 -23
- package/src/edit/index.ts +254 -89
- package/src/edit/modes/chunk.ts +336 -57
- package/src/edit/modes/hashline.ts +51 -26
- package/src/edit/modes/patch.ts +16 -10
- package/src/edit/modes/replace.ts +15 -7
- package/src/edit/renderer.ts +248 -94
- package/src/export/html/template.css +82 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +614 -97
- package/src/extensibility/custom-tools/types.ts +0 -3
- package/src/extensibility/extensions/loader.ts +16 -0
- package/src/extensibility/extensions/runner.ts +2 -7
- package/src/extensibility/extensions/types.ts +8 -4
- package/src/internal-urls/docs-index.generated.ts +4 -4
- package/src/internal-urls/jobs-protocol.ts +2 -1
- package/src/ipy/executor.ts +447 -52
- package/src/ipy/kernel.ts +39 -13
- package/src/lsp/client.ts +55 -1
- package/src/lsp/index.ts +8 -0
- package/src/lsp/types.ts +6 -0
- package/src/main.ts +6 -2
- package/src/memories/index.ts +7 -6
- package/src/modes/acp/acp-agent.ts +4 -1
- package/src/modes/components/bash-execution.ts +16 -4
- package/src/modes/components/model-selector.ts +221 -64
- package/src/modes/components/status-line/presets.ts +17 -6
- package/src/modes/components/status-line/segments.ts +15 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +7 -1
- package/src/modes/components/tool-execution.ts +145 -75
- package/src/modes/controllers/command-controller.ts +42 -1
- package/src/modes/controllers/event-controller.ts +4 -1
- package/src/modes/controllers/extension-ui-controller.ts +28 -5
- package/src/modes/controllers/input-controller.ts +9 -3
- package/src/modes/controllers/selector-controller.ts +17 -6
- package/src/modes/interactive-mode.ts +19 -3
- package/src/modes/print-mode.ts +13 -4
- package/src/modes/prompt-action-autocomplete.ts +3 -5
- package/src/modes/rpc/rpc-mode.ts +8 -2
- package/src/modes/shared.ts +2 -2
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/bash.md +16 -1
- package/src/prompts/tools/cancel-job.md +1 -1
- package/src/prompts/tools/chunk-edit.md +191 -163
- package/src/prompts/tools/hashline.md +11 -11
- package/src/prompts/tools/patch.md +10 -5
- package/src/prompts/tools/{await.md → poll.md} +1 -1
- package/src/prompts/tools/read-chunk.md +12 -3
- package/src/prompts/tools/read.md +9 -0
- package/src/prompts/tools/task.md +2 -2
- package/src/prompts/tools/vim.md +98 -0
- package/src/prompts/tools/write.md +1 -0
- package/src/sdk.ts +758 -725
- package/src/session/agent-session.ts +187 -40
- package/src/session/session-manager.ts +50 -4
- package/src/slash-commands/builtin-registry.ts +17 -0
- package/src/task/executor.ts +9 -5
- package/src/task/index.ts +3 -5
- package/src/task/types.ts +2 -2
- package/src/tools/bash.ts +240 -57
- package/src/tools/cancel-job.ts +2 -1
- package/src/tools/find.ts +5 -2
- package/src/tools/grep.ts +77 -8
- package/src/tools/index.ts +48 -19
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/{await-tool.ts → poll-tool.ts} +38 -31
- package/src/tools/python.ts +293 -278
- package/src/tools/read.ts +218 -1
- package/src/tools/sqlite-reader.ts +623 -0
- package/src/tools/submit-result.ts +5 -2
- package/src/tools/todo-write.ts +8 -2
- package/src/tools/vim.ts +966 -0
- package/src/tools/write.ts +187 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/git.ts +24 -1
- package/src/utils/session-color.ts +55 -0
- package/src/utils/title-generator.ts +16 -7
- package/src/vim/buffer.ts +309 -0
- package/src/vim/commands.ts +382 -0
- package/src/vim/engine.ts +2426 -0
- package/src/vim/parser.ts +151 -0
- package/src/vim/render.ts +252 -0
- package/src/vim/types.ts +197 -0
|
@@ -28,11 +28,21 @@ import {
|
|
|
28
28
|
import { isRecord, logger } from "@oh-my-pi/pi-utils";
|
|
29
29
|
import { type Static, Type } from "@sinclair/typebox";
|
|
30
30
|
import { type ConfigError, ConfigFile } from "../config";
|
|
31
|
-
import { parseModelString } from "../config/model-resolver";
|
|
31
|
+
import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
|
|
32
32
|
import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
|
|
33
33
|
import type { AuthStorage, OAuthCredential } from "../session/auth-storage";
|
|
34
|
+
import {
|
|
35
|
+
buildCanonicalModelIndex,
|
|
36
|
+
type CanonicalModelIndex,
|
|
37
|
+
type CanonicalModelRecord,
|
|
38
|
+
type CanonicalModelVariant,
|
|
39
|
+
formatCanonicalVariantSelector,
|
|
40
|
+
type ModelEquivalenceConfig,
|
|
41
|
+
} from "./model-equivalence";
|
|
34
42
|
import { type Settings, settings } from "./settings";
|
|
35
43
|
|
|
44
|
+
export type { CanonicalModelIndex, CanonicalModelRecord, CanonicalModelVariant, ModelEquivalenceConfig };
|
|
45
|
+
|
|
36
46
|
export const kNoAuth = "N/A";
|
|
37
47
|
|
|
38
48
|
export function isAuthenticated(apiKey: string | undefined | null): apiKey is string {
|
|
@@ -150,6 +160,7 @@ const OpenAICompatSchema = Type.Object({
|
|
|
150
160
|
vercelGatewayRouting: Type.Optional(VercelGatewayRoutingSchema),
|
|
151
161
|
extraBody: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
152
162
|
supportsStrictMode: Type.Optional(Type.Boolean()),
|
|
163
|
+
toolStrictMode: Type.Optional(Type.Union([Type.Literal("all_strict"), Type.Literal("none")])),
|
|
153
164
|
});
|
|
154
165
|
|
|
155
166
|
const EffortSchema = Type.Union([
|
|
@@ -263,8 +274,14 @@ const ProviderConfigSchema = Type.Object({
|
|
|
263
274
|
modelOverrides: Type.Optional(Type.Record(Type.String(), ModelOverrideSchema)),
|
|
264
275
|
});
|
|
265
276
|
|
|
277
|
+
const EquivalenceConfigSchema = Type.Object({
|
|
278
|
+
overrides: Type.Optional(Type.Record(Type.String(), Type.String({ minLength: 1 }))),
|
|
279
|
+
exclude: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
280
|
+
});
|
|
281
|
+
|
|
266
282
|
const ModelsConfigSchema = Type.Object({
|
|
267
|
-
providers: Type.Record(Type.String(), ProviderConfigSchema),
|
|
283
|
+
providers: Type.Optional(Type.Record(Type.String(), ProviderConfigSchema)),
|
|
284
|
+
equivalence: Type.Optional(EquivalenceConfigSchema),
|
|
268
285
|
});
|
|
269
286
|
|
|
270
287
|
type ModelsConfig = Static<typeof ModelsConfigSchema>;
|
|
@@ -356,7 +373,7 @@ function validateProviderConfiguration(
|
|
|
356
373
|
export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsConfigSchema).withValidation(
|
|
357
374
|
"models",
|
|
358
375
|
config => {
|
|
359
|
-
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
|
|
376
|
+
for (const [providerName, providerConfig] of Object.entries(config.providers ?? {})) {
|
|
360
377
|
validateProviderConfiguration(
|
|
361
378
|
providerName,
|
|
362
379
|
{
|
|
@@ -405,6 +422,11 @@ export interface ProviderDiscoveryState {
|
|
|
405
422
|
error?: string;
|
|
406
423
|
}
|
|
407
424
|
|
|
425
|
+
export interface CanonicalModelQueryOptions {
|
|
426
|
+
availableOnly?: boolean;
|
|
427
|
+
candidates?: readonly Model<Api>[];
|
|
428
|
+
}
|
|
429
|
+
|
|
408
430
|
/** Result of loading custom models from models.json */
|
|
409
431
|
interface CustomModelsResult {
|
|
410
432
|
models?: CustomModelOverlay[];
|
|
@@ -413,6 +435,7 @@ interface CustomModelsResult {
|
|
|
413
435
|
keylessProviders?: Set<string>;
|
|
414
436
|
discoverableProviders?: DiscoveryProviderConfig[];
|
|
415
437
|
configuredProviders?: Set<string>;
|
|
438
|
+
equivalence?: ModelEquivalenceConfig;
|
|
416
439
|
error?: ConfigError;
|
|
417
440
|
found: boolean;
|
|
418
441
|
}
|
|
@@ -698,31 +721,6 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
|
|
|
698
721
|
} as Model<Api>);
|
|
699
722
|
}
|
|
700
723
|
|
|
701
|
-
function buildCustomModel(
|
|
702
|
-
providerName: string,
|
|
703
|
-
providerBaseUrl: string,
|
|
704
|
-
providerApi: Api | undefined,
|
|
705
|
-
providerHeaders: Record<string, string> | undefined,
|
|
706
|
-
providerApiKey: string | undefined,
|
|
707
|
-
authHeader: boolean | undefined,
|
|
708
|
-
providerCompat: Model<Api>["compat"] | undefined,
|
|
709
|
-
modelDef: CustomModelDefinitionLike,
|
|
710
|
-
options: CustomModelBuildOptions,
|
|
711
|
-
): Model<Api> | undefined {
|
|
712
|
-
const model = buildCustomModelOverlay(
|
|
713
|
-
providerName,
|
|
714
|
-
providerBaseUrl,
|
|
715
|
-
providerApi,
|
|
716
|
-
providerHeaders,
|
|
717
|
-
providerApiKey,
|
|
718
|
-
authHeader,
|
|
719
|
-
providerCompat,
|
|
720
|
-
modelDef,
|
|
721
|
-
);
|
|
722
|
-
if (!model) return undefined;
|
|
723
|
-
return finalizeCustomModel(model, options);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
724
|
function normalizeSuppressedSelector(selector: string): string {
|
|
727
725
|
const trimmed = selector.trim();
|
|
728
726
|
if (!trimmed) return trimmed;
|
|
@@ -739,17 +737,27 @@ function getDisabledProviderIdsFromSettings(): Set<string> {
|
|
|
739
737
|
}
|
|
740
738
|
}
|
|
741
739
|
|
|
740
|
+
function getConfiguredProviderOrderFromSettings(): string[] {
|
|
741
|
+
try {
|
|
742
|
+
return settings.get("modelProviderOrder");
|
|
743
|
+
} catch {
|
|
744
|
+
return [];
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
742
748
|
/**
|
|
743
749
|
* Model registry - loads and manages models, resolves API keys via AuthStorage.
|
|
744
750
|
*/
|
|
745
751
|
export class ModelRegistry {
|
|
746
752
|
#models: Model<Api>[] = [];
|
|
753
|
+
#canonicalIndex: CanonicalModelIndex = { records: [], byId: new Map(), bySelector: new Map() };
|
|
747
754
|
#customProviderApiKeys: Map<string, string> = new Map();
|
|
748
755
|
#keylessProviders: Set<string> = new Set();
|
|
749
756
|
#discoverableProviders: DiscoveryProviderConfig[] = [];
|
|
750
757
|
#customModelOverlays: CustomModelOverlay[] = [];
|
|
751
758
|
#providerOverrides: Map<string, ProviderOverride> = new Map();
|
|
752
759
|
#modelOverrides: Map<string, Map<string, ModelOverride>> = new Map();
|
|
760
|
+
#equivalenceConfig: ModelEquivalenceConfig | undefined;
|
|
753
761
|
#configError: ConfigError | undefined = undefined;
|
|
754
762
|
#modelsConfigFile: ConfigFile<ModelsConfig>;
|
|
755
763
|
#registeredProviderSources: Set<string> = new Set();
|
|
@@ -758,6 +766,12 @@ export class ModelRegistry {
|
|
|
758
766
|
#suppressedSelectors: Map<string, number> = new Map();
|
|
759
767
|
#backgroundRefresh?: Promise<void>;
|
|
760
768
|
#lastDiscoveryWarnings: Map<string, string> = new Map();
|
|
769
|
+
// Runtime extension model overlays — persist across refresh() cycles so that
|
|
770
|
+
// models registered by extensions survive the model selector's offline reload.
|
|
771
|
+
#runtimeModelOverlays: CustomModelOverlay[] = [];
|
|
772
|
+
#runtimeProviderApiKeys: Map<string, string> = new Map();
|
|
773
|
+
#runtimeProvidersBySource: Map<string, Set<string>> = new Map();
|
|
774
|
+
#runtimeProviderSourceByName: Map<string, string> = new Map();
|
|
761
775
|
|
|
762
776
|
/**
|
|
763
777
|
* @param authStorage - Auth storage for API key resolution
|
|
@@ -822,8 +836,14 @@ export class ModelRegistry {
|
|
|
822
836
|
this.#customProviderApiKeys.clear();
|
|
823
837
|
this.#keylessProviders.clear();
|
|
824
838
|
this.#discoverableProviders = [];
|
|
839
|
+
// Restore runtime API keys before #loadModels — survives because
|
|
840
|
+
// #loadModels only calls .set() on #customProviderApiKeys, never reassigns it.
|
|
841
|
+
for (const [k, v] of this.#runtimeProviderApiKeys) {
|
|
842
|
+
this.#customProviderApiKeys.set(k, v);
|
|
843
|
+
}
|
|
825
844
|
this.#providerOverrides.clear();
|
|
826
845
|
this.#modelOverrides.clear();
|
|
846
|
+
this.#equivalenceConfig = undefined;
|
|
827
847
|
this.#configError = undefined;
|
|
828
848
|
this.#providerDiscoveryStates.clear();
|
|
829
849
|
this.#loadModels();
|
|
@@ -845,6 +865,7 @@ export class ModelRegistry {
|
|
|
845
865
|
keylessProviders = new Set(),
|
|
846
866
|
discoverableProviders = [],
|
|
847
867
|
configuredProviders = new Set(),
|
|
868
|
+
equivalence,
|
|
848
869
|
error: configError,
|
|
849
870
|
} = this.#loadCustomModels();
|
|
850
871
|
this.#configError = configError;
|
|
@@ -853,14 +874,18 @@ export class ModelRegistry {
|
|
|
853
874
|
this.#customModelOverlays = customModels;
|
|
854
875
|
this.#providerOverrides = overrides;
|
|
855
876
|
this.#modelOverrides = modelOverrides;
|
|
877
|
+
this.#equivalenceConfig = equivalence;
|
|
856
878
|
|
|
857
879
|
this.#addImplicitDiscoverableProviders(configuredProviders);
|
|
858
880
|
const builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
|
|
859
881
|
const cachedDiscoveries = this.#applyHardcodedModelPolicies(this.#loadCachedDiscoverableModels());
|
|
860
882
|
const resolvedDefaults = this.#mergeResolvedModels(builtInModels, cachedDiscoveries);
|
|
861
|
-
const
|
|
883
|
+
const withConfigModels = this.#mergeCustomModels(resolvedDefaults, this.#customModelOverlays);
|
|
884
|
+
// Merge runtime extension models so they survive refresh() cycles
|
|
885
|
+
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
|
|
862
886
|
|
|
863
887
|
this.#models = this.#applyModelOverrides(combined, this.#modelOverrides);
|
|
888
|
+
this.#rebuildCanonicalIndex();
|
|
864
889
|
}
|
|
865
890
|
|
|
866
891
|
/** Load built-in models, applying provider-level overrides only.
|
|
@@ -1045,9 +1070,10 @@ export class ModelRegistry {
|
|
|
1045
1070
|
const allModelOverrides = new Map<string, Map<string, ModelOverride>>();
|
|
1046
1071
|
const keylessProviders = new Set<string>();
|
|
1047
1072
|
const discoverableProviders: DiscoveryProviderConfig[] = [];
|
|
1048
|
-
const
|
|
1073
|
+
const providerEntries = Object.entries(value.providers ?? {});
|
|
1074
|
+
const configuredProviders = new Set(Object.keys(value.providers ?? {}));
|
|
1049
1075
|
|
|
1050
|
-
for (const [providerName, providerConfig] of
|
|
1076
|
+
for (const [providerName, providerConfig] of providerEntries) {
|
|
1051
1077
|
// Always set overrides when baseUrl/headers/apiKey/compat are present
|
|
1052
1078
|
if (providerConfig.baseUrl || providerConfig.headers || providerConfig.apiKey || providerConfig.compat) {
|
|
1053
1079
|
overrides.set(providerName, {
|
|
@@ -1097,6 +1123,7 @@ export class ModelRegistry {
|
|
|
1097
1123
|
keylessProviders,
|
|
1098
1124
|
discoverableProviders,
|
|
1099
1125
|
configuredProviders,
|
|
1126
|
+
equivalence: value.equivalence,
|
|
1100
1127
|
found: true,
|
|
1101
1128
|
};
|
|
1102
1129
|
}
|
|
@@ -1145,8 +1172,11 @@ export class ModelRegistry {
|
|
|
1145
1172
|
}),
|
|
1146
1173
|
);
|
|
1147
1174
|
const resolved = this.#mergeResolvedModels(this.#models, discoveredModels);
|
|
1148
|
-
const
|
|
1175
|
+
const withConfigModels = this.#mergeCustomModels(resolved, this.#customModelOverlays);
|
|
1176
|
+
// Merge runtime extension models so they survive online discovery completion
|
|
1177
|
+
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
|
|
1149
1178
|
this.#models = this.#applyModelOverrides(combined, this.#modelOverrides);
|
|
1179
|
+
this.#rebuildCanonicalIndex();
|
|
1150
1180
|
}
|
|
1151
1181
|
|
|
1152
1182
|
async #discoverProviderModels(
|
|
@@ -1653,10 +1683,14 @@ export class ModelRegistry {
|
|
|
1653
1683
|
});
|
|
1654
1684
|
}
|
|
1655
1685
|
|
|
1686
|
+
#rebuildCanonicalIndex(): void {
|
|
1687
|
+
this.#canonicalIndex = buildCanonicalModelIndex(this.#models, this.#equivalenceConfig);
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1656
1690
|
#parseModels(config: ModelsConfig): CustomModelOverlay[] {
|
|
1657
1691
|
const models: CustomModelOverlay[] = [];
|
|
1658
1692
|
|
|
1659
|
-
for (const [providerName, providerConfig] of Object.entries(config.providers)) {
|
|
1693
|
+
for (const [providerName, providerConfig] of Object.entries(config.providers ?? {})) {
|
|
1660
1694
|
const modelDefs = providerConfig.models ?? [];
|
|
1661
1695
|
if (modelDefs.length === 0) continue; // Override-only, no custom models
|
|
1662
1696
|
if (providerConfig.apiKey) {
|
|
@@ -1688,17 +1722,139 @@ export class ModelRegistry {
|
|
|
1688
1722
|
return this.#models;
|
|
1689
1723
|
}
|
|
1690
1724
|
|
|
1725
|
+
#isModelAvailable(model: Model<Api>): boolean {
|
|
1726
|
+
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
1727
|
+
return (
|
|
1728
|
+
!disabledProviders.has(model.provider) &&
|
|
1729
|
+
(this.#keylessProviders.has(model.provider) || this.authStorage.hasAuth(model.provider))
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
#filterCanonicalVariants(
|
|
1734
|
+
record: CanonicalModelRecord,
|
|
1735
|
+
options: CanonicalModelQueryOptions | undefined,
|
|
1736
|
+
): CanonicalModelVariant[] {
|
|
1737
|
+
const candidateKeys = options?.candidates
|
|
1738
|
+
? new Set(options.candidates.map(candidate => formatCanonicalVariantSelector(candidate)))
|
|
1739
|
+
: undefined;
|
|
1740
|
+
return record.variants.filter(variant => {
|
|
1741
|
+
if (candidateKeys && !candidateKeys.has(variant.selector)) {
|
|
1742
|
+
return false;
|
|
1743
|
+
}
|
|
1744
|
+
if (options?.availableOnly && !this.#isModelAvailable(variant.model)) {
|
|
1745
|
+
return false;
|
|
1746
|
+
}
|
|
1747
|
+
return true;
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
#providerRank(models: readonly Model<Api>[]): Map<string, number> {
|
|
1752
|
+
const configuredProviders = getConfiguredProviderOrderFromSettings();
|
|
1753
|
+
const result = new Map<string, number>();
|
|
1754
|
+
let nextRank = 0;
|
|
1755
|
+
for (const provider of configuredProviders) {
|
|
1756
|
+
const normalized = provider.trim().toLowerCase();
|
|
1757
|
+
if (!normalized || result.has(normalized)) {
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
result.set(normalized, nextRank);
|
|
1761
|
+
nextRank += 1;
|
|
1762
|
+
}
|
|
1763
|
+
for (const model of models) {
|
|
1764
|
+
const normalized = model.provider.toLowerCase();
|
|
1765
|
+
if (result.has(normalized)) {
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
result.set(normalized, nextRank);
|
|
1769
|
+
nextRank += 1;
|
|
1770
|
+
}
|
|
1771
|
+
return result;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
#resolveCanonicalVariant(
|
|
1775
|
+
variants: readonly CanonicalModelVariant[],
|
|
1776
|
+
allCandidates: readonly Model<Api>[],
|
|
1777
|
+
): CanonicalModelVariant | undefined {
|
|
1778
|
+
if (variants.length === 0) {
|
|
1779
|
+
return undefined;
|
|
1780
|
+
}
|
|
1781
|
+
const providerRank = this.#providerRank(allCandidates);
|
|
1782
|
+
const modelOrder = new Map<string, number>();
|
|
1783
|
+
for (let index = 0; index < allCandidates.length; index += 1) {
|
|
1784
|
+
modelOrder.set(formatCanonicalVariantSelector(allCandidates[index]!), index);
|
|
1785
|
+
}
|
|
1786
|
+
const sourceRank: Record<CanonicalModelVariant["source"], number> = {
|
|
1787
|
+
override: 1,
|
|
1788
|
+
bundled: 1,
|
|
1789
|
+
heuristic: 2,
|
|
1790
|
+
fallback: 3,
|
|
1791
|
+
};
|
|
1792
|
+
return [...variants].sort((left, right) => {
|
|
1793
|
+
const leftProviderRank = providerRank.get(left.model.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
|
|
1794
|
+
const rightProviderRank = providerRank.get(right.model.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
|
|
1795
|
+
if (leftProviderRank !== rightProviderRank) {
|
|
1796
|
+
return leftProviderRank - rightProviderRank;
|
|
1797
|
+
}
|
|
1798
|
+
const leftExact = left.model.id === left.canonicalId ? 0 : 1;
|
|
1799
|
+
const rightExact = right.model.id === right.canonicalId ? 0 : 1;
|
|
1800
|
+
if (leftExact !== rightExact) {
|
|
1801
|
+
return leftExact - rightExact;
|
|
1802
|
+
}
|
|
1803
|
+
if (sourceRank[left.source] !== sourceRank[right.source]) {
|
|
1804
|
+
return sourceRank[left.source] - sourceRank[right.source];
|
|
1805
|
+
}
|
|
1806
|
+
if (left.model.id.length !== right.model.id.length) {
|
|
1807
|
+
return left.model.id.length - right.model.id.length;
|
|
1808
|
+
}
|
|
1809
|
+
const leftOrder = modelOrder.get(left.selector) ?? Number.MAX_SAFE_INTEGER;
|
|
1810
|
+
const rightOrder = modelOrder.get(right.selector) ?? Number.MAX_SAFE_INTEGER;
|
|
1811
|
+
return leftOrder - rightOrder;
|
|
1812
|
+
})[0];
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
getCanonicalModels(options?: CanonicalModelQueryOptions): CanonicalModelRecord[] {
|
|
1816
|
+
const records: CanonicalModelRecord[] = [];
|
|
1817
|
+
for (const record of this.#canonicalIndex.records) {
|
|
1818
|
+
const variants = this.#filterCanonicalVariants(record, options);
|
|
1819
|
+
if (variants.length === 0) {
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
records.push({
|
|
1823
|
+
id: record.id,
|
|
1824
|
+
name: record.name,
|
|
1825
|
+
variants,
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
return records;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[] {
|
|
1832
|
+
const record = this.#canonicalIndex.byId.get(canonicalId.trim().toLowerCase());
|
|
1833
|
+
if (!record) {
|
|
1834
|
+
return [];
|
|
1835
|
+
}
|
|
1836
|
+
return this.#filterCanonicalVariants(record, options);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
resolveCanonicalModel(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined {
|
|
1840
|
+
const variants = this.getCanonicalVariants(canonicalId, options);
|
|
1841
|
+
if (variants.length === 0) {
|
|
1842
|
+
return undefined;
|
|
1843
|
+
}
|
|
1844
|
+
const candidates = options?.candidates ?? (options?.availableOnly ? this.getAvailable() : this.getAll());
|
|
1845
|
+
return this.#resolveCanonicalVariant(variants, candidates)?.model;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
getCanonicalId(model: Model<Api>): string | undefined {
|
|
1849
|
+
return this.#canonicalIndex.bySelector.get(formatCanonicalVariantSelector(model).toLowerCase());
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1691
1852
|
/**
|
|
1692
1853
|
* Get only models that have auth configured.
|
|
1693
1854
|
* This is a fast check that doesn't refresh OAuth tokens.
|
|
1694
1855
|
*/
|
|
1695
1856
|
getAvailable(): Model<Api>[] {
|
|
1696
|
-
|
|
1697
|
-
return this.#models.filter(
|
|
1698
|
-
m =>
|
|
1699
|
-
!disabledProviders.has(m.provider) &&
|
|
1700
|
-
(this.#keylessProviders.has(m.provider) || this.authStorage.hasAuth(m.provider)),
|
|
1701
|
-
);
|
|
1857
|
+
return this.#models.filter(model => this.#isModelAvailable(model));
|
|
1702
1858
|
}
|
|
1703
1859
|
|
|
1704
1860
|
getDiscoverableProviders(): string[] {
|
|
@@ -1716,7 +1872,7 @@ export class ModelRegistry {
|
|
|
1716
1872
|
* Find a model by provider and ID.
|
|
1717
1873
|
*/
|
|
1718
1874
|
find(provider: string, modelId: string): Model<Api> | undefined {
|
|
1719
|
-
return
|
|
1875
|
+
return resolveProviderModelReference(provider, modelId, this.#models);
|
|
1720
1876
|
}
|
|
1721
1877
|
|
|
1722
1878
|
/**
|
|
@@ -1766,6 +1922,21 @@ export class ModelRegistry {
|
|
|
1766
1922
|
clearSourceRegistrations(sourceId: string): void {
|
|
1767
1923
|
unregisterCustomApis(sourceId);
|
|
1768
1924
|
unregisterOAuthProviders(sourceId);
|
|
1925
|
+
const sourceProviders = this.#runtimeProvidersBySource.get(sourceId);
|
|
1926
|
+
if (!sourceProviders || sourceProviders.size === 0) {
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
this.#runtimeProvidersBySource.delete(sourceId);
|
|
1930
|
+
for (const providerName of sourceProviders) {
|
|
1931
|
+
if (this.#runtimeProviderSourceByName.get(providerName) !== sourceId) {
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
this.#runtimeProviderSourceByName.delete(providerName);
|
|
1935
|
+
this.#runtimeProviderApiKeys.delete(providerName);
|
|
1936
|
+
this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(overlay => overlay.provider !== providerName);
|
|
1937
|
+
}
|
|
1938
|
+
this.#reloadStaticModels();
|
|
1939
|
+
this.#rebuildCanonicalIndex();
|
|
1769
1940
|
}
|
|
1770
1941
|
|
|
1771
1942
|
/**
|
|
@@ -1824,15 +1995,30 @@ export class ModelRegistry {
|
|
|
1824
1995
|
|
|
1825
1996
|
if (sourceId) {
|
|
1826
1997
|
this.#registeredProviderSources.add(sourceId);
|
|
1998
|
+
const previousSourceId = this.#runtimeProviderSourceByName.get(providerName);
|
|
1999
|
+
if (previousSourceId && previousSourceId !== sourceId) {
|
|
2000
|
+
const previousProviders = this.#runtimeProvidersBySource.get(previousSourceId);
|
|
2001
|
+
previousProviders?.delete(providerName);
|
|
2002
|
+
if (previousProviders && previousProviders.size === 0) {
|
|
2003
|
+
this.#runtimeProvidersBySource.delete(previousSourceId);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
const sourceProviders = this.#runtimeProvidersBySource.get(sourceId) ?? new Set<string>();
|
|
2007
|
+
sourceProviders.add(providerName);
|
|
2008
|
+
this.#runtimeProvidersBySource.set(sourceId, sourceProviders);
|
|
2009
|
+
this.#runtimeProviderSourceByName.set(providerName, sourceId);
|
|
1827
2010
|
}
|
|
1828
2011
|
if (config.apiKey) {
|
|
1829
2012
|
this.#customProviderApiKeys.set(providerName, config.apiKey);
|
|
2013
|
+
// Persist runtime API keys so they survive #reloadStaticModels() cycles
|
|
2014
|
+
this.#runtimeProviderApiKeys.set(providerName, config.apiKey);
|
|
1830
2015
|
}
|
|
1831
2016
|
|
|
1832
2017
|
if (config.models && config.models.length > 0) {
|
|
1833
|
-
|
|
2018
|
+
// Build model overlays that persist across refresh() cycles
|
|
2019
|
+
const newOverlays: CustomModelOverlay[] = [];
|
|
1834
2020
|
for (const modelDef of config.models) {
|
|
1835
|
-
const
|
|
2021
|
+
const overlay = buildCustomModelOverlay(
|
|
1836
2022
|
providerName,
|
|
1837
2023
|
config.baseUrl!,
|
|
1838
2024
|
config.api,
|
|
@@ -1841,23 +2027,33 @@ export class ModelRegistry {
|
|
|
1841
2027
|
config.authHeader,
|
|
1842
2028
|
config.compat,
|
|
1843
2029
|
modelDef as CustomModelDefinitionLike,
|
|
1844
|
-
{ useDefaults: true },
|
|
1845
2030
|
);
|
|
1846
|
-
if (!
|
|
2031
|
+
if (!overlay) {
|
|
1847
2032
|
throw new Error(`Provider ${providerName}, model ${modelDef.id}: no "api" specified.`);
|
|
1848
2033
|
}
|
|
1849
|
-
|
|
2034
|
+
newOverlays.push(overlay);
|
|
2035
|
+
}
|
|
2036
|
+
// Store as runtime overlays so they survive #reloadStaticModels()
|
|
2037
|
+
this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(m => m.provider !== providerName);
|
|
2038
|
+
this.#runtimeModelOverlays.push(...newOverlays);
|
|
2039
|
+
|
|
2040
|
+
// Also update #models immediately for the current cycle
|
|
2041
|
+
const nextModels = this.#models.filter(m => m.provider !== providerName);
|
|
2042
|
+
for (const overlay of newOverlays) {
|
|
2043
|
+
nextModels.push(finalizeCustomModel(overlay, { useDefaults: true }));
|
|
1850
2044
|
}
|
|
1851
2045
|
|
|
1852
2046
|
if (config.oauth?.modifyModels) {
|
|
1853
2047
|
const credential = this.authStorage.getOAuthCredential(providerName);
|
|
1854
2048
|
if (credential) {
|
|
1855
2049
|
this.#models = config.oauth.modifyModels(nextModels, credential);
|
|
2050
|
+
this.#rebuildCanonicalIndex();
|
|
1856
2051
|
return;
|
|
1857
2052
|
}
|
|
1858
2053
|
}
|
|
1859
2054
|
|
|
1860
2055
|
this.#models = nextModels;
|
|
2056
|
+
this.#rebuildCanonicalIndex();
|
|
1861
2057
|
return;
|
|
1862
2058
|
}
|
|
1863
2059
|
|
|
@@ -1870,6 +2066,7 @@ export class ModelRegistry {
|
|
|
1870
2066
|
headers: config.headers ? { ...m.headers, ...config.headers } : m.headers,
|
|
1871
2067
|
};
|
|
1872
2068
|
});
|
|
2069
|
+
this.#rebuildCanonicalIndex();
|
|
1873
2070
|
}
|
|
1874
2071
|
}
|
|
1875
2072
|
|