@earendil-works/pi-coding-agent 0.80.7 → 0.80.9
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 +64 -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-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +3 -0
- package/dist/core/agent-session-runtime.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 +512 -0
- package/dist/core/model-config.d.ts.map +1 -0
- package/dist/core/model-config.js +243 -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 +18 -18
- 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 +157 -143
- 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 +2 -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/README.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/kimi-deferred-tools.ts +61 -0
- 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,12 +4049,17 @@ 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
|
-
const
|
|
4060
|
+
const oauthProvider = providerOptions?.find((provider) => provider.authType === "oauth");
|
|
4061
|
+
const oauthLoginLabel = oauthProvider?.method && "loginLabel" in oauthProvider.method ? oauthProvider.method.loginLabel : undefined;
|
|
4062
|
+
const subscriptionLabel = oauthLoginLabel ?? "Sign in with an account";
|
|
4060
4063
|
const apiKeyLabel = "Sign in with an API key";
|
|
4061
4064
|
const availableAuthTypes = providerOptions
|
|
4062
4065
|
? new Set(providerOptions.map((provider) => provider.authType))
|
|
@@ -4113,7 +4116,7 @@ export class InteractiveMode {
|
|
|
4113
4116
|
return;
|
|
4114
4117
|
}
|
|
4115
4118
|
this.showSelector((done) => {
|
|
4116
|
-
const selector = new OAuthSelectorComponent("login",
|
|
4119
|
+
const selector = new OAuthSelectorComponent("login", providerOptions, async (providerId, selectedAuthType) => {
|
|
4117
4120
|
done();
|
|
4118
4121
|
const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
|
|
4119
4122
|
if (!providerOption) {
|
|
@@ -4128,7 +4131,7 @@ export class InteractiveMode {
|
|
|
4128
4131
|
else {
|
|
4129
4132
|
this.ui.requestRender();
|
|
4130
4133
|
}
|
|
4131
|
-
},
|
|
4134
|
+
}, initialSearchInput);
|
|
4132
4135
|
return { component: selector, focus: selector };
|
|
4133
4136
|
});
|
|
4134
4137
|
}
|
|
@@ -4137,21 +4140,20 @@ export class InteractiveMode {
|
|
|
4137
4140
|
this.showLoginAuthTypeSelector();
|
|
4138
4141
|
return;
|
|
4139
4142
|
}
|
|
4140
|
-
const providerOptions = this.getLogoutProviderOptions();
|
|
4143
|
+
const providerOptions = await this.getLogoutProviderOptions();
|
|
4141
4144
|
if (providerOptions.length === 0) {
|
|
4142
4145
|
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
4143
4146
|
return;
|
|
4144
4147
|
}
|
|
4145
4148
|
this.showSelector((done) => {
|
|
4146
|
-
const selector = new OAuthSelectorComponent(mode,
|
|
4149
|
+
const selector = new OAuthSelectorComponent(mode, providerOptions, async (providerId) => {
|
|
4147
4150
|
done();
|
|
4148
4151
|
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
4149
4152
|
if (!providerOption) {
|
|
4150
4153
|
return;
|
|
4151
4154
|
}
|
|
4152
4155
|
try {
|
|
4153
|
-
this.session.
|
|
4154
|
-
this.session.modelRegistry.refresh();
|
|
4156
|
+
await this.session.modelRuntime.logout(providerOption.id);
|
|
4155
4157
|
await this.updateAvailableProviderCount();
|
|
4156
4158
|
const message = providerOption.authType === "oauth"
|
|
4157
4159
|
? `Logged out of ${providerOption.name}`
|
|
@@ -4169,12 +4171,12 @@ export class InteractiveMode {
|
|
|
4169
4171
|
});
|
|
4170
4172
|
}
|
|
4171
4173
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
4172
|
-
this.session.
|
|
4174
|
+
await this.session.modelRuntime.getAvailable();
|
|
4173
4175
|
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
4174
4176
|
let selectedModel;
|
|
4175
4177
|
let selectionError;
|
|
4176
4178
|
if (isUnknownModel(previousModel)) {
|
|
4177
|
-
const availableModels = this.session.
|
|
4179
|
+
const availableModels = await this.session.modelRuntime.getAvailable();
|
|
4178
4180
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
4179
4181
|
if (!hasDefaultModelProvider(providerId)) {
|
|
4180
4182
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
@@ -4218,6 +4220,20 @@ export class InteractiveMode {
|
|
|
4218
4220
|
}
|
|
4219
4221
|
}
|
|
4220
4222
|
}
|
|
4223
|
+
showAmbientAuthDialog(providerOption) {
|
|
4224
|
+
const restoreEditor = () => {
|
|
4225
|
+
this.editorContainer.clear();
|
|
4226
|
+
this.editorContainer.addChild(this.editor);
|
|
4227
|
+
this.ui.setFocus(this.editor);
|
|
4228
|
+
this.ui.requestRender();
|
|
4229
|
+
};
|
|
4230
|
+
const dialog = new LoginDialogComponent(this.ui, providerOption.id, () => restoreEditor(), providerOption.name, `${providerOption.name} setup`);
|
|
4231
|
+
dialog.showInfo(`${providerOption.method?.name ?? "Authentication"} is configured outside pi.`, [], true);
|
|
4232
|
+
this.editorContainer.clear();
|
|
4233
|
+
this.editorContainer.addChild(dialog);
|
|
4234
|
+
this.ui.setFocus(dialog);
|
|
4235
|
+
this.ui.requestRender();
|
|
4236
|
+
}
|
|
4221
4237
|
async showApiKeyLoginDialog(providerId, providerName) {
|
|
4222
4238
|
const previousModel = this.session.model;
|
|
4223
4239
|
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
@@ -4241,11 +4257,7 @@ export class InteractiveMode {
|
|
|
4241
4257
|
this.ui.requestRender();
|
|
4242
4258
|
};
|
|
4243
4259
|
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 });
|
|
4260
|
+
await this.loginProvider(dialog, providerId, "api_key");
|
|
4249
4261
|
restoreEditor();
|
|
4250
4262
|
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
4251
4263
|
}
|
|
@@ -4257,8 +4269,8 @@ export class InteractiveMode {
|
|
|
4257
4269
|
}
|
|
4258
4270
|
}
|
|
4259
4271
|
}
|
|
4260
|
-
|
|
4261
|
-
return new Promise((resolve) => {
|
|
4272
|
+
showAuthSelect(dialog, prompt) {
|
|
4273
|
+
return new Promise((resolve, reject) => {
|
|
4262
4274
|
const restoreDialog = () => {
|
|
4263
4275
|
this.editorContainer.clear();
|
|
4264
4276
|
this.editorContainer.addChild(dialog);
|
|
@@ -4268,10 +4280,14 @@ export class InteractiveMode {
|
|
|
4268
4280
|
const labels = prompt.options.map((option) => option.label);
|
|
4269
4281
|
const selector = new ExtensionSelectorComponent(prompt.message, labels, (optionLabel) => {
|
|
4270
4282
|
restoreDialog();
|
|
4271
|
-
|
|
4283
|
+
const id = prompt.options.find((option) => option.label === optionLabel)?.id;
|
|
4284
|
+
if (id)
|
|
4285
|
+
resolve(id);
|
|
4286
|
+
else
|
|
4287
|
+
reject(new Error("Login cancelled"));
|
|
4272
4288
|
}, () => {
|
|
4273
4289
|
restoreDialog();
|
|
4274
|
-
|
|
4290
|
+
reject(new Error("Login cancelled"));
|
|
4275
4291
|
});
|
|
4276
4292
|
this.editorContainer.clear();
|
|
4277
4293
|
this.editorContainer.addChild(selector);
|
|
@@ -4279,30 +4295,64 @@ export class InteractiveMode {
|
|
|
4279
4295
|
this.ui.requestRender();
|
|
4280
4296
|
});
|
|
4281
4297
|
}
|
|
4298
|
+
async showAuthPrompt(dialog, prompt) {
|
|
4299
|
+
let response;
|
|
4300
|
+
if (prompt.type === "select") {
|
|
4301
|
+
response = this.showAuthSelect(dialog, prompt);
|
|
4302
|
+
}
|
|
4303
|
+
else if (prompt.type === "manual_code") {
|
|
4304
|
+
response = dialog.showManualInput(prompt.message);
|
|
4305
|
+
}
|
|
4306
|
+
else {
|
|
4307
|
+
response = dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
4308
|
+
}
|
|
4309
|
+
if (!prompt.signal)
|
|
4310
|
+
return response;
|
|
4311
|
+
if (prompt.signal.aborted)
|
|
4312
|
+
throw new Error("Login cancelled");
|
|
4313
|
+
const signal = prompt.signal;
|
|
4314
|
+
let onAbort;
|
|
4315
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
4316
|
+
onAbort = () => reject(new Error("Login cancelled"));
|
|
4317
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4318
|
+
});
|
|
4319
|
+
try {
|
|
4320
|
+
return await Promise.race([response, aborted]);
|
|
4321
|
+
}
|
|
4322
|
+
finally {
|
|
4323
|
+
if (onAbort)
|
|
4324
|
+
signal.removeEventListener("abort", onAbort);
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
notifyAuthDialog(dialog, event) {
|
|
4328
|
+
if (event.type === "auth_url") {
|
|
4329
|
+
dialog.showAuth(event.url, event.instructions);
|
|
4330
|
+
}
|
|
4331
|
+
else if (event.type === "device_code") {
|
|
4332
|
+
dialog.showDeviceCode(event);
|
|
4333
|
+
dialog.showWaiting("Waiting for authentication...");
|
|
4334
|
+
}
|
|
4335
|
+
else if (event.type === "info") {
|
|
4336
|
+
dialog.showInfo(event.message, event.links);
|
|
4337
|
+
}
|
|
4338
|
+
else {
|
|
4339
|
+
dialog.showProgress(event.message);
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
async loginProvider(dialog, providerId, method) {
|
|
4343
|
+
await this.session.modelRuntime.login(providerId, method, {
|
|
4344
|
+
signal: dialog.signal,
|
|
4345
|
+
prompt: (prompt) => this.showAuthPrompt(dialog, prompt),
|
|
4346
|
+
notify: (event) => this.notifyAuthDialog(dialog, event),
|
|
4347
|
+
});
|
|
4348
|
+
}
|
|
4282
4349
|
async showLoginDialog(providerId, providerName) {
|
|
4283
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
4284
|
-
.getOAuthProviders()
|
|
4285
|
-
.find((provider) => provider.id === providerId);
|
|
4286
4350
|
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
|
|
4351
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => { }, providerName);
|
|
4294
4352
|
this.editorContainer.clear();
|
|
4295
4353
|
this.editorContainer.addChild(dialog);
|
|
4296
4354
|
this.ui.setFocus(dialog);
|
|
4297
4355
|
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
4356
|
const restoreEditor = () => {
|
|
4307
4357
|
this.editorContainer.clear();
|
|
4308
4358
|
this.editorContainer.addChild(this.editor);
|
|
@@ -4310,43 +4360,7 @@ export class InteractiveMode {
|
|
|
4310
4360
|
this.ui.requestRender();
|
|
4311
4361
|
};
|
|
4312
4362
|
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
|
|
4363
|
+
await this.loginProvider(dialog, providerId, "oauth");
|
|
4350
4364
|
restoreEditor();
|
|
4351
4365
|
await this.completeProviderAuthentication(providerId, providerName, "oauth", previousModel);
|
|
4352
4366
|
}
|
|
@@ -4434,7 +4448,7 @@ export class InteractiveMode {
|
|
|
4434
4448
|
showDiagnosticsWhenQuiet: true,
|
|
4435
4449
|
});
|
|
4436
4450
|
const savedImplicitProjectTrust = this.maybeSaveImplicitProjectTrustAfterReload();
|
|
4437
|
-
const modelsJsonError = this.session.
|
|
4451
|
+
const modelsJsonError = this.session.modelRuntime.getError();
|
|
4438
4452
|
if (modelsJsonError) {
|
|
4439
4453
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
4440
4454
|
}
|
|
@@ -4662,7 +4676,7 @@ export class InteractiveMode {
|
|
|
4662
4676
|
const stats = this.session.getSessionStats();
|
|
4663
4677
|
const sessionName = this.sessionManager.getSessionName();
|
|
4664
4678
|
const entries = this.sessionManager.getEntries();
|
|
4665
|
-
const cacheWaste = computeCacheWaste(entries, this.session.
|
|
4679
|
+
const cacheWaste = computeCacheWaste(entries, this.session.modelRuntime);
|
|
4666
4680
|
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
4667
4681
|
// resolves to a concrete responseModel), sorted by cost descending.
|
|
4668
4682
|
const perModelMap = new Map();
|