@averyyy/pi-coding-agent 0.80.6-piclient.9 → 0.80.10-piclient.1
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 +96 -1
- 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 +4 -6
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +11 -9
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +9 -7
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +64 -35
- 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/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.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 -733
- 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 +19 -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/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +7 -2
- package/dist/core/package-manager.js.map +1 -1
- 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 +2 -0
- package/dist/core/radius.d.ts.map +1 -0
- package/dist/core/radius.js +2 -0
- package/dist/core/radius.js.map +1 -0
- 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 +5 -6
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +38 -33
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -11
- package/dist/core/system-prompt.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 +158 -144
- 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 +8 -11
- package/docs/extensions.md +22 -1
- package/docs/models.md +4 -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/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 -36
- 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";
|
|
@@ -21,7 +20,6 @@ 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
22
|
import { getPiClientUpdateMarkerPath, PI_CLIENT_RUNTIME_RELOAD_EXIT_CODE, readPiClientUpdateMarker, writePiClientRuntimeReloadState, } from "../../core/pi-client-runtime-update.js";
|
|
24
|
-
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
25
23
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
26
24
|
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
|
|
27
25
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
@@ -95,7 +93,7 @@ function isDeadTerminalError(error) {
|
|
|
95
93
|
const code = error.code;
|
|
96
94
|
return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
|
|
97
95
|
}
|
|
98
|
-
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.";
|
|
96
|
+
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.";
|
|
99
97
|
function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
100
98
|
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
|
101
99
|
}
|
|
@@ -126,16 +124,6 @@ export function formatResumeCommand(sessionManager) {
|
|
|
126
124
|
function hasDefaultModelProvider(providerId) {
|
|
127
125
|
return providerId in defaultModelPerProvider;
|
|
128
126
|
}
|
|
129
|
-
const BUILT_IN_MODEL_PROVIDERS = new Set(getProviders());
|
|
130
|
-
export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProviderIds = BUILT_IN_MODEL_PROVIDERS) {
|
|
131
|
-
if (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
if (builtInProviderIds.has(providerId)) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
return !oauthProviderIds.has(providerId);
|
|
138
|
-
}
|
|
139
127
|
const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
|
|
140
128
|
function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
|
|
141
129
|
const filtered = fuzzyFilter(items, prefix, getSearchText);
|
|
@@ -362,11 +350,11 @@ export class InteractiveMode {
|
|
|
362
350
|
}));
|
|
363
351
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
364
352
|
if (modelCommand) {
|
|
365
|
-
modelCommand.getArgumentCompletions = (prefix) => {
|
|
353
|
+
modelCommand.getArgumentCompletions = async (prefix) => {
|
|
366
354
|
// Get available models (scoped or from registry)
|
|
367
355
|
const models = this.session.scopedModels.length > 0
|
|
368
356
|
? this.session.scopedModels.map((s) => s.model)
|
|
369
|
-
: this.session.
|
|
357
|
+
: await this.session.modelRuntime.getAvailable();
|
|
370
358
|
if (models.length === 0)
|
|
371
359
|
return null;
|
|
372
360
|
// Create items with provider/id format
|
|
@@ -599,10 +587,18 @@ export class InteractiveMode {
|
|
|
599
587
|
}
|
|
600
588
|
});
|
|
601
589
|
// Start package update check asynchronously
|
|
602
|
-
this.checkForPackageUpdates()
|
|
590
|
+
this.checkForPackageUpdates()
|
|
591
|
+
.then((updates) => {
|
|
603
592
|
if (updates.length > 0) {
|
|
604
593
|
this.showPackageUpdateNotification(updates);
|
|
605
594
|
}
|
|
595
|
+
})
|
|
596
|
+
.finally(() => {
|
|
597
|
+
// On Windows, npm can overwrite the shared console title while checking
|
|
598
|
+
// extension package versions. Restore Pi's title after the startup check.
|
|
599
|
+
if (process.platform === "win32" && this.isInitialized) {
|
|
600
|
+
this.updateTerminalTitle();
|
|
601
|
+
}
|
|
606
602
|
});
|
|
607
603
|
// Check tmux keyboard setup asynchronously
|
|
608
604
|
this.checkTmuxKeyboardSetup().then((warning) => {
|
|
@@ -615,7 +611,7 @@ export class InteractiveMode {
|
|
|
615
611
|
if (migratedProviders && migratedProviders.length > 0) {
|
|
616
612
|
this.showWarning(`Migrated credentials to auth.json: ${migratedProviders.join(", ")}`);
|
|
617
613
|
}
|
|
618
|
-
const modelsJsonError = this.session.
|
|
614
|
+
const modelsJsonError = this.session.modelRuntime.getError();
|
|
619
615
|
if (modelsJsonError) {
|
|
620
616
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
621
617
|
}
|
|
@@ -1346,7 +1342,7 @@ export class InteractiveMode {
|
|
|
1346
1342
|
hasUI: true,
|
|
1347
1343
|
cwd: this.sessionManager.getCwd(),
|
|
1348
1344
|
sessionManager: this.sessionManager,
|
|
1349
|
-
modelRegistry:
|
|
1345
|
+
modelRegistry: extensionRunner.getModelRegistry(),
|
|
1350
1346
|
model: this.session.model,
|
|
1351
1347
|
isIdle: () => this.session.isIdle,
|
|
1352
1348
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
@@ -2658,7 +2654,7 @@ export class InteractiveMode {
|
|
|
2658
2654
|
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2659
2655
|
// list and re-inject them after the assistant messages that paid for them.
|
|
2660
2656
|
const cacheMisses = this.settingsManager.getShowCacheMissNotices()
|
|
2661
|
-
? collectCacheMisses(this.sessionManager.getEntries(), this.session.
|
|
2657
|
+
? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRuntime)
|
|
2662
2658
|
: new Map();
|
|
2663
2659
|
if (options.updateFooter) {
|
|
2664
2660
|
this.footer.invalidate();
|
|
@@ -2749,7 +2745,7 @@ export class InteractiveMode {
|
|
|
2749
2745
|
if (!this.settingsManager.getShowCacheMissNotices())
|
|
2750
2746
|
return;
|
|
2751
2747
|
// Entries don't contain `message` yet: message_end fires before persistence.
|
|
2752
|
-
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.
|
|
2748
|
+
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.modelRuntime);
|
|
2753
2749
|
if (miss)
|
|
2754
2750
|
this.addCacheMissNotice(miss);
|
|
2755
2751
|
}
|
|
@@ -3590,9 +3586,9 @@ export class InteractiveMode {
|
|
|
3590
3586
|
if (this.session.scopedModels.length > 0) {
|
|
3591
3587
|
return this.session.scopedModels.map((scoped) => scoped.model);
|
|
3592
3588
|
}
|
|
3593
|
-
this.session.modelRegistry.refresh();
|
|
3594
3589
|
try {
|
|
3595
|
-
|
|
3590
|
+
await this.session.modelRuntime.refresh();
|
|
3591
|
+
return [...(await this.session.modelRuntime.getAvailable())];
|
|
3596
3592
|
}
|
|
3597
3593
|
catch {
|
|
3598
3594
|
return [];
|
|
@@ -3614,14 +3610,13 @@ export class InteractiveMode {
|
|
|
3614
3610
|
if (!model || model.provider !== "anthropic") {
|
|
3615
3611
|
return;
|
|
3616
3612
|
}
|
|
3617
|
-
const storedCredential = this.session.modelRegistry.authStorage.get("anthropic");
|
|
3618
|
-
if (storedCredential?.type === "oauth") {
|
|
3619
|
-
this.anthropicSubscriptionWarningShown = true;
|
|
3620
|
-
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3621
|
-
return;
|
|
3622
|
-
}
|
|
3623
3613
|
try {
|
|
3624
|
-
|
|
3614
|
+
if ((await this.session.modelRuntime.checkAuth("anthropic"))?.type === "oauth") {
|
|
3615
|
+
this.anthropicSubscriptionWarningShown = true;
|
|
3616
|
+
this.showWarning(ANTHROPIC_SUBSCRIPTION_AUTH_WARNING);
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
const apiKey = (await this.session.modelRuntime.getAuth(model.provider))?.auth.apiKey;
|
|
3625
3620
|
if (!isAnthropicSubscriptionAuthKey(apiKey)) {
|
|
3626
3621
|
return;
|
|
3627
3622
|
}
|
|
@@ -3679,7 +3674,7 @@ export class InteractiveMode {
|
|
|
3679
3674
|
}
|
|
3680
3675
|
showModelSelector(initialSearchInput) {
|
|
3681
3676
|
this.showSelector((done) => {
|
|
3682
|
-
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.
|
|
3677
|
+
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRuntime, this.session.scopedModels, async (model) => {
|
|
3683
3678
|
try {
|
|
3684
3679
|
await this.session.setModel(model);
|
|
3685
3680
|
this.footer.invalidate();
|
|
@@ -3702,8 +3697,8 @@ export class InteractiveMode {
|
|
|
3702
3697
|
}
|
|
3703
3698
|
async showModelsSelector() {
|
|
3704
3699
|
// Get all available models
|
|
3705
|
-
this.session.
|
|
3706
|
-
const allModels = this.session.
|
|
3700
|
+
await this.session.modelRuntime.refresh();
|
|
3701
|
+
const allModels = [...(await this.session.modelRuntime.getAvailable())];
|
|
3707
3702
|
if (allModels.length === 0) {
|
|
3708
3703
|
this.showStatus("No models available");
|
|
3709
3704
|
return;
|
|
@@ -3721,7 +3716,7 @@ export class InteractiveMode {
|
|
|
3721
3716
|
// Fall back to settings
|
|
3722
3717
|
const patterns = this.settingsManager.getEnabledModels();
|
|
3723
3718
|
if (patterns !== undefined && patterns.length > 0) {
|
|
3724
|
-
const scopedModels = await resolveModelScope(patterns, this.session.
|
|
3719
|
+
const scopedModels = await resolveModelScope(patterns, this.session.modelRuntime);
|
|
3725
3720
|
currentEnabledIds = scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3726
3721
|
}
|
|
3727
3722
|
}
|
|
@@ -3729,7 +3724,7 @@ export class InteractiveMode {
|
|
|
3729
3724
|
const updateSessionModels = async (enabledIds) => {
|
|
3730
3725
|
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
3731
3726
|
if (enabledIds && enabledIds.length > 0 && enabledIds.length < allModels.length) {
|
|
3732
|
-
const newScopedModels = await resolveModelScope(enabledIds, this.session.
|
|
3727
|
+
const newScopedModels = await resolveModelScope(enabledIds, this.session.modelRuntime);
|
|
3733
3728
|
this.session.setScopedModels(newScopedModels.map((sm) => ({
|
|
3734
3729
|
model: sm.model,
|
|
3735
3730
|
thinkingLevel: sm.thinkingLevel,
|
|
@@ -3989,44 +3984,46 @@ export class InteractiveMode {
|
|
|
3989
3984
|
}
|
|
3990
3985
|
}
|
|
3991
3986
|
getLoginProviderOptions(authType) {
|
|
3992
|
-
const authStorage = this.session.modelRegistry.authStorage;
|
|
3993
|
-
const oauthProviders = authStorage.getOAuthProviders();
|
|
3994
|
-
const oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));
|
|
3995
|
-
const options = oauthProviders.map((provider) => ({
|
|
3996
|
-
id: provider.id,
|
|
3997
|
-
name: provider.name,
|
|
3998
|
-
authType: "oauth",
|
|
3999
|
-
}));
|
|
4000
|
-
const modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));
|
|
4001
|
-
for (const providerId of modelProviders) {
|
|
4002
|
-
if (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {
|
|
4003
|
-
continue;
|
|
4004
|
-
}
|
|
4005
|
-
options.push({
|
|
4006
|
-
id: providerId,
|
|
4007
|
-
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
4008
|
-
authType: "api_key",
|
|
4009
|
-
});
|
|
4010
|
-
}
|
|
4011
|
-
const filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;
|
|
4012
|
-
return filteredOptions.sort((a, b) => a.name.localeCompare(b.name));
|
|
4013
|
-
}
|
|
4014
|
-
getLogoutProviderOptions() {
|
|
4015
|
-
const authStorage = this.session.modelRegistry.authStorage;
|
|
4016
3987
|
const options = [];
|
|
4017
|
-
for (const
|
|
4018
|
-
const
|
|
4019
|
-
|
|
4020
|
-
|
|
3988
|
+
for (const provider of this.session.modelRuntime.getProviders()) {
|
|
3989
|
+
const authStatus = this.session.modelRuntime.getProviderAuthStatus(provider.id);
|
|
3990
|
+
const status = authStatus.configured
|
|
3991
|
+
? {
|
|
3992
|
+
type: this.session.modelRuntime.isUsingOAuth(provider.id) ? "oauth" : "api_key",
|
|
3993
|
+
source: authStatus.label ?? authStatus.source,
|
|
3994
|
+
}
|
|
3995
|
+
: undefined;
|
|
3996
|
+
if ((!authType || authType === "oauth") && provider.auth.oauth) {
|
|
3997
|
+
options.push({
|
|
3998
|
+
id: provider.id,
|
|
3999
|
+
name: provider.name,
|
|
4000
|
+
authType: "oauth",
|
|
4001
|
+
method: provider.auth.oauth,
|
|
4002
|
+
status,
|
|
4003
|
+
});
|
|
4004
|
+
}
|
|
4005
|
+
if ((!authType || authType === "api_key") && provider.auth.apiKey) {
|
|
4006
|
+
options.push({
|
|
4007
|
+
id: provider.id,
|
|
4008
|
+
name: provider.name,
|
|
4009
|
+
authType: "api_key",
|
|
4010
|
+
method: provider.auth.apiKey,
|
|
4011
|
+
status,
|
|
4012
|
+
});
|
|
4021
4013
|
}
|
|
4022
|
-
options.push({
|
|
4023
|
-
id: providerId,
|
|
4024
|
-
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
4025
|
-
authType: credential.type,
|
|
4026
|
-
});
|
|
4027
4014
|
}
|
|
4028
4015
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4029
4016
|
}
|
|
4017
|
+
async getLogoutProviderOptions() {
|
|
4018
|
+
return (await this.session.modelRuntime.listCredentials())
|
|
4019
|
+
.map(({ providerId, type }) => ({
|
|
4020
|
+
id: providerId,
|
|
4021
|
+
name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
|
|
4022
|
+
authType: type,
|
|
4023
|
+
status: { type, source: "stored credential" },
|
|
4024
|
+
}))
|
|
4025
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
4026
|
+
}
|
|
4030
4027
|
findLoginProviderOptions(providerRef) {
|
|
4031
4028
|
const normalizedProviderRef = providerRef.trim().toLowerCase();
|
|
4032
4029
|
if (!normalizedProviderRef) {
|
|
@@ -4036,6 +4033,7 @@ export class InteractiveMode {
|
|
|
4036
4033
|
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4037
4034
|
}
|
|
4038
4035
|
async handleLoginCommand(providerRef) {
|
|
4036
|
+
await this.session.modelRuntime.getAvailable();
|
|
4039
4037
|
if (!providerRef) {
|
|
4040
4038
|
this.showLoginAuthTypeSelector();
|
|
4041
4039
|
return;
|
|
@@ -4058,13 +4056,18 @@ export class InteractiveMode {
|
|
|
4058
4056
|
if (providerOption.authType === "oauth") {
|
|
4059
4057
|
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4060
4058
|
}
|
|
4061
|
-
else {
|
|
4059
|
+
else if (providerOption.method?.login) {
|
|
4062
4060
|
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
4063
4061
|
}
|
|
4062
|
+
else {
|
|
4063
|
+
this.showAmbientAuthDialog(providerOption);
|
|
4064
|
+
}
|
|
4064
4065
|
}
|
|
4065
4066
|
showLoginAuthTypeSelector(providerOptions) {
|
|
4066
|
-
const
|
|
4067
|
-
const
|
|
4067
|
+
const oauthProvider = providerOptions?.find((provider) => provider.authType === "oauth");
|
|
4068
|
+
const oauthLoginLabel = oauthProvider?.method && "loginLabel" in oauthProvider.method ? oauthProvider.method.loginLabel : undefined;
|
|
4069
|
+
const subscriptionLabel = oauthLoginLabel ?? "Sign in with an account";
|
|
4070
|
+
const apiKeyLabel = "Sign in with an API key";
|
|
4068
4071
|
const availableAuthTypes = providerOptions
|
|
4069
4072
|
? new Set(providerOptions.map((provider) => provider.authType))
|
|
4070
4073
|
: new Set(["oauth", "api_key"]);
|
|
@@ -4120,7 +4123,7 @@ export class InteractiveMode {
|
|
|
4120
4123
|
return;
|
|
4121
4124
|
}
|
|
4122
4125
|
this.showSelector((done) => {
|
|
4123
|
-
const selector = new OAuthSelectorComponent("login",
|
|
4126
|
+
const selector = new OAuthSelectorComponent("login", providerOptions, async (providerId, selectedAuthType) => {
|
|
4124
4127
|
done();
|
|
4125
4128
|
const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
|
|
4126
4129
|
if (!providerOption) {
|
|
@@ -4135,7 +4138,7 @@ export class InteractiveMode {
|
|
|
4135
4138
|
else {
|
|
4136
4139
|
this.ui.requestRender();
|
|
4137
4140
|
}
|
|
4138
|
-
},
|
|
4141
|
+
}, initialSearchInput);
|
|
4139
4142
|
return { component: selector, focus: selector };
|
|
4140
4143
|
});
|
|
4141
4144
|
}
|
|
@@ -4144,21 +4147,20 @@ export class InteractiveMode {
|
|
|
4144
4147
|
this.showLoginAuthTypeSelector();
|
|
4145
4148
|
return;
|
|
4146
4149
|
}
|
|
4147
|
-
const providerOptions = this.getLogoutProviderOptions();
|
|
4150
|
+
const providerOptions = await this.getLogoutProviderOptions();
|
|
4148
4151
|
if (providerOptions.length === 0) {
|
|
4149
4152
|
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
4150
4153
|
return;
|
|
4151
4154
|
}
|
|
4152
4155
|
this.showSelector((done) => {
|
|
4153
|
-
const selector = new OAuthSelectorComponent(mode,
|
|
4156
|
+
const selector = new OAuthSelectorComponent(mode, providerOptions, async (providerId) => {
|
|
4154
4157
|
done();
|
|
4155
4158
|
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
4156
4159
|
if (!providerOption) {
|
|
4157
4160
|
return;
|
|
4158
4161
|
}
|
|
4159
4162
|
try {
|
|
4160
|
-
this.session.
|
|
4161
|
-
this.session.modelRegistry.refresh();
|
|
4163
|
+
await this.session.modelRuntime.logout(providerOption.id);
|
|
4162
4164
|
await this.updateAvailableProviderCount();
|
|
4163
4165
|
const message = providerOption.authType === "oauth"
|
|
4164
4166
|
? `Logged out of ${providerOption.name}`
|
|
@@ -4176,12 +4178,12 @@ export class InteractiveMode {
|
|
|
4176
4178
|
});
|
|
4177
4179
|
}
|
|
4178
4180
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
4179
|
-
this.session.
|
|
4181
|
+
await this.session.modelRuntime.getAvailable();
|
|
4180
4182
|
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
4181
4183
|
let selectedModel;
|
|
4182
4184
|
let selectionError;
|
|
4183
4185
|
if (isUnknownModel(previousModel)) {
|
|
4184
|
-
const availableModels = this.session.
|
|
4186
|
+
const availableModels = await this.session.modelRuntime.getAvailable();
|
|
4185
4187
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
4186
4188
|
if (!hasDefaultModelProvider(providerId)) {
|
|
4187
4189
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
@@ -4225,6 +4227,20 @@ export class InteractiveMode {
|
|
|
4225
4227
|
}
|
|
4226
4228
|
}
|
|
4227
4229
|
}
|
|
4230
|
+
showAmbientAuthDialog(providerOption) {
|
|
4231
|
+
const restoreEditor = () => {
|
|
4232
|
+
this.editorContainer.clear();
|
|
4233
|
+
this.editorContainer.addChild(this.editor);
|
|
4234
|
+
this.ui.setFocus(this.editor);
|
|
4235
|
+
this.ui.requestRender();
|
|
4236
|
+
};
|
|
4237
|
+
const dialog = new LoginDialogComponent(this.ui, providerOption.id, () => restoreEditor(), providerOption.name, `${providerOption.name} setup`);
|
|
4238
|
+
dialog.showInfo(`${providerOption.method?.name ?? "Authentication"} is configured outside pi.`, [], true);
|
|
4239
|
+
this.editorContainer.clear();
|
|
4240
|
+
this.editorContainer.addChild(dialog);
|
|
4241
|
+
this.ui.setFocus(dialog);
|
|
4242
|
+
this.ui.requestRender();
|
|
4243
|
+
}
|
|
4228
4244
|
async showApiKeyLoginDialog(providerId, providerName) {
|
|
4229
4245
|
const previousModel = this.session.model;
|
|
4230
4246
|
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
@@ -4248,11 +4264,7 @@ export class InteractiveMode {
|
|
|
4248
4264
|
this.ui.requestRender();
|
|
4249
4265
|
};
|
|
4250
4266
|
try {
|
|
4251
|
-
|
|
4252
|
-
if (!apiKey) {
|
|
4253
|
-
throw new Error("API key cannot be empty.");
|
|
4254
|
-
}
|
|
4255
|
-
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
4267
|
+
await this.loginProvider(dialog, providerId, "api_key");
|
|
4256
4268
|
restoreEditor();
|
|
4257
4269
|
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
4258
4270
|
}
|
|
@@ -4264,8 +4276,8 @@ export class InteractiveMode {
|
|
|
4264
4276
|
}
|
|
4265
4277
|
}
|
|
4266
4278
|
}
|
|
4267
|
-
|
|
4268
|
-
return new Promise((resolve) => {
|
|
4279
|
+
showAuthSelect(dialog, prompt) {
|
|
4280
|
+
return new Promise((resolve, reject) => {
|
|
4269
4281
|
const restoreDialog = () => {
|
|
4270
4282
|
this.editorContainer.clear();
|
|
4271
4283
|
this.editorContainer.addChild(dialog);
|
|
@@ -4275,10 +4287,14 @@ export class InteractiveMode {
|
|
|
4275
4287
|
const labels = prompt.options.map((option) => option.label);
|
|
4276
4288
|
const selector = new ExtensionSelectorComponent(prompt.message, labels, (optionLabel) => {
|
|
4277
4289
|
restoreDialog();
|
|
4278
|
-
|
|
4290
|
+
const id = prompt.options.find((option) => option.label === optionLabel)?.id;
|
|
4291
|
+
if (id)
|
|
4292
|
+
resolve(id);
|
|
4293
|
+
else
|
|
4294
|
+
reject(new Error("Login cancelled"));
|
|
4279
4295
|
}, () => {
|
|
4280
4296
|
restoreDialog();
|
|
4281
|
-
|
|
4297
|
+
reject(new Error("Login cancelled"));
|
|
4282
4298
|
});
|
|
4283
4299
|
this.editorContainer.clear();
|
|
4284
4300
|
this.editorContainer.addChild(selector);
|
|
@@ -4286,30 +4302,64 @@ export class InteractiveMode {
|
|
|
4286
4302
|
this.ui.requestRender();
|
|
4287
4303
|
});
|
|
4288
4304
|
}
|
|
4305
|
+
async showAuthPrompt(dialog, prompt) {
|
|
4306
|
+
let response;
|
|
4307
|
+
if (prompt.type === "select") {
|
|
4308
|
+
response = this.showAuthSelect(dialog, prompt);
|
|
4309
|
+
}
|
|
4310
|
+
else if (prompt.type === "manual_code") {
|
|
4311
|
+
response = dialog.showManualInput(prompt.message);
|
|
4312
|
+
}
|
|
4313
|
+
else {
|
|
4314
|
+
response = dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
4315
|
+
}
|
|
4316
|
+
if (!prompt.signal)
|
|
4317
|
+
return response;
|
|
4318
|
+
if (prompt.signal.aborted)
|
|
4319
|
+
throw new Error("Login cancelled");
|
|
4320
|
+
const signal = prompt.signal;
|
|
4321
|
+
let onAbort;
|
|
4322
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
4323
|
+
onAbort = () => reject(new Error("Login cancelled"));
|
|
4324
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
4325
|
+
});
|
|
4326
|
+
try {
|
|
4327
|
+
return await Promise.race([response, aborted]);
|
|
4328
|
+
}
|
|
4329
|
+
finally {
|
|
4330
|
+
if (onAbort)
|
|
4331
|
+
signal.removeEventListener("abort", onAbort);
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
notifyAuthDialog(dialog, event) {
|
|
4335
|
+
if (event.type === "auth_url") {
|
|
4336
|
+
dialog.showAuth(event.url, event.instructions);
|
|
4337
|
+
}
|
|
4338
|
+
else if (event.type === "device_code") {
|
|
4339
|
+
dialog.showDeviceCode(event);
|
|
4340
|
+
dialog.showWaiting("Waiting for authentication...");
|
|
4341
|
+
}
|
|
4342
|
+
else if (event.type === "info") {
|
|
4343
|
+
dialog.showInfo(event.message, event.links);
|
|
4344
|
+
}
|
|
4345
|
+
else {
|
|
4346
|
+
dialog.showProgress(event.message);
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
async loginProvider(dialog, providerId, method) {
|
|
4350
|
+
await this.session.modelRuntime.login(providerId, method, {
|
|
4351
|
+
signal: dialog.signal,
|
|
4352
|
+
prompt: (prompt) => this.showAuthPrompt(dialog, prompt),
|
|
4353
|
+
notify: (event) => this.notifyAuthDialog(dialog, event),
|
|
4354
|
+
});
|
|
4355
|
+
}
|
|
4289
4356
|
async showLoginDialog(providerId, providerName) {
|
|
4290
|
-
const providerInfo = this.session.modelRegistry.authStorage
|
|
4291
|
-
.getOAuthProviders()
|
|
4292
|
-
.find((provider) => provider.id === providerId);
|
|
4293
4357
|
const previousModel = this.session.model;
|
|
4294
|
-
|
|
4295
|
-
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
4296
|
-
// Create login dialog component
|
|
4297
|
-
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
4298
|
-
// Completion handled below
|
|
4299
|
-
}, providerName);
|
|
4300
|
-
// Show dialog in editor container
|
|
4358
|
+
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => { }, providerName);
|
|
4301
4359
|
this.editorContainer.clear();
|
|
4302
4360
|
this.editorContainer.addChild(dialog);
|
|
4303
4361
|
this.ui.setFocus(dialog);
|
|
4304
4362
|
this.ui.requestRender();
|
|
4305
|
-
// Promise for manual code input (racing with callback server)
|
|
4306
|
-
let manualCodeResolve;
|
|
4307
|
-
let manualCodeReject;
|
|
4308
|
-
const manualCodePromise = new Promise((resolve, reject) => {
|
|
4309
|
-
manualCodeResolve = resolve;
|
|
4310
|
-
manualCodeReject = reject;
|
|
4311
|
-
});
|
|
4312
|
-
// Restore editor helper
|
|
4313
4363
|
const restoreEditor = () => {
|
|
4314
4364
|
this.editorContainer.clear();
|
|
4315
4365
|
this.editorContainer.addChild(this.editor);
|
|
@@ -4317,43 +4367,7 @@ export class InteractiveMode {
|
|
|
4317
4367
|
this.ui.requestRender();
|
|
4318
4368
|
};
|
|
4319
4369
|
try {
|
|
4320
|
-
await this.
|
|
4321
|
-
onAuth: (info) => {
|
|
4322
|
-
dialog.showAuth(info.url, info.instructions);
|
|
4323
|
-
if (usesCallbackServer) {
|
|
4324
|
-
// Show input for manual paste, racing with callback
|
|
4325
|
-
dialog
|
|
4326
|
-
.showManualInput("Paste redirect URL below, or complete login in browser:")
|
|
4327
|
-
.then((value) => {
|
|
4328
|
-
if (value && manualCodeResolve) {
|
|
4329
|
-
manualCodeResolve(value);
|
|
4330
|
-
manualCodeResolve = undefined;
|
|
4331
|
-
}
|
|
4332
|
-
})
|
|
4333
|
-
.catch(() => {
|
|
4334
|
-
if (manualCodeReject) {
|
|
4335
|
-
manualCodeReject(new Error("Login cancelled"));
|
|
4336
|
-
manualCodeReject = undefined;
|
|
4337
|
-
}
|
|
4338
|
-
});
|
|
4339
|
-
}
|
|
4340
|
-
// For Anthropic: onPrompt is called immediately after
|
|
4341
|
-
},
|
|
4342
|
-
onDeviceCode: (info) => {
|
|
4343
|
-
dialog.showDeviceCode(info);
|
|
4344
|
-
dialog.showWaiting("Waiting for authentication...");
|
|
4345
|
-
},
|
|
4346
|
-
onPrompt: async (prompt) => {
|
|
4347
|
-
return dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
4348
|
-
},
|
|
4349
|
-
onProgress: (message) => {
|
|
4350
|
-
dialog.showProgress(message);
|
|
4351
|
-
},
|
|
4352
|
-
onSelect: (prompt) => this.showOAuthLoginSelect(dialog, prompt),
|
|
4353
|
-
onManualCodeInput: () => manualCodePromise,
|
|
4354
|
-
signal: dialog.signal,
|
|
4355
|
-
});
|
|
4356
|
-
// Success
|
|
4370
|
+
await this.loginProvider(dialog, providerId, "oauth");
|
|
4357
4371
|
restoreEditor();
|
|
4358
4372
|
await this.completeProviderAuthentication(providerId, providerName, "oauth", previousModel);
|
|
4359
4373
|
}
|
|
@@ -4460,7 +4474,7 @@ export class InteractiveMode {
|
|
|
4460
4474
|
showDiagnosticsWhenQuiet: true,
|
|
4461
4475
|
});
|
|
4462
4476
|
const savedImplicitProjectTrust = this.maybeSaveImplicitProjectTrustAfterReload();
|
|
4463
|
-
const modelsJsonError = this.session.
|
|
4477
|
+
const modelsJsonError = this.session.modelRuntime.getError();
|
|
4464
4478
|
if (modelsJsonError) {
|
|
4465
4479
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
4466
4480
|
}
|
|
@@ -4688,7 +4702,7 @@ export class InteractiveMode {
|
|
|
4688
4702
|
const stats = this.session.getSessionStats();
|
|
4689
4703
|
const sessionName = this.sessionManager.getSessionName();
|
|
4690
4704
|
const entries = this.sessionManager.getEntries();
|
|
4691
|
-
const cacheWaste = computeCacheWaste(entries, this.session.
|
|
4705
|
+
const cacheWaste = computeCacheWaste(entries, this.session.modelRuntime);
|
|
4692
4706
|
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
4693
4707
|
// resolves to a concrete responseModel), sorted by cost descending.
|
|
4694
4708
|
const perModelMap = new Map();
|