@kolisachint/hoocode-agent 0.4.107 → 0.4.108
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 +2 -0
- package/dist/modes/interactive/bash-execution-controller.d.ts +34 -0
- package/dist/modes/interactive/bash-execution-controller.d.ts.map +1 -0
- package/dist/modes/interactive/bash-execution-controller.js +98 -0
- package/dist/modes/interactive/bash-execution-controller.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +5 -45
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +91 -779
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/login-controller.d.ts +50 -0
- package/dist/modes/interactive/login-controller.d.ts.map +1 -0
- package/dist/modes/interactive/login-controller.js +352 -0
- package/dist/modes/interactive/login-controller.js.map +1 -0
- package/dist/modes/interactive/message-queue-controller.d.ts +49 -0
- package/dist/modes/interactive/message-queue-controller.d.ts.map +1 -0
- package/dist/modes/interactive/message-queue-controller.js +184 -0
- package/dist/modes/interactive/message-queue-controller.js.map +1 -0
- package/dist/modes/interactive/model-controller.d.ts +43 -0
- package/dist/modes/interactive/model-controller.d.ts.map +1 -0
- package/dist/modes/interactive/model-controller.js +184 -0
- package/dist/modes/interactive/model-controller.js.map +1 -0
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth / login flows for the interactive mode (/login, /logout).
|
|
3
|
+
*
|
|
4
|
+
* Owns the provider auth-type selector, the OAuth and API-key login dialogs,
|
|
5
|
+
* the Bedrock setup notice, and post-login model selection. Extracted from
|
|
6
|
+
* interactive-mode.ts behind a narrow LoginControllerDeps interface; the
|
|
7
|
+
* `/login` and `/logout` commands call `showOAuthSelector`.
|
|
8
|
+
*/
|
|
9
|
+
import { type Model } from "@kolisachint/hoocode-ai";
|
|
10
|
+
import type { Component, Container, TUI } from "@kolisachint/hoocode-tui";
|
|
11
|
+
import type { AgentSession } from "../../core/agent-session.js";
|
|
12
|
+
export declare function isApiKeyLoginProvider(providerId: string, oauthProviderIds: ReadonlySet<string>, builtInProviderIds?: ReadonlySet<string>): boolean;
|
|
13
|
+
/** The slice of the interactive mode the login flows need. */
|
|
14
|
+
export interface LoginControllerDeps {
|
|
15
|
+
ui: TUI;
|
|
16
|
+
/** The active session (read at call time; the session can be swapped). */
|
|
17
|
+
get session(): AgentSession;
|
|
18
|
+
/** The prompt editor (read at call time; the editor can be swapped). */
|
|
19
|
+
getEditor(): Component;
|
|
20
|
+
/** Container that hosts the prompt editor and login dialogs. */
|
|
21
|
+
editorContainer: Container;
|
|
22
|
+
showSelector(create: (done: () => void) => {
|
|
23
|
+
component: Component;
|
|
24
|
+
focus: Component;
|
|
25
|
+
}): void;
|
|
26
|
+
showStatus(message: string): void;
|
|
27
|
+
showError(errorMessage: string): void;
|
|
28
|
+
/** Recompute the footer's available provider count. */
|
|
29
|
+
updateAvailableProviderCount(): Promise<void>;
|
|
30
|
+
updateEditorBorderColor(): void;
|
|
31
|
+
invalidateFooter(): void;
|
|
32
|
+
maybeWarnAboutAnthropicSubscriptionAuth(model?: Model<any>): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export declare class LoginController {
|
|
35
|
+
private readonly deps;
|
|
36
|
+
constructor(deps: LoginControllerDeps);
|
|
37
|
+
private get session();
|
|
38
|
+
private restoreEditor;
|
|
39
|
+
private getLoginProviderOptions;
|
|
40
|
+
private getLogoutProviderOptions;
|
|
41
|
+
private showLoginAuthTypeSelector;
|
|
42
|
+
private showLoginProviderSelector;
|
|
43
|
+
showOAuthSelector(mode: "login" | "logout"): Promise<void>;
|
|
44
|
+
private completeProviderAuthentication;
|
|
45
|
+
private showBedrockSetupDialog;
|
|
46
|
+
private showApiKeyLoginDialog;
|
|
47
|
+
private showOAuthLoginSelect;
|
|
48
|
+
private showLoginDialog;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=login-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-controller.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/login-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAgB,KAAK,KAAK,EAAgD,MAAM,yBAAyB,CAAC;AACjH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAE1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAYhE,wBAAgB,qBAAqB,CACpC,UAAU,EAAE,MAAM,EAClB,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,EACrC,kBAAkB,GAAE,WAAW,CAAC,MAAM,CAA4B,GAChE,OAAO,CAQT;AAUD,8DAA8D;AAC9D,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,GAAG,CAAC;IACR,0EAA0E;IAC1E,IAAI,OAAO,IAAI,YAAY,CAAC;IAC5B,wEAAwE;IACxE,SAAS,IAAI,SAAS,CAAC;IACvB,gEAAgE;IAChE,eAAe,EAAE,SAAS,CAAC;IAC3B,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7F,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,uDAAuD;IACvD,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,uBAAuB,IAAI,IAAI,CAAC;IAChC,gBAAgB,IAAI,IAAI,CAAC;IACzB,uCAAuC,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3E;AAED,qBAAa,eAAe;IACf,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,mBAAmB,EAAI;IAE1D,OAAO,KAAK,OAAO,GAElB;IAED,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,uBAAuB;IA0B/B,OAAO,CAAC,wBAAwB;IAmBhC,OAAO,CAAC,yBAAyB;IAqBjC,OAAO,CAAC,yBAAyB;IAwC3B,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA+C/D;YAEa,8BAA8B;IAoD5C,OAAO,CAAC,sBAAsB;YAqBhB,qBAAqB;IAoCnC,OAAO,CAAC,oBAAoB;YA4Bd,eAAe;CAuF7B","sourcesContent":["/**\n * Auth / login flows for the interactive mode (/login, /logout).\n *\n * Owns the provider auth-type selector, the OAuth and API-key login dialogs,\n * the Bedrock setup notice, and post-login model selection. Extracted from\n * interactive-mode.ts behind a narrow LoginControllerDeps interface; the\n * `/login` and `/logout` commands call `showOAuthSelector`.\n */\n\nimport * as path from \"node:path\";\nimport { getProviders, type Model, type OAuthProviderId, type OAuthSelectPrompt } from \"@kolisachint/hoocode-ai\";\nimport type { Component, Container, TUI } from \"@kolisachint/hoocode-tui\";\nimport { getAuthPath, getDocsPath } from \"../../config.js\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport { defaultModelPerProvider } from \"../../core/model-resolver.js\";\nimport { BUILT_IN_PROVIDER_DISPLAY_NAMES } from \"../../core/provider-display-names.js\";\nimport { ExtensionSelectorComponent } from \"./components/extension-selector.js\";\nimport { LoginDialogComponent } from \"./components/login-dialog.js\";\nimport { type AuthSelectorProvider, OAuthSelectorComponent } from \"./components/oauth-selector.js\";\nimport { theme } from \"./theme/theme.js\";\n\nconst BEDROCK_PROVIDER_ID = \"amazon-bedrock\";\n\nconst BUILT_IN_MODEL_PROVIDERS = new Set<string>(getProviders());\n\nexport function isApiKeyLoginProvider(\n\tproviderId: string,\n\toauthProviderIds: ReadonlySet<string>,\n\tbuiltInProviderIds: ReadonlySet<string> = BUILT_IN_MODEL_PROVIDERS,\n): boolean {\n\tif (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {\n\t\treturn true;\n\t}\n\tif (builtInProviderIds.has(providerId)) {\n\t\treturn false;\n\t}\n\treturn !oauthProviderIds.has(providerId);\n}\n\nfunction isUnknownModel(model: Model<any> | undefined): boolean {\n\treturn !!model && model.provider === \"unknown\" && model.id === \"unknown\" && model.api === \"unknown\";\n}\n\nfunction hasDefaultModelProvider(providerId: string): providerId is keyof typeof defaultModelPerProvider {\n\treturn providerId in defaultModelPerProvider;\n}\n\n/** The slice of the interactive mode the login flows need. */\nexport interface LoginControllerDeps {\n\tui: TUI;\n\t/** The active session (read at call time; the session can be swapped). */\n\tget session(): AgentSession;\n\t/** The prompt editor (read at call time; the editor can be swapped). */\n\tgetEditor(): Component;\n\t/** Container that hosts the prompt editor and login dialogs. */\n\teditorContainer: Container;\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n\t/** Recompute the footer's available provider count. */\n\tupdateAvailableProviderCount(): Promise<void>;\n\tupdateEditorBorderColor(): void;\n\tinvalidateFooter(): void;\n\tmaybeWarnAboutAnthropicSubscriptionAuth(model?: Model<any>): Promise<void>;\n}\n\nexport class LoginController {\n\tconstructor(private readonly deps: LoginControllerDeps) {}\n\n\tprivate get session(): AgentSession {\n\t\treturn this.deps.session;\n\t}\n\n\tprivate restoreEditor(): void {\n\t\tconst { deps } = this;\n\t\tdeps.editorContainer.clear();\n\t\tdeps.editorContainer.addChild(deps.getEditor());\n\t\tdeps.ui.setFocus(deps.getEditor());\n\t\tdeps.ui.requestRender();\n\t}\n\n\tprivate getLoginProviderOptions(authType?: \"oauth\" | \"api_key\"): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst oauthProviders = authStorage.getOAuthProviders();\n\t\tconst oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));\n\t\tconst options: AuthSelectorProvider[] = oauthProviders.map((provider) => ({\n\t\t\tid: provider.id,\n\t\t\tname: provider.name,\n\t\t\tauthType: \"oauth\",\n\t\t}));\n\n\t\tconst modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));\n\t\tfor (const providerId of modelProviders) {\n\t\t\tif (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: \"api_key\",\n\t\t\t});\n\t\t}\n\n\t\tconst filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;\n\t\treturn filteredOptions.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate getLogoutProviderOptions(): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst options: AuthSelectorProvider[] = [];\n\n\t\tfor (const providerId of authStorage.list()) {\n\t\t\tconst credential = authStorage.get(providerId);\n\t\t\tif (!credential) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: credential.type,\n\t\t\t});\n\t\t}\n\n\t\treturn options.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate showLoginAuthTypeSelector(): void {\n\t\tconst subscriptionLabel = \"Use a subscription\";\n\t\tconst apiKeyLabel = \"Use an API key\";\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\t\"Select authentication method:\",\n\t\t\t\t[subscriptionLabel, apiKeyLabel],\n\t\t\t\t(option) => {\n\t\t\t\t\tdone();\n\t\t\t\t\tconst authType = option === subscriptionLabel ? \"oauth\" : \"api_key\";\n\t\t\t\t\tthis.showLoginProviderSelector(authType);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate showLoginProviderSelector(authType: \"oauth\" | \"api_key\"): void {\n\t\tconst providerOptions = this.getLoginProviderOptions(authType);\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\tauthType === \"oauth\" ? \"No subscription providers available.\" : \"No API key providers available.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\t\"login\",\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (providerOption.authType === \"oauth\") {\n\t\t\t\t\t\tawait this.showLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t} else if (providerOption.id === BEDROCK_PROVIDER_ID) {\n\t\t\t\t\t\tthis.showBedrockSetupDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait this.showApiKeyLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\t\t},\n\t\t\t\t(providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId),\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tasync showOAuthSelector(mode: \"login\" | \"logout\"): Promise<void> {\n\t\tif (mode === \"login\") {\n\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tconst providerOptions = this.getLogoutProviderOptions();\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\t\"No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\tmode,\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.session.modelRegistry.authStorage.logout(providerOption.id);\n\t\t\t\t\t\tthis.session.modelRegistry.refresh();\n\t\t\t\t\t\tawait this.deps.updateAvailableProviderCount();\n\t\t\t\t\t\tconst message =\n\t\t\t\t\t\t\tproviderOption.authType === \"oauth\"\n\t\t\t\t\t\t\t\t? `Logged out of ${providerOption.name}`\n\t\t\t\t\t\t\t\t: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;\n\t\t\t\t\t\tthis.deps.showStatus(message);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tthis.deps.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate async completeProviderAuthentication(\n\t\tproviderId: string,\n\t\tproviderName: string,\n\t\tauthType: \"oauth\" | \"api_key\",\n\t\tpreviousModel: Model<any> | undefined,\n\t): Promise<void> {\n\t\tthis.session.modelRegistry.refresh();\n\n\t\tconst actionLabel = authType === \"oauth\" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;\n\n\t\tlet selectedModel: Model<any> | undefined;\n\t\tlet selectionError: string | undefined;\n\t\tif (isUnknownModel(previousModel)) {\n\t\t\tconst availableModels = this.session.modelRegistry.getAvailable();\n\t\t\tconst providerModels = availableModels.filter((model) => model.provider === providerId);\n\t\t\tif (!hasDefaultModelProvider(providerId)) {\n\t\t\t\tselectionError = `${actionLabel}, but no default model is configured for provider \"${providerId}\". Use /model to select a model.`;\n\t\t\t} else if (providerModels.length === 0) {\n\t\t\t\tselectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;\n\t\t\t} else {\n\t\t\t\tconst defaultModelId = defaultModelPerProvider[providerId];\n\t\t\t\tselectedModel = providerModels.find((model) => model.id === defaultModelId);\n\t\t\t\tif (!selectedModel) {\n\t\t\t\t\tselectionError = `${actionLabel}, but its default model \"${defaultModelId}\" is not available. Use /model to select a model.`;\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.session.setModel(selectedModel);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tselectedModel = undefined;\n\t\t\t\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\tselectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tawait this.deps.updateAvailableProviderCount();\n\t\tthis.deps.invalidateFooter();\n\t\tthis.deps.updateEditorBorderColor();\n\t\tif (selectedModel) {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);\n\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);\n\t\t} else {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);\n\t\t\tif (selectionError) {\n\t\t\t\tthis.deps.showError(selectionError);\n\t\t\t} else {\n\t\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate showBedrockSetupDialog(providerId: string, providerName: string): void {\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t() => this.restoreEditor(),\n\t\t\tproviderName,\n\t\t\t\"Amazon Bedrock setup\",\n\t\t);\n\t\tdialog.showInfo([\n\t\t\ttheme.fg(\"text\", \"Amazon Bedrock uses AWS credentials instead of a single API key.\"),\n\t\t\ttheme.fg(\"text\", \"Configure an AWS profile, IAM keys, bearer token, or role-based credentials.\"),\n\t\t\ttheme.fg(\"muted\", \"See:\"),\n\t\t\ttheme.fg(\"accent\", ` ${path.join(getDocsPath(), \"providers.md\")}`),\n\t\t]);\n\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\t}\n\n\tprivate async showApiKeyLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst previousModel = this.session.model;\n\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\ttry {\n\t\t\tconst apiKey = (await dialog.showPrompt(\"Enter API key:\")).trim();\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(\"API key cannot be empty.\");\n\t\t\t}\n\n\t\t\tthis.session.modelRegistry.authStorage.set(providerId, { type: \"api_key\", key: apiKey });\n\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"api_key\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate showOAuthLoginSelect(dialog: LoginDialogComponent, prompt: OAuthSelectPrompt): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst restoreDialog = () => {\n\t\t\t\tthis.deps.editorContainer.clear();\n\t\t\t\tthis.deps.editorContainer.addChild(dialog);\n\t\t\t\tthis.deps.ui.setFocus(dialog);\n\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t};\n\t\t\tconst labels = prompt.options.map((option) => option.label);\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\tprompt.message,\n\t\t\t\tlabels,\n\t\t\t\t(optionLabel) => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(prompt.options.find((option) => option.label === optionLabel)?.id);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(undefined);\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.deps.editorContainer.clear();\n\t\t\tthis.deps.editorContainer.addChild(selector);\n\t\t\tthis.deps.ui.setFocus(selector);\n\t\t\tthis.deps.ui.requestRender();\n\t\t});\n\t}\n\n\tprivate async showLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst providerInfo = this.session.modelRegistry.authStorage\n\t\t\t.getOAuthProviders()\n\t\t\t.find((provider) => provider.id === providerId);\n\t\tconst previousModel = this.session.model;\n\n\t\t// Providers that use callback servers (can paste redirect URL)\n\t\tconst usesCallbackServer = providerInfo?.usesCallbackServer ?? false;\n\n\t\t// Create login dialog component\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\t// Show dialog in editor container\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\t// Promise for manual code input (racing with callback server)\n\t\tlet manualCodeResolve: ((code: string) => void) | undefined;\n\t\tlet manualCodeReject: ((err: Error) => void) | undefined;\n\t\tconst manualCodePromise = new Promise<string>((resolve, reject) => {\n\t\t\tmanualCodeResolve = resolve;\n\t\t\tmanualCodeReject = reject;\n\t\t});\n\n\t\ttry {\n\t\t\tawait this.session.modelRegistry.authStorage.login(providerId as OAuthProviderId, {\n\t\t\t\tonAuth: (info: { url: string; instructions?: string }) => {\n\t\t\t\t\tdialog.showAuth(info.url, info.instructions);\n\n\t\t\t\t\tif (usesCallbackServer) {\n\t\t\t\t\t\t// Show input for manual paste, racing with callback\n\t\t\t\t\t\tdialog\n\t\t\t\t\t\t\t.showManualInput(\"Paste redirect URL below, or complete login in browser:\")\n\t\t\t\t\t\t\t.then((value) => {\n\t\t\t\t\t\t\t\tif (value && manualCodeResolve) {\n\t\t\t\t\t\t\t\t\tmanualCodeResolve(value);\n\t\t\t\t\t\t\t\t\tmanualCodeResolve = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\t\t\tif (manualCodeReject) {\n\t\t\t\t\t\t\t\t\tmanualCodeReject(new Error(\"Login cancelled\"));\n\t\t\t\t\t\t\t\t\tmanualCodeReject = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t} else if (providerId === \"github-copilot\") {\n\t\t\t\t\t\t// GitHub Copilot polls after onAuth\n\t\t\t\t\t\tdialog.showWaiting(\"Waiting for browser authentication...\");\n\t\t\t\t\t}\n\t\t\t\t\t// For Anthropic: onPrompt is called immediately after\n\t\t\t\t},\n\n\t\t\t\tonPrompt: async (prompt: { message: string; placeholder?: string }) => {\n\t\t\t\t\treturn dialog.showPrompt(prompt.message, prompt.placeholder);\n\t\t\t\t},\n\n\t\t\t\tonProgress: (message: string) => {\n\t\t\t\t\tdialog.showProgress(message);\n\t\t\t\t},\n\n\t\t\t\tonSelect: (prompt: OAuthSelectPrompt) => this.showOAuthLoginSelect(dialog, prompt),\n\n\t\t\t\tonManualCodeInput: () => manualCodePromise,\n\n\t\t\t\tsignal: dialog.signal,\n\t\t\t});\n\n\t\t\t// Success\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"oauth\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to login to ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth / login flows for the interactive mode (/login, /logout).
|
|
3
|
+
*
|
|
4
|
+
* Owns the provider auth-type selector, the OAuth and API-key login dialogs,
|
|
5
|
+
* the Bedrock setup notice, and post-login model selection. Extracted from
|
|
6
|
+
* interactive-mode.ts behind a narrow LoginControllerDeps interface; the
|
|
7
|
+
* `/login` and `/logout` commands call `showOAuthSelector`.
|
|
8
|
+
*/
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import { getProviders } from "@kolisachint/hoocode-ai";
|
|
11
|
+
import { getAuthPath, getDocsPath } from "../../config.js";
|
|
12
|
+
import { defaultModelPerProvider } from "../../core/model-resolver.js";
|
|
13
|
+
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
14
|
+
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
15
|
+
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
16
|
+
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
17
|
+
import { theme } from "./theme/theme.js";
|
|
18
|
+
const BEDROCK_PROVIDER_ID = "amazon-bedrock";
|
|
19
|
+
const BUILT_IN_MODEL_PROVIDERS = new Set(getProviders());
|
|
20
|
+
export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProviderIds = BUILT_IN_MODEL_PROVIDERS) {
|
|
21
|
+
if (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (builtInProviderIds.has(providerId)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return !oauthProviderIds.has(providerId);
|
|
28
|
+
}
|
|
29
|
+
function isUnknownModel(model) {
|
|
30
|
+
return !!model && model.provider === "unknown" && model.id === "unknown" && model.api === "unknown";
|
|
31
|
+
}
|
|
32
|
+
function hasDefaultModelProvider(providerId) {
|
|
33
|
+
return providerId in defaultModelPerProvider;
|
|
34
|
+
}
|
|
35
|
+
export class LoginController {
|
|
36
|
+
deps;
|
|
37
|
+
constructor(deps) {
|
|
38
|
+
this.deps = deps;
|
|
39
|
+
}
|
|
40
|
+
get session() {
|
|
41
|
+
return this.deps.session;
|
|
42
|
+
}
|
|
43
|
+
restoreEditor() {
|
|
44
|
+
const { deps } = this;
|
|
45
|
+
deps.editorContainer.clear();
|
|
46
|
+
deps.editorContainer.addChild(deps.getEditor());
|
|
47
|
+
deps.ui.setFocus(deps.getEditor());
|
|
48
|
+
deps.ui.requestRender();
|
|
49
|
+
}
|
|
50
|
+
getLoginProviderOptions(authType) {
|
|
51
|
+
const authStorage = this.session.modelRegistry.authStorage;
|
|
52
|
+
const oauthProviders = authStorage.getOAuthProviders();
|
|
53
|
+
const oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));
|
|
54
|
+
const options = oauthProviders.map((provider) => ({
|
|
55
|
+
id: provider.id,
|
|
56
|
+
name: provider.name,
|
|
57
|
+
authType: "oauth",
|
|
58
|
+
}));
|
|
59
|
+
const modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));
|
|
60
|
+
for (const providerId of modelProviders) {
|
|
61
|
+
if (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
options.push({
|
|
65
|
+
id: providerId,
|
|
66
|
+
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
67
|
+
authType: "api_key",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;
|
|
71
|
+
return filteredOptions.sort((a, b) => a.name.localeCompare(b.name));
|
|
72
|
+
}
|
|
73
|
+
getLogoutProviderOptions() {
|
|
74
|
+
const authStorage = this.session.modelRegistry.authStorage;
|
|
75
|
+
const options = [];
|
|
76
|
+
for (const providerId of authStorage.list()) {
|
|
77
|
+
const credential = authStorage.get(providerId);
|
|
78
|
+
if (!credential) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
options.push({
|
|
82
|
+
id: providerId,
|
|
83
|
+
name: this.session.modelRegistry.getProviderDisplayName(providerId),
|
|
84
|
+
authType: credential.type,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
88
|
+
}
|
|
89
|
+
showLoginAuthTypeSelector() {
|
|
90
|
+
const subscriptionLabel = "Use a subscription";
|
|
91
|
+
const apiKeyLabel = "Use an API key";
|
|
92
|
+
this.deps.showSelector((done) => {
|
|
93
|
+
const selector = new ExtensionSelectorComponent("Select authentication method:", [subscriptionLabel, apiKeyLabel], (option) => {
|
|
94
|
+
done();
|
|
95
|
+
const authType = option === subscriptionLabel ? "oauth" : "api_key";
|
|
96
|
+
this.showLoginProviderSelector(authType);
|
|
97
|
+
}, () => {
|
|
98
|
+
done();
|
|
99
|
+
this.deps.ui.requestRender();
|
|
100
|
+
});
|
|
101
|
+
return { component: selector, focus: selector };
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
showLoginProviderSelector(authType) {
|
|
105
|
+
const providerOptions = this.getLoginProviderOptions(authType);
|
|
106
|
+
if (providerOptions.length === 0) {
|
|
107
|
+
this.deps.showStatus(authType === "oauth" ? "No subscription providers available." : "No API key providers available.");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.deps.showSelector((done) => {
|
|
111
|
+
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
112
|
+
done();
|
|
113
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
114
|
+
if (!providerOption) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (providerOption.authType === "oauth") {
|
|
118
|
+
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
119
|
+
}
|
|
120
|
+
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
121
|
+
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
125
|
+
}
|
|
126
|
+
}, () => {
|
|
127
|
+
done();
|
|
128
|
+
this.showLoginAuthTypeSelector();
|
|
129
|
+
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId));
|
|
130
|
+
return { component: selector, focus: selector };
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async showOAuthSelector(mode) {
|
|
134
|
+
if (mode === "login") {
|
|
135
|
+
this.showLoginAuthTypeSelector();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const providerOptions = this.getLogoutProviderOptions();
|
|
139
|
+
if (providerOptions.length === 0) {
|
|
140
|
+
this.deps.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.deps.showSelector((done) => {
|
|
144
|
+
const selector = new OAuthSelectorComponent(mode, this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
145
|
+
done();
|
|
146
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
147
|
+
if (!providerOption) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
this.session.modelRegistry.authStorage.logout(providerOption.id);
|
|
152
|
+
this.session.modelRegistry.refresh();
|
|
153
|
+
await this.deps.updateAvailableProviderCount();
|
|
154
|
+
const message = providerOption.authType === "oauth"
|
|
155
|
+
? `Logged out of ${providerOption.name}`
|
|
156
|
+
: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;
|
|
157
|
+
this.deps.showStatus(message);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
this.deps.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
161
|
+
}
|
|
162
|
+
}, () => {
|
|
163
|
+
done();
|
|
164
|
+
this.deps.ui.requestRender();
|
|
165
|
+
});
|
|
166
|
+
return { component: selector, focus: selector };
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
170
|
+
this.session.modelRegistry.refresh();
|
|
171
|
+
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
172
|
+
let selectedModel;
|
|
173
|
+
let selectionError;
|
|
174
|
+
if (isUnknownModel(previousModel)) {
|
|
175
|
+
const availableModels = this.session.modelRegistry.getAvailable();
|
|
176
|
+
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
177
|
+
if (!hasDefaultModelProvider(providerId)) {
|
|
178
|
+
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
179
|
+
}
|
|
180
|
+
else if (providerModels.length === 0) {
|
|
181
|
+
selectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const defaultModelId = defaultModelPerProvider[providerId];
|
|
185
|
+
selectedModel = providerModels.find((model) => model.id === defaultModelId);
|
|
186
|
+
if (!selectedModel) {
|
|
187
|
+
selectionError = `${actionLabel}, but its default model "${defaultModelId}" is not available. Use /model to select a model.`;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
try {
|
|
191
|
+
await this.session.setModel(selectedModel);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
selectedModel = undefined;
|
|
195
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
196
|
+
selectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
await this.deps.updateAvailableProviderCount();
|
|
202
|
+
this.deps.invalidateFooter();
|
|
203
|
+
this.deps.updateEditorBorderColor();
|
|
204
|
+
if (selectedModel) {
|
|
205
|
+
this.deps.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
|
|
206
|
+
void this.deps.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
this.deps.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
|
|
210
|
+
if (selectionError) {
|
|
211
|
+
this.deps.showError(selectionError);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
void this.deps.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
showBedrockSetupDialog(providerId, providerName) {
|
|
219
|
+
const dialog = new LoginDialogComponent(this.deps.ui, providerId, () => this.restoreEditor(), providerName, "Amazon Bedrock setup");
|
|
220
|
+
dialog.showInfo([
|
|
221
|
+
theme.fg("text", "Amazon Bedrock uses AWS credentials instead of a single API key."),
|
|
222
|
+
theme.fg("text", "Configure an AWS profile, IAM keys, bearer token, or role-based credentials."),
|
|
223
|
+
theme.fg("muted", "See:"),
|
|
224
|
+
theme.fg("accent", ` ${path.join(getDocsPath(), "providers.md")}`),
|
|
225
|
+
]);
|
|
226
|
+
this.deps.editorContainer.clear();
|
|
227
|
+
this.deps.editorContainer.addChild(dialog);
|
|
228
|
+
this.deps.ui.setFocus(dialog);
|
|
229
|
+
this.deps.ui.requestRender();
|
|
230
|
+
}
|
|
231
|
+
async showApiKeyLoginDialog(providerId, providerName) {
|
|
232
|
+
const previousModel = this.session.model;
|
|
233
|
+
const dialog = new LoginDialogComponent(this.deps.ui, providerId, (_success, _message) => {
|
|
234
|
+
// Completion handled below
|
|
235
|
+
}, providerName);
|
|
236
|
+
this.deps.editorContainer.clear();
|
|
237
|
+
this.deps.editorContainer.addChild(dialog);
|
|
238
|
+
this.deps.ui.setFocus(dialog);
|
|
239
|
+
this.deps.ui.requestRender();
|
|
240
|
+
try {
|
|
241
|
+
const apiKey = (await dialog.showPrompt("Enter API key:")).trim();
|
|
242
|
+
if (!apiKey) {
|
|
243
|
+
throw new Error("API key cannot be empty.");
|
|
244
|
+
}
|
|
245
|
+
this.session.modelRegistry.authStorage.set(providerId, { type: "api_key", key: apiKey });
|
|
246
|
+
this.restoreEditor();
|
|
247
|
+
await this.completeProviderAuthentication(providerId, providerName, "api_key", previousModel);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
this.restoreEditor();
|
|
251
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
252
|
+
if (errorMsg !== "Login cancelled") {
|
|
253
|
+
this.deps.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
showOAuthLoginSelect(dialog, prompt) {
|
|
258
|
+
return new Promise((resolve) => {
|
|
259
|
+
const restoreDialog = () => {
|
|
260
|
+
this.deps.editorContainer.clear();
|
|
261
|
+
this.deps.editorContainer.addChild(dialog);
|
|
262
|
+
this.deps.ui.setFocus(dialog);
|
|
263
|
+
this.deps.ui.requestRender();
|
|
264
|
+
};
|
|
265
|
+
const labels = prompt.options.map((option) => option.label);
|
|
266
|
+
const selector = new ExtensionSelectorComponent(prompt.message, labels, (optionLabel) => {
|
|
267
|
+
restoreDialog();
|
|
268
|
+
resolve(prompt.options.find((option) => option.label === optionLabel)?.id);
|
|
269
|
+
}, () => {
|
|
270
|
+
restoreDialog();
|
|
271
|
+
resolve(undefined);
|
|
272
|
+
});
|
|
273
|
+
this.deps.editorContainer.clear();
|
|
274
|
+
this.deps.editorContainer.addChild(selector);
|
|
275
|
+
this.deps.ui.setFocus(selector);
|
|
276
|
+
this.deps.ui.requestRender();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
async showLoginDialog(providerId, providerName) {
|
|
280
|
+
const providerInfo = this.session.modelRegistry.authStorage
|
|
281
|
+
.getOAuthProviders()
|
|
282
|
+
.find((provider) => provider.id === providerId);
|
|
283
|
+
const previousModel = this.session.model;
|
|
284
|
+
// Providers that use callback servers (can paste redirect URL)
|
|
285
|
+
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
286
|
+
// Create login dialog component
|
|
287
|
+
const dialog = new LoginDialogComponent(this.deps.ui, providerId, (_success, _message) => {
|
|
288
|
+
// Completion handled below
|
|
289
|
+
}, providerName);
|
|
290
|
+
// Show dialog in editor container
|
|
291
|
+
this.deps.editorContainer.clear();
|
|
292
|
+
this.deps.editorContainer.addChild(dialog);
|
|
293
|
+
this.deps.ui.setFocus(dialog);
|
|
294
|
+
this.deps.ui.requestRender();
|
|
295
|
+
// Promise for manual code input (racing with callback server)
|
|
296
|
+
let manualCodeResolve;
|
|
297
|
+
let manualCodeReject;
|
|
298
|
+
const manualCodePromise = new Promise((resolve, reject) => {
|
|
299
|
+
manualCodeResolve = resolve;
|
|
300
|
+
manualCodeReject = reject;
|
|
301
|
+
});
|
|
302
|
+
try {
|
|
303
|
+
await this.session.modelRegistry.authStorage.login(providerId, {
|
|
304
|
+
onAuth: (info) => {
|
|
305
|
+
dialog.showAuth(info.url, info.instructions);
|
|
306
|
+
if (usesCallbackServer) {
|
|
307
|
+
// Show input for manual paste, racing with callback
|
|
308
|
+
dialog
|
|
309
|
+
.showManualInput("Paste redirect URL below, or complete login in browser:")
|
|
310
|
+
.then((value) => {
|
|
311
|
+
if (value && manualCodeResolve) {
|
|
312
|
+
manualCodeResolve(value);
|
|
313
|
+
manualCodeResolve = undefined;
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
.catch(() => {
|
|
317
|
+
if (manualCodeReject) {
|
|
318
|
+
manualCodeReject(new Error("Login cancelled"));
|
|
319
|
+
manualCodeReject = undefined;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
else if (providerId === "github-copilot") {
|
|
324
|
+
// GitHub Copilot polls after onAuth
|
|
325
|
+
dialog.showWaiting("Waiting for browser authentication...");
|
|
326
|
+
}
|
|
327
|
+
// For Anthropic: onPrompt is called immediately after
|
|
328
|
+
},
|
|
329
|
+
onPrompt: async (prompt) => {
|
|
330
|
+
return dialog.showPrompt(prompt.message, prompt.placeholder);
|
|
331
|
+
},
|
|
332
|
+
onProgress: (message) => {
|
|
333
|
+
dialog.showProgress(message);
|
|
334
|
+
},
|
|
335
|
+
onSelect: (prompt) => this.showOAuthLoginSelect(dialog, prompt),
|
|
336
|
+
onManualCodeInput: () => manualCodePromise,
|
|
337
|
+
signal: dialog.signal,
|
|
338
|
+
});
|
|
339
|
+
// Success
|
|
340
|
+
this.restoreEditor();
|
|
341
|
+
await this.completeProviderAuthentication(providerId, providerName, "oauth", previousModel);
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
this.restoreEditor();
|
|
345
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
346
|
+
if (errorMsg !== "Login cancelled") {
|
|
347
|
+
this.deps.showError(`Failed to login to ${providerName}: ${errorMsg}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=login-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-controller.js","sourceRoot":"","sources":["../../../src/modes/interactive/login-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,YAAY,EAA4D,MAAM,yBAAyB,CAAC;AAEjH,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,+BAA+B,EAAE,MAAM,sCAAsC,CAAC;AACvF,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAA6B,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACnG,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAE7C,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAS,YAAY,EAAE,CAAC,CAAC;AAEjE,MAAM,UAAU,qBAAqB,CACpC,UAAkB,EAClB,gBAAqC,EACrC,kBAAkB,GAAwB,wBAAwB,EACxD;IACV,IAAI,+BAA+B,CAAC,UAAU,CAAC,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC;IACb,CAAC;IACD,IAAI,kBAAkB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AAAA,CACzC;AAED,SAAS,cAAc,CAAC,KAA6B,EAAW;IAC/D,OAAO,CAAC,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,EAAE,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,CAAC;AAAA,CACpG;AAED,SAAS,uBAAuB,CAAC,UAAkB,EAAsD;IACxG,OAAO,UAAU,IAAI,uBAAuB,CAAC;AAAA,CAC7C;AAqBD,MAAM,OAAO,eAAe;IACE,IAAI;IAAjC,YAA6B,IAAyB,EAAE;oBAA3B,IAAI;IAAwB,CAAC;IAE1D,IAAY,OAAO,GAAiB;QACnC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;IAAA,CACzB;IAEO,aAAa,GAAS;QAC7B,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;IAAA,CACxB;IAEO,uBAAuB,CAAC,QAA8B,EAA0B;QACvF,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC;QAC3D,MAAM,cAAc,GAAG,WAAW,CAAC,iBAAiB,EAAE,CAAC;QACvD,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QAChF,MAAM,OAAO,GAA2B,cAAc,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACzE,EAAE,EAAE,QAAQ,CAAC,EAAE;YACf,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,QAAQ,EAAE,OAAO;SACjB,CAAC,CAAC,CAAC;QAEJ,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;QACnG,KAAK,MAAM,UAAU,IAAI,cAAc,EAAE,CAAC;YACzC,IAAI,CAAC,qBAAqB,CAAC,UAAU,EAAE,gBAAgB,CAAC,EAAE,CAAC;gBAC1D,SAAS;YACV,CAAC;YACD,OAAO,CAAC,IAAI,CAAC;gBACZ,EAAE,EAAE,UAAU;gBACd,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,sBAAsB,CAAC,UAAU,CAAC;gBACnE,QAAQ,EAAE,SAAS;aACnB,CAAC,CAAC;QACJ,CAAC;QAED,MAAM,eAAe,GAAG,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QACtG,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAAA,CACpE;IAEO,wBAAwB,GAA2B;QAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC;QAC3D,MAAM,OAAO,GAA2B,EAAE,CAAC;QAE3C,KAAK,MAAM,UAAU,IAAI,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7C,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC/C,IAAI,CAAC,UAAU,EAAE,CAAC;gBACjB,SAAS;YACV,CAAC;YACD,OAAO,CAAC,IAAI,CAAC;gBACZ,EAAE,EAAE,UAAU;gBACd,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,sBAAsB,CAAC,UAAU,CAAC;gBACnE,QAAQ,EAAE,UAAU,CAAC,IAAI;aACzB,CAAC,CAAC;QACJ,CAAC;QAED,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAAA,CAC5D;IAEO,yBAAyB,GAAS;QACzC,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;QAC/C,MAAM,WAAW,GAAG,gBAAgB,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAChC,MAAM,QAAQ,GAAG,IAAI,0BAA0B,CAC9C,+BAA+B,EAC/B,CAAC,iBAAiB,EAAE,WAAW,CAAC,EAChC,CAAC,MAAM,EAAE,EAAE,CAAC;gBACX,IAAI,EAAE,CAAC;gBACP,MAAM,QAAQ,GAAG,MAAM,KAAK,iBAAiB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;gBACpE,IAAI,CAAC,yBAAyB,CAAC,QAAQ,CAAC,CAAC;YAAA,CACzC,EACD,GAAG,EAAE,CAAC;gBACL,IAAI,EAAE,CAAC;gBACP,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;YAAA,CAC7B,CACD,CAAC;YACF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QAAA,CAChD,CAAC,CAAC;IAAA,CACH;IAEO,yBAAyB,CAAC,QAA6B,EAAQ;QACtE,MAAM,eAAe,GAAG,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QAC/D,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,UAAU,CACnB,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,sCAAsC,CAAC,CAAC,CAAC,iCAAiC,CACjG,CAAC;YACF,OAAO;QACR,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAChC,MAAM,QAAQ,GAAG,IAAI,sBAAsB,CAC1C,OAAO,EACP,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,EACtC,eAAe,EACf,KAAK,EAAE,UAAkB,EAAE,EAAE,CAAC;gBAC7B,IAAI,EAAE,CAAC;gBAEP,MAAM,cAAc,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC;gBACtF,IAAI,CAAC,cAAc,EAAE,CAAC;oBACrB,OAAO;gBACR,CAAC;gBAED,IAAI,cAAc,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;oBACzC,MAAM,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,EAAE,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC;gBACpE,CAAC;qBAAM,IAAI,cAAc,CAAC,EAAE,KAAK,mBAAmB,EAAE,CAAC;oBACtD,IAAI,CAAC,sBAAsB,CAAC,cAAc,CAAC,EAAE,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC;gBACrE,CAAC;qBAAM,CAAC;oBACP,MAAM,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,EAAE,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC;gBAC1E,CAAC;YAAA,CACD,EACD,GAAG,EAAE,CAAC;gBACL,IAAI,EAAE,CAAC;gBACP,IAAI,CAAC,yBAAyB,EAAE,CAAC;YAAA,CACjC,EACD,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAC5E,CAAC;YACF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QAAA,CAChD,CAAC,CAAC;IAAA,CACH;IAED,KAAK,CAAC,iBAAiB,CAAC,IAAwB,EAAiB;QAChE,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,yBAAyB,EAAE,CAAC;YACjC,OAAO;QACR,CAAC;QAED,MAAM,eAAe,GAAG,IAAI,CAAC,wBAAwB,EAAE,CAAC;QACxD,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,UAAU,CACnB,gJAAgJ,CAChJ,CAAC;YACF,OAAO;QACR,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAChC,MAAM,QAAQ,GAAG,IAAI,sBAAsB,CAC1C,IAAI,EACJ,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,EACtC,eAAe,EACf,KAAK,EAAE,UAAkB,EAAE,EAAE,CAAC;gBAC7B,IAAI,EAAE,CAAC;gBAEP,MAAM,cAAc,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC;gBACtF,IAAI,CAAC,cAAc,EAAE,CAAC;oBACrB,OAAO;gBACR,CAAC;gBAED,IAAI,CAAC;oBACJ,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;oBACjE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;oBACrC,MAAM,IAAI,CAAC,IAAI,CAAC,4BAA4B,EAAE,CAAC;oBAC/C,MAAM,OAAO,GACZ,cAAc,CAAC,QAAQ,KAAK,OAAO;wBAClC,CAAC,CAAC,iBAAiB,cAAc,CAAC,IAAI,EAAE;wBACxC,CAAC,CAAC,8BAA8B,cAAc,CAAC,IAAI,+DAA+D,CAAC;oBACrH,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;gBAC/B,CAAC;gBAAC,OAAO,KAAc,EAAE,CAAC;oBACzB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,kBAAkB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBACjG,CAAC;YAAA,CACD,EACD,GAAG,EAAE,CAAC;gBACL,IAAI,EAAE,CAAC;gBACP,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;YAAA,CAC7B,CACD,CAAC;YACF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;QAAA,CAChD,CAAC,CAAC;IAAA,CACH;IAEO,KAAK,CAAC,8BAA8B,CAC3C,UAAkB,EAClB,YAAoB,EACpB,QAA6B,EAC7B,aAAqC,EACrB;QAChB,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;QAErC,MAAM,WAAW,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,gBAAgB,YAAY,EAAE,CAAC,CAAC,CAAC,qBAAqB,YAAY,EAAE,CAAC;QAEhH,IAAI,aAAqC,CAAC;QAC1C,IAAI,cAAkC,CAAC;QACvC,IAAI,cAAc,CAAC,aAAa,CAAC,EAAE,CAAC;YACnC,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC;YAClE,MAAM,cAAc,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;YACxF,IAAI,CAAC,uBAAuB,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC1C,cAAc,GAAG,GAAG,WAAW,sDAAsD,UAAU,kCAAkC,CAAC;YACnI,CAAC;iBAAM,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxC,cAAc,GAAG,GAAG,WAAW,gFAAgF,CAAC;YACjH,CAAC;iBAAM,CAAC;gBACP,MAAM,cAAc,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC;gBAC3D,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;gBAC5E,IAAI,CAAC,aAAa,EAAE,CAAC;oBACpB,cAAc,GAAG,GAAG,WAAW,4BAA4B,cAAc,mDAAmD,CAAC;gBAC9H,CAAC;qBAAM,CAAC;oBACP,IAAI,CAAC;wBACJ,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;oBAC5C,CAAC;oBAAC,OAAO,KAAc,EAAE,CAAC;wBACzB,aAAa,GAAG,SAAS,CAAC;wBAC1B,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;wBAC5E,cAAc,GAAG,GAAG,WAAW,6CAA6C,YAAY,iCAAiC,CAAC;oBAC3H,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QAED,MAAM,IAAI,CAAC,IAAI,CAAC,4BAA4B,EAAE,CAAC;QAC/C,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,uBAAuB,EAAE,CAAC;QACpC,IAAI,aAAa,EAAE,CAAC;YACnB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,WAAW,cAAc,aAAa,CAAC,EAAE,0BAA0B,WAAW,EAAE,EAAE,CAAC,CAAC;YAC5G,KAAK,IAAI,CAAC,IAAI,CAAC,uCAAuC,CAAC,aAAa,CAAC,CAAC;QACvE,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,WAAW,0BAA0B,WAAW,EAAE,EAAE,CAAC,CAAC;YAC9E,IAAI,cAAc,EAAE,CAAC;gBACpB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;YACrC,CAAC;iBAAM,CAAC;gBACP,KAAK,IAAI,CAAC,IAAI,CAAC,uCAAuC,EAAE,CAAC;YAC1D,CAAC;QACF,CAAC;IAAA,CACD;IAEO,sBAAsB,CAAC,UAAkB,EAAE,YAAoB,EAAQ;QAC9E,MAAM,MAAM,GAAG,IAAI,oBAAoB,CACtC,IAAI,CAAC,IAAI,CAAC,EAAE,EACZ,UAAU,EACV,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,EAC1B,YAAY,EACZ,sBAAsB,CACtB,CAAC;QACF,MAAM,CAAC,QAAQ,CAAC;YACf,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,kEAAkE,CAAC;YACpF,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,8EAA8E,CAAC;YAChG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;YACzB,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,cAAc,CAAC,EAAE,CAAC;SACnE,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;IAAA,CAC7B;IAEO,KAAK,CAAC,qBAAqB,CAAC,UAAkB,EAAE,YAAoB,EAAiB;QAC5F,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAEzC,MAAM,MAAM,GAAG,IAAI,oBAAoB,CACtC,IAAI,CAAC,IAAI,CAAC,EAAE,EACZ,UAAU,EACV,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC;YACvB,2BAA2B;QADH,CAExB,EACD,YAAY,CACZ,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QAE7B,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAClE,IAAI,CAAC,MAAM,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;YAC7C,CAAC;YAED,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;YAEzF,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,MAAM,IAAI,CAAC,8BAA8B,CAAC,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;QAC/F,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACzB,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,MAAM,QAAQ,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACxE,IAAI,QAAQ,KAAK,iBAAiB,EAAE,CAAC;gBACpC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,8BAA8B,YAAY,KAAK,QAAQ,EAAE,CAAC,CAAC;YAChF,CAAC;QACF,CAAC;IAAA,CACD;IAEO,oBAAoB,CAAC,MAA4B,EAAE,MAAyB,EAA+B;QAClH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YAC/B,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC;gBAC3B,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;gBAClC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAC3C,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAC9B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;YAAA,CAC7B,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5D,MAAM,QAAQ,GAAG,IAAI,0BAA0B,CAC9C,MAAM,CAAC,OAAO,EACd,MAAM,EACN,CAAC,WAAW,EAAE,EAAE,CAAC;gBAChB,aAAa,EAAE,CAAC;gBAChB,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;YAAA,CAC3E,EACD,GAAG,EAAE,CAAC;gBACL,aAAa,EAAE,CAAC;gBAChB,OAAO,CAAC,SAAS,CAAC,CAAC;YAAA,CACnB,CACD,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC7C,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAChC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QAAA,CAC7B,CAAC,CAAC;IAAA,CACH;IAEO,KAAK,CAAC,eAAe,CAAC,UAAkB,EAAE,YAAoB,EAAiB;QACtF,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW;aACzD,iBAAiB,EAAE;aACnB,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC;QACjD,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAEzC,+DAA+D;QAC/D,MAAM,kBAAkB,GAAG,YAAY,EAAE,kBAAkB,IAAI,KAAK,CAAC;QAErE,gCAAgC;QAChC,MAAM,MAAM,GAAG,IAAI,oBAAoB,CACtC,IAAI,CAAC,IAAI,CAAC,EAAE,EACZ,UAAU,EACV,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC;YACvB,2BAA2B;QADH,CAExB,EACD,YAAY,CACZ,CAAC;QAEF,kCAAkC;QAClC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC;QAE7B,8DAA8D;QAC9D,IAAI,iBAAuD,CAAC;QAC5D,IAAI,gBAAoD,CAAC;QACzD,MAAM,iBAAiB,GAAG,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;YAClE,iBAAiB,GAAG,OAAO,CAAC;YAC5B,gBAAgB,GAAG,MAAM,CAAC;QAAA,CAC1B,CAAC,CAAC;QAEH,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC,KAAK,CAAC,UAA6B,EAAE;gBACjF,MAAM,EAAE,CAAC,IAA4C,EAAE,EAAE,CAAC;oBACzD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;oBAE7C,IAAI,kBAAkB,EAAE,CAAC;wBACxB,oDAAoD;wBACpD,MAAM;6BACJ,eAAe,CAAC,yDAAyD,CAAC;6BAC1E,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;4BAChB,IAAI,KAAK,IAAI,iBAAiB,EAAE,CAAC;gCAChC,iBAAiB,CAAC,KAAK,CAAC,CAAC;gCACzB,iBAAiB,GAAG,SAAS,CAAC;4BAC/B,CAAC;wBAAA,CACD,CAAC;6BACD,KAAK,CAAC,GAAG,EAAE,CAAC;4BACZ,IAAI,gBAAgB,EAAE,CAAC;gCACtB,gBAAgB,CAAC,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;gCAC/C,gBAAgB,GAAG,SAAS,CAAC;4BAC9B,CAAC;wBAAA,CACD,CAAC,CAAC;oBACL,CAAC;yBAAM,IAAI,UAAU,KAAK,gBAAgB,EAAE,CAAC;wBAC5C,oCAAoC;wBACpC,MAAM,CAAC,WAAW,CAAC,uCAAuC,CAAC,CAAC;oBAC7D,CAAC;oBACD,sDAAsD;gBADrD,CAED;gBAED,QAAQ,EAAE,KAAK,EAAE,MAAiD,EAAE,EAAE,CAAC;oBACtE,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;gBAAA,CAC7D;gBAED,UAAU,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC;oBAChC,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;gBAAA,CAC7B;gBAED,QAAQ,EAAE,CAAC,MAAyB,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC;gBAElF,iBAAiB,EAAE,GAAG,EAAE,CAAC,iBAAiB;gBAE1C,MAAM,EAAE,MAAM,CAAC,MAAM;aACrB,CAAC,CAAC;YAEH,UAAU;YACV,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,MAAM,IAAI,CAAC,8BAA8B,CAAC,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QAC7F,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACzB,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,MAAM,QAAQ,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACxE,IAAI,QAAQ,KAAK,iBAAiB,EAAE,CAAC;gBACpC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,sBAAsB,YAAY,KAAK,QAAQ,EAAE,CAAC,CAAC;YACxE,CAAC;QACF,CAAC;IAAA,CACD;CACD","sourcesContent":["/**\n * Auth / login flows for the interactive mode (/login, /logout).\n *\n * Owns the provider auth-type selector, the OAuth and API-key login dialogs,\n * the Bedrock setup notice, and post-login model selection. Extracted from\n * interactive-mode.ts behind a narrow LoginControllerDeps interface; the\n * `/login` and `/logout` commands call `showOAuthSelector`.\n */\n\nimport * as path from \"node:path\";\nimport { getProviders, type Model, type OAuthProviderId, type OAuthSelectPrompt } from \"@kolisachint/hoocode-ai\";\nimport type { Component, Container, TUI } from \"@kolisachint/hoocode-tui\";\nimport { getAuthPath, getDocsPath } from \"../../config.js\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport { defaultModelPerProvider } from \"../../core/model-resolver.js\";\nimport { BUILT_IN_PROVIDER_DISPLAY_NAMES } from \"../../core/provider-display-names.js\";\nimport { ExtensionSelectorComponent } from \"./components/extension-selector.js\";\nimport { LoginDialogComponent } from \"./components/login-dialog.js\";\nimport { type AuthSelectorProvider, OAuthSelectorComponent } from \"./components/oauth-selector.js\";\nimport { theme } from \"./theme/theme.js\";\n\nconst BEDROCK_PROVIDER_ID = \"amazon-bedrock\";\n\nconst BUILT_IN_MODEL_PROVIDERS = new Set<string>(getProviders());\n\nexport function isApiKeyLoginProvider(\n\tproviderId: string,\n\toauthProviderIds: ReadonlySet<string>,\n\tbuiltInProviderIds: ReadonlySet<string> = BUILT_IN_MODEL_PROVIDERS,\n): boolean {\n\tif (BUILT_IN_PROVIDER_DISPLAY_NAMES[providerId]) {\n\t\treturn true;\n\t}\n\tif (builtInProviderIds.has(providerId)) {\n\t\treturn false;\n\t}\n\treturn !oauthProviderIds.has(providerId);\n}\n\nfunction isUnknownModel(model: Model<any> | undefined): boolean {\n\treturn !!model && model.provider === \"unknown\" && model.id === \"unknown\" && model.api === \"unknown\";\n}\n\nfunction hasDefaultModelProvider(providerId: string): providerId is keyof typeof defaultModelPerProvider {\n\treturn providerId in defaultModelPerProvider;\n}\n\n/** The slice of the interactive mode the login flows need. */\nexport interface LoginControllerDeps {\n\tui: TUI;\n\t/** The active session (read at call time; the session can be swapped). */\n\tget session(): AgentSession;\n\t/** The prompt editor (read at call time; the editor can be swapped). */\n\tgetEditor(): Component;\n\t/** Container that hosts the prompt editor and login dialogs. */\n\teditorContainer: Container;\n\tshowSelector(create: (done: () => void) => { component: Component; focus: Component }): void;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n\t/** Recompute the footer's available provider count. */\n\tupdateAvailableProviderCount(): Promise<void>;\n\tupdateEditorBorderColor(): void;\n\tinvalidateFooter(): void;\n\tmaybeWarnAboutAnthropicSubscriptionAuth(model?: Model<any>): Promise<void>;\n}\n\nexport class LoginController {\n\tconstructor(private readonly deps: LoginControllerDeps) {}\n\n\tprivate get session(): AgentSession {\n\t\treturn this.deps.session;\n\t}\n\n\tprivate restoreEditor(): void {\n\t\tconst { deps } = this;\n\t\tdeps.editorContainer.clear();\n\t\tdeps.editorContainer.addChild(deps.getEditor());\n\t\tdeps.ui.setFocus(deps.getEditor());\n\t\tdeps.ui.requestRender();\n\t}\n\n\tprivate getLoginProviderOptions(authType?: \"oauth\" | \"api_key\"): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst oauthProviders = authStorage.getOAuthProviders();\n\t\tconst oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));\n\t\tconst options: AuthSelectorProvider[] = oauthProviders.map((provider) => ({\n\t\t\tid: provider.id,\n\t\t\tname: provider.name,\n\t\t\tauthType: \"oauth\",\n\t\t}));\n\n\t\tconst modelProviders = new Set(this.session.modelRegistry.getAll().map((model) => model.provider));\n\t\tfor (const providerId of modelProviders) {\n\t\t\tif (!isApiKeyLoginProvider(providerId, oauthProviderIds)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: \"api_key\",\n\t\t\t});\n\t\t}\n\n\t\tconst filteredOptions = authType ? options.filter((option) => option.authType === authType) : options;\n\t\treturn filteredOptions.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate getLogoutProviderOptions(): AuthSelectorProvider[] {\n\t\tconst authStorage = this.session.modelRegistry.authStorage;\n\t\tconst options: AuthSelectorProvider[] = [];\n\n\t\tfor (const providerId of authStorage.list()) {\n\t\t\tconst credential = authStorage.get(providerId);\n\t\t\tif (!credential) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toptions.push({\n\t\t\t\tid: providerId,\n\t\t\t\tname: this.session.modelRegistry.getProviderDisplayName(providerId),\n\t\t\t\tauthType: credential.type,\n\t\t\t});\n\t\t}\n\n\t\treturn options.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tprivate showLoginAuthTypeSelector(): void {\n\t\tconst subscriptionLabel = \"Use a subscription\";\n\t\tconst apiKeyLabel = \"Use an API key\";\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\t\"Select authentication method:\",\n\t\t\t\t[subscriptionLabel, apiKeyLabel],\n\t\t\t\t(option) => {\n\t\t\t\t\tdone();\n\t\t\t\t\tconst authType = option === subscriptionLabel ? \"oauth\" : \"api_key\";\n\t\t\t\t\tthis.showLoginProviderSelector(authType);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate showLoginProviderSelector(authType: \"oauth\" | \"api_key\"): void {\n\t\tconst providerOptions = this.getLoginProviderOptions(authType);\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\tauthType === \"oauth\" ? \"No subscription providers available.\" : \"No API key providers available.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\t\"login\",\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (providerOption.authType === \"oauth\") {\n\t\t\t\t\t\tawait this.showLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t} else if (providerOption.id === BEDROCK_PROVIDER_ID) {\n\t\t\t\t\t\tthis.showBedrockSetupDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait this.showApiKeyLoginDialog(providerOption.id, providerOption.name);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\t\t},\n\t\t\t\t(providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId),\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tasync showOAuthSelector(mode: \"login\" | \"logout\"): Promise<void> {\n\t\tif (mode === \"login\") {\n\t\t\tthis.showLoginAuthTypeSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tconst providerOptions = this.getLogoutProviderOptions();\n\t\tif (providerOptions.length === 0) {\n\t\t\tthis.deps.showStatus(\n\t\t\t\t\"No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.deps.showSelector((done) => {\n\t\t\tconst selector = new OAuthSelectorComponent(\n\t\t\t\tmode,\n\t\t\t\tthis.session.modelRegistry.authStorage,\n\t\t\t\tproviderOptions,\n\t\t\t\tasync (providerId: string) => {\n\t\t\t\t\tdone();\n\n\t\t\t\t\tconst providerOption = providerOptions.find((provider) => provider.id === providerId);\n\t\t\t\t\tif (!providerOption) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.session.modelRegistry.authStorage.logout(providerOption.id);\n\t\t\t\t\t\tthis.session.modelRegistry.refresh();\n\t\t\t\t\t\tawait this.deps.updateAvailableProviderCount();\n\t\t\t\t\t\tconst message =\n\t\t\t\t\t\t\tproviderOption.authType === \"oauth\"\n\t\t\t\t\t\t\t\t? `Logged out of ${providerOption.name}`\n\t\t\t\t\t\t\t\t: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;\n\t\t\t\t\t\tthis.deps.showStatus(message);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tthis.deps.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\tdone();\n\t\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn { component: selector, focus: selector };\n\t\t});\n\t}\n\n\tprivate async completeProviderAuthentication(\n\t\tproviderId: string,\n\t\tproviderName: string,\n\t\tauthType: \"oauth\" | \"api_key\",\n\t\tpreviousModel: Model<any> | undefined,\n\t): Promise<void> {\n\t\tthis.session.modelRegistry.refresh();\n\n\t\tconst actionLabel = authType === \"oauth\" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;\n\n\t\tlet selectedModel: Model<any> | undefined;\n\t\tlet selectionError: string | undefined;\n\t\tif (isUnknownModel(previousModel)) {\n\t\t\tconst availableModels = this.session.modelRegistry.getAvailable();\n\t\t\tconst providerModels = availableModels.filter((model) => model.provider === providerId);\n\t\t\tif (!hasDefaultModelProvider(providerId)) {\n\t\t\t\tselectionError = `${actionLabel}, but no default model is configured for provider \"${providerId}\". Use /model to select a model.`;\n\t\t\t} else if (providerModels.length === 0) {\n\t\t\t\tselectionError = `${actionLabel}, but no models are available for that provider. Use /model to select a model.`;\n\t\t\t} else {\n\t\t\t\tconst defaultModelId = defaultModelPerProvider[providerId];\n\t\t\t\tselectedModel = providerModels.find((model) => model.id === defaultModelId);\n\t\t\t\tif (!selectedModel) {\n\t\t\t\t\tselectionError = `${actionLabel}, but its default model \"${defaultModelId}\" is not available. Use /model to select a model.`;\n\t\t\t\t} else {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.session.setModel(selectedModel);\n\t\t\t\t\t} catch (error: unknown) {\n\t\t\t\t\t\tselectedModel = undefined;\n\t\t\t\t\t\tconst errorMessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\tselectionError = `${actionLabel}, but selecting its default model failed: ${errorMessage}. Use /model to select a model.`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tawait this.deps.updateAvailableProviderCount();\n\t\tthis.deps.invalidateFooter();\n\t\tthis.deps.updateEditorBorderColor();\n\t\tif (selectedModel) {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);\n\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);\n\t\t} else {\n\t\t\tthis.deps.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);\n\t\t\tif (selectionError) {\n\t\t\t\tthis.deps.showError(selectionError);\n\t\t\t} else {\n\t\t\t\tvoid this.deps.maybeWarnAboutAnthropicSubscriptionAuth();\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate showBedrockSetupDialog(providerId: string, providerName: string): void {\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t() => this.restoreEditor(),\n\t\t\tproviderName,\n\t\t\t\"Amazon Bedrock setup\",\n\t\t);\n\t\tdialog.showInfo([\n\t\t\ttheme.fg(\"text\", \"Amazon Bedrock uses AWS credentials instead of a single API key.\"),\n\t\t\ttheme.fg(\"text\", \"Configure an AWS profile, IAM keys, bearer token, or role-based credentials.\"),\n\t\t\ttheme.fg(\"muted\", \"See:\"),\n\t\t\ttheme.fg(\"accent\", ` ${path.join(getDocsPath(), \"providers.md\")}`),\n\t\t]);\n\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\t}\n\n\tprivate async showApiKeyLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst previousModel = this.session.model;\n\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\ttry {\n\t\t\tconst apiKey = (await dialog.showPrompt(\"Enter API key:\")).trim();\n\t\t\tif (!apiKey) {\n\t\t\t\tthrow new Error(\"API key cannot be empty.\");\n\t\t\t}\n\n\t\t\tthis.session.modelRegistry.authStorage.set(providerId, { type: \"api_key\", key: apiKey });\n\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"api_key\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate showOAuthLoginSelect(dialog: LoginDialogComponent, prompt: OAuthSelectPrompt): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst restoreDialog = () => {\n\t\t\t\tthis.deps.editorContainer.clear();\n\t\t\t\tthis.deps.editorContainer.addChild(dialog);\n\t\t\t\tthis.deps.ui.setFocus(dialog);\n\t\t\t\tthis.deps.ui.requestRender();\n\t\t\t};\n\t\t\tconst labels = prompt.options.map((option) => option.label);\n\t\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\t\tprompt.message,\n\t\t\t\tlabels,\n\t\t\t\t(optionLabel) => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(prompt.options.find((option) => option.label === optionLabel)?.id);\n\t\t\t\t},\n\t\t\t\t() => {\n\t\t\t\t\trestoreDialog();\n\t\t\t\t\tresolve(undefined);\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.deps.editorContainer.clear();\n\t\t\tthis.deps.editorContainer.addChild(selector);\n\t\t\tthis.deps.ui.setFocus(selector);\n\t\t\tthis.deps.ui.requestRender();\n\t\t});\n\t}\n\n\tprivate async showLoginDialog(providerId: string, providerName: string): Promise<void> {\n\t\tconst providerInfo = this.session.modelRegistry.authStorage\n\t\t\t.getOAuthProviders()\n\t\t\t.find((provider) => provider.id === providerId);\n\t\tconst previousModel = this.session.model;\n\n\t\t// Providers that use callback servers (can paste redirect URL)\n\t\tconst usesCallbackServer = providerInfo?.usesCallbackServer ?? false;\n\n\t\t// Create login dialog component\n\t\tconst dialog = new LoginDialogComponent(\n\t\t\tthis.deps.ui,\n\t\t\tproviderId,\n\t\t\t(_success, _message) => {\n\t\t\t\t// Completion handled below\n\t\t\t},\n\t\t\tproviderName,\n\t\t);\n\n\t\t// Show dialog in editor container\n\t\tthis.deps.editorContainer.clear();\n\t\tthis.deps.editorContainer.addChild(dialog);\n\t\tthis.deps.ui.setFocus(dialog);\n\t\tthis.deps.ui.requestRender();\n\n\t\t// Promise for manual code input (racing with callback server)\n\t\tlet manualCodeResolve: ((code: string) => void) | undefined;\n\t\tlet manualCodeReject: ((err: Error) => void) | undefined;\n\t\tconst manualCodePromise = new Promise<string>((resolve, reject) => {\n\t\t\tmanualCodeResolve = resolve;\n\t\t\tmanualCodeReject = reject;\n\t\t});\n\n\t\ttry {\n\t\t\tawait this.session.modelRegistry.authStorage.login(providerId as OAuthProviderId, {\n\t\t\t\tonAuth: (info: { url: string; instructions?: string }) => {\n\t\t\t\t\tdialog.showAuth(info.url, info.instructions);\n\n\t\t\t\t\tif (usesCallbackServer) {\n\t\t\t\t\t\t// Show input for manual paste, racing with callback\n\t\t\t\t\t\tdialog\n\t\t\t\t\t\t\t.showManualInput(\"Paste redirect URL below, or complete login in browser:\")\n\t\t\t\t\t\t\t.then((value) => {\n\t\t\t\t\t\t\t\tif (value && manualCodeResolve) {\n\t\t\t\t\t\t\t\t\tmanualCodeResolve(value);\n\t\t\t\t\t\t\t\t\tmanualCodeResolve = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\t\t\tif (manualCodeReject) {\n\t\t\t\t\t\t\t\t\tmanualCodeReject(new Error(\"Login cancelled\"));\n\t\t\t\t\t\t\t\t\tmanualCodeReject = undefined;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t} else if (providerId === \"github-copilot\") {\n\t\t\t\t\t\t// GitHub Copilot polls after onAuth\n\t\t\t\t\t\tdialog.showWaiting(\"Waiting for browser authentication...\");\n\t\t\t\t\t}\n\t\t\t\t\t// For Anthropic: onPrompt is called immediately after\n\t\t\t\t},\n\n\t\t\t\tonPrompt: async (prompt: { message: string; placeholder?: string }) => {\n\t\t\t\t\treturn dialog.showPrompt(prompt.message, prompt.placeholder);\n\t\t\t\t},\n\n\t\t\t\tonProgress: (message: string) => {\n\t\t\t\t\tdialog.showProgress(message);\n\t\t\t\t},\n\n\t\t\t\tonSelect: (prompt: OAuthSelectPrompt) => this.showOAuthLoginSelect(dialog, prompt),\n\n\t\t\t\tonManualCodeInput: () => manualCodePromise,\n\n\t\t\t\tsignal: dialog.signal,\n\t\t\t});\n\n\t\t\t// Success\n\t\t\tthis.restoreEditor();\n\t\t\tawait this.completeProviderAuthentication(providerId, providerName, \"oauth\", previousModel);\n\t\t} catch (error: unknown) {\n\t\t\tthis.restoreEditor();\n\t\t\tconst errorMsg = error instanceof Error ? error.message : String(error);\n\t\t\tif (errorMsg !== \"Login cancelled\") {\n\t\t\t\tthis.deps.showError(`Failed to login to ${providerName}: ${errorMsg}`);\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message queueing for the interactive mode.
|
|
3
|
+
*
|
|
4
|
+
* Owns the compaction queue (messages typed while a compaction is running) and
|
|
5
|
+
* the pending-messages display above the editor. The session owns the live
|
|
6
|
+
* steering / follow-up queues; this controller merges them with the compaction
|
|
7
|
+
* queue for display, restores everything back into the editor on demand, and
|
|
8
|
+
* flushes the compaction queue once compaction finishes. Extracted from
|
|
9
|
+
* interactive-mode.ts behind a narrow MessageQueueControllerDeps interface.
|
|
10
|
+
*/
|
|
11
|
+
import { type Container, type EditorComponent } from "@kolisachint/hoocode-tui";
|
|
12
|
+
import type { AgentSession } from "../../core/agent-session.js";
|
|
13
|
+
/** The slice of the interactive mode the message queue needs. */
|
|
14
|
+
export interface MessageQueueControllerDeps {
|
|
15
|
+
/** The active session (read at call time; the session can be swapped). */
|
|
16
|
+
get session(): AgentSession;
|
|
17
|
+
/** The prompt editor (read at call time; the editor can be swapped). */
|
|
18
|
+
getEditor(): EditorComponent;
|
|
19
|
+
/** Container that renders the queued-message list above the editor. */
|
|
20
|
+
pendingMessagesContainer: Container;
|
|
21
|
+
showStatus(message: string): void;
|
|
22
|
+
showError(errorMessage: string): void;
|
|
23
|
+
}
|
|
24
|
+
export declare class MessageQueueController {
|
|
25
|
+
private readonly deps;
|
|
26
|
+
private compactionQueuedMessages;
|
|
27
|
+
constructor(deps: MessageQueueControllerDeps);
|
|
28
|
+
private get session();
|
|
29
|
+
private get editor();
|
|
30
|
+
/** Drop the compaction queue without restoring it (session switch/reset). */
|
|
31
|
+
resetCompactionQueue(): void;
|
|
32
|
+
private getAllQueuedMessages;
|
|
33
|
+
/**
|
|
34
|
+
* Clear all queued messages and return their contents.
|
|
35
|
+
* Clears both session queue and compaction queue.
|
|
36
|
+
*/
|
|
37
|
+
private clearAllQueues;
|
|
38
|
+
updatePendingMessagesDisplay(): void;
|
|
39
|
+
restoreQueuedMessagesToEditor(options?: {
|
|
40
|
+
abort?: boolean;
|
|
41
|
+
currentText?: string;
|
|
42
|
+
}): number;
|
|
43
|
+
queueCompactionMessage(text: string, mode: "steer" | "followUp"): void;
|
|
44
|
+
isExtensionCommand(text: string): boolean;
|
|
45
|
+
flushCompactionQueue(options?: {
|
|
46
|
+
willRetry?: boolean;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=message-queue-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-queue-controller.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/message-queue-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAyB,MAAM,0BAA0B,CAAC;AACvG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAShE,iEAAiE;AACjE,MAAM,WAAW,0BAA0B;IAC1C,0EAA0E;IAC1E,IAAI,OAAO,IAAI,YAAY,CAAC;IAC5B,wEAAwE;IACxE,SAAS,IAAI,eAAe,CAAC;IAC7B,uEAAuE;IACvE,wBAAwB,EAAE,SAAS,CAAC;IACpC,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAED,qBAAa,sBAAsB;IAItB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,wBAAwB,CAAiC;IAEjE,YAA6B,IAAI,EAAE,0BAA0B,EAAI;IAEjE,OAAO,KAAK,OAAO,GAElB;IAED,OAAO,KAAK,MAAM,GAEjB;IAED,6EAA6E;IAC7E,oBAAoB,IAAI,IAAI,CAE3B;IAED,OAAO,CAAC,oBAAoB;IAa5B;;;OAGG;IACH,OAAO,CAAC,cAAc;IAetB,4BAA4B,IAAI,IAAI,CAiBnC;IAED,6BAA6B,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAmBzF;IAED,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAMrE;IAED,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQxC;IAEK,oBAAoB,CAAC,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2E3E;CACD","sourcesContent":["/**\n * Message queueing for the interactive mode.\n *\n * Owns the compaction queue (messages typed while a compaction is running) and\n * the pending-messages display above the editor. The session owns the live\n * steering / follow-up queues; this controller merges them with the compaction\n * queue for display, restores everything back into the editor on demand, and\n * flushes the compaction queue once compaction finishes. Extracted from\n * interactive-mode.ts behind a narrow MessageQueueControllerDeps interface.\n */\n\nimport { type Container, type EditorComponent, Spacer, TruncatedText } from \"@kolisachint/hoocode-tui\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport { keyDisplayText } from \"./components/keybinding-hints.js\";\nimport { theme } from \"./theme/theme.js\";\n\ntype CompactionQueuedMessage = {\n\ttext: string;\n\tmode: \"steer\" | \"followUp\";\n};\n\n/** The slice of the interactive mode the message queue needs. */\nexport interface MessageQueueControllerDeps {\n\t/** The active session (read at call time; the session can be swapped). */\n\tget session(): AgentSession;\n\t/** The prompt editor (read at call time; the editor can be swapped). */\n\tgetEditor(): EditorComponent;\n\t/** Container that renders the queued-message list above the editor. */\n\tpendingMessagesContainer: Container;\n\tshowStatus(message: string): void;\n\tshowError(errorMessage: string): void;\n}\n\nexport class MessageQueueController {\n\t// Messages queued while compaction is running\n\tprivate compactionQueuedMessages: CompactionQueuedMessage[] = [];\n\n\tconstructor(private readonly deps: MessageQueueControllerDeps) {}\n\n\tprivate get session(): AgentSession {\n\t\treturn this.deps.session;\n\t}\n\n\tprivate get editor(): EditorComponent {\n\t\treturn this.deps.getEditor();\n\t}\n\n\t/** Drop the compaction queue without restoring it (session switch/reset). */\n\tresetCompactionQueue(): void {\n\t\tthis.compactionQueuedMessages = [];\n\t}\n\n\tprivate getAllQueuedMessages(): { steering: string[]; followUp: string[] } {\n\t\treturn {\n\t\t\tsteering: [\n\t\t\t\t...this.session.getSteeringMessages(),\n\t\t\t\t...this.compactionQueuedMessages.filter((msg) => msg.mode === \"steer\").map((msg) => msg.text),\n\t\t\t],\n\t\t\tfollowUp: [\n\t\t\t\t...this.session.getFollowUpMessages(),\n\t\t\t\t...this.compactionQueuedMessages.filter((msg) => msg.mode === \"followUp\").map((msg) => msg.text),\n\t\t\t],\n\t\t};\n\t}\n\n\t/**\n\t * Clear all queued messages and return their contents.\n\t * Clears both session queue and compaction queue.\n\t */\n\tprivate clearAllQueues(): { steering: string[]; followUp: string[] } {\n\t\tconst { steering, followUp } = this.session.clearQueue();\n\t\tconst compactionSteering = this.compactionQueuedMessages\n\t\t\t.filter((msg) => msg.mode === \"steer\")\n\t\t\t.map((msg) => msg.text);\n\t\tconst compactionFollowUp = this.compactionQueuedMessages\n\t\t\t.filter((msg) => msg.mode === \"followUp\")\n\t\t\t.map((msg) => msg.text);\n\t\tthis.compactionQueuedMessages = [];\n\t\treturn {\n\t\t\tsteering: [...steering, ...compactionSteering],\n\t\t\tfollowUp: [...followUp, ...compactionFollowUp],\n\t\t};\n\t}\n\n\tupdatePendingMessagesDisplay(): void {\n\t\tthis.deps.pendingMessagesContainer.clear();\n\t\tconst { steering: steeringMessages, followUp: followUpMessages } = this.getAllQueuedMessages();\n\t\tif (steeringMessages.length > 0 || followUpMessages.length > 0) {\n\t\t\tthis.deps.pendingMessagesContainer.addChild(new Spacer(1));\n\t\t\tfor (const message of steeringMessages) {\n\t\t\t\tconst text = theme.fg(\"dim\", `Steering: ${message}`);\n\t\t\t\tthis.deps.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));\n\t\t\t}\n\t\t\tfor (const message of followUpMessages) {\n\t\t\t\tconst text = theme.fg(\"dim\", `Follow-up: ${message}`);\n\t\t\t\tthis.deps.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));\n\t\t\t}\n\t\t\tconst dequeueHint = keyDisplayText(\"app.message.dequeue\");\n\t\t\tconst hintText = theme.fg(\"dim\", `↳ ${dequeueHint} to edit all queued messages`);\n\t\t\tthis.deps.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));\n\t\t}\n\t}\n\n\trestoreQueuedMessagesToEditor(options?: { abort?: boolean; currentText?: string }): number {\n\t\tconst { steering, followUp } = this.clearAllQueues();\n\t\tconst allQueued = [...steering, ...followUp];\n\t\tif (allQueued.length === 0) {\n\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\tif (options?.abort) {\n\t\t\t\tthis.session.agent.abort();\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t\tconst queuedText = allQueued.join(\"\\n\\n\");\n\t\tconst currentText = options?.currentText ?? this.editor.getText();\n\t\tconst combinedText = [queuedText, currentText].filter((t) => t.trim()).join(\"\\n\\n\");\n\t\tthis.editor.setText(combinedText);\n\t\tthis.updatePendingMessagesDisplay();\n\t\tif (options?.abort) {\n\t\t\tthis.session.agent.abort();\n\t\t}\n\t\treturn allQueued.length;\n\t}\n\n\tqueueCompactionMessage(text: string, mode: \"steer\" | \"followUp\"): void {\n\t\tthis.compactionQueuedMessages.push({ text, mode });\n\t\tthis.editor.addToHistory?.(text);\n\t\tthis.editor.setText(\"\");\n\t\tthis.updatePendingMessagesDisplay();\n\t\tthis.deps.showStatus(\"Queued message for after compaction\");\n\t}\n\n\tisExtensionCommand(text: string): boolean {\n\t\tif (!text.startsWith(\"/\")) return false;\n\n\t\tconst extensionRunner = this.session.extensionRunner;\n\n\t\tconst spaceIndex = text.indexOf(\" \");\n\t\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\t\treturn !!extensionRunner.getCommand(commandName);\n\t}\n\n\tasync flushCompactionQueue(options?: { willRetry?: boolean }): Promise<void> {\n\t\tif (this.compactionQueuedMessages.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst queuedMessages = [...this.compactionQueuedMessages];\n\t\tthis.compactionQueuedMessages = [];\n\t\tthis.updatePendingMessagesDisplay();\n\n\t\tconst restoreQueue = (error: unknown) => {\n\t\t\tthis.session.clearQueue();\n\t\t\tthis.compactionQueuedMessages = queuedMessages;\n\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\tthis.deps.showError(\n\t\t\t\t`Failed to send queued message${queuedMessages.length > 1 ? \"s\" : \"\"}: ${\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t}`,\n\t\t\t);\n\t\t};\n\n\t\ttry {\n\t\t\tif (options?.willRetry) {\n\t\t\t\t// When retry is pending, queue messages for the retry turn\n\t\t\t\tfor (const message of queuedMessages) {\n\t\t\t\t\tif (this.isExtensionCommand(message.text)) {\n\t\t\t\t\t\tawait this.session.prompt(message.text);\n\t\t\t\t\t} else if (message.mode === \"followUp\") {\n\t\t\t\t\t\tawait this.session.followUp(message.text);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait this.session.steer(message.text);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Find first non-extension-command message to use as prompt\n\t\t\tconst firstPromptIndex = queuedMessages.findIndex((message) => !this.isExtensionCommand(message.text));\n\t\t\tif (firstPromptIndex === -1) {\n\t\t\t\t// All extension commands - execute them all\n\t\t\t\tfor (const message of queuedMessages) {\n\t\t\t\t\tawait this.session.prompt(message.text);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Execute any extension commands before the first prompt\n\t\t\tconst preCommands = queuedMessages.slice(0, firstPromptIndex);\n\t\t\tconst firstPrompt = queuedMessages[firstPromptIndex];\n\t\t\tconst rest = queuedMessages.slice(firstPromptIndex + 1);\n\n\t\t\tfor (const message of preCommands) {\n\t\t\t\tawait this.session.prompt(message.text);\n\t\t\t}\n\n\t\t\t// Send first prompt (starts streaming)\n\t\t\tconst promptPromise = this.session.prompt(firstPrompt.text).catch((error) => {\n\t\t\t\trestoreQueue(error);\n\t\t\t});\n\n\t\t\t// Queue remaining messages\n\t\t\tfor (const message of rest) {\n\t\t\t\tif (this.isExtensionCommand(message.text)) {\n\t\t\t\t\tawait this.session.prompt(message.text);\n\t\t\t\t} else if (message.mode === \"followUp\") {\n\t\t\t\t\tawait this.session.followUp(message.text);\n\t\t\t\t} else {\n\t\t\t\t\tawait this.session.steer(message.text);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.updatePendingMessagesDisplay();\n\t\t\tvoid promptPromise;\n\t\t} catch (error) {\n\t\t\trestoreQueue(error);\n\t\t}\n\t}\n}\n"]}
|