@oh-my-pi/pi-coding-agent 13.9.15 → 13.9.16
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 +38 -0
- package/package.json +7 -7
- package/src/cli/web-search-cli.ts +2 -4
- package/src/config/model-registry.ts +258 -122
- package/src/config/settings-schema.ts +13 -0
- package/src/extensibility/extensions/runner.ts +42 -5
- package/src/extensibility/extensions/types.ts +13 -0
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/main.ts +12 -4
- package/src/modes/components/model-selector.ts +65 -5
- package/src/modes/components/tree-selector.ts +7 -3
- package/src/modes/controllers/input-controller.ts +8 -13
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/modes/interactive-mode.ts +66 -6
- package/src/modes/types.ts +12 -1
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/python.md +1 -1
- package/src/prompts/tools/task.md +1 -1
- package/src/sdk.ts +8 -3
- package/src/session/agent-session.ts +2 -2
- package/src/session/compaction/utils.ts +14 -1
- package/src/tools/write.ts +6 -2
- package/src/utils/external-editor.ts +1 -1
- package/src/web/search/index.ts +19 -47
- package/src/web/search/render.ts +2 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.9.16] - 2026-03-10
|
|
6
|
+
### Breaking Changes
|
|
7
|
+
|
|
8
|
+
- Web search tool no longer accepts `provider` parameter in tool calls; use internal provider resolution instead
|
|
9
|
+
- Removed `no_fallback` option from search parameters
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added `before_provider_request` extension event to intercept and modify provider request payloads before sending
|
|
14
|
+
- Added `emitBeforeProviderRequest()` method to ExtensionRunner for chaining payload transformations across extensions
|
|
15
|
+
- Added `refreshInBackground()` method to ModelRegistry for non-blocking model discovery
|
|
16
|
+
- Added `refreshProvider()` method to refresh models for a specific provider on demand
|
|
17
|
+
- Added `getDiscoverableProviders()` method to list all configured discoverable providers
|
|
18
|
+
- Added `getProviderDiscoveryState()` method to inspect provider discovery status, cache age, and errors
|
|
19
|
+
- Added provider discovery state tracking with status indicators (idle, ok, cached, unavailable, unauthenticated)
|
|
20
|
+
- Added model caching with 24-hour TTL to preserve discovered models across sessions
|
|
21
|
+
- Added provider-specific empty state messages in model selector showing cache age and discovery status
|
|
22
|
+
- Added live provider refresh when switching provider tabs in model selector
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Changed model discovery to load cached models immediately before attempting live refresh, improving startup performance
|
|
27
|
+
- Changed model selector to refresh offline by default when reloading config, deferring live discovery to background
|
|
28
|
+
- Changed model discovery timeout from 3000ms to 250ms for faster failure detection
|
|
29
|
+
- Changed model discovery error handling to preserve cached models when live refresh fails
|
|
30
|
+
- Changed `refresh()` strategy parameter to support 'offline' mode for config-only reloads
|
|
31
|
+
- Changed main.ts to defer model refresh until needed (--list-models or background refresh)
|
|
32
|
+
- Changed SDK session creation to use background refresh instead of blocking on model discovery
|
|
33
|
+
- Removed `provider` parameter from web search tool schema; provider selection now handled internally
|
|
34
|
+
- Removed `no_fallback` parameter from web search parameters; fallback behavior now automatic based on provider availability
|
|
35
|
+
- Renamed `SearchParams` type to `SearchToolParams` for tool execution; introduced `SearchQueryParams` for CLI queries with optional provider selection
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- Fixed model discovery to continue using cached models when provider is temporarily unavailable
|
|
40
|
+
- Fixed unauthenticated provider discovery to preserve cached models instead of discarding them
|
|
41
|
+
- Fixed model selector to show discovery status messages when provider has no models
|
|
42
|
+
|
|
5
43
|
## [13.9.15] - 2026-03-10
|
|
6
44
|
### Added
|
|
7
45
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "13.9.
|
|
4
|
+
"version": "13.9.16",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mozilla/readability": "^0.6",
|
|
44
|
-
"@oh-my-pi/omp-stats": "13.9.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "13.9.
|
|
46
|
-
"@oh-my-pi/pi-ai": "13.9.
|
|
47
|
-
"@oh-my-pi/pi-natives": "13.9.
|
|
48
|
-
"@oh-my-pi/pi-tui": "13.9.
|
|
49
|
-
"@oh-my-pi/pi-utils": "13.9.
|
|
44
|
+
"@oh-my-pi/omp-stats": "13.9.16",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.9.16",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.9.16",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.9.16",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.9.16",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.9.16",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { APP_NAME } from "@oh-my-pi/pi-utils";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import { initTheme, theme } from "../modes/theme/theme";
|
|
10
|
-
import { runSearchQuery, type
|
|
10
|
+
import { runSearchQuery, type SearchQueryParams } from "../web/search/index";
|
|
11
11
|
import { SEARCH_PROVIDER_ORDER } from "../web/search/provider";
|
|
12
12
|
import { renderSearchResult } from "../web/search/render";
|
|
13
13
|
import type { SearchProviderId } from "../web/search/types";
|
|
@@ -87,18 +87,16 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
|
|
|
87
87
|
|
|
88
88
|
await initTheme();
|
|
89
89
|
|
|
90
|
-
const params:
|
|
90
|
+
const params: SearchQueryParams = {
|
|
91
91
|
query: cmd.query,
|
|
92
92
|
provider: cmd.provider,
|
|
93
93
|
recency: cmd.recency,
|
|
94
94
|
limit: cmd.limit,
|
|
95
|
-
no_fallback: cmd.provider !== undefined && cmd.provider !== "auto",
|
|
96
95
|
};
|
|
97
96
|
|
|
98
97
|
const result = await runSearchQuery(params);
|
|
99
98
|
const component = renderSearchResult(result, { expanded: cmd.expanded, isPartial: false }, theme, {
|
|
100
99
|
query: cmd.query,
|
|
101
|
-
provider: cmd.provider,
|
|
102
100
|
allowLongAnswer: true,
|
|
103
101
|
maxAnswerLines: cmd.expanded ? undefined : 6,
|
|
104
102
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
1
2
|
import {
|
|
2
3
|
type Api,
|
|
3
4
|
type AssistantMessageEventStream,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
type OAuthLoginCallbacks,
|
|
17
18
|
openaiCodexModelManagerOptions,
|
|
18
19
|
PROVIDER_DESCRIPTORS,
|
|
20
|
+
readModelCache,
|
|
19
21
|
registerCustomApi,
|
|
20
22
|
registerOAuthProvider,
|
|
21
23
|
type SimpleStreamOptions,
|
|
@@ -308,14 +310,19 @@ interface DiscoveryProviderConfig {
|
|
|
308
310
|
baseUrl?: string;
|
|
309
311
|
headers?: Record<string, string>;
|
|
310
312
|
discovery: ProviderDiscovery;
|
|
313
|
+
optional?: boolean;
|
|
311
314
|
}
|
|
312
315
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
316
|
+
export type ProviderDiscoveryStatus = "idle" | "ok" | "cached" | "unavailable" | "unauthenticated";
|
|
317
|
+
|
|
318
|
+
export interface ProviderDiscoveryState {
|
|
319
|
+
provider: string;
|
|
320
|
+
status: ProviderDiscoveryStatus;
|
|
321
|
+
optional: boolean;
|
|
322
|
+
stale: boolean;
|
|
323
|
+
fetchedAt?: number;
|
|
324
|
+
models: string[];
|
|
325
|
+
error?: string;
|
|
319
326
|
}
|
|
320
327
|
|
|
321
328
|
/** Result of loading custom models from models.json */
|
|
@@ -507,6 +514,10 @@ export class ModelRegistry {
|
|
|
507
514
|
#configError: ConfigError | undefined = undefined;
|
|
508
515
|
#modelsConfigFile: ConfigFile<ModelsConfig>;
|
|
509
516
|
#registeredProviderSources: Set<string> = new Set();
|
|
517
|
+
#providerDiscoveryStates: Map<string, ProviderDiscoveryState> = new Map();
|
|
518
|
+
#cacheDbPath?: string;
|
|
519
|
+
#backgroundRefresh?: Promise<void>;
|
|
520
|
+
#lastDiscoveryWarnings: Map<string, string> = new Map();
|
|
510
521
|
|
|
511
522
|
/**
|
|
512
523
|
* @param authStorage - Auth storage for API key resolution
|
|
@@ -516,6 +527,7 @@ export class ModelRegistry {
|
|
|
516
527
|
modelsPath?: string,
|
|
517
528
|
) {
|
|
518
529
|
this.#modelsConfigFile = ModelsConfigFile.relocate(modelsPath);
|
|
530
|
+
this.#cacheDbPath = modelsPath ? path.join(path.dirname(modelsPath), "models.db") : undefined;
|
|
519
531
|
// Set up fallback resolver for custom provider API keys
|
|
520
532
|
this.authStorage.setFallbackResolver(provider => {
|
|
521
533
|
const keyConfig = this.#customProviderApiKeys.get(provider);
|
|
@@ -532,14 +544,42 @@ export class ModelRegistry {
|
|
|
532
544
|
* Reload models from disk (built-in + custom from models.json).
|
|
533
545
|
*/
|
|
534
546
|
async refresh(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
|
|
547
|
+
this.#reloadStaticModels();
|
|
548
|
+
await this.#refreshRuntimeDiscoveries(strategy);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
refreshInBackground(strategy: ModelRefreshStrategy = "online-if-uncached"): void {
|
|
552
|
+
if (this.#backgroundRefresh) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const refreshPromise = this.refresh(strategy)
|
|
556
|
+
.catch(error => {
|
|
557
|
+
logger.warn("background model refresh failed", {
|
|
558
|
+
error: error instanceof Error ? error.message : String(error),
|
|
559
|
+
});
|
|
560
|
+
})
|
|
561
|
+
.finally(() => {
|
|
562
|
+
if (this.#backgroundRefresh === refreshPromise) {
|
|
563
|
+
this.#backgroundRefresh = undefined;
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
this.#backgroundRefresh = refreshPromise;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async refreshProvider(providerId: string, strategy: ModelRefreshStrategy = "online"): Promise<void> {
|
|
570
|
+
this.#reloadStaticModels();
|
|
571
|
+
await this.#refreshRuntimeDiscoveries(strategy, new Set([providerId]));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
#reloadStaticModels(): void {
|
|
535
575
|
this.#modelsConfigFile.invalidate();
|
|
536
576
|
this.#customProviderApiKeys.clear();
|
|
537
577
|
this.#keylessProviders.clear();
|
|
538
578
|
this.#discoverableProviders = [];
|
|
539
579
|
this.#modelOverrides.clear();
|
|
540
580
|
this.#configError = undefined;
|
|
581
|
+
this.#providerDiscoveryStates.clear();
|
|
541
582
|
this.#loadModels();
|
|
542
|
-
await this.#refreshRuntimeDiscoveries(strategy);
|
|
543
583
|
}
|
|
544
584
|
|
|
545
585
|
/**
|
|
@@ -567,7 +607,8 @@ export class ModelRegistry {
|
|
|
567
607
|
|
|
568
608
|
this.#addImplicitDiscoverableProviders(configuredProviders);
|
|
569
609
|
const builtInModels = this.#loadBuiltInModels(overrides, modelOverrides);
|
|
570
|
-
const
|
|
610
|
+
const cachedDiscoveries = this.#loadCachedDiscoverableModels();
|
|
611
|
+
const combined = this.#mergeCustomModels(builtInModels, [...customModels, ...cachedDiscoveries]);
|
|
571
612
|
|
|
572
613
|
this.#models = this.#applyHardcodedModelPolicies(combined);
|
|
573
614
|
}
|
|
@@ -614,6 +655,34 @@ export class ModelRegistry {
|
|
|
614
655
|
return merged;
|
|
615
656
|
}
|
|
616
657
|
|
|
658
|
+
#loadCachedDiscoverableModels(): Model<Api>[] {
|
|
659
|
+
const cachedModels: Model<Api>[] = [];
|
|
660
|
+
for (const providerConfig of this.#discoverableProviders) {
|
|
661
|
+
const cache = readModelCache<Api>(providerConfig.provider, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
|
|
662
|
+
if (!cache) {
|
|
663
|
+
this.#providerDiscoveryStates.set(providerConfig.provider, {
|
|
664
|
+
provider: providerConfig.provider,
|
|
665
|
+
status: "idle",
|
|
666
|
+
optional: providerConfig.optional ?? false,
|
|
667
|
+
stale: false,
|
|
668
|
+
models: [],
|
|
669
|
+
});
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const models = this.#applyProviderModelOverrides(providerConfig.provider, cache.models);
|
|
673
|
+
cachedModels.push(...models);
|
|
674
|
+
this.#providerDiscoveryStates.set(providerConfig.provider, {
|
|
675
|
+
provider: providerConfig.provider,
|
|
676
|
+
status: "cached",
|
|
677
|
+
optional: providerConfig.optional ?? false,
|
|
678
|
+
stale: !cache.fresh || !cache.authoritative,
|
|
679
|
+
fetchedAt: cache.updatedAt,
|
|
680
|
+
models: models.map(model => model.id),
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
return cachedModels;
|
|
684
|
+
}
|
|
685
|
+
|
|
617
686
|
#addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
|
|
618
687
|
if (!configuredProviders.has("ollama")) {
|
|
619
688
|
this.#discoverableProviders.push({
|
|
@@ -621,6 +690,7 @@ export class ModelRegistry {
|
|
|
621
690
|
api: "openai-completions",
|
|
622
691
|
baseUrl: Bun.env.OLLAMA_BASE_URL || "http://127.0.0.1:11434",
|
|
623
692
|
discovery: { type: "ollama" },
|
|
693
|
+
optional: true,
|
|
624
694
|
});
|
|
625
695
|
this.#keylessProviders.add("ollama");
|
|
626
696
|
}
|
|
@@ -630,6 +700,7 @@ export class ModelRegistry {
|
|
|
630
700
|
api: "openai-completions",
|
|
631
701
|
baseUrl: Bun.env.LM_STUDIO_BASE_URL || "http://127.0.0.1:1234/v1",
|
|
632
702
|
discovery: { type: "lm-studio" },
|
|
703
|
+
optional: true,
|
|
633
704
|
});
|
|
634
705
|
this.#keylessProviders.add("lm-studio");
|
|
635
706
|
}
|
|
@@ -689,6 +760,7 @@ export class ModelRegistry {
|
|
|
689
760
|
baseUrl: providerConfig.baseUrl,
|
|
690
761
|
headers: providerConfig.headers,
|
|
691
762
|
discovery: providerConfig.discovery,
|
|
763
|
+
optional: false,
|
|
692
764
|
});
|
|
693
765
|
}
|
|
694
766
|
|
|
@@ -718,16 +790,22 @@ export class ModelRegistry {
|
|
|
718
790
|
};
|
|
719
791
|
}
|
|
720
792
|
|
|
721
|
-
async #refreshRuntimeDiscoveries(
|
|
793
|
+
async #refreshRuntimeDiscoveries(
|
|
794
|
+
strategy: ModelRefreshStrategy,
|
|
795
|
+
providerFilter?: ReadonlySet<string>,
|
|
796
|
+
): Promise<void> {
|
|
797
|
+
const selectedDiscoverableProviders = providerFilter
|
|
798
|
+
? this.#discoverableProviders.filter(provider => providerFilter.has(provider.provider))
|
|
799
|
+
: this.#discoverableProviders;
|
|
722
800
|
const configuredDiscoveriesPromise =
|
|
723
|
-
|
|
801
|
+
selectedDiscoverableProviders.length === 0
|
|
724
802
|
? Promise.resolve<Model<Api>[]>([])
|
|
725
|
-
: Promise.all(
|
|
726
|
-
|
|
727
|
-
);
|
|
803
|
+
: Promise.all(
|
|
804
|
+
selectedDiscoverableProviders.map(provider => this.#discoverProviderModels(provider, strategy)),
|
|
805
|
+
).then(results => results.flat());
|
|
728
806
|
const [configuredDiscovered, builtInDiscovered] = await Promise.all([
|
|
729
807
|
configuredDiscoveriesPromise,
|
|
730
|
-
this.#discoverBuiltInProviderModels(strategy),
|
|
808
|
+
this.#discoverBuiltInProviderModels(strategy, providerFilter),
|
|
731
809
|
]);
|
|
732
810
|
const discovered = [...configuredDiscovered, ...builtInDiscovered];
|
|
733
811
|
if (discovered.length === 0) {
|
|
@@ -751,21 +829,100 @@ export class ModelRegistry {
|
|
|
751
829
|
this.#models = this.#applyHardcodedModelPolicies(this.#applyModelOverrides(merged, this.#modelOverrides));
|
|
752
830
|
}
|
|
753
831
|
|
|
754
|
-
async #discoverProviderModels(
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
832
|
+
async #discoverProviderModels(
|
|
833
|
+
providerConfig: DiscoveryProviderConfig,
|
|
834
|
+
strategy: ModelRefreshStrategy,
|
|
835
|
+
): Promise<Model<Api>[]> {
|
|
836
|
+
const cached = readModelCache<Api>(providerConfig.provider, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
|
|
837
|
+
const requiresAuth = !this.#keylessProviders.has(providerConfig.provider);
|
|
838
|
+
if (requiresAuth) {
|
|
839
|
+
const apiKey = await this.#peekApiKeyForProvider(providerConfig.provider);
|
|
840
|
+
if (!isAuthenticated(apiKey)) {
|
|
841
|
+
this.#providerDiscoveryStates.set(providerConfig.provider, {
|
|
842
|
+
provider: providerConfig.provider,
|
|
843
|
+
status: "unauthenticated",
|
|
844
|
+
optional: providerConfig.optional ?? false,
|
|
845
|
+
stale: cached !== null,
|
|
846
|
+
fetchedAt: cached?.updatedAt,
|
|
847
|
+
models: cached?.models.map(model => model.id) ?? [],
|
|
848
|
+
});
|
|
849
|
+
this.#lastDiscoveryWarnings.delete(providerConfig.provider);
|
|
850
|
+
return cached?.models ?? [];
|
|
851
|
+
}
|
|
760
852
|
}
|
|
853
|
+
|
|
854
|
+
let fetchError: string | undefined;
|
|
855
|
+
const fetchDynamicModels = async (): Promise<readonly Model<Api>[] | null> => {
|
|
856
|
+
try {
|
|
857
|
+
const models =
|
|
858
|
+
providerConfig.discovery.type === "ollama"
|
|
859
|
+
? await this.#discoverOllamaModels(providerConfig)
|
|
860
|
+
: await this.#discoverLmStudioModels(providerConfig);
|
|
861
|
+
this.#lastDiscoveryWarnings.delete(providerConfig.provider);
|
|
862
|
+
return models;
|
|
863
|
+
} catch (error) {
|
|
864
|
+
fetchError = error instanceof Error ? error.message : String(error);
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
const manager = createModelManager<Api>({
|
|
870
|
+
providerId: providerConfig.provider,
|
|
871
|
+
staticModels: [],
|
|
872
|
+
cacheDbPath: this.#cacheDbPath,
|
|
873
|
+
cacheTtlMs: 24 * 60 * 60 * 1000,
|
|
874
|
+
fetchDynamicModels,
|
|
875
|
+
});
|
|
876
|
+
const result = await manager.refresh(strategy);
|
|
877
|
+
const status = fetchError
|
|
878
|
+
? result.models.length > 0
|
|
879
|
+
? "cached"
|
|
880
|
+
: "unavailable"
|
|
881
|
+
: result.models.length > 0 && strategy !== "offline"
|
|
882
|
+
? "ok"
|
|
883
|
+
: cached
|
|
884
|
+
? "cached"
|
|
885
|
+
: "idle";
|
|
886
|
+
this.#providerDiscoveryStates.set(providerConfig.provider, {
|
|
887
|
+
provider: providerConfig.provider,
|
|
888
|
+
status,
|
|
889
|
+
optional: providerConfig.optional ?? false,
|
|
890
|
+
stale: result.stale || status === "cached",
|
|
891
|
+
fetchedAt: fetchError ? cached?.updatedAt : Date.now(),
|
|
892
|
+
models: result.models.map(model => model.id),
|
|
893
|
+
error: fetchError,
|
|
894
|
+
});
|
|
895
|
+
if (fetchError) {
|
|
896
|
+
this.#warnProviderDiscoveryFailure(providerConfig, fetchError);
|
|
897
|
+
}
|
|
898
|
+
return this.#applyProviderModelOverrides(providerConfig.provider, result.models);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
#warnProviderDiscoveryFailure(providerConfig: DiscoveryProviderConfig, error: string): void {
|
|
902
|
+
const previous = this.#lastDiscoveryWarnings.get(providerConfig.provider);
|
|
903
|
+
if (previous === error) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
this.#lastDiscoveryWarnings.set(providerConfig.provider, error);
|
|
907
|
+
logger.warn("model discovery failed for provider", {
|
|
908
|
+
provider: providerConfig.provider,
|
|
909
|
+
url: providerConfig.baseUrl,
|
|
910
|
+
error,
|
|
911
|
+
});
|
|
761
912
|
}
|
|
762
913
|
|
|
763
|
-
async #discoverBuiltInProviderModels(
|
|
914
|
+
async #discoverBuiltInProviderModels(
|
|
915
|
+
strategy: ModelRefreshStrategy,
|
|
916
|
+
providerFilter?: ReadonlySet<string>,
|
|
917
|
+
): Promise<Model<Api>[]> {
|
|
764
918
|
// Skip providers already handled by configured discovery (e.g. user-configured ollama with discovery.type)
|
|
765
919
|
const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(p => p.provider));
|
|
766
|
-
const managerOptions = (await this.#collectBuiltInModelManagerOptions()).filter(
|
|
767
|
-
|
|
768
|
-
|
|
920
|
+
const managerOptions = (await this.#collectBuiltInModelManagerOptions()).filter(opts => {
|
|
921
|
+
if (configuredDiscoveryProviders.has(opts.providerId)) {
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
return providerFilter ? providerFilter.has(opts.providerId) : true;
|
|
925
|
+
});
|
|
769
926
|
if (managerOptions.length === 0) {
|
|
770
927
|
return [];
|
|
771
928
|
}
|
|
@@ -849,7 +1006,7 @@ export class ModelRegistry {
|
|
|
849
1006
|
strategy: ModelRefreshStrategy,
|
|
850
1007
|
): Promise<Model<Api>[]> {
|
|
851
1008
|
try {
|
|
852
|
-
const manager = createModelManager(options);
|
|
1009
|
+
const manager = createModelManager({ ...options, cacheDbPath: this.#cacheDbPath });
|
|
853
1010
|
const result = await manager.refresh(strategy);
|
|
854
1011
|
return result.models.map(model =>
|
|
855
1012
|
model.provider === options.providerId ? model : { ...model, provider: options.providerId },
|
|
@@ -874,7 +1031,7 @@ export class ModelRegistry {
|
|
|
874
1031
|
method: "POST",
|
|
875
1032
|
headers: { ...(headers ?? {}), "Content-Type": "application/json" },
|
|
876
1033
|
body: JSON.stringify({ model: modelId }),
|
|
877
|
-
signal: AbortSignal.timeout(
|
|
1034
|
+
signal: AbortSignal.timeout(150),
|
|
878
1035
|
});
|
|
879
1036
|
if (!response.ok) {
|
|
880
1037
|
return null;
|
|
@@ -911,57 +1068,42 @@ export class ModelRegistry {
|
|
|
911
1068
|
const endpoint = this.#normalizeOllamaBaseUrl(providerConfig.baseUrl);
|
|
912
1069
|
const tagsUrl = `${endpoint}/api/tags`;
|
|
913
1070
|
const headers = { ...(providerConfig.headers ?? {}) };
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
return id ? [{ id, name: item.name || id }] : [];
|
|
931
|
-
});
|
|
932
|
-
const metadataById = new Map(
|
|
933
|
-
await Promise.all(
|
|
934
|
-
entries.map(
|
|
935
|
-
async entry =>
|
|
936
|
-
[entry.id, await this.#discoverOllamaModelMetadata(endpoint, entry.id, headers)] as const,
|
|
937
|
-
),
|
|
1071
|
+
const response = await fetch(tagsUrl, {
|
|
1072
|
+
headers,
|
|
1073
|
+
signal: AbortSignal.timeout(250),
|
|
1074
|
+
});
|
|
1075
|
+
if (!response.ok) {
|
|
1076
|
+
throw new Error(`HTTP ${response.status} from ${tagsUrl}`);
|
|
1077
|
+
}
|
|
1078
|
+
const payload = (await response.json()) as { models?: Array<{ name?: string; model?: string }> };
|
|
1079
|
+
const entries = (payload.models ?? []).flatMap(item => {
|
|
1080
|
+
const id = item.model || item.name;
|
|
1081
|
+
return id ? [{ id, name: item.name || id }] : [];
|
|
1082
|
+
});
|
|
1083
|
+
const metadataById = new Map(
|
|
1084
|
+
await Promise.all(
|
|
1085
|
+
entries.map(
|
|
1086
|
+
async entry => [entry.id, await this.#discoverOllamaModelMetadata(endpoint, entry.id, headers)] as const,
|
|
938
1087
|
),
|
|
939
|
-
)
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
baseUrl: `${endpoint}/v1`,
|
|
948
|
-
reasoning: metadata?.reasoning ?? false,
|
|
949
|
-
input: metadata?.input ?? ["text"],
|
|
950
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
951
|
-
contextWindow: 128000,
|
|
952
|
-
maxTokens: 8192,
|
|
953
|
-
headers: providerConfig.headers,
|
|
954
|
-
});
|
|
955
|
-
});
|
|
956
|
-
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
957
|
-
} catch (error) {
|
|
958
|
-
logger.warn("model discovery failed for provider", {
|
|
1088
|
+
),
|
|
1089
|
+
);
|
|
1090
|
+
const discovered = entries.map(entry => {
|
|
1091
|
+
const metadata = metadataById.get(entry.id);
|
|
1092
|
+
return enrichModelThinking({
|
|
1093
|
+
id: entry.id,
|
|
1094
|
+
name: entry.name,
|
|
1095
|
+
api: providerConfig.api,
|
|
959
1096
|
provider: providerConfig.provider,
|
|
960
|
-
|
|
961
|
-
|
|
1097
|
+
baseUrl: `${endpoint}/v1`,
|
|
1098
|
+
reasoning: metadata?.reasoning ?? false,
|
|
1099
|
+
input: metadata?.input ?? ["text"],
|
|
1100
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1101
|
+
contextWindow: 128000,
|
|
1102
|
+
maxTokens: 8192,
|
|
1103
|
+
headers: providerConfig.headers,
|
|
962
1104
|
});
|
|
963
|
-
|
|
964
|
-
|
|
1105
|
+
});
|
|
1106
|
+
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
965
1107
|
}
|
|
966
1108
|
|
|
967
1109
|
async #discoverLmStudioModels(providerConfig: DiscoveryProviderConfig): Promise<Model<Api>[]> {
|
|
@@ -974,55 +1116,41 @@ export class ModelRegistry {
|
|
|
974
1116
|
headers.Authorization = `Bearer ${apiKey}`;
|
|
975
1117
|
}
|
|
976
1118
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1119
|
+
const response = await fetch(modelsUrl, {
|
|
1120
|
+
headers,
|
|
1121
|
+
signal: AbortSignal.timeout(250),
|
|
1122
|
+
});
|
|
1123
|
+
if (!response.ok) {
|
|
1124
|
+
throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
|
|
1125
|
+
}
|
|
1126
|
+
const payload = (await response.json()) as { data?: Array<{ id: string }> };
|
|
1127
|
+
const models = payload.data ?? [];
|
|
1128
|
+
const discovered: Model<Api>[] = [];
|
|
1129
|
+
for (const item of models) {
|
|
1130
|
+
const id = item.id;
|
|
1131
|
+
if (!id) continue;
|
|
1132
|
+
discovered.push(
|
|
1133
|
+
enrichModelThinking({
|
|
1134
|
+
id,
|
|
1135
|
+
name: id,
|
|
1136
|
+
api: providerConfig.api,
|
|
984
1137
|
provider: providerConfig.provider,
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
name: id,
|
|
1000
|
-
api: providerConfig.api,
|
|
1001
|
-
provider: providerConfig.provider,
|
|
1002
|
-
baseUrl,
|
|
1003
|
-
reasoning: false,
|
|
1004
|
-
input: ["text"],
|
|
1005
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1006
|
-
contextWindow: 128000,
|
|
1007
|
-
maxTokens: 8192,
|
|
1008
|
-
headers,
|
|
1009
|
-
compat: {
|
|
1010
|
-
supportsStore: false,
|
|
1011
|
-
supportsDeveloperRole: false,
|
|
1012
|
-
supportsReasoningEffort: false,
|
|
1013
|
-
},
|
|
1014
|
-
}),
|
|
1015
|
-
);
|
|
1016
|
-
}
|
|
1017
|
-
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
1018
|
-
} catch (error) {
|
|
1019
|
-
logger.warn("model discovery failed for provider", {
|
|
1020
|
-
provider: providerConfig.provider,
|
|
1021
|
-
url: modelsUrl,
|
|
1022
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1023
|
-
});
|
|
1024
|
-
return [];
|
|
1138
|
+
baseUrl,
|
|
1139
|
+
reasoning: false,
|
|
1140
|
+
input: ["text"],
|
|
1141
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1142
|
+
contextWindow: 128000,
|
|
1143
|
+
maxTokens: 8192,
|
|
1144
|
+
headers,
|
|
1145
|
+
compat: {
|
|
1146
|
+
supportsStore: false,
|
|
1147
|
+
supportsDeveloperRole: false,
|
|
1148
|
+
supportsReasoningEffort: false,
|
|
1149
|
+
},
|
|
1150
|
+
}),
|
|
1151
|
+
);
|
|
1025
1152
|
}
|
|
1153
|
+
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
1026
1154
|
}
|
|
1027
1155
|
|
|
1028
1156
|
#normalizeLmStudioBaseUrl(baseUrl?: string): string {
|
|
@@ -1119,6 +1247,14 @@ export class ModelRegistry {
|
|
|
1119
1247
|
return this.#models.filter(m => this.#keylessProviders.has(m.provider) || this.authStorage.hasAuth(m.provider));
|
|
1120
1248
|
}
|
|
1121
1249
|
|
|
1250
|
+
getDiscoverableProviders(): string[] {
|
|
1251
|
+
return this.#discoverableProviders.map(provider => provider.provider);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined {
|
|
1255
|
+
return this.#providerDiscoveryStates.get(provider);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1122
1258
|
/**
|
|
1123
1259
|
* Find a model by provider and ID.
|
|
1124
1260
|
*/
|
|
@@ -1140,7 +1276,7 @@ export class ModelRegistry {
|
|
|
1140
1276
|
if (this.#keylessProviders.has(model.provider)) {
|
|
1141
1277
|
return kNoAuth;
|
|
1142
1278
|
}
|
|
1143
|
-
return this.authStorage.getApiKey(model.provider, sessionId, { baseUrl: model.baseUrl });
|
|
1279
|
+
return this.authStorage.getApiKey(model.provider, sessionId, { baseUrl: model.baseUrl, modelId: model.id });
|
|
1144
1280
|
}
|
|
1145
1281
|
|
|
1146
1282
|
/**
|
|
@@ -242,6 +242,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
242
242
|
description: "Action when pressing Escape twice with empty editor",
|
|
243
243
|
},
|
|
244
244
|
},
|
|
245
|
+
treeFilterMode: {
|
|
246
|
+
type: "enum",
|
|
247
|
+
values: ["default", "no-tools", "user-only", "labeled-only", "all"] as const,
|
|
248
|
+
default: "default",
|
|
249
|
+
ui: {
|
|
250
|
+
tab: "input",
|
|
251
|
+
label: "Tree filter mode",
|
|
252
|
+
description: "Default filter mode when opening the session tree",
|
|
253
|
+
},
|
|
254
|
+
},
|
|
245
255
|
shellPath: { type: "string", default: undefined },
|
|
246
256
|
collapseChangelog: {
|
|
247
257
|
type: "boolean",
|
|
@@ -1310,6 +1320,9 @@ export type StatusLinePreset = SettingValue<"statusLine.preset">;
|
|
|
1310
1320
|
/** Status line separator style - derived from schema */
|
|
1311
1321
|
export type StatusLineSeparatorStyle = SettingValue<"statusLine.separator">;
|
|
1312
1322
|
|
|
1323
|
+
/** Tree selector filter mode - derived from schema */
|
|
1324
|
+
export type TreeFilterMode = SettingValue<"treeFilterMode">;
|
|
1325
|
+
|
|
1313
1326
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1314
1327
|
// Typed Group Definitions
|
|
1315
1328
|
// ═══════════════════════════════════════════════════════════════════════════
|