@oh-my-pi/pi-coding-agent 12.5.1 → 12.7.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 +25 -0
- package/docs/extensions.md +31 -0
- package/package.json +7 -7
- package/src/cli/update-cli.ts +14 -9
- package/src/cli/web-search-cli.ts +2 -0
- package/src/commands/web-search.ts +1 -0
- package/src/config/model-registry.ts +168 -0
- package/src/config/model-resolver.ts +102 -3
- package/src/config/settings-schema.ts +1 -1
- package/src/extensibility/extensions/index.ts +12 -0
- package/src/extensibility/extensions/loader.ts +11 -0
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/extensions/types.ts +180 -6
- package/src/extensibility/plugins/git-url.ts +18 -7
- package/src/index.ts +2 -0
- package/src/main.ts +57 -21
- package/src/modes/components/footer.ts +10 -18
- package/src/modes/components/settings-defs.ts +7 -1
- package/src/modes/components/welcome.ts +44 -14
- package/src/modes/controllers/extension-ui-controller.ts +22 -0
- package/src/modes/controllers/selector-controller.ts +3 -1
- package/src/modes/interactive-mode.ts +2 -0
- package/src/modes/rpc/rpc-mode.ts +7 -2
- package/src/sdk.ts +63 -30
- package/src/session/agent-session.ts +84 -14
- package/src/session/auth-storage.ts +43 -11
- package/src/task/render.ts +1 -1
- package/src/web/search/index.ts +15 -5
- package/src/web/search/provider.ts +3 -1
- package/src/web/search/providers/zai.ts +352 -0
- package/src/web/search/types.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [12.7.0] - 2026-02-16
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added Z.AI web search provider support via remote MCP endpoint (webSearchPrime)
|
|
9
|
+
- Added `zai` as a selectable web search provider option in settings
|
|
10
|
+
- Added Z.AI to automatic provider fallback chain for web search
|
|
11
|
+
|
|
12
|
+
## [12.6.0] - 2026-02-16
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added runtime tests covering extension provider registration and deferred model pattern resolution behavior.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Improved welcome screen responsiveness to dynamically show or hide the right column based on available terminal width
|
|
20
|
+
- Extended extension `registerProvider()` typing with OAuth provider support and source-aware registration metadata.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Fixed welcome screen layout to gracefully handle small terminal widths and prevent rendering errors on narrow displays
|
|
25
|
+
- Fixed welcome screen title truncation to prevent overflow when content exceeds available width
|
|
26
|
+
- Fixed deferred `--model` resolution so extension-provided models are matched before fallback selection and unresolved explicit patterns no longer silently fallback.
|
|
27
|
+
- Fixed CLI `--api-key` handling for deferred model resolution by applying runtime API key overrides after extension model selection.
|
|
28
|
+
- Fixed extension provider registration cleanup to remove stale source-scoped custom API/OAuth providers across extension reloads.
|
|
29
|
+
|
|
5
30
|
## [12.5.1] - 2026-02-15
|
|
6
31
|
### Added
|
|
7
32
|
|
package/docs/extensions.md
CHANGED
|
@@ -886,6 +886,37 @@ pi.registerCommand("stats", {
|
|
|
886
886
|
},
|
|
887
887
|
});
|
|
888
888
|
```
|
|
889
|
+
### pi.registerProvider(name, config)
|
|
890
|
+
|
|
891
|
+
Register or override providers/models at runtime:
|
|
892
|
+
|
|
893
|
+
```typescript
|
|
894
|
+
pi.registerProvider("my-provider", {
|
|
895
|
+
baseUrl: "https://api.example.com/v1",
|
|
896
|
+
apiKey: "MY_PROVIDER_API_KEY",
|
|
897
|
+
api: "openai-completions",
|
|
898
|
+
models: [
|
|
899
|
+
{
|
|
900
|
+
id: "my-model",
|
|
901
|
+
name: "My Model",
|
|
902
|
+
reasoning: false,
|
|
903
|
+
input: ["text"],
|
|
904
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
905
|
+
contextWindow: 128000,
|
|
906
|
+
maxTokens: 8192,
|
|
907
|
+
},
|
|
908
|
+
],
|
|
909
|
+
});
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
`registerProvider()` also supports:
|
|
913
|
+
|
|
914
|
+
- `streamSimple` for custom API adapters
|
|
915
|
+
- `headers` / `authHeader` for request customization
|
|
916
|
+
- `oauth` for `/login <provider>` support with extension-defined login/refresh behavior
|
|
917
|
+
|
|
918
|
+
Provider registrations are queued during extension load and applied when the session initializes.
|
|
919
|
+
|
|
889
920
|
|
|
890
921
|
### pi.registerMessageRenderer(customType, renderer)
|
|
891
922
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.7.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -84,12 +84,12 @@
|
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
86
|
"@mozilla/readability": "0.6.0",
|
|
87
|
-
"@oh-my-pi/omp-stats": "12.
|
|
88
|
-
"@oh-my-pi/pi-agent-core": "12.
|
|
89
|
-
"@oh-my-pi/pi-ai": "12.
|
|
90
|
-
"@oh-my-pi/pi-natives": "12.
|
|
91
|
-
"@oh-my-pi/pi-tui": "12.
|
|
92
|
-
"@oh-my-pi/pi-utils": "12.
|
|
87
|
+
"@oh-my-pi/omp-stats": "12.7.0",
|
|
88
|
+
"@oh-my-pi/pi-agent-core": "12.7.0",
|
|
89
|
+
"@oh-my-pi/pi-ai": "12.7.0",
|
|
90
|
+
"@oh-my-pi/pi-natives": "12.7.0",
|
|
91
|
+
"@oh-my-pi/pi-tui": "12.7.0",
|
|
92
|
+
"@oh-my-pi/pi-utils": "12.7.0",
|
|
93
93
|
"@sinclair/typebox": "^0.34.48",
|
|
94
94
|
"@xterm/headless": "^6.0.0",
|
|
95
95
|
"ajv": "^8.18.0",
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -59,23 +59,28 @@ function hasBun(): boolean {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* Get the latest release info from
|
|
62
|
+
* Get the latest release info from the npm registry.
|
|
63
|
+
* Uses npm instead of GitHub API to avoid unauthenticated rate limiting.
|
|
63
64
|
*/
|
|
64
65
|
async function getLatestRelease(): Promise<ReleaseInfo> {
|
|
65
|
-
const response = await fetch(`https://
|
|
66
|
+
const response = await fetch(`https://registry.npmjs.org/${PACKAGE}/latest`);
|
|
66
67
|
if (!response.ok) {
|
|
67
68
|
throw new Error(`Failed to fetch release info: ${response.statusText}`);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
const data = (await response.json()) as {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
};
|
|
71
|
+
const data = (await response.json()) as { version: string };
|
|
72
|
+
const version = data.version;
|
|
73
|
+
const tag = `v${version}`;
|
|
74
74
|
|
|
75
|
+
// Construct deterministic GitHub release download URLs for the current platform
|
|
76
|
+
const makeAsset = (name: string) => ({
|
|
77
|
+
name,
|
|
78
|
+
url: `https://github.com/${REPO}/releases/download/${tag}/${name}`,
|
|
79
|
+
});
|
|
75
80
|
return {
|
|
76
|
-
tag
|
|
77
|
-
version
|
|
78
|
-
assets:
|
|
81
|
+
tag,
|
|
82
|
+
version,
|
|
83
|
+
assets: [makeAsset(getBinaryName()), makeAsset(getNativeAddonName())],
|
|
79
84
|
};
|
|
80
85
|
}
|
|
81
86
|
|
|
@@ -25,6 +25,7 @@ const PROVIDERS: Array<SearchProviderId | "auto"> = [
|
|
|
25
25
|
"perplexity",
|
|
26
26
|
"exa",
|
|
27
27
|
"jina",
|
|
28
|
+
"zai",
|
|
28
29
|
"gemini",
|
|
29
30
|
"codex",
|
|
30
31
|
];
|
|
@@ -99,6 +100,7 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
|
|
|
99
100
|
provider: cmd.provider,
|
|
100
101
|
recency: cmd.recency,
|
|
101
102
|
limit: cmd.limit,
|
|
103
|
+
no_fallback: cmd.provider !== undefined && cmd.provider !== "auto",
|
|
102
104
|
};
|
|
103
105
|
|
|
104
106
|
const result = await runSearchQuery(params);
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Api,
|
|
3
|
+
type AssistantMessageEventStream,
|
|
4
|
+
type Context,
|
|
3
5
|
getGitHubCopilotBaseUrl,
|
|
4
6
|
getModels,
|
|
5
7
|
getProviders,
|
|
6
8
|
type Model,
|
|
7
9
|
normalizeDomain,
|
|
10
|
+
type OAuthCredentials,
|
|
11
|
+
type OAuthLoginCallbacks,
|
|
12
|
+
registerCustomApi,
|
|
13
|
+
registerOAuthProvider,
|
|
14
|
+
type SimpleStreamOptions,
|
|
15
|
+
unregisterCustomApis,
|
|
16
|
+
unregisterOAuthProviders,
|
|
8
17
|
} from "@oh-my-pi/pi-ai";
|
|
9
18
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
10
19
|
import { type Static, Type } from "@sinclair/typebox";
|
|
@@ -291,6 +300,7 @@ export class ModelRegistry {
|
|
|
291
300
|
#modelOverrides: Map<string, Map<string, ModelOverride>> = new Map();
|
|
292
301
|
#configError: ConfigError | undefined = undefined;
|
|
293
302
|
#modelsConfigFile: ConfigFile<ModelsConfig>;
|
|
303
|
+
#registeredProviderSources: Set<string> = new Set();
|
|
294
304
|
|
|
295
305
|
/**
|
|
296
306
|
* @param authStorage - Auth storage for API key resolution
|
|
@@ -705,4 +715,162 @@ export class ModelRegistry {
|
|
|
705
715
|
isUsingOAuth(model: Model<Api>): boolean {
|
|
706
716
|
return this.authStorage.hasOAuth(model.provider);
|
|
707
717
|
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Remove custom API/OAuth registrations for a specific extension source.
|
|
721
|
+
*/
|
|
722
|
+
clearSourceRegistrations(sourceId: string): void {
|
|
723
|
+
unregisterCustomApis(sourceId);
|
|
724
|
+
unregisterOAuthProviders(sourceId);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Remove registrations for extension sources that are no longer active.
|
|
729
|
+
*/
|
|
730
|
+
syncExtensionSources(activeSourceIds: string[]): void {
|
|
731
|
+
const activeSources = new Set(activeSourceIds);
|
|
732
|
+
for (const sourceId of this.#registeredProviderSources) {
|
|
733
|
+
if (activeSources.has(sourceId)) {
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
this.clearSourceRegistrations(sourceId);
|
|
737
|
+
this.#registeredProviderSources.delete(sourceId);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Register a provider dynamically (from extensions).
|
|
743
|
+
*
|
|
744
|
+
* If provider has models: replaces all existing models for this provider.
|
|
745
|
+
* If provider has only baseUrl/headers: overrides existing models' URLs.
|
|
746
|
+
* If provider has streamSimple: registers a custom API streaming function.
|
|
747
|
+
* If provider has oauth: registers OAuth provider for /login support.
|
|
748
|
+
*/
|
|
749
|
+
registerProvider(providerName: string, config: ProviderConfigInput, sourceId?: string): void {
|
|
750
|
+
if (config.streamSimple && !config.api) {
|
|
751
|
+
throw new Error(`Provider ${providerName}: "api" is required when registering streamSimple.`);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (config.models && config.models.length > 0) {
|
|
755
|
+
if (!config.baseUrl) {
|
|
756
|
+
throw new Error(`Provider ${providerName}: "baseUrl" is required when defining models.`);
|
|
757
|
+
}
|
|
758
|
+
if (!config.apiKey && !config.oauth) {
|
|
759
|
+
throw new Error(`Provider ${providerName}: "apiKey" or "oauth" is required when defining models.`);
|
|
760
|
+
}
|
|
761
|
+
for (const modelDef of config.models) {
|
|
762
|
+
const api = modelDef.api || config.api;
|
|
763
|
+
if (!api) {
|
|
764
|
+
throw new Error(`Provider ${providerName}, model ${modelDef.id}: no "api" specified.`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (config.streamSimple && config.api) {
|
|
770
|
+
const streamSimple = config.streamSimple;
|
|
771
|
+
registerCustomApi(config.api, streamSimple, sourceId, (model, context, options) =>
|
|
772
|
+
streamSimple(model, context, options as SimpleStreamOptions),
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (config.oauth) {
|
|
777
|
+
registerOAuthProvider({
|
|
778
|
+
...config.oauth,
|
|
779
|
+
id: providerName,
|
|
780
|
+
sourceId,
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (sourceId) {
|
|
785
|
+
this.#registeredProviderSources.add(sourceId);
|
|
786
|
+
}
|
|
787
|
+
if (config.apiKey) {
|
|
788
|
+
this.#customProviderApiKeys.set(providerName, config.apiKey);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (config.models && config.models.length > 0) {
|
|
792
|
+
const nextModels = this.#models.filter(m => m.provider !== providerName);
|
|
793
|
+
for (const modelDef of config.models) {
|
|
794
|
+
const api = modelDef.api || config.api;
|
|
795
|
+
if (!api) {
|
|
796
|
+
throw new Error(`Provider ${providerName}, model ${modelDef.id}: no "api" specified.`);
|
|
797
|
+
}
|
|
798
|
+
let headers = config.headers || modelDef.headers ? { ...config.headers, ...modelDef.headers } : undefined;
|
|
799
|
+
if (config.authHeader && config.apiKey) {
|
|
800
|
+
const resolvedKey = resolveApiKeyConfig(config.apiKey);
|
|
801
|
+
if (resolvedKey) {
|
|
802
|
+
headers = { ...headers, Authorization: `Bearer ${resolvedKey}` };
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
nextModels.push({
|
|
807
|
+
id: modelDef.id,
|
|
808
|
+
name: modelDef.name,
|
|
809
|
+
api,
|
|
810
|
+
provider: providerName,
|
|
811
|
+
baseUrl: config.baseUrl!,
|
|
812
|
+
reasoning: modelDef.reasoning,
|
|
813
|
+
input: modelDef.input as ("text" | "image")[],
|
|
814
|
+
cost: modelDef.cost,
|
|
815
|
+
contextWindow: modelDef.contextWindow,
|
|
816
|
+
maxTokens: modelDef.maxTokens,
|
|
817
|
+
headers,
|
|
818
|
+
compat: modelDef.compat,
|
|
819
|
+
} as Model<Api>);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (config.oauth?.modifyModels) {
|
|
823
|
+
const credential = this.authStorage.getOAuthCredential(providerName);
|
|
824
|
+
if (credential) {
|
|
825
|
+
this.#models = config.oauth.modifyModels(nextModels, credential);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
this.#models = nextModels;
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (config.baseUrl) {
|
|
835
|
+
this.#models = this.#models.map(m => {
|
|
836
|
+
if (m.provider !== providerName) return m;
|
|
837
|
+
return {
|
|
838
|
+
...m,
|
|
839
|
+
baseUrl: config.baseUrl ?? m.baseUrl,
|
|
840
|
+
headers: config.headers ? { ...m.headers, ...config.headers } : m.headers,
|
|
841
|
+
};
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Input type for registerProvider API (from extensions).
|
|
849
|
+
*/
|
|
850
|
+
export interface ProviderConfigInput {
|
|
851
|
+
baseUrl?: string;
|
|
852
|
+
apiKey?: string;
|
|
853
|
+
api?: Api;
|
|
854
|
+
streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
855
|
+
headers?: Record<string, string>;
|
|
856
|
+
authHeader?: boolean;
|
|
857
|
+
oauth?: {
|
|
858
|
+
name: string;
|
|
859
|
+
login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials | string>;
|
|
860
|
+
refreshToken?(credentials: OAuthCredentials): Promise<OAuthCredentials>;
|
|
861
|
+
getApiKey?(credentials: OAuthCredentials): string;
|
|
862
|
+
modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
|
|
863
|
+
};
|
|
864
|
+
models?: Array<{
|
|
865
|
+
id: string;
|
|
866
|
+
name: string;
|
|
867
|
+
api?: Api;
|
|
868
|
+
reasoning: boolean;
|
|
869
|
+
input: ("text" | "image")[];
|
|
870
|
+
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
871
|
+
contextWindow: number;
|
|
872
|
+
maxTokens: number;
|
|
873
|
+
headers?: Record<string, string>;
|
|
874
|
+
compat?: Model<Api>["compat"];
|
|
875
|
+
}>;
|
|
708
876
|
}
|
|
@@ -258,6 +258,7 @@ function parseModelPatternWithContext(
|
|
|
258
258
|
pattern: string,
|
|
259
259
|
availableModels: Model<Api>[],
|
|
260
260
|
context: ModelPreferenceContext,
|
|
261
|
+
options?: { allowInvalidThinkingLevelFallback?: boolean },
|
|
261
262
|
): ParsedModelResult {
|
|
262
263
|
// Try exact match first
|
|
263
264
|
const exactMatch = tryMatchModel(pattern, availableModels, context);
|
|
@@ -277,7 +278,7 @@ function parseModelPatternWithContext(
|
|
|
277
278
|
|
|
278
279
|
if (isValidThinkingLevel(suffix)) {
|
|
279
280
|
// Valid thinking level - recurse on prefix and use this level
|
|
280
|
-
const result = parseModelPatternWithContext(prefix, availableModels, context);
|
|
281
|
+
const result = parseModelPatternWithContext(prefix, availableModels, context, options);
|
|
281
282
|
if (result.model) {
|
|
282
283
|
// Only use this thinking level if no warning from inner recursion
|
|
283
284
|
const explicitThinkingLevel = !result.warning;
|
|
@@ -291,8 +292,13 @@ function parseModelPatternWithContext(
|
|
|
291
292
|
return result;
|
|
292
293
|
}
|
|
293
294
|
|
|
295
|
+
const allowFallback = options?.allowInvalidThinkingLevelFallback ?? true;
|
|
296
|
+
if (!allowFallback) {
|
|
297
|
+
return { model: undefined, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
|
|
298
|
+
}
|
|
299
|
+
|
|
294
300
|
// Invalid suffix - recurse on prefix and warn
|
|
295
|
-
const result = parseModelPatternWithContext(prefix, availableModels, context);
|
|
301
|
+
const result = parseModelPatternWithContext(prefix, availableModels, context, options);
|
|
296
302
|
if (result.model) {
|
|
297
303
|
return {
|
|
298
304
|
model: result.model,
|
|
@@ -308,9 +314,10 @@ export function parseModelPattern(
|
|
|
308
314
|
pattern: string,
|
|
309
315
|
availableModels: Model<Api>[],
|
|
310
316
|
preferences?: ModelMatchPreferences,
|
|
317
|
+
options?: { allowInvalidThinkingLevelFallback?: boolean },
|
|
311
318
|
): ParsedModelResult {
|
|
312
319
|
const context = buildPreferenceContext(availableModels, preferences);
|
|
313
|
-
return parseModelPatternWithContext(pattern, availableModels, context);
|
|
320
|
+
return parseModelPatternWithContext(pattern, availableModels, context, options);
|
|
314
321
|
}
|
|
315
322
|
|
|
316
323
|
const PREFIX_MODEL_ROLE = "pi/";
|
|
@@ -486,6 +493,98 @@ export async function resolveModelScope(
|
|
|
486
493
|
return scopedModels;
|
|
487
494
|
}
|
|
488
495
|
|
|
496
|
+
export interface ResolveCliModelResult {
|
|
497
|
+
model: Model<Api> | undefined;
|
|
498
|
+
thinkingLevel?: ThinkingLevel;
|
|
499
|
+
warning: string | undefined;
|
|
500
|
+
error: string | undefined;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Resolve a single model from CLI flags.
|
|
505
|
+
*/
|
|
506
|
+
export function resolveCliModel(options: {
|
|
507
|
+
cliProvider?: string;
|
|
508
|
+
cliModel?: string;
|
|
509
|
+
modelRegistry: ModelRegistry;
|
|
510
|
+
preferences?: ModelMatchPreferences;
|
|
511
|
+
}): ResolveCliModelResult {
|
|
512
|
+
const { cliProvider, cliModel, modelRegistry, preferences } = options;
|
|
513
|
+
|
|
514
|
+
if (!cliModel) {
|
|
515
|
+
return { model: undefined, warning: undefined, error: undefined };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const availableModels = modelRegistry.getAll();
|
|
519
|
+
if (availableModels.length === 0) {
|
|
520
|
+
return {
|
|
521
|
+
model: undefined,
|
|
522
|
+
warning: undefined,
|
|
523
|
+
error: "No models available. Check your installation or add models to models.json.",
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const providerMap = new Map<string, string>();
|
|
528
|
+
for (const model of availableModels) {
|
|
529
|
+
providerMap.set(model.provider.toLowerCase(), model.provider);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
let provider = cliProvider ? providerMap.get(cliProvider.toLowerCase()) : undefined;
|
|
533
|
+
if (cliProvider && !provider) {
|
|
534
|
+
return {
|
|
535
|
+
model: undefined,
|
|
536
|
+
warning: undefined,
|
|
537
|
+
error: `Unknown provider "${cliProvider}". Use --list-models to see available providers/models.`,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (!provider) {
|
|
542
|
+
const lower = cliModel.toLowerCase();
|
|
543
|
+
const exact = availableModels.find(
|
|
544
|
+
model => model.id.toLowerCase() === lower || `${model.provider}/${model.id}`.toLowerCase() === lower,
|
|
545
|
+
);
|
|
546
|
+
if (exact) {
|
|
547
|
+
return { model: exact, warning: undefined, thinkingLevel: undefined, error: undefined };
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
let pattern = cliModel;
|
|
552
|
+
|
|
553
|
+
if (!provider) {
|
|
554
|
+
const slashIndex = cliModel.indexOf("/");
|
|
555
|
+
if (slashIndex !== -1) {
|
|
556
|
+
const maybeProvider = cliModel.substring(0, slashIndex);
|
|
557
|
+
const canonical = providerMap.get(maybeProvider.toLowerCase());
|
|
558
|
+
if (canonical) {
|
|
559
|
+
provider = canonical;
|
|
560
|
+
pattern = cliModel.substring(slashIndex + 1);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
const prefix = `${provider}/`;
|
|
565
|
+
if (cliModel.toLowerCase().startsWith(prefix.toLowerCase())) {
|
|
566
|
+
pattern = cliModel.substring(prefix.length);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const candidates = provider ? availableModels.filter(model => model.provider === provider) : availableModels;
|
|
571
|
+
const { model, thinkingLevel, warning } = parseModelPattern(pattern, candidates, preferences, {
|
|
572
|
+
allowInvalidThinkingLevelFallback: false,
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
if (!model) {
|
|
576
|
+
const display = provider ? `${provider}/${pattern}` : cliModel;
|
|
577
|
+
return {
|
|
578
|
+
model: undefined,
|
|
579
|
+
thinkingLevel: undefined,
|
|
580
|
+
warning,
|
|
581
|
+
error: `Model "${display}" not found. Use --list-models to see available models.`,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return { model, thinkingLevel, warning, error: undefined };
|
|
586
|
+
}
|
|
587
|
+
|
|
489
588
|
export interface InitialModelResult {
|
|
490
589
|
model: Model<Api> | undefined;
|
|
491
590
|
thinkingLevel: ThinkingLevel;
|
|
@@ -617,7 +617,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
617
617
|
// ─────────────────────────────────────────────────────────────────────────
|
|
618
618
|
"providers.webSearch": {
|
|
619
619
|
type: "enum",
|
|
620
|
-
values: ["auto", "exa", "jina", "perplexity", "anthropic"] as const,
|
|
620
|
+
values: ["auto", "exa", "jina", "zai", "perplexity", "anthropic"] as const,
|
|
621
621
|
default: "auto",
|
|
622
622
|
ui: { tab: "services", label: "Web search provider", description: "Provider for web search tool", submenu: true },
|
|
623
623
|
},
|
|
@@ -67,9 +67,16 @@ export type {
|
|
|
67
67
|
InputEventResult,
|
|
68
68
|
KeybindingsManager,
|
|
69
69
|
LoadExtensionsResult,
|
|
70
|
+
// Events - Message
|
|
71
|
+
MessageEndEvent,
|
|
70
72
|
// Message Rendering
|
|
71
73
|
MessageRenderer,
|
|
72
74
|
MessageRenderOptions,
|
|
75
|
+
MessageStartEvent,
|
|
76
|
+
MessageUpdateEvent,
|
|
77
|
+
// Provider Registration
|
|
78
|
+
ProviderConfig,
|
|
79
|
+
ProviderModelConfig,
|
|
73
80
|
ReadToolCallEvent,
|
|
74
81
|
ReadToolResultEvent,
|
|
75
82
|
// Commands
|
|
@@ -101,11 +108,16 @@ export type {
|
|
|
101
108
|
SetActiveToolsHandler,
|
|
102
109
|
SetModelHandler,
|
|
103
110
|
SetThinkingLevelHandler,
|
|
111
|
+
TerminalInputHandler,
|
|
104
112
|
// Events - Tool
|
|
105
113
|
ToolCallEvent,
|
|
106
114
|
ToolCallEventResult,
|
|
107
115
|
// Tools
|
|
108
116
|
ToolDefinition,
|
|
117
|
+
// Events - Tool Execution
|
|
118
|
+
ToolExecutionEndEvent,
|
|
119
|
+
ToolExecutionStartEvent,
|
|
120
|
+
ToolExecutionUpdateEvent,
|
|
109
121
|
ToolRenderResultOptions,
|
|
110
122
|
ToolResultEvent,
|
|
111
123
|
ToolResultEventResult,
|
|
@@ -52,6 +52,8 @@ export class ExtensionRuntimeNotInitializedError extends Error {
|
|
|
52
52
|
*/
|
|
53
53
|
export class ExtensionRuntime implements IExtensionRuntime {
|
|
54
54
|
flagValues = new Map<string, boolean | string>();
|
|
55
|
+
pendingProviderRegistrations: Array<{ name: string; config: import("./types").ProviderConfig; sourceId: string }> =
|
|
56
|
+
[];
|
|
55
57
|
|
|
56
58
|
sendMessage(): void {
|
|
57
59
|
throw new ExtensionRuntimeNotInitializedError();
|
|
@@ -108,6 +110,11 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
|
|
108
110
|
readonly typebox = TypeBox;
|
|
109
111
|
readonly pi = piCodingAgent;
|
|
110
112
|
readonly flagValues = new Map<string, boolean | string>();
|
|
113
|
+
readonly pendingProviderRegistrations: Array<{
|
|
114
|
+
name: string;
|
|
115
|
+
config: import("./types").ProviderConfig;
|
|
116
|
+
sourceId: string;
|
|
117
|
+
}> = [];
|
|
111
118
|
|
|
112
119
|
constructor(
|
|
113
120
|
private readonly extension: Extension,
|
|
@@ -222,6 +229,10 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
|
|
222
229
|
setThinkingLevel(level: ThinkingLevel, persist?: boolean): void {
|
|
223
230
|
this.runtime.setThinkingLevel(level, persist);
|
|
224
231
|
}
|
|
232
|
+
|
|
233
|
+
registerProvider(name: string, config: import("./types").ProviderConfig): void {
|
|
234
|
+
this.runtime.pendingProviderRegistrations.push({ name, config, sourceId: this.extension.path });
|
|
235
|
+
}
|
|
225
236
|
}
|
|
226
237
|
|
|
227
238
|
/**
|
|
@@ -130,6 +130,7 @@ const noOpUIContext: ExtensionUIContext = {
|
|
|
130
130
|
confirm: async (_title, _message, _dialogOptions) => false,
|
|
131
131
|
input: async (_title, _placeholder, _dialogOptions) => undefined,
|
|
132
132
|
notify: () => {},
|
|
133
|
+
onTerminalInput: () => () => {},
|
|
133
134
|
setStatus: () => {},
|
|
134
135
|
setWorkingMessage: () => {},
|
|
135
136
|
setWidget: () => {},
|