@oh-my-pi/pi-coding-agent 14.0.5 → 14.1.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 +41 -0
- package/package.json +7 -7
- package/src/async/index.ts +1 -0
- package/src/async/support.ts +5 -0
- package/src/cli/list-models.ts +96 -57
- package/src/commit/model-selection.ts +16 -13
- package/src/config/model-equivalence.ts +674 -0
- package/src/config/model-registry.ts +179 -11
- package/src/config/model-resolver.ts +171 -50
- package/src/config/settings-schema.ts +23 -0
- package/src/export/html/template.css +82 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +612 -97
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/internal-urls/jobs-protocol.ts +2 -1
- package/src/lsp/client.ts +1 -1
- package/src/main.ts +6 -1
- package/src/memories/index.ts +7 -6
- package/src/modes/components/model-selector.ts +221 -64
- package/src/modes/controllers/command-controller.ts +18 -0
- package/src/modes/controllers/selector-controller.ts +13 -5
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/bash.md +15 -0
- package/src/prompts/tools/cancel-job.md +1 -1
- package/src/prompts/tools/read-chunk.md +9 -0
- package/src/prompts/tools/read.md +9 -0
- package/src/prompts/tools/write.md +1 -0
- package/src/sdk.ts +7 -4
- package/src/session/agent-session.ts +23 -6
- package/src/task/executor.ts +5 -1
- package/src/tools/await-tool.ts +2 -1
- package/src/tools/bash.ts +221 -56
- package/src/tools/cancel-job.ts +2 -1
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/read.ts +218 -1
- package/src/tools/sqlite-reader.ts +623 -0
- package/src/tools/write.ts +187 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/git.ts +24 -1
- package/src/utils/title-generator.ts +1 -1
|
@@ -58,6 +58,10 @@ export function formatModelString(model: Model<Api>): string {
|
|
|
58
58
|
return `${model.provider}/${model.id}`;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
export function formatModelSelectorValue(selector: string, thinkingLevel: ThinkingLevel | undefined): string {
|
|
62
|
+
return thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit ? `${selector}:${thinkingLevel}` : selector;
|
|
63
|
+
}
|
|
64
|
+
|
|
61
65
|
export interface ModelMatchPreferences {
|
|
62
66
|
/** Most-recently-used model keys (provider/modelId) to prefer when ambiguous. */
|
|
63
67
|
usageOrder?: string[];
|
|
@@ -65,6 +69,14 @@ export interface ModelMatchPreferences {
|
|
|
65
69
|
deprioritizeProviders?: string[];
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
export type CanonicalModelRegistry = Partial<
|
|
73
|
+
Pick<ModelRegistry, "resolveCanonicalModel" | "getCanonicalVariants" | "getCanonicalId">
|
|
74
|
+
>;
|
|
75
|
+
export type ModelLookupRegistry = Pick<ModelRegistry, "getAvailable"> & Partial<CanonicalModelRegistry>;
|
|
76
|
+
type CliModelRegistry = Pick<ModelRegistry, "getAll"> & Partial<CanonicalModelRegistry>;
|
|
77
|
+
type InitialModelRegistry = Pick<ModelRegistry, "getAvailable" | "find">;
|
|
78
|
+
type RestorableModelRegistry = Pick<ModelRegistry, "getAvailable" | "find" | "getApiKey">;
|
|
79
|
+
|
|
68
80
|
interface ModelPreferenceContext {
|
|
69
81
|
modelUsageRank: Map<string, number>;
|
|
70
82
|
providerUsageRank: Map<string, number>;
|
|
@@ -142,9 +154,8 @@ function isAlias(id: string): boolean {
|
|
|
142
154
|
}
|
|
143
155
|
|
|
144
156
|
/**
|
|
145
|
-
* Find an exact model
|
|
146
|
-
*
|
|
147
|
-
* When matching by bare id, ambiguous matches across providers are rejected.
|
|
157
|
+
* Find an exact explicit provider/model match.
|
|
158
|
+
* Bare model ids are handled separately so canonical ids can coalesce variants.
|
|
148
159
|
*/
|
|
149
160
|
export function findExactModelReferenceMatch(
|
|
150
161
|
modelReference: string,
|
|
@@ -155,18 +166,6 @@ export function findExactModelReferenceMatch(
|
|
|
155
166
|
return undefined;
|
|
156
167
|
}
|
|
157
168
|
|
|
158
|
-
const normalizedReference = trimmedReference.toLowerCase();
|
|
159
|
-
|
|
160
|
-
const canonicalMatches = availableModels.filter(
|
|
161
|
-
model => `${model.provider}/${model.id}`.toLowerCase() === normalizedReference,
|
|
162
|
-
);
|
|
163
|
-
if (canonicalMatches.length === 1) {
|
|
164
|
-
return canonicalMatches[0];
|
|
165
|
-
}
|
|
166
|
-
if (canonicalMatches.length > 1) {
|
|
167
|
-
return undefined;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
169
|
const slashIndex = trimmedReference.indexOf("/");
|
|
171
170
|
if (slashIndex !== -1) {
|
|
172
171
|
const provider = trimmedReference.substring(0, slashIndex).trim();
|
|
@@ -185,9 +184,25 @@ export function findExactModelReferenceMatch(
|
|
|
185
184
|
}
|
|
186
185
|
}
|
|
187
186
|
}
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
188
189
|
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
function findExactCanonicalModelMatch(
|
|
191
|
+
modelReference: string,
|
|
192
|
+
availableModels: Model<Api>[],
|
|
193
|
+
modelRegistry: CanonicalModelRegistry | undefined,
|
|
194
|
+
): Model<Api> | undefined {
|
|
195
|
+
if (!modelRegistry) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
const trimmedReference = modelReference.trim();
|
|
199
|
+
if (!trimmedReference || trimmedReference.includes("/")) {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
return modelRegistry.resolveCanonicalModel?.(trimmedReference, {
|
|
203
|
+
availableOnly: false,
|
|
204
|
+
candidates: availableModels,
|
|
205
|
+
});
|
|
191
206
|
}
|
|
192
207
|
|
|
193
208
|
/**
|
|
@@ -198,13 +213,20 @@ function tryMatchModel(
|
|
|
198
213
|
modelPattern: string,
|
|
199
214
|
availableModels: Model<Api>[],
|
|
200
215
|
context: ModelPreferenceContext,
|
|
216
|
+
options?: { modelRegistry?: CanonicalModelRegistry },
|
|
201
217
|
): Model<Api> | undefined {
|
|
202
|
-
//
|
|
218
|
+
// Explicit provider/model selectors always bypass canonical coalescing.
|
|
203
219
|
const exactRefMatch = findExactModelReferenceMatch(modelPattern, availableModels);
|
|
204
220
|
if (exactRefMatch) {
|
|
205
221
|
return exactRefMatch;
|
|
206
222
|
}
|
|
207
223
|
|
|
224
|
+
// Exact canonical ids coalesce provider variants before bare-id matching.
|
|
225
|
+
const exactCanonicalMatch = findExactCanonicalModelMatch(modelPattern, availableModels, options?.modelRegistry);
|
|
226
|
+
if (exactCanonicalMatch) {
|
|
227
|
+
return exactCanonicalMatch;
|
|
228
|
+
}
|
|
229
|
+
|
|
208
230
|
// Check for provider/modelId format — fuzzy match within provider
|
|
209
231
|
const slashIndex = modelPattern.indexOf("/");
|
|
210
232
|
if (slashIndex !== -1) {
|
|
@@ -300,10 +322,10 @@ function parseModelPatternWithContext(
|
|
|
300
322
|
pattern: string,
|
|
301
323
|
availableModels: Model<Api>[],
|
|
302
324
|
context: ModelPreferenceContext,
|
|
303
|
-
options?: { allowInvalidThinkingSelectorFallback?: boolean },
|
|
325
|
+
options?: { allowInvalidThinkingSelectorFallback?: boolean; modelRegistry?: CanonicalModelRegistry },
|
|
304
326
|
): ParsedModelResult {
|
|
305
327
|
// Try exact match first
|
|
306
|
-
const exactMatch = tryMatchModel(pattern, availableModels, context);
|
|
328
|
+
const exactMatch = tryMatchModel(pattern, availableModels, context, options);
|
|
307
329
|
if (exactMatch) {
|
|
308
330
|
return { model: exactMatch, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
|
|
309
331
|
}
|
|
@@ -357,7 +379,7 @@ export function parseModelPattern(
|
|
|
357
379
|
pattern: string,
|
|
358
380
|
availableModels: Model<Api>[],
|
|
359
381
|
preferences?: ModelMatchPreferences,
|
|
360
|
-
options?: { allowInvalidThinkingSelectorFallback?: boolean },
|
|
382
|
+
options?: { allowInvalidThinkingSelectorFallback?: boolean; modelRegistry?: CanonicalModelRegistry },
|
|
361
383
|
): ParsedModelResult {
|
|
362
384
|
const context = buildPreferenceContext(availableModels, preferences);
|
|
363
385
|
return parseModelPatternWithContext(pattern, availableModels, context, options);
|
|
@@ -469,7 +491,7 @@ export interface ResolvedModelRoleValue {
|
|
|
469
491
|
export function resolveModelRoleValue(
|
|
470
492
|
roleValue: string | undefined,
|
|
471
493
|
availableModels: Model<Api>[],
|
|
472
|
-
options?: { settings?: Settings; matchPreferences?: ModelMatchPreferences },
|
|
494
|
+
options?: { settings?: Settings; matchPreferences?: ModelMatchPreferences; modelRegistry?: CanonicalModelRegistry },
|
|
473
495
|
): ResolvedModelRoleValue {
|
|
474
496
|
if (!roleValue) {
|
|
475
497
|
return { model: undefined, thinkingLevel: undefined, explicitThinkingLevel: false, warning: undefined };
|
|
@@ -490,7 +512,9 @@ export function resolveModelRoleValue(
|
|
|
490
512
|
|
|
491
513
|
let warning: string | undefined;
|
|
492
514
|
for (const effectivePattern of effectivePatterns) {
|
|
493
|
-
const resolved = parseModelPattern(effectivePattern, availableModels, options?.matchPreferences
|
|
515
|
+
const resolved = parseModelPattern(effectivePattern, availableModels, options?.matchPreferences, {
|
|
516
|
+
modelRegistry: options?.modelRegistry,
|
|
517
|
+
});
|
|
494
518
|
if (resolved.model) {
|
|
495
519
|
return {
|
|
496
520
|
model: resolved.model,
|
|
@@ -543,13 +567,14 @@ export function resolveModelFromString(
|
|
|
543
567
|
value: string,
|
|
544
568
|
available: Model<Api>[],
|
|
545
569
|
matchPreferences?: ModelMatchPreferences,
|
|
570
|
+
modelRegistry?: CanonicalModelRegistry,
|
|
546
571
|
): Model<Api> | undefined {
|
|
547
572
|
const parsed = parseModelString(value);
|
|
548
573
|
if (parsed) {
|
|
549
574
|
const exact = available.find(model => model.provider === parsed.provider && model.id === parsed.id);
|
|
550
575
|
if (exact) return exact;
|
|
551
576
|
}
|
|
552
|
-
return parseModelPattern(value, available, matchPreferences).model;
|
|
577
|
+
return parseModelPattern(value, available, matchPreferences, { modelRegistry }).model;
|
|
553
578
|
}
|
|
554
579
|
|
|
555
580
|
/**
|
|
@@ -560,13 +585,19 @@ export function resolveModelFromSettings(options: {
|
|
|
560
585
|
availableModels: Model<Api>[];
|
|
561
586
|
matchPreferences?: ModelMatchPreferences;
|
|
562
587
|
roleOrder?: readonly ModelRole[];
|
|
588
|
+
modelRegistry?: CanonicalModelRegistry;
|
|
563
589
|
}): Model<Api> | undefined {
|
|
564
|
-
const { settings, availableModels, matchPreferences, roleOrder } = options;
|
|
590
|
+
const { settings, availableModels, matchPreferences, roleOrder, modelRegistry } = options;
|
|
565
591
|
const roles = roleOrder ?? MODEL_ROLE_IDS;
|
|
566
592
|
for (const role of roles) {
|
|
567
593
|
const configured = settings.getModelRole(role);
|
|
568
594
|
if (!configured) continue;
|
|
569
|
-
const resolved = resolveModelFromString(
|
|
595
|
+
const resolved = resolveModelFromString(
|
|
596
|
+
expandRoleAlias(configured, settings),
|
|
597
|
+
availableModels,
|
|
598
|
+
matchPreferences,
|
|
599
|
+
modelRegistry,
|
|
600
|
+
);
|
|
570
601
|
if (resolved) return resolved;
|
|
571
602
|
}
|
|
572
603
|
return availableModels[0];
|
|
@@ -577,7 +608,7 @@ export function resolveModelFromSettings(options: {
|
|
|
577
608
|
*/
|
|
578
609
|
export function resolveModelOverride(
|
|
579
610
|
modelPatterns: string[],
|
|
580
|
-
modelRegistry:
|
|
611
|
+
modelRegistry: ModelLookupRegistry,
|
|
581
612
|
settings?: Settings,
|
|
582
613
|
): { model?: Model<Api>; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } {
|
|
583
614
|
if (modelPatterns.length === 0) return { explicitThinkingLevel: false };
|
|
@@ -587,6 +618,7 @@ export function resolveModelOverride(
|
|
|
587
618
|
const { model, thinkingLevel, explicitThinkingLevel } = resolveModelRoleValue(pattern, availableModels, {
|
|
588
619
|
settings,
|
|
589
620
|
matchPreferences,
|
|
621
|
+
modelRegistry,
|
|
590
622
|
});
|
|
591
623
|
if (model) {
|
|
592
624
|
return { model, thinkingLevel, explicitThinkingLevel };
|
|
@@ -602,12 +634,14 @@ export function resolveRoleSelection(
|
|
|
602
634
|
roles: readonly string[],
|
|
603
635
|
settings: Settings,
|
|
604
636
|
availableModels: Model<Api>[],
|
|
637
|
+
modelRegistry?: CanonicalModelRegistry,
|
|
605
638
|
): { model: Model<Api>; thinkingLevel?: ThinkingLevel } | undefined {
|
|
606
639
|
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
|
607
640
|
for (const role of roles) {
|
|
608
641
|
const resolved = resolveModelRoleValue(settings.getModelRole(role), availableModels, {
|
|
609
642
|
settings,
|
|
610
643
|
matchPreferences,
|
|
644
|
+
modelRegistry,
|
|
611
645
|
});
|
|
612
646
|
if (resolved.model) {
|
|
613
647
|
return { model: resolved.model, thinkingLevel: resolved.thinkingLevel };
|
|
@@ -616,6 +650,36 @@ export function resolveRoleSelection(
|
|
|
616
650
|
return undefined;
|
|
617
651
|
}
|
|
618
652
|
|
|
653
|
+
function resolveExactCanonicalScopePattern(
|
|
654
|
+
pattern: string,
|
|
655
|
+
modelRegistry: Pick<ModelRegistry, "getCanonicalVariants">,
|
|
656
|
+
availableModels: Model<Api>[],
|
|
657
|
+
): { models: Model<Api>[]; thinkingLevel?: ThinkingLevel; explicitThinkingLevel: boolean } | undefined {
|
|
658
|
+
const lastColonIndex = pattern.lastIndexOf(":");
|
|
659
|
+
let canonicalId = pattern;
|
|
660
|
+
let thinkingLevel: ThinkingLevel | undefined;
|
|
661
|
+
let explicitThinkingLevel = false;
|
|
662
|
+
|
|
663
|
+
if (lastColonIndex !== -1) {
|
|
664
|
+
const suffix = pattern.substring(lastColonIndex + 1);
|
|
665
|
+
const parsedThinkingLevel = parseThinkingLevel(suffix);
|
|
666
|
+
if (parsedThinkingLevel) {
|
|
667
|
+
canonicalId = pattern.substring(0, lastColonIndex);
|
|
668
|
+
thinkingLevel = parsedThinkingLevel;
|
|
669
|
+
explicitThinkingLevel = true;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const variants = modelRegistry
|
|
674
|
+
.getCanonicalVariants(canonicalId, { availableOnly: true, candidates: availableModels })
|
|
675
|
+
.map(variant => variant.model);
|
|
676
|
+
if (variants.length === 0) {
|
|
677
|
+
return undefined;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return { models: variants, thinkingLevel, explicitThinkingLevel };
|
|
681
|
+
}
|
|
682
|
+
|
|
619
683
|
/**
|
|
620
684
|
* Resolve model patterns to actual Model objects with optional thinking levels
|
|
621
685
|
* Format: "pattern:level" where :level is optional
|
|
@@ -629,7 +693,7 @@ export function resolveRoleSelection(
|
|
|
629
693
|
*/
|
|
630
694
|
export async function resolveModelScope(
|
|
631
695
|
patterns: string[],
|
|
632
|
-
modelRegistry: ModelRegistry,
|
|
696
|
+
modelRegistry: Pick<ModelRegistry, "getAvailable" | "getCanonicalVariants">,
|
|
633
697
|
preferences?: ModelMatchPreferences,
|
|
634
698
|
): Promise<ScopedModel[]> {
|
|
635
699
|
const availableModels = modelRegistry.getAvailable();
|
|
@@ -682,10 +746,28 @@ export async function resolveModelScope(
|
|
|
682
746
|
continue;
|
|
683
747
|
}
|
|
684
748
|
|
|
749
|
+
const exactCanonical = resolveExactCanonicalScopePattern(pattern, modelRegistry, availableModels);
|
|
750
|
+
if (exactCanonical) {
|
|
751
|
+
for (const model of exactCanonical.models) {
|
|
752
|
+
if (!scopedModels.find(sm => modelsAreEqual(sm.model, model))) {
|
|
753
|
+
scopedModels.push({
|
|
754
|
+
model,
|
|
755
|
+
thinkingLevel: exactCanonical.explicitThinkingLevel
|
|
756
|
+
? (resolveThinkingLevelForModel(model, exactCanonical.thinkingLevel) ??
|
|
757
|
+
exactCanonical.thinkingLevel)
|
|
758
|
+
: exactCanonical.thinkingLevel,
|
|
759
|
+
explicitThinkingLevel: exactCanonical.explicitThinkingLevel,
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
|
|
685
766
|
const { model, thinkingLevel, warning, explicitThinkingLevel } = parseModelPatternWithContext(
|
|
686
767
|
pattern,
|
|
687
768
|
availableModels,
|
|
688
769
|
context,
|
|
770
|
+
{ modelRegistry },
|
|
689
771
|
);
|
|
690
772
|
|
|
691
773
|
if (warning) {
|
|
@@ -714,6 +796,7 @@ export async function resolveModelScope(
|
|
|
714
796
|
|
|
715
797
|
export interface ResolveCliModelResult {
|
|
716
798
|
model: Model<Api> | undefined;
|
|
799
|
+
selector?: string;
|
|
717
800
|
thinkingLevel?: ThinkingLevel;
|
|
718
801
|
warning: string | undefined;
|
|
719
802
|
error: string | undefined;
|
|
@@ -725,19 +808,20 @@ export interface ResolveCliModelResult {
|
|
|
725
808
|
export function resolveCliModel(options: {
|
|
726
809
|
cliProvider?: string;
|
|
727
810
|
cliModel?: string;
|
|
728
|
-
modelRegistry:
|
|
811
|
+
modelRegistry: CliModelRegistry;
|
|
729
812
|
preferences?: ModelMatchPreferences;
|
|
730
813
|
}): ResolveCliModelResult {
|
|
731
814
|
const { cliProvider, cliModel, modelRegistry, preferences } = options;
|
|
732
815
|
|
|
733
816
|
if (!cliModel) {
|
|
734
|
-
return { model: undefined, warning: undefined, error: undefined };
|
|
817
|
+
return { model: undefined, selector: undefined, warning: undefined, error: undefined };
|
|
735
818
|
}
|
|
736
819
|
|
|
737
820
|
const availableModels = modelRegistry.getAll();
|
|
738
821
|
if (availableModels.length === 0) {
|
|
739
822
|
return {
|
|
740
823
|
model: undefined,
|
|
824
|
+
selector: undefined,
|
|
741
825
|
warning: undefined,
|
|
742
826
|
error: "No models available. Check your installation or add models to models.json.",
|
|
743
827
|
};
|
|
@@ -752,13 +836,15 @@ export function resolveCliModel(options: {
|
|
|
752
836
|
if (cliProvider && !provider) {
|
|
753
837
|
return {
|
|
754
838
|
model: undefined,
|
|
839
|
+
selector: undefined,
|
|
755
840
|
warning: undefined,
|
|
756
841
|
error: `Unknown provider "${cliProvider}". Use --list-models to see available providers/models.`,
|
|
757
842
|
};
|
|
758
843
|
}
|
|
759
844
|
|
|
845
|
+
const trimmedModel = cliModel.trim();
|
|
760
846
|
if (!provider) {
|
|
761
|
-
const lower =
|
|
847
|
+
const lower = trimmedModel.toLowerCase();
|
|
762
848
|
// When input has provider/id format (e.g. "zai/glm-5"), prefer decomposed
|
|
763
849
|
// provider+id match over flat id match. Without this, a model with id
|
|
764
850
|
// "zai/glm-5" on provider "vercel-ai-gateway" wins over provider "zai"
|
|
@@ -772,17 +858,35 @@ export function resolveCliModel(options: {
|
|
|
772
858
|
model => model.provider.toLowerCase() === prefix && model.id.toLowerCase() === suffix,
|
|
773
859
|
);
|
|
774
860
|
}
|
|
861
|
+
if (!exact && !trimmedModel.includes(":")) {
|
|
862
|
+
const canonicalMatch = modelRegistry.resolveCanonicalModel?.(trimmedModel, { availableOnly: false });
|
|
863
|
+
if (canonicalMatch) {
|
|
864
|
+
return {
|
|
865
|
+
model: canonicalMatch,
|
|
866
|
+
selector: modelRegistry.getCanonicalId?.(canonicalMatch) ?? trimmedModel,
|
|
867
|
+
warning: undefined,
|
|
868
|
+
thinkingLevel: undefined,
|
|
869
|
+
error: undefined,
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
}
|
|
775
873
|
if (!exact) {
|
|
776
874
|
exact = availableModels.find(
|
|
777
875
|
model => model.id.toLowerCase() === lower || `${model.provider}/${model.id}`.toLowerCase() === lower,
|
|
778
876
|
);
|
|
779
877
|
}
|
|
780
878
|
if (exact) {
|
|
781
|
-
return {
|
|
879
|
+
return {
|
|
880
|
+
model: exact,
|
|
881
|
+
selector: formatModelString(exact),
|
|
882
|
+
warning: undefined,
|
|
883
|
+
thinkingLevel: undefined,
|
|
884
|
+
error: undefined,
|
|
885
|
+
};
|
|
782
886
|
}
|
|
783
887
|
}
|
|
784
888
|
|
|
785
|
-
let pattern =
|
|
889
|
+
let pattern = trimmedModel;
|
|
786
890
|
|
|
787
891
|
if (!provider) {
|
|
788
892
|
const slashIndex = cliModel.indexOf("/");
|
|
@@ -804,19 +908,42 @@ export function resolveCliModel(options: {
|
|
|
804
908
|
const candidates = provider ? availableModels.filter(model => model.provider === provider) : availableModels;
|
|
805
909
|
const { model, thinkingLevel, warning } = parseModelPattern(pattern, candidates, preferences, {
|
|
806
910
|
allowInvalidThinkingSelectorFallback: false,
|
|
911
|
+
modelRegistry,
|
|
807
912
|
});
|
|
808
913
|
|
|
809
914
|
if (!model) {
|
|
810
915
|
const display = provider ? `${provider}/${pattern}` : cliModel;
|
|
811
916
|
return {
|
|
812
917
|
model: undefined,
|
|
918
|
+
selector: undefined,
|
|
813
919
|
thinkingLevel: undefined,
|
|
814
920
|
warning,
|
|
815
921
|
error: `Model "${display}" not found. Use --list-models to see available models.`,
|
|
816
922
|
};
|
|
817
923
|
}
|
|
818
924
|
|
|
819
|
-
|
|
925
|
+
let selector = provider ? formatModelString(model) : undefined;
|
|
926
|
+
if (!provider) {
|
|
927
|
+
const lastColonIndex = pattern.lastIndexOf(":");
|
|
928
|
+
const canonicalCandidate =
|
|
929
|
+
lastColonIndex !== -1 && parseThinkingLevel(pattern.substring(lastColonIndex + 1))
|
|
930
|
+
? pattern.substring(0, lastColonIndex)
|
|
931
|
+
: pattern;
|
|
932
|
+
if (!canonicalCandidate.includes("/")) {
|
|
933
|
+
const canonicalResolved = modelRegistry.resolveCanonicalModel?.(canonicalCandidate, { availableOnly: false });
|
|
934
|
+
if (canonicalResolved && canonicalResolved.provider === model.provider && canonicalResolved.id === model.id) {
|
|
935
|
+
selector = modelRegistry.getCanonicalId?.(canonicalResolved) ?? canonicalCandidate;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
return {
|
|
941
|
+
model,
|
|
942
|
+
selector,
|
|
943
|
+
thinkingLevel,
|
|
944
|
+
warning,
|
|
945
|
+
error: undefined,
|
|
946
|
+
};
|
|
820
947
|
}
|
|
821
948
|
|
|
822
949
|
export interface InitialModelResult {
|
|
@@ -841,7 +968,7 @@ export async function findInitialModel(options: {
|
|
|
841
968
|
defaultProvider?: string;
|
|
842
969
|
defaultModelId?: string;
|
|
843
970
|
defaultThinkingSelector?: Effort;
|
|
844
|
-
modelRegistry:
|
|
971
|
+
modelRegistry: InitialModelRegistry;
|
|
845
972
|
}): Promise<InitialModelResult> {
|
|
846
973
|
const {
|
|
847
974
|
cliProvider,
|
|
@@ -923,7 +1050,7 @@ export async function restoreModelFromSession(
|
|
|
923
1050
|
savedModelId: string,
|
|
924
1051
|
currentModel: Model<Api> | undefined,
|
|
925
1052
|
shouldPrintMessages: boolean,
|
|
926
|
-
modelRegistry:
|
|
1053
|
+
modelRegistry: RestorableModelRegistry,
|
|
927
1054
|
): Promise<{ model: Model<Api> | undefined; fallbackMessage: string | undefined }> {
|
|
928
1055
|
const restoredModel = modelRegistry.find(savedProvider, savedModelId);
|
|
929
1056
|
|
|
@@ -998,7 +1125,7 @@ export async function restoreModelFromSession(
|
|
|
998
1125
|
* @returns The best available smol model, or undefined if none found
|
|
999
1126
|
*/
|
|
1000
1127
|
export async function findSmolModel(
|
|
1001
|
-
modelRegistry:
|
|
1128
|
+
modelRegistry: ModelLookupRegistry,
|
|
1002
1129
|
savedModel?: string,
|
|
1003
1130
|
): Promise<Model<Api> | undefined> {
|
|
1004
1131
|
const availableModels = modelRegistry.getAvailable();
|
|
@@ -1006,11 +1133,8 @@ export async function findSmolModel(
|
|
|
1006
1133
|
|
|
1007
1134
|
// 1. Try saved model from settings
|
|
1008
1135
|
if (savedModel) {
|
|
1009
|
-
const
|
|
1010
|
-
if (
|
|
1011
|
-
const match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
|
|
1012
|
-
if (match) return match;
|
|
1013
|
-
}
|
|
1136
|
+
const match = resolveModelFromString(savedModel, availableModels, undefined, modelRegistry);
|
|
1137
|
+
if (match) return match;
|
|
1014
1138
|
}
|
|
1015
1139
|
|
|
1016
1140
|
// 2. Try priority chain
|
|
@@ -1020,7 +1144,7 @@ export async function findSmolModel(
|
|
|
1020
1144
|
if (providerMatch) return providerMatch;
|
|
1021
1145
|
|
|
1022
1146
|
// Try exact match first
|
|
1023
|
-
const exactMatch =
|
|
1147
|
+
const exactMatch = parseModelPattern(pattern, availableModels, undefined, { modelRegistry }).model;
|
|
1024
1148
|
if (exactMatch) return exactMatch;
|
|
1025
1149
|
|
|
1026
1150
|
// Try fuzzy match (substring)
|
|
@@ -1041,7 +1165,7 @@ export async function findSmolModel(
|
|
|
1041
1165
|
* @returns The best available slow model, or undefined if none found
|
|
1042
1166
|
*/
|
|
1043
1167
|
export async function findSlowModel(
|
|
1044
|
-
modelRegistry:
|
|
1168
|
+
modelRegistry: ModelLookupRegistry,
|
|
1045
1169
|
savedModel?: string,
|
|
1046
1170
|
): Promise<Model<Api> | undefined> {
|
|
1047
1171
|
const availableModels = modelRegistry.getAvailable();
|
|
@@ -1049,17 +1173,14 @@ export async function findSlowModel(
|
|
|
1049
1173
|
|
|
1050
1174
|
// 1. Try saved model from settings
|
|
1051
1175
|
if (savedModel) {
|
|
1052
|
-
const
|
|
1053
|
-
if (
|
|
1054
|
-
const match = availableModels.find(m => m.provider === parsed.provider && m.id === parsed.id);
|
|
1055
|
-
if (match) return match;
|
|
1056
|
-
}
|
|
1176
|
+
const match = resolveModelFromString(savedModel, availableModels, undefined, modelRegistry);
|
|
1177
|
+
if (match) return match;
|
|
1057
1178
|
}
|
|
1058
1179
|
|
|
1059
1180
|
// 2. Try priority chain
|
|
1060
1181
|
for (const pattern of MODEL_PRIO.slow) {
|
|
1061
1182
|
// Try exact match first
|
|
1062
|
-
const exactMatch =
|
|
1183
|
+
const exactMatch = parseModelPattern(pattern, availableModels, undefined, { modelRegistry }).model;
|
|
1063
1184
|
if (exactMatch) return exactMatch;
|
|
1064
1185
|
|
|
1065
1186
|
// Try fuzzy match (substring)
|
|
@@ -229,6 +229,8 @@ export const SETTINGS_SCHEMA = {
|
|
|
229
229
|
|
|
230
230
|
modelTags: { type: "record", default: EMPTY_MODEL_TAGS_RECORD },
|
|
231
231
|
|
|
232
|
+
modelProviderOrder: { type: "array", default: EMPTY_STRING_ARRAY },
|
|
233
|
+
|
|
232
234
|
cycleOrder: { type: "array", default: DEFAULT_CYCLE_ORDER },
|
|
233
235
|
|
|
234
236
|
// ────────────────────────────────────────────────────────────────────────
|
|
@@ -1383,6 +1385,27 @@ export const SETTINGS_SCHEMA = {
|
|
|
1383
1385
|
},
|
|
1384
1386
|
},
|
|
1385
1387
|
|
|
1388
|
+
"bash.autoBackground.enabled": {
|
|
1389
|
+
type: "boolean",
|
|
1390
|
+
default: false,
|
|
1391
|
+
ui: {
|
|
1392
|
+
tab: "tools",
|
|
1393
|
+
label: "Bash Auto-Background",
|
|
1394
|
+
description: "Automatically background long-running bash commands and deliver the result later",
|
|
1395
|
+
},
|
|
1396
|
+
},
|
|
1397
|
+
|
|
1398
|
+
"bash.autoBackground.thresholdMs": {
|
|
1399
|
+
type: "number",
|
|
1400
|
+
default: 60_000,
|
|
1401
|
+
ui: {
|
|
1402
|
+
tab: "tools",
|
|
1403
|
+
label: "Bash Auto-Background Delay",
|
|
1404
|
+
description: "Milliseconds to wait before a bash command is moved to the background (0 = immediately)",
|
|
1405
|
+
submenu: true,
|
|
1406
|
+
},
|
|
1407
|
+
},
|
|
1408
|
+
|
|
1386
1409
|
// MCP
|
|
1387
1410
|
"mcp.enableProjectConfig": {
|
|
1388
1411
|
type: "boolean",
|
|
@@ -705,6 +705,88 @@
|
|
|
705
705
|
color: var(--error);
|
|
706
706
|
}
|
|
707
707
|
|
|
708
|
+
/* Tool renderer extras */
|
|
709
|
+
.tool-meta {
|
|
710
|
+
margin-top: 4px;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.tool-badge {
|
|
714
|
+
display: inline-block;
|
|
715
|
+
padding: 0 6px;
|
|
716
|
+
margin-right: 4px;
|
|
717
|
+
border-radius: 3px;
|
|
718
|
+
background: rgba(255, 255, 255, 0.06);
|
|
719
|
+
color: var(--dim);
|
|
720
|
+
font-size: 11px;
|
|
721
|
+
font-weight: normal;
|
|
722
|
+
vertical-align: baseline;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.tool-pattern {
|
|
726
|
+
color: var(--warning);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.tool-args {
|
|
730
|
+
margin-top: 4px;
|
|
731
|
+
color: var(--toolOutput);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.tool-arg {
|
|
735
|
+
display: block;
|
|
736
|
+
line-height: var(--line-height);
|
|
737
|
+
white-space: pre-wrap;
|
|
738
|
+
word-break: break-word;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.tool-arg-key {
|
|
742
|
+
color: var(--dim);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.tool-arg-val {
|
|
746
|
+
color: var(--text);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.tool-cell {
|
|
750
|
+
margin-top: var(--line-height);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.tool-cell-title {
|
|
754
|
+
color: var(--dim);
|
|
755
|
+
font-size: 11px;
|
|
756
|
+
margin-bottom: 2px;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/* Todo write tree */
|
|
760
|
+
.todo-tree {
|
|
761
|
+
margin-top: var(--line-height);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.todo-phase {
|
|
765
|
+
margin-top: 6px;
|
|
766
|
+
color: var(--accent);
|
|
767
|
+
font-weight: bold;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.todo-task {
|
|
771
|
+
padding-left: 12px;
|
|
772
|
+
line-height: var(--line-height);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.todo-icon {
|
|
776
|
+
display: inline-block;
|
|
777
|
+
width: 14px;
|
|
778
|
+
text-align: center;
|
|
779
|
+
color: var(--dim);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
.todo-completed { color: var(--toolDiffAdded); }
|
|
783
|
+
.todo-completed .todo-icon { color: var(--toolDiffAdded); }
|
|
784
|
+
.todo-in_progress { color: var(--warning); }
|
|
785
|
+
.todo-in_progress .todo-icon { color: var(--warning); }
|
|
786
|
+
.todo-abandoned { color: var(--toolDiffRemoved); }
|
|
787
|
+
.todo-abandoned .todo-icon { color: var(--toolDiffRemoved); }
|
|
788
|
+
.todo-pending { color: var(--toolOutput); }
|
|
789
|
+
|
|
708
790
|
/* Images */
|
|
709
791
|
.message-images {
|
|
710
792
|
margin-bottom: 12px;
|