@oh-my-pi/pi-coding-agent 12.5.1 → 12.6.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 +18 -0
- package/docs/extensions.md +31 -0
- package/package.json +7 -7
- package/src/config/model-registry.ts +168 -0
- package/src/extensibility/extensions/index.ts +3 -0
- package/src/extensibility/extensions/loader.ts +11 -0
- package/src/extensibility/extensions/types.ts +112 -1
- package/src/index.ts +2 -0
- package/src/main.ts +19 -8
- package/src/modes/components/welcome.ts +44 -14
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/sdk.ts +63 -30
- package/src/session/auth-storage.ts +43 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [12.6.0] - 2026-02-16
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added runtime tests covering extension provider registration and deferred model pattern resolution behavior.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Improved welcome screen responsiveness to dynamically show or hide the right column based on available terminal width
|
|
13
|
+
- Extended extension `registerProvider()` typing with OAuth provider support and source-aware registration metadata.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed welcome screen layout to gracefully handle small terminal widths and prevent rendering errors on narrow displays
|
|
18
|
+
- Fixed welcome screen title truncation to prevent overflow when content exceeds available width
|
|
19
|
+
- Fixed deferred `--model` resolution so extension-provided models are matched before fallback selection and unresolved explicit patterns no longer silently fallback.
|
|
20
|
+
- Fixed CLI `--api-key` handling for deferred model resolution by applying runtime API key overrides after extension model selection.
|
|
21
|
+
- Fixed extension provider registration cleanup to remove stale source-scoped custom API/OAuth providers across extension reloads.
|
|
22
|
+
|
|
5
23
|
## [12.5.1] - 2026-02-15
|
|
6
24
|
### Added
|
|
7
25
|
|
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.6.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.6.0",
|
|
88
|
+
"@oh-my-pi/pi-agent-core": "12.6.0",
|
|
89
|
+
"@oh-my-pi/pi-ai": "12.6.0",
|
|
90
|
+
"@oh-my-pi/pi-natives": "12.6.0",
|
|
91
|
+
"@oh-my-pi/pi-tui": "12.6.0",
|
|
92
|
+
"@oh-my-pi/pi-utils": "12.6.0",
|
|
93
93
|
"@sinclair/typebox": "^0.34.48",
|
|
94
94
|
"@xterm/headless": "^6.0.0",
|
|
95
95
|
"ajv": "^8.18.0",
|
|
@@ -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
|
}
|
|
@@ -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
|
/**
|
|
@@ -8,7 +8,18 @@
|
|
|
8
8
|
* - Interact with the user via UI primitives
|
|
9
9
|
*/
|
|
10
10
|
import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
Api,
|
|
13
|
+
AssistantMessageEventStream,
|
|
14
|
+
Context,
|
|
15
|
+
ImageContent,
|
|
16
|
+
Model,
|
|
17
|
+
OAuthCredentials,
|
|
18
|
+
OAuthLoginCallbacks,
|
|
19
|
+
SimpleStreamOptions,
|
|
20
|
+
TextContent,
|
|
21
|
+
ToolResultMessage,
|
|
22
|
+
} from "@oh-my-pi/pi-ai";
|
|
12
23
|
import type * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
|
13
24
|
import type { AutocompleteItem, Component, EditorComponent, EditorTheme, KeyId, TUI } from "@oh-my-pi/pi-tui";
|
|
14
25
|
import type { Static, TSchema } from "@sinclair/typebox";
|
|
@@ -976,10 +987,108 @@ export interface ExtensionAPI {
|
|
|
976
987
|
/** Set thinking level (clamped to model capabilities). */
|
|
977
988
|
setThinkingLevel(level: ThinkingLevel): void;
|
|
978
989
|
|
|
990
|
+
// =========================================================================
|
|
991
|
+
// Provider Registration
|
|
992
|
+
// =========================================================================
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Register or override a model provider.
|
|
996
|
+
*
|
|
997
|
+
* If `models` is provided: replaces all existing models for this provider.
|
|
998
|
+
* If only `baseUrl` is provided: overrides the URL for existing models.
|
|
999
|
+
* If `streamSimple` is provided: registers a custom API stream handler.
|
|
1000
|
+
*
|
|
1001
|
+
* @example
|
|
1002
|
+
* // Register a new provider with custom models and streaming
|
|
1003
|
+
* pi.registerProvider("google-vertex-claude", {
|
|
1004
|
+
* baseUrl: "https://us-east5-aiplatform.googleapis.com",
|
|
1005
|
+
* apiKey: "GOOGLE_CLOUD_PROJECT",
|
|
1006
|
+
* api: "vertex-claude-api",
|
|
1007
|
+
* streamSimple: myStreamFunction,
|
|
1008
|
+
* models: [
|
|
1009
|
+
* {
|
|
1010
|
+
* id: "claude-sonnet-4@20250514",
|
|
1011
|
+
* name: "Claude Sonnet 4 (Vertex)",
|
|
1012
|
+
* reasoning: true,
|
|
1013
|
+
* input: ["text", "image"],
|
|
1014
|
+
* cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
1015
|
+
* contextWindow: 200000,
|
|
1016
|
+
* maxTokens: 64000,
|
|
1017
|
+
* }
|
|
1018
|
+
* ]
|
|
1019
|
+
* });
|
|
1020
|
+
*
|
|
1021
|
+
* @example
|
|
1022
|
+
* // Override baseUrl for an existing provider
|
|
1023
|
+
* pi.registerProvider("anthropic", {
|
|
1024
|
+
* baseUrl: "https://proxy.example.com"
|
|
1025
|
+
* });
|
|
1026
|
+
*/
|
|
1027
|
+
registerProvider(name: string, config: ProviderConfig): void;
|
|
1028
|
+
|
|
979
1029
|
/** Shared event bus for extension communication. */
|
|
980
1030
|
events: EventBus;
|
|
981
1031
|
}
|
|
982
1032
|
|
|
1033
|
+
// ============================================================================
|
|
1034
|
+
// Provider Registration Types
|
|
1035
|
+
// ============================================================================
|
|
1036
|
+
|
|
1037
|
+
/** Configuration for registering a provider via pi.registerProvider(). */
|
|
1038
|
+
export interface ProviderConfig {
|
|
1039
|
+
/** Base URL for the API endpoint. Required when defining models. */
|
|
1040
|
+
baseUrl?: string;
|
|
1041
|
+
/** API key or environment variable name. Required when defining models unless oauth is provided. */
|
|
1042
|
+
apiKey?: string;
|
|
1043
|
+
/** API type identifier. Required when registering streamSimple or when models don't specify one. */
|
|
1044
|
+
api?: Api;
|
|
1045
|
+
/** Custom streaming function for non-built-in APIs. */
|
|
1046
|
+
streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
1047
|
+
/** Custom headers to include in requests. */
|
|
1048
|
+
headers?: Record<string, string>;
|
|
1049
|
+
/** If true, adds Authorization: Bearer header with the resolved API key. */
|
|
1050
|
+
authHeader?: boolean;
|
|
1051
|
+
/** Models to register. If provided, replaces all existing models for this provider. */
|
|
1052
|
+
models?: ProviderModelConfig[];
|
|
1053
|
+
/** OAuth provider for /login support. */
|
|
1054
|
+
oauth?: {
|
|
1055
|
+
/** Display name in login UI. */
|
|
1056
|
+
name: string;
|
|
1057
|
+
/** Run the provider login flow and return credentials (or a plain API key) to persist. */
|
|
1058
|
+
login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials | string>;
|
|
1059
|
+
/** Refresh expired credentials. */
|
|
1060
|
+
refreshToken?(credentials: OAuthCredentials): Promise<OAuthCredentials>;
|
|
1061
|
+
/** Convert credentials to an API key string for requests. */
|
|
1062
|
+
getApiKey?(credentials: OAuthCredentials): string;
|
|
1063
|
+
/** Optional model rewrite hook for credential-aware routing (e.g., enterprise URLs). */
|
|
1064
|
+
modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/** Configuration for a model within a provider. */
|
|
1069
|
+
export interface ProviderModelConfig {
|
|
1070
|
+
/** Model ID (e.g., "claude-sonnet-4@20250514"). */
|
|
1071
|
+
id: string;
|
|
1072
|
+
/** Display name (e.g., "Claude Sonnet 4 (Vertex)"). */
|
|
1073
|
+
name: string;
|
|
1074
|
+
/** API type override for this model. */
|
|
1075
|
+
api?: Api;
|
|
1076
|
+
/** Whether the model supports extended thinking. */
|
|
1077
|
+
reasoning: boolean;
|
|
1078
|
+
/** Supported input types. */
|
|
1079
|
+
input: ("text" | "image")[];
|
|
1080
|
+
/** Cost per million tokens. */
|
|
1081
|
+
cost: { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
1082
|
+
/** Maximum context window size in tokens. */
|
|
1083
|
+
contextWindow: number;
|
|
1084
|
+
/** Maximum output tokens. */
|
|
1085
|
+
maxTokens: number;
|
|
1086
|
+
/** Custom headers for this model. */
|
|
1087
|
+
headers?: Record<string, string>;
|
|
1088
|
+
/** OpenAI compatibility settings. */
|
|
1089
|
+
compat?: Model<Api>["compat"];
|
|
1090
|
+
}
|
|
1091
|
+
|
|
983
1092
|
/** Extension factory function type. Supports both sync and async initialization. */
|
|
984
1093
|
export type ExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;
|
|
985
1094
|
|
|
@@ -1038,6 +1147,8 @@ export type SetThinkingLevelHandler = (level: ThinkingLevel, persist?: boolean)
|
|
|
1038
1147
|
/** Shared state created by loader, used during registration and runtime. */
|
|
1039
1148
|
export interface ExtensionRuntimeState {
|
|
1040
1149
|
flagValues: Map<string, boolean | string>;
|
|
1150
|
+
/** Provider registrations queued during extension loading, processed during session initialization */
|
|
1151
|
+
pendingProviderRegistrations: Array<{ name: string; config: ProviderConfig; sourceId: string }>;
|
|
1041
1152
|
}
|
|
1042
1153
|
|
|
1043
1154
|
/** Action implementations for ExtensionAPI methods. */
|
package/src/index.ts
CHANGED
package/src/main.ts
CHANGED
|
@@ -372,7 +372,7 @@ async function buildSessionOptions(
|
|
|
372
372
|
|
|
373
373
|
// Model from CLI (--model) - uses same fuzzy matching as --models
|
|
374
374
|
if (parsed.model) {
|
|
375
|
-
const available = modelRegistry.
|
|
375
|
+
const available = modelRegistry.getAll();
|
|
376
376
|
const modelMatchPreferences = {
|
|
377
377
|
usageOrder: settings.getStorage()?.getModelUsageOrder(),
|
|
378
378
|
};
|
|
@@ -381,11 +381,13 @@ async function buildSessionOptions(
|
|
|
381
381
|
writeStderr(chalk.yellow(`Warning: ${warning}`));
|
|
382
382
|
}
|
|
383
383
|
if (!model) {
|
|
384
|
-
|
|
385
|
-
|
|
384
|
+
// Model not found in built-in registry — defer resolution to after extensions load
|
|
385
|
+
// (extensions may register additional providers/models via registerProvider)
|
|
386
|
+
options.modelPattern = parsed.model;
|
|
387
|
+
} else {
|
|
388
|
+
options.model = model;
|
|
389
|
+
settings.overrideModelRoles({ default: `${model.provider}/${model.id}` });
|
|
386
390
|
}
|
|
387
|
-
options.model = model;
|
|
388
|
-
settings.overrideModelRoles({ default: `${model.provider}/${model.id}` });
|
|
389
391
|
} else if (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {
|
|
390
392
|
const remembered = settings.getModelRole("default");
|
|
391
393
|
if (remembered) {
|
|
@@ -610,11 +612,13 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
610
612
|
|
|
611
613
|
// Handle CLI --api-key as runtime override (not persisted)
|
|
612
614
|
if (parsedArgs.apiKey) {
|
|
613
|
-
if (!sessionOptions.model) {
|
|
615
|
+
if (!sessionOptions.model && !sessionOptions.modelPattern) {
|
|
614
616
|
writeStderr(chalk.red("--api-key requires a model to be specified via --provider/--model or -m/--models"));
|
|
615
617
|
process.exit(1);
|
|
616
618
|
}
|
|
617
|
-
|
|
619
|
+
if (sessionOptions.model) {
|
|
620
|
+
authStorage.setRuntimeApiKey(sessionOptions.model.provider, parsedArgs.apiKey);
|
|
621
|
+
}
|
|
618
622
|
}
|
|
619
623
|
|
|
620
624
|
time("buildSessionOptions");
|
|
@@ -622,6 +626,9 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
622
626
|
await createAgentSession(sessionOptions);
|
|
623
627
|
debugStartup("main:createAgentSession");
|
|
624
628
|
time("createAgentSession");
|
|
629
|
+
if (parsedArgs.apiKey && !sessionOptions.model && session.model) {
|
|
630
|
+
authStorage.setRuntimeApiKey(session.model.provider, parsedArgs.apiKey);
|
|
631
|
+
}
|
|
625
632
|
|
|
626
633
|
if (modelFallbackMessage) {
|
|
627
634
|
notifs.push({ kind: "warn", message: modelFallbackMessage });
|
|
@@ -660,7 +667,11 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
660
667
|
debugStartup("main:applyExtensionFlags");
|
|
661
668
|
|
|
662
669
|
if (!isInteractive && !session.model) {
|
|
663
|
-
|
|
670
|
+
if (modelFallbackMessage) {
|
|
671
|
+
writeStderr(chalk.red(modelFallbackMessage));
|
|
672
|
+
} else {
|
|
673
|
+
writeStderr(chalk.red("No models available."));
|
|
674
|
+
}
|
|
664
675
|
writeStderr(chalk.yellow("\nSet an API key environment variable:"));
|
|
665
676
|
writeStderr(" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.");
|
|
666
677
|
writeStderr(chalk.yellow(`\nOr create ${ModelsConfigFile.path()}`));
|
|
@@ -41,12 +41,31 @@ export class WelcomeComponent implements Component {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
render(termWidth: number): string[] {
|
|
44
|
-
// Box dimensions - responsive with
|
|
45
|
-
const minWidth = 80;
|
|
44
|
+
// Box dimensions - responsive with max width and small-terminal support
|
|
46
45
|
const maxWidth = 100;
|
|
47
|
-
const boxWidth = Math.
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
const boxWidth = Math.min(maxWidth, Math.max(0, termWidth - 2));
|
|
47
|
+
if (boxWidth < 4) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
const dualContentWidth = boxWidth - 3; // 3 = │ + │ + │
|
|
51
|
+
const preferredLeftCol = 26;
|
|
52
|
+
const minLeftCol = 14; // logo width
|
|
53
|
+
const minRightCol = 20;
|
|
54
|
+
const leftMinContentWidth = Math.max(
|
|
55
|
+
minLeftCol,
|
|
56
|
+
visibleWidth("Welcome back!"),
|
|
57
|
+
visibleWidth(this.modelName),
|
|
58
|
+
visibleWidth(this.providerName),
|
|
59
|
+
);
|
|
60
|
+
const desiredLeftCol = Math.min(preferredLeftCol, Math.max(minLeftCol, Math.floor(dualContentWidth * 0.35)));
|
|
61
|
+
const dualLeftCol =
|
|
62
|
+
dualContentWidth >= minRightCol + 1
|
|
63
|
+
? Math.min(desiredLeftCol, dualContentWidth - minRightCol)
|
|
64
|
+
: Math.max(1, dualContentWidth - 1);
|
|
65
|
+
const dualRightCol = Math.max(1, dualContentWidth - dualLeftCol);
|
|
66
|
+
const showRightColumn = dualLeftCol >= leftMinContentWidth && dualRightCol >= minRightCol;
|
|
67
|
+
const leftCol = showRightColumn ? dualLeftCol : boxWidth - 2;
|
|
68
|
+
const rightCol = showRightColumn ? dualRightCol : 0;
|
|
50
69
|
|
|
51
70
|
// Block-based OMP logo (gradient: magenta → cyan)
|
|
52
71
|
// biome-ignore format: preserve ASCII art layout
|
|
@@ -67,7 +86,7 @@ export class WelcomeComponent implements Component {
|
|
|
67
86
|
];
|
|
68
87
|
|
|
69
88
|
// Right column separator
|
|
70
|
-
const separatorWidth = rightCol - 2; // padding on each side
|
|
89
|
+
const separatorWidth = Math.max(0, rightCol - 2); // padding on each side
|
|
71
90
|
const separator = ` ${theme.fg("dim", theme.boxRound.horizontal.repeat(separatorWidth))}`;
|
|
72
91
|
|
|
73
92
|
// Recent sessions content
|
|
@@ -131,20 +150,31 @@ export class WelcomeComponent implements Component {
|
|
|
131
150
|
const titlePrefixRaw = hChar.repeat(3);
|
|
132
151
|
const titleStyled = theme.fg("dim", titlePrefixRaw) + theme.fg("muted", title);
|
|
133
152
|
const titleVisLen = visibleWidth(titlePrefixRaw) + visibleWidth(title);
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
153
|
+
const titleSpace = boxWidth - 2;
|
|
154
|
+
if (titleVisLen >= titleSpace) {
|
|
155
|
+
lines.push(tl + truncateToWidth(titleStyled, titleSpace) + tr);
|
|
156
|
+
} else {
|
|
157
|
+
const afterTitle = titleSpace - titleVisLen;
|
|
158
|
+
lines.push(tl + titleStyled + theme.fg("dim", hChar.repeat(afterTitle)) + tr);
|
|
159
|
+
}
|
|
137
160
|
|
|
138
161
|
// Content rows
|
|
139
|
-
const maxRows = Math.max(leftLines.length, rightLines.length);
|
|
162
|
+
const maxRows = showRightColumn ? Math.max(leftLines.length, rightLines.length) : leftLines.length;
|
|
140
163
|
for (let i = 0; i < maxRows; i++) {
|
|
141
164
|
const left = this.#fitToWidth(leftLines[i] ?? "", leftCol);
|
|
142
|
-
|
|
143
|
-
|
|
165
|
+
if (showRightColumn) {
|
|
166
|
+
const right = this.#fitToWidth(rightLines[i] ?? "", rightCol);
|
|
167
|
+
lines.push(v + left + v + right + v);
|
|
168
|
+
} else {
|
|
169
|
+
lines.push(v + left + v);
|
|
170
|
+
}
|
|
144
171
|
}
|
|
145
|
-
|
|
146
172
|
// Bottom border
|
|
147
|
-
|
|
173
|
+
if (showRightColumn) {
|
|
174
|
+
lines.push(bl + h.repeat(leftCol) + theme.fg("dim", theme.boxSharp.teeUp) + h.repeat(rightCol) + br);
|
|
175
|
+
} else {
|
|
176
|
+
lines.push(bl + h.repeat(leftCol) + br);
|
|
177
|
+
}
|
|
148
178
|
|
|
149
179
|
return lines;
|
|
150
180
|
}
|
package/src/sdk.ts
CHANGED
|
@@ -8,7 +8,7 @@ import chalk from "chalk";
|
|
|
8
8
|
import { loadCapability } from "./capability";
|
|
9
9
|
import { type Rule, ruleCapability } from "./capability/rule";
|
|
10
10
|
import { ModelRegistry } from "./config/model-registry";
|
|
11
|
-
import { formatModelString, parseModelString } from "./config/model-resolver";
|
|
11
|
+
import { formatModelString, parseModelPattern, parseModelString } from "./config/model-resolver";
|
|
12
12
|
import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./config/prompt-templates";
|
|
13
13
|
import { Settings, type SkillsSettings } from "./config/settings";
|
|
14
14
|
import { CursorExecHandlers } from "./cursor";
|
|
@@ -101,6 +101,9 @@ export interface CreateAgentSessionOptions {
|
|
|
101
101
|
|
|
102
102
|
/** Model to use. Default: from settings, else first available */
|
|
103
103
|
model?: Model;
|
|
104
|
+
/** Raw model pattern string (e.g. from --model CLI flag) to resolve after extensions load.
|
|
105
|
+
* Used when model lookup is deferred because extension-provided models aren't registered yet. */
|
|
106
|
+
modelPattern?: string;
|
|
104
107
|
/** Thinking level. Default: from settings, else 'off' (clamped to model capabilities) */
|
|
105
108
|
thinkingLevel?: ThinkingLevel;
|
|
106
109
|
/** Models available for cycling (Ctrl+P in interactive mode) */
|
|
@@ -582,13 +585,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
582
585
|
const hasExistingSession = existingSession.messages.length > 0;
|
|
583
586
|
const hasThinkingEntry = sessionManager.getBranch().some(entry => entry.type === "thinking_level_change");
|
|
584
587
|
|
|
585
|
-
const hasExplicitModel = options.model !== undefined;
|
|
588
|
+
const hasExplicitModel = options.model !== undefined || options.modelPattern !== undefined;
|
|
586
589
|
let model = options.model;
|
|
587
590
|
let modelFallbackMessage: string | undefined;
|
|
588
|
-
|
|
589
|
-
//
|
|
591
|
+
// If session has data, try to restore model from it.
|
|
592
|
+
// Skip restore when an explicit model was requested.
|
|
590
593
|
const defaultModelStr = existingSession.models.default;
|
|
591
|
-
if (!model && hasExistingSession && defaultModelStr) {
|
|
594
|
+
if (!hasExplicitModel && !model && hasExistingSession && defaultModelStr) {
|
|
592
595
|
const parsedModel = parseModelString(defaultModelStr);
|
|
593
596
|
if (parsedModel) {
|
|
594
597
|
const restoredModel = modelRegistry.find(parsedModel.provider, parsedModel.id);
|
|
@@ -601,8 +604,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
601
604
|
}
|
|
602
605
|
}
|
|
603
606
|
|
|
604
|
-
// If still no model, try settings default
|
|
605
|
-
|
|
607
|
+
// If still no model, try settings default.
|
|
608
|
+
// Skip settings fallback when an explicit model was requested.
|
|
609
|
+
if (!hasExplicitModel && !model) {
|
|
606
610
|
const settingsDefaultModel = settings.getModelRole("default");
|
|
607
611
|
if (settingsDefaultModel) {
|
|
608
612
|
const parsedModel = parseModelString(settingsDefaultModel);
|
|
@@ -615,29 +619,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
615
619
|
}
|
|
616
620
|
}
|
|
617
621
|
|
|
618
|
-
// Fall back to first available model with a valid API key
|
|
619
|
-
if (!model) {
|
|
620
|
-
const allModels = modelRegistry.getAll();
|
|
621
|
-
for (const candidate of allModels) {
|
|
622
|
-
if (await hasModelApiKey(candidate)) {
|
|
623
|
-
model = candidate;
|
|
624
|
-
break;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
time("findAvailableModel");
|
|
628
|
-
if (model) {
|
|
629
|
-
if (modelFallbackMessage) {
|
|
630
|
-
modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
|
|
631
|
-
}
|
|
632
|
-
} else {
|
|
633
|
-
// No models available - set message so user knows to /login or configure keys
|
|
634
|
-
modelFallbackMessage =
|
|
635
|
-
"No models available. Use /login or set an API key environment variable. Then use /model to select a model.";
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
time("findModel");
|
|
640
|
-
|
|
641
622
|
// For subagent sessions using GitHub Copilot, add X-Initiator header
|
|
642
623
|
// to ensure proper billing (agent-initiated vs user-initiated)
|
|
643
624
|
const taskDepth = options.taskDepth ?? 0;
|
|
@@ -899,6 +880,58 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
899
880
|
}
|
|
900
881
|
}
|
|
901
882
|
|
|
883
|
+
// Process provider registrations queued during extension loading.
|
|
884
|
+
// This must happen before the runner is created so that models registered by
|
|
885
|
+
// extensions are available for model selection on session resume / fallback.
|
|
886
|
+
const activeExtensionSources = extensionsResult.extensions.map(extension => extension.path);
|
|
887
|
+
modelRegistry.syncExtensionSources(activeExtensionSources);
|
|
888
|
+
for (const sourceId of new Set(activeExtensionSources)) {
|
|
889
|
+
modelRegistry.clearSourceRegistrations(sourceId);
|
|
890
|
+
}
|
|
891
|
+
if (extensionsResult.runtime.pendingProviderRegistrations.length > 0) {
|
|
892
|
+
for (const { name, config, sourceId } of extensionsResult.runtime.pendingProviderRegistrations) {
|
|
893
|
+
modelRegistry.registerProvider(name, config, sourceId);
|
|
894
|
+
}
|
|
895
|
+
extensionsResult.runtime.pendingProviderRegistrations = [];
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Resolve deferred --model pattern now that extension models are registered.
|
|
899
|
+
if (!model && options.modelPattern) {
|
|
900
|
+
const availableModels = modelRegistry.getAll();
|
|
901
|
+
const matchPreferences = {
|
|
902
|
+
usageOrder: settings.getStorage()?.getModelUsageOrder(),
|
|
903
|
+
};
|
|
904
|
+
const { model: resolved } = parseModelPattern(options.modelPattern, availableModels, matchPreferences);
|
|
905
|
+
if (resolved) {
|
|
906
|
+
model = resolved;
|
|
907
|
+
modelFallbackMessage = undefined;
|
|
908
|
+
} else {
|
|
909
|
+
modelFallbackMessage = `Model "${options.modelPattern}" not found`;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Fall back to first available model with a valid API key.
|
|
914
|
+
// Skip fallback if the user explicitly requested a model via --model that wasn't found.
|
|
915
|
+
if (!model && !options.modelPattern) {
|
|
916
|
+
const allModels = modelRegistry.getAll();
|
|
917
|
+
for (const candidate of allModels) {
|
|
918
|
+
if (await hasModelApiKey(candidate)) {
|
|
919
|
+
model = candidate;
|
|
920
|
+
break;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
time("findAvailableModel");
|
|
924
|
+
if (model) {
|
|
925
|
+
if (modelFallbackMessage) {
|
|
926
|
+
modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
|
|
927
|
+
}
|
|
928
|
+
} else {
|
|
929
|
+
modelFallbackMessage =
|
|
930
|
+
"No models available. Use /login or set an API key environment variable. Then use /model to select a model.";
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
time("findModel");
|
|
902
935
|
// Discover custom commands (TypeScript slash commands)
|
|
903
936
|
const customCommandsResult: CustomCommandsLoadResult = options.disableExtensionDiscovery
|
|
904
937
|
? { commands: [], errors: [] }
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
claudeUsageProvider,
|
|
8
8
|
getEnvApiKey,
|
|
9
9
|
getOAuthApiKey,
|
|
10
|
+
getOAuthProvider,
|
|
10
11
|
githubCopilotUsageProvider,
|
|
11
12
|
googleGeminiCliUsageProvider,
|
|
12
13
|
kimiUsageProvider,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
type OAuthController,
|
|
26
27
|
type OAuthCredentials,
|
|
27
28
|
type OAuthProvider,
|
|
29
|
+
type OAuthProviderId,
|
|
28
30
|
openaiCodexUsageProvider,
|
|
29
31
|
type Provider,
|
|
30
32
|
type UsageCache,
|
|
@@ -634,7 +636,7 @@ export class AuthStorage {
|
|
|
634
636
|
* Login to an OAuth provider.
|
|
635
637
|
*/
|
|
636
638
|
async login(
|
|
637
|
-
provider:
|
|
639
|
+
provider: OAuthProviderId,
|
|
638
640
|
ctrl: OAuthController & {
|
|
639
641
|
/** onAuth is required by auth-storage but optional in OAuthController */
|
|
640
642
|
onAuth: (info: { url: string; instructions?: string }) => void;
|
|
@@ -709,8 +711,26 @@ export class AuthStorage {
|
|
|
709
711
|
await saveApiKeyCredential(apiKey);
|
|
710
712
|
return;
|
|
711
713
|
}
|
|
712
|
-
default:
|
|
713
|
-
|
|
714
|
+
default: {
|
|
715
|
+
const customProvider = getOAuthProvider(provider);
|
|
716
|
+
if (!customProvider) {
|
|
717
|
+
throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
718
|
+
}
|
|
719
|
+
const customLoginResult = await customProvider.login({
|
|
720
|
+
onAuth: info => ctrl.onAuth(info),
|
|
721
|
+
onProgress: ctrl.onProgress,
|
|
722
|
+
onPrompt: ctrl.onPrompt,
|
|
723
|
+
onManualCodeInput: async () =>
|
|
724
|
+
ctrl.onPrompt({ message: "Paste the authorization code (or full redirect URL):" }),
|
|
725
|
+
signal: ctrl.signal,
|
|
726
|
+
});
|
|
727
|
+
if (typeof customLoginResult === "string") {
|
|
728
|
+
await saveApiKeyCredential(customLoginResult);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
credentials = customLoginResult;
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
714
734
|
}
|
|
715
735
|
const newCredential: OAuthCredential = { type: "oauth", ...credentials };
|
|
716
736
|
const existing = this.#getCredentialsForProvider(provider);
|
|
@@ -1170,14 +1190,28 @@ export class AuthStorage {
|
|
|
1170
1190
|
}
|
|
1171
1191
|
}
|
|
1172
1192
|
|
|
1173
|
-
const oauthCreds: Record<string, OAuthCredentials> = {
|
|
1174
|
-
[provider]: selection.credential,
|
|
1175
|
-
};
|
|
1176
|
-
|
|
1177
1193
|
try {
|
|
1178
|
-
|
|
1194
|
+
let result: { newCredentials: OAuthCredentials; apiKey: string } | null;
|
|
1195
|
+
const customProvider = getOAuthProvider(provider);
|
|
1196
|
+
if (customProvider) {
|
|
1197
|
+
let refreshedCredentials: OAuthCredentials = selection.credential;
|
|
1198
|
+
if (Date.now() >= refreshedCredentials.expires) {
|
|
1199
|
+
if (!customProvider.refreshToken) {
|
|
1200
|
+
throw new Error(`OAuth provider "${provider}" does not support token refresh`);
|
|
1201
|
+
}
|
|
1202
|
+
refreshedCredentials = await customProvider.refreshToken(refreshedCredentials);
|
|
1203
|
+
}
|
|
1204
|
+
const apiKey = customProvider.getApiKey
|
|
1205
|
+
? customProvider.getApiKey(refreshedCredentials)
|
|
1206
|
+
: refreshedCredentials.access;
|
|
1207
|
+
result = { newCredentials: refreshedCredentials, apiKey };
|
|
1208
|
+
} else {
|
|
1209
|
+
const oauthCreds: Record<string, OAuthCredentials> = {
|
|
1210
|
+
[provider]: selection.credential,
|
|
1211
|
+
};
|
|
1212
|
+
result = await getOAuthApiKey(provider as OAuthProvider, oauthCreds);
|
|
1213
|
+
}
|
|
1179
1214
|
if (!result) return undefined;
|
|
1180
|
-
|
|
1181
1215
|
const updated: OAuthCredential = {
|
|
1182
1216
|
type: "oauth",
|
|
1183
1217
|
access: result.newCredentials.access,
|
|
@@ -1189,7 +1223,6 @@ export class AuthStorage {
|
|
|
1189
1223
|
enterpriseUrl: result.newCredentials.enterpriseUrl ?? selection.credential.enterpriseUrl,
|
|
1190
1224
|
};
|
|
1191
1225
|
this.#replaceCredentialAt(provider, selection.index, updated);
|
|
1192
|
-
|
|
1193
1226
|
if (checkUsage && !allowBlocked) {
|
|
1194
1227
|
const sameAccount = selection.credential.accountId === updated.accountId;
|
|
1195
1228
|
if (!usageChecked || !sameAccount) {
|
|
@@ -1205,7 +1238,6 @@ export class AuthStorage {
|
|
|
1205
1238
|
return undefined;
|
|
1206
1239
|
}
|
|
1207
1240
|
}
|
|
1208
|
-
|
|
1209
1241
|
this.#recordSessionCredential(provider, sessionId, "oauth", selection.index);
|
|
1210
1242
|
return result.apiKey;
|
|
1211
1243
|
} catch (error) {
|