@earendil-works/pi-coding-agent 0.80.7 → 0.80.8
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 +40 -0
- package/README.md +6 -6
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +2 -0
- package/dist/bun/cli.js.map +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/list-models.d.ts +2 -2
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +3 -3
- package/dist/cli/list-models.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +3 -6
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +10 -9
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +5 -6
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +57 -30
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +15 -99
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +46 -259
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/cache-stats.d.ts +2 -2
- package/dist/core/cache-stats.d.ts.map +1 -1
- package/dist/core/cache-stats.js +1 -1
- package/dist/core/cache-stats.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +8 -2
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +3 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +9 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-config.d.ts +507 -0
- package/dist/core/model-config.d.ts.map +1 -0
- package/dist/core/model-config.js +242 -0
- package/dist/core/model-config.js.map +1 -0
- package/dist/core/model-registry.d.ts +13 -123
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +44 -747
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +6 -6
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +17 -17
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +77 -0
- package/dist/core/model-runtime.d.ts.map +1 -0
- package/dist/core/model-runtime.js +418 -0
- package/dist/core/model-runtime.js.map +1 -0
- package/dist/core/models-store.d.ts +17 -0
- package/dist/core/models-store.d.ts.map +1 -0
- package/dist/core/models-store.js +45 -0
- package/dist/core/models-store.js.map +1 -0
- package/dist/core/provider-composer.d.ts +55 -0
- package/dist/core/provider-composer.d.ts.map +1 -0
- package/dist/core/provider-composer.js +375 -0
- package/dist/core/provider-composer.js.map +1 -0
- package/dist/core/radius.d.ts +0 -19
- package/dist/core/radius.d.ts.map +1 -1
- package/dist/core/radius.js +0 -28
- package/dist/core/radius.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +5 -0
- package/dist/core/remote-catalog-provider.d.ts.map +1 -0
- package/dist/core/remote-catalog-provider.js +83 -0
- package/dist/core/remote-catalog-provider.js.map +1 -0
- package/dist/core/runtime-credentials.d.ts +15 -0
- package/dist/core/runtime-credentials.d.ts.map +1 -0
- package/dist/core/runtime-credentials.js +36 -0
- package/dist/core/runtime-credentials.js.map +1 -0
- package/dist/core/sdk.d.ts +3 -6
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +14 -25
- package/dist/core/sdk.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +11 -13
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +22 -10
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +4 -8
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +14 -12
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +11 -4
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +72 -41
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +4 -4
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +14 -27
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +6 -3
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +154 -142
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -2
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +67 -5
- package/dist/package-manager-cli.js.map +1 -1
- package/docs/custom-provider.md +6 -11
- package/docs/extensions.md +22 -1
- package/docs/models.md +1 -0
- package/docs/packages.md +1 -0
- package/docs/providers.md +13 -1
- package/docs/sdk.md +40 -50
- package/docs/usage.md +1 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/02-custom-model.ts +5 -8
- package/examples/sdk/09-api-keys-and-oauth.ts +13 -31
- package/examples/sdk/12-full-control.ts +7 -12
- package/examples/sdk/README.md +14 -18
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
- package/dist/core/provider-display-names.d.ts +0 -2
- package/dist/core/provider-display-names.d.ts.map +0 -1
- package/dist/core/provider-display-names.js +0 -37
- package/dist/core/provider-display-names.js.map +0 -1
|
@@ -6,7 +6,6 @@ import * as crypto from "node:crypto";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { getProviders, } from "@earendil-works/pi-ai/compat";
|
|
10
9
|
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
|
|
11
10
|
import chalk from "chalk";
|
|
12
11
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -20,7 +19,6 @@ import { KeybindingsManager } from "../../core/keybindings.js";
|
|
|
20
19
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
21
20
|
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
22
21
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
23
|
-
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
24
22
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
25
23
|
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
|
|
26
24
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
@@ -94,7 +92,7 @@ function isDeadTerminalError(error) {
|
|
|
94
92
|
const code = error.code;
|
|
95
93
|
return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
|
|
96
94
|
}
|
|
97
|
-
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.";
|
|
95
|
+
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage. Disable this warning in /settings.";
|
|
98
96
|
function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
99
97
|
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
|
100
98
|
}
|
|
@@ -125,16 +123,6 @@ export function formatResumeCommand(sessionManager) {
|
|
|
125
123
|
function hasDefaultModelProvider(providerId) {
|
|
126
124
|
return providerId in defaultModelPerProvider;
|
|
127
125
|
}
|
|
128
|
-
const BUILT_IN_MODEL_PROVIDERS = new Set(getProviders());
|
|
129
|
-
export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProviderIds = BUILT_IN_MODEL_PROVIDERS) {
|
|
130
|
-
if (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
if (builtInProviderIds.has(providerId)) {
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
return !oauthProviderIds.has(providerId);
|
|
137
|
-
}
|
|
138
126
|
const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
|
|
139
127
|
function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
|
|
140
128
|
const filtered = fuzzyFilter(items, prefix, getSearchText);
|
|
@@ -361,11 +349,11 @@ export class InteractiveMode {
|
|
|
361
349
|
}));
|
|
362
350
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
363
351
|
if (modelCommand) {
|
|
364
|
-
modelCommand.getArgumentCompletions = (prefix) => {
|
|
352
|
+
modelCommand.getArgumentCompletions = async (prefix) => {
|
|
365
353
|
// Get available models (scoped or from registry)
|
|
366
354
|
const models = this.session.scopedModels.length > 0
|
|
367
355
|
? this.session.scopedModels.map((s) => s.model)
|
|
368
|
-
: this.session.
|
|
356
|
+
: await this.session.modelRuntime.getAvailable();
|
|
369
357
|
if (models.length === 0)
|
|
370
358
|
return null;
|
|
371
359
|
// Create items with provider/id format
|
|
@@ -598,10 +586,18 @@ export class InteractiveMode {
|
|
|
598
586
|
}
|
|
599
587
|
});
|
|
600
588
|
// Start package update check asynchronously
|
|
601
|
-
this.checkForPackageUpdates()
|
|
589
|
+
this.checkForPackageUpdates()
|
|
590
|
+
.then((updates) => {
|
|
602
591
|
if (updates.length > 0) {
|
|
603
592
|
this.showPackageUpdateNotification(updates);
|
|
604
593
|
}
|
|
594
|
+
})
|
|
595
|
+
.finally(() => {
|
|
596
|
+
// On Windows, npm can overwrite the shared console title while checking
|
|
597
|
+
// extension package versions. Restore Pi's title after the startup check.
|
|
598
|
+
if (process.platform === "win32" && this.isInitialized) {
|
|
599
|
+
this.updateTerminalTitle();
|
|
600
|
+
}
|
|
605
601
|
});
|
|
606
602
|
// Check tmux keyboard setup asynchronously
|
|
607
603
|
this.checkTmuxKeyboardSetup().then((warning) => {
|
|
@@ -614,7 +610,7 @@ export class InteractiveMode {
|
|
|
614
610
|
if (migratedProviders && migratedProviders.length > 0) {
|
|
615
611
|
this.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(", ")}`);
|
|
616
612
|
}
|
|
617
|
-
const modelsJsonError = this.session.
|
|
613
|
+
const modelsJsonError = this.session.modelRuntime.getError();
|
|
618
614
|
if (modelsJsonError) {
|
|
619
615
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
620
616
|
}
|
|
@@ -1345,7 +1341,7 @@ export class InteractiveMode {
|
|
|
1345
1341
|
hasUI: true,
|
|
1346
1342
|
cwd: this.sessionManager.getCwd(),
|
|
1347
1343
|
sessionManager: this.sessionManager,
|
|
1348
|
-
modelRegistry:
|
|
1344
|
+
modelRegistry: extensionRunner.getModelRegistry(),
|
|
1349
1345
|
model: this.session.model,
|
|
1350
1346
|
isIdle: () => this.session.isIdle,
|
|
1351
1347
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
@@ -2651,7 +2647,7 @@ export class InteractiveMode {
|
|
|
2651
2647
|
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2652
2648
|
// list and re-inject them after the assistant messages that paid for them.
|
|
2653
2649
|
const cacheMisses = this.settingsManager.getShowCacheMissNotices()
|
|
2654
|
-
? collectCacheMisses(this.sessionManager.getEntries(), this.session.
|
|
2650
|
+
? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
|
|
2655
2651
|
: new Map();
|
|
2656
2652
|
if (options.updateFooter) {
|
|
2657
2653
|
this.footer.invalidate();
|
|
@@ -2742,7 +2738,7 @@ export class InteractiveMode {
|
|
|
2742
2738
|
if (!this.settingsManager.getShowCacheMissNotices())
|
|
2743
2739
|
return;
|
|
2744
2740
|
// Entries don't contain `message` yet: message_end fires before persistence.
|
|
2745
|
-
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.
|
|
2741
|
+
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.modelRuntime);
|
|
2746
2742
|
if (miss)
|
|
2747
2743
|
this.addCacheMissNotice(miss);
|
|
2748
2744
|
}
|
|
@@ -3583,9 +3579,9 @@ export class InteractiveMode {
|
|
|
3583
3579
|
if (this.session.scopedModels.length > 0) {
|
|
3584
3580
|
return this.session.scopedModels.map((scoped) => scoped.model);
|
|
3585
3581
|
}
|
|
3586
|
-
this.session.modelRegistry.refresh();
|
|
3587
3582
|
try {
|
|
3588
|
-
|
|
3583
|
+
await this.session.modelRuntime.refresh();
|
|
3584
|
+
return [...(await this.session.modelRuntime.getAvailable())];
|
|
3589
3585
|
}
|
|
3590
3586
|
catch {
|
|
3591
3587
|
return [];
|
|
@@ -3607,14 +3603,13 @@ export class InteractiveMode {
|
|
|
3607
3603
|
if (!model || model.provider !== "anthropic") {
|
|
3608
3604
|
return;
|
|
3609
3605
|
}
|
|
3610
|
-
const storedCredential = this.session.modelRegistry.authStorage.get("anthropic");
|
|
3611
|
-
if (storedCredential?.type === "oauth") {
|
|
3612
|
-
this.anthropicSubscriptionWarningShown = true;
|
|
3613
|
-
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3614
|
-
return;
|
|
3615
|
-
}
|
|
3616
3606
|
try {
|
|
3617
|
-
|
|
3607
|
+
if ((await this.session.modelRuntime.checkAuth("anthropic"))?.type === "oauth") {
|
|
3608
|
+
this.anthropicSubscriptionWarningShown = true;
|
|
3609
|
+
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3612
|
+
const apiKey = (await this.session.modelRuntime.getAuth(model.provider))?.auth.apiKey;
|
|
3618
3613
|
if (!isAnthropicSubscriptionAuthKey(apiKey)) {
|
|
3619
3614
|
return;
|
|
3620
3615
|
}
|
|
@@ -3672,7 +3667,7 @@ export class InteractiveMode {
|
|
|
3672
3667
|
}
|
|
3673
3668
|
showModelSelector(initialSearchInput) {
|
|
3674
3669
|
this.showSelector((done) => {
|
|
3675
|
-
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.
|
|
3670
|
+
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRuntime, this.session.scopedModels, async (model) => {
|
|
3676
3671
|
try {
|
|
3677
3672
|
await this.session.setModel(model);
|
|
3678
3673
|
this.footer.invalidate();
|
|
@@ -3695,8 +3690,8 @@ export class InteractiveMode {
|
|
|
3695
3690
|
}
|
|
3696
3691
|
async showModelsSelector() {
|
|
3697
3692
|
// Get all available models
|
|
3698
|
-
this.session.
|
|
3699
|
-
const allModels = this.session.
|
|
3693
|
+
await this.session.modelRuntime.refresh();
|
|
3694
|
+
const allModels = [...(await this.session.modelRuntime.getAvailable())];
|
|
3700
3695
|
if (allModels.length === 0) {
|
|
3701
3696
|
this.showStatus("No models available");
|
|
3702
3697
|
return;
|
|
@@ -3714,7 +3709,7 @@ export class InteractiveMode {
|
|
|
3714
3709
|
// Fall back to settings
|
|
3715
3710
|
const patterns = this.settingsManager.getEnabledModels();
|
|
3716
3711
|
if (patterns !== undefined && patterns.length > 0) {
|
|
3717
|
-
const scopedModels = await resolveModelScope(patterns, this.session.
|
|
3712
|
+
const scopedModels = await resolveModelScope(patterns, this.session.modelRuntime);
|
|
3718
3713
|
currentEnabledIds = scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3719
3714
|
}
|
|
3720
3715
|
}
|
|
@@ -3722,7 +3717,7 @@ export class InteractiveMode {
|
|
|
3722
3717
|
const updateSessionModels = async (enabledIds) => {
|
|
3723
3718
|
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
3724
3719
|
if (enabledIds && enabledIds.length > 0 && enabledIds.length < allModels.length) {
|
|
3725
|
-
const newScopedModels = await resolveModelScope(enabledIds, this.session.
|
|
3720
|
+
const newScopedModels = await resolveModelScope(enabledIds, this.session.modelRuntime);
|
|
3726
3721
|
this.session.setScopedModels(newScopedModels.map((sm) => ({
|
|
3727
3722
|
model: sm.model,
|
|
3728
3723
|
thinkingLevel: sm.thinkingLevel,
|
|
@@ -3982,44 +3977,46 @@ export class InteractiveMode {
|
|
|
3982
3977
|
}
|
|
3983
3978
|
}
|
|
3984
3979
|
getLoginProviderOptions(authType) {
|
|
3985
|
-
const authStorage = this.session.modelRegistry.authStorage;
|
|
3986
|
-
const oauthProviders = authStorage.getOAuthProviders();
|
|
3987
|
-
const oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));
|
|
3988
|
-
const options = oauthProviders.map((provider) => ({
|
|
3989
|
-
id: provider.id,
|
|
3990
|
-
name: provider.name,
|
|
3991
|
-
authType: "oauth",
|
|
3992
|
-
}));
|
|
3993
|
-
const modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));
|
|
3994
|
-
for (const providerId of modelProviders) {
|
|
3995
|
-
if (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {
|
|
3996
|
-
continue;
|
|
3997
|
-
}
|
|
3998
|
-
options.push({
|
|
3999
|
-
id: providerId,
|
|
4000
|
-
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
4001
|
-
authType: "api_key",
|
|
4002
|
-
});
|
|
4003
|
-
}
|
|
4004
|
-
const filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;
|
|
4005
|
-
return filteredOptions.sort((a, b) => a.name.localeCompare(b.name));
|
|
4006
|
-
}
|
|
4007
|
-
getLogoutProviderOptions() {
|
|
4008
|
-
const authStorage = this.session.modelRegistry.authStorage;
|
|
4009
3980
|
const options = [];
|
|
4010
|
-
for (const
|
|
4011
|
-
const
|
|
4012
|
-
|
|
4013
|
-
|
|
3981
|
+
for (const provider of this.session.modelRuntime.getProviders()) {
|
|
3982
|
+
const authStatus = this.session.modelRuntime.getProviderAuthStatus(provider.id);
|
|
3983
|
+
const status = authStatus.configured
|
|
3984
|
+
? {
|
|
3985
|
+
type: this.session.modelRuntime.isUsingOAuth(provider.id) ? "oauth" : "api_key",
|
|
3986
|
+
source: authStatus.label ?? authStatus.source,
|
|
3987
|
+
}
|
|
3988
|
+
: undefined;
|
|
3989
|
+
if ((!authType || authType === "oauth") && provider.auth.oauth) {
|
|
3990
|
+
options.push({
|
|
3991
|
+
id: provider.id,
|
|
3992
|
+
name: provider.name,
|
|
3993
|
+
authType: "oauth",
|
|
3994
|
+
method: provider.auth.oauth,
|
|
3995
|
+
status,
|
|
3996
|
+
});
|
|
3997
|
+
}
|
|
3998
|
+
if ((!authType || authType === "api_key") && provider.auth.apiKey) {
|
|
3999
|
+
options.push({
|
|
4000
|
+
id: provider.id,
|
|
4001
|
+
name: provider.name,
|
|
4002
|
+
authType: "api_key",
|
|
4003
|
+
method: provider.auth.apiKey,
|
|
4004
|
+
status,
|
|
4005
|
+
});
|
|
4014
4006
|
}
|
|
4015
|
-
options.push({
|
|
4016
|
-
id: providerId,
|
|
4017
|
-
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
4018
|
-
authType: credential.type,
|
|
4019
|
-
});
|
|
4020
4007
|
}
|
|
4021
4008
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4022
4009
|
}
|
|
4010
|
+
async getLogoutProviderOptions() {
|
|
4011
|
+
return (await this.session.modelRuntime.listCredentials())
|
|
4012
|
+
.map(({ providerId, type }) => ({
|
|
4013
|
+
id: providerId,
|
|
4014
|
+
name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
|
|
4015
|
+
authType: type,
|
|
4016
|
+
status: { type, source: "stored credential" },
|
|
4017
|
+
}))
|
|
4018
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
4019
|
+
}
|
|
4023
4020
|
findLoginProviderOptions(providerRef) {
|
|
4024
4021
|
const normalizedProviderRef = providerRef.trim().toLowerCase();
|
|
4025
4022
|
if (!normalizedProviderRef) {
|
|
@@ -4029,6 +4026,7 @@ export class InteractiveMode {
|
|
|
4029
4026
|
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4030
4027
|
}
|
|
4031
4028
|
async handleLoginCommand(providerRef) {
|
|
4029
|
+
await this.session.modelRuntime.getAvailable();
|
|
4032
4030
|
if (!providerRef) {
|
|
4033
4031
|
this.showLoginAuthTypeSelector();
|
|
4034
4032
|
return;
|
|
@@ -4051,9 +4049,12 @@ export class InteractiveMode {
|
|
|
4051
4049
|
if (providerOption.authType === "oauth") {
|
|
4052
4050
|
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4053
4051
|
}
|
|
4054
|
-
else {
|
|
4052
|
+
else if (providerOption.method?.login) {
|
|
4055
4053
|
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
4056
4054
|
}
|
|
4055
|
+
else {
|
|
4056
|
+
this.showAmbientAuthDialog(providerOption);
|
|
4057
|
+
}
|
|
4057
4058
|
}
|
|
4058
4059
|
showLoginAuthTypeSelector(providerOptions) {
|
|
4059
4060
|
const subscriptionLabel = "Sign in with an account";
|
|
@@ -4113,7 +4114,7 @@ export class InteractiveMode {
|
|
|
4113
4114
|
return;
|
|
4114
4115
|
}
|
|
4115
4116
|
this.showSelector((done) => {
|
|
4116
|
-
const selector = new OAuthSelectorComponent("login",
|
|
4117
|
+
const selector = new OAuthSelectorComponent("login", providerOptions, async (providerId, selectedAuthType) => {
|
|
4117
4118
|
done();
|
|
4118
4119
|
const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
|
|
4119
4120
|
if (!providerOption) {
|
|
@@ -4128,7 +4129,7 @@ export class InteractiveMode {
|
|
|
4128
4129
|
else {
|
|
4129
4130
|
this.ui.requestRender();
|
|
4130
4131
|
}
|
|
4131
|
-
},
|
|
4132
|
+
}, initialSearchInput);
|
|
4132
4133
|
return { component: selector, focus: selector };
|
|
4133
4134
|
});
|
|
4134
4135
|
}
|
|
@@ -4137,21 +4138,20 @@ export class InteractiveMode {
|
|
|
4137
4138
|
this.showLoginAuthTypeSelector();
|
|
4138
4139
|
return;
|
|
4139
4140
|
}
|
|
4140
|
-
const providerOptions = this.getLogoutProviderOptions();
|
|
4141
|
+
const providerOptions = await this.getLogoutProviderOptions();
|
|
4141
4142
|
if (providerOptions.length === 0) {
|
|
4142
4143
|
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
4143
4144
|
return;
|
|
4144
4145
|
}
|
|
4145
4146
|
this.showSelector((done) => {
|
|
4146
|
-
const selector = new OAuthSelectorComponent(mode,
|
|
4147
|
+
const selector = new OAuthSelectorComponent(mode, providerOptions, async (providerId) => {
|
|
4147
4148
|
done();
|
|
4148
4149
|
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
4149
4150
|
if (!providerOption) {
|
|
4150
4151
|
return;
|
|
4151
4152
|
}
|
|
4152
4153
|
try {
|
|
4153
|
-
this.session.
|
|
4154
|
-
this.session.modelRegistry.refresh();
|
|
4154
|
+
await this.session.modelRuntime.logout(providerOption.id);
|
|
4155
4155
|
await this.updateAvailableProviderCount();
|
|
4156
4156
|
const message = providerOption.authType === "oauth"
|
|
4157
4157
|
? `Logged out of ${providerOption.name}`
|
|
@@ -4169,12 +4169,12 @@ export class InteractiveMode {
|
|
|
4169
4169
|
});
|
|
4170
4170
|
}
|
|
4171
4171
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
4172
|
-
this.session.
|
|
4172
|
+
await this.session.modelRuntime.getAvailable();
|
|
4173
4173
|
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
4174
4174
|
let selectedModel;
|
|
4175
4175
|
let selectionError;
|
|
4176
4176
|
if (isUnknownModel(previousModel)) {
|
|
4177
|
-
const availableModels = this.session.
|
|
4177
|
+
const availableModels = await this.session.modelRuntime.getAvailable();
|
|
4178
4178
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
4179
4179
|
if (!hasDefaultModelProvider(providerId)) {
|
|
4180
4180
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
@@ -4218,6 +4218,20 @@ export class InteractiveMode {
|
|
|
4218
4218
|
}
|
|
4219
4219
|
}
|
|
4220
4220
|
}
|
|
4221
|
+
showAmbientAuthDialog(providerOption) {
|
|
4222
|
+
const restoreEditor = () => {
|
|
4223
|
+
this.editorContainer.clear();
|
|
4224
|
+
this.editorContainer.addChild(this.editor);
|
|
4225
|
+
this.ui.setFocus(this.editor);
|
|
4226
|
+
this.ui.requestRender();
|
|
4227
|
+
};
|
|
4228
|
+
const dialog = new LoginDialogComponent(this.ui, providerOption.id, () => restoreEditor(), providerOption.name, `${providerOption.name} setup`);
|
|
4229
|
+
dialog.showInfo(`${providerOption.method?.name ?? "Authentication"} is configured outside pi.`, [], true);
|
|
4230
|
+
this.editorContainer.clear();
|
|
4231
|
+
this.editorContainer.addChild(dialog);
|
|
4232
|
+
this.ui.setFocus(dialog);
|
|
4233
|
+
this.ui.requestRender();
|
|
4234
|
+
}
|
|
4221
4235
|
async showApiKeyLoginDialog(providerId, providerName) {
|
|
4222
4236
|
const previousModel = this.session.model;
|
|
4223
4237
|
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
@@ -4241,11 +4255,7 @@ export class InteractiveMode {
|
|
|
4241
4255
|
this.ui.requestRender();
|
|
4242
4256
|
};
|
|
4243
4257
|
try {
|
|
4244
|
-
|
|
4245
|
-
if (!apiKey) {
|
|
4246
|
-
throw new Error("API key cannot be empty.");
|
|
4247
|
-
}
|
|
4248
|
-
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
4258
|
+
await this.loginProvider(dialog, providerId, "api_key");
|
|
4249
4259
|
restoreEditor();
|
|
4250
4260
|
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
4251
4261
|
}
|
|
@@ -4257,8 +4267,8 @@ export class InteractiveMode {
|
|
|
4257
4267
|
}
|
|
4258
4268
|
}
|
|
4259
4269
|
}
|
|
4260
|
-
|
|
4261
|
-
return new Promise((resolve) => {
|
|
4270
|
+
showAuthSelect(dialog, prompt) {
|
|
4271
|
+
return new Promise((resolve, reject) => {
|
|
4262
4272
|
const restoreDialog = () => {
|
|
4263
4273
|
this.editorContainer.clear();
|
|
4264
4274
|
this.editorContainer.addChild(dialog);
|
|
@@ -4268,10 +4278,14 @@ export class InteractiveMode {
|
|
|
4268
4278
|
const labels = prompt.options.map((option) => option.label);
|
|
4269
4279
|
const selector = new ExtensionSelectorComponent(prompt.message, labels, (optionLabel) => {
|
|
4270
4280
|
restoreDialog();
|
|
4271
|
-
|
|
4281
|
+
const id = prompt.options.find((option) => option.label === optionLabel)?.id;
|
|
4282
|
+
if (id)
|
|
4283
|
+
resolve(id);
|
|
4284
|
+
else
|
|
4285
|
+
reject(new Error("Login cancelled"));
|
|
4272
4286
|
}, () => {
|
|
4273
4287
|
restoreDialog();
|
|
4274
|
-
|
|
4288
|
+
reject(new Error("Login cancelled"));
|
|
4275
4289
|
});
|
|
4276
4290
|
this.editorContainer.clear();
|
|
4277
4291
|
this.editorContainer.addChild(selector);
|
|
@@ -4279,30 +4293,64 @@ export class InteractiveMode {
|
|
|
4279
4293
|
this.ui.requestRender();
|
|
4280
4294
|
});
|
|
4281
4295
|
}
|
|
4296
|
+
async showAuthPrompt(dialog, prompt) {
|
|
4297
|
+
let response;
|
|
4298
|
+
if (prompt.type === "select") {
|
|
4299
|
+
response = this.showAuthSelect(dialog, prompt);
|
|
4300
|
+
}
|
|
4301
|
+
else if (prompt.type === "manual_code") {
|
|
4302
|
+
response = dialog.showManualInput(prompt.message);
|
|
4303
|
+
}
|
|
4304
|
+
else {
|
|
4305
|
+
response = dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
4306
|
+
}
|
|
4307
|
+
if (!prompt.signal)
|
|
4308
|
+
return response;
|
|
4309
|
+
if (prompt.signal.aborted)
|
|
4310
|
+
throw new Error("Login cancelled");
|
|
4311
|
+
const signal = prompt.signal;
|
|
4312
|
+
let onAbort;
|
|
4313
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
4314
|
+
onAbort = () => reject(new Error("Login cancelled"));
|
|
4315
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4316
|
+
});
|
|
4317
|
+
try {
|
|
4318
|
+
return await Promise.race([response, aborted]);
|
|
4319
|
+
}
|
|
4320
|
+
finally {
|
|
4321
|
+
if (onAbort)
|
|
4322
|
+
signal.removeEventListener("abort", onAbort);
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
notifyAuthDialog(dialog, event) {
|
|
4326
|
+
if (event.type === "auth_url") {
|
|
4327
|
+
dialog.showAuth(event.url, event.instructions);
|
|
4328
|
+
}
|
|
4329
|
+
else if (event.type === "device_code") {
|
|
4330
|
+
dialog.showDeviceCode(event);
|
|
4331
|
+
dialog.showWaiting("Waiting for authentication...");
|
|
4332
|
+
}
|
|
4333
|
+
else if (event.type === "info") {
|
|
4334
|
+
dialog.showInfo(event.message, event.links);
|
|
4335
|
+
}
|
|
4336
|
+
else {
|
|
4337
|
+
dialog.showProgress(event.message);
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
async loginProvider(dialog, providerId, method) {
|
|
4341
|
+
await this.session.modelRuntime.login(providerId, method, {
|
|
4342
|
+
signal: dialog.signal,
|
|
4343
|
+
prompt: (prompt) => this.showAuthPrompt(dialog, prompt),
|
|
4344
|
+
notify: (event) => this.notifyAuthDialog(dialog, event),
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4282
4347
|
async showLoginDialog(providerId, providerName) {
|
|
4283
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
4284
|
-
.getOAuthProviders()
|
|
4285
|
-
.find((provider) => provider.id === providerId);
|
|
4286
4348
|
const previousModel = this.session.model;
|
|
4287
|
-
|
|
4288
|
-
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
4289
|
-
// Create login dialog component
|
|
4290
|
-
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
4291
|
-
// Completion handled below
|
|
4292
|
-
}, providerName);
|
|
4293
|
-
// Show dialog in editor container
|
|
4349
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => { }, providerName);
|
|
4294
4350
|
this.editorContainer.clear();
|
|
4295
4351
|
this.editorContainer.addChild(dialog);
|
|
4296
4352
|
this.ui.setFocus(dialog);
|
|
4297
4353
|
this.ui.requestRender();
|
|
4298
|
-
// Promise for manual code input (racing with callback server)
|
|
4299
|
-
let manualCodeResolve;
|
|
4300
|
-
let manualCodeReject;
|
|
4301
|
-
const manualCodePromise = new Promise((resolve, reject) => {
|
|
4302
|
-
manualCodeResolve = resolve;
|
|
4303
|
-
manualCodeReject = reject;
|
|
4304
|
-
});
|
|
4305
|
-
// Restore editor helper
|
|
4306
4354
|
const restoreEditor = () => {
|
|
4307
4355
|
this.editorContainer.clear();
|
|
4308
4356
|
this.editorContainer.addChild(this.editor);
|
|
@@ -4310,43 +4358,7 @@ export class InteractiveMode {
|
|
|
4310
4358
|
this.ui.requestRender();
|
|
4311
4359
|
};
|
|
4312
4360
|
try {
|
|
4313
|
-
await this.
|
|
4314
|
-
onAuth: (info) => {
|
|
4315
|
-
dialog.showAuth(info.url, info.instructions);
|
|
4316
|
-
if (usesCallbackServer) {
|
|
4317
|
-
// Show input for manual paste, racing with callback
|
|
4318
|
-
dialog
|
|
4319
|
-
.showManualInput("Paste redirect URL below, or complete login in browser:")
|
|
4320
|
-
.then((value) => {
|
|
4321
|
-
if (value && manualCodeResolve) {
|
|
4322
|
-
manualCodeResolve(value);
|
|
4323
|
-
manualCodeResolve = undefined;
|
|
4324
|
-
}
|
|
4325
|
-
})
|
|
4326
|
-
.catch(() => {
|
|
4327
|
-
if (manualCodeReject) {
|
|
4328
|
-
manualCodeReject(new Error("Login cancelled"));
|
|
4329
|
-
manualCodeReject = undefined;
|
|
4330
|
-
}
|
|
4331
|
-
});
|
|
4332
|
-
}
|
|
4333
|
-
// For Anthropic: onPrompt is called immediately after
|
|
4334
|
-
},
|
|
4335
|
-
onDeviceCode: (info) => {
|
|
4336
|
-
dialog.showDeviceCode(info);
|
|
4337
|
-
dialog.showWaiting("Waiting for authentication...");
|
|
4338
|
-
},
|
|
4339
|
-
onPrompt: async (prompt) => {
|
|
4340
|
-
return dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
4341
|
-
},
|
|
4342
|
-
onProgress: (message) => {
|
|
4343
|
-
dialog.showProgress(message);
|
|
4344
|
-
},
|
|
4345
|
-
onSelect: (prompt) => this.showOAuthLoginSelect(dialog, prompt),
|
|
4346
|
-
onManualCodeInput: () => manualCodePromise,
|
|
4347
|
-
signal: dialog.signal,
|
|
4348
|
-
});
|
|
4349
|
-
// Success
|
|
4361
|
+
await this.loginProvider(dialog, providerId, "oauth");
|
|
4350
4362
|
restoreEditor();
|
|
4351
4363
|
await this.completeProviderAuthentication(providerId, providerName, "oauth", previousModel);
|
|
4352
4364
|
}
|
|
@@ -4434,7 +4446,7 @@ export class InteractiveMode {
|
|
|
4434
4446
|
showDiagnosticsWhenQuiet: true,
|
|
4435
4447
|
});
|
|
4436
4448
|
const savedImplicitProjectTrust = this.maybeSaveImplicitProjectTrustAfterReload();
|
|
4437
|
-
const modelsJsonError = this.session.
|
|
4449
|
+
const modelsJsonError = this.session.modelRuntime.getError();
|
|
4438
4450
|
if (modelsJsonError) {
|
|
4439
4451
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
4440
4452
|
}
|
|
@@ -4662,7 +4674,7 @@ export class InteractiveMode {
|
|
|
4662
4674
|
const stats = this.session.getSessionStats();
|
|
4663
4675
|
const sessionName = this.sessionManager.getSessionName();
|
|
4664
4676
|
const entries = this.sessionManager.getEntries();
|
|
4665
|
-
const cacheWaste = computeCacheWaste(entries, this.session.
|
|
4677
|
+
const cacheWaste = computeCacheWaste(entries, this.session.modelRuntime);
|
|
4666
4678
|
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
4667
4679
|
// resolves to a concrete responseModel), sorted by cost descending.
|
|
4668
4680
|
const perModelMap = new Map();
|