Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use SecretStorage #5

Merged
merged 10 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,11 @@ If you have any feedback or suggestions for improvement, please open an issue. T

1. Install the extension.
2. Create an OpenAI account and get an API key (if you're using ChatGPT for generation).
3. Add your OpenAI API key to your settings under `wingman.openai.apiKey`: open Settings, search for "wingman", and paste your API key into the input field labeled "Api key".
4. Open VScode's bottom panel by pressing <kbd>CTRL + J</kbd> or <kbd>CMD + J</kbd> and select the `Wingman` tab (pictured above).
5. Highlight a block of code and click "Refactor" in the Wingman tab to refactor the selected code. The generated code will automatically replace the selected text.
6. Explore all of the other commands.
7. Write your other commands in your settings under `wingman.userCommands`. See the [Command](#command) section for more details.
3. Open the command palette (<kbd>CTRL + SHIFT + P</kbd> or <kbd>CMD + SHIFT + P</kbd>) and run `Wingman: Set API key`. Select `openai` as the provider and paste in your API key.
4. Open the bottom panel (<kbd>CTRL + J</kbd> or <kbd>CMD + J</kbd>) and select the `Wingman` tab (pictured above).
5. In the Wingman panel, expand "Analysis", highlight a block of code and click "Analyze for bugs".
6. Explore all of the other builtin commands.
7. Create your own commands that compliment your workflow in settings under `wingman.userCommands`. See the [Command](#command) section for more details.

## Features

Expand Down Expand Up @@ -355,3 +355,8 @@ If you wanted to ask your project a question only about the CSS styling of your
| `wingman.context.include.permittedFileExtensions` | `[ "js", "ts", "jsx", "tsx", "cpp", "py", "go", "java", "html", "css", "php", "rb", "cs", "swift", "kt", "scala", "h", "m", "mm", "c", "cc", "cxx", "hxx", "hpp", "hh", "s", "asm", "pl", "pm", "t", "r", "sh" ]` | Only files with these extensions are included in context. |

If a `.wmignore` file is discovered, it's treated the same as a `.gitignore` file.

## FAQ

Q: How do I remove or change my API key?
A: Run the `Wingman: Set API key` command again, select the relevant provider. Paste in your new key, or leave it blank to remove it. Press enter.
17 changes: 6 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,25 +74,20 @@
}
]
},
"commands": [
{
"command": "wingman.setApiKey",
"title": "Wingman: Set API key"
}
],
"configuration": {
"title": "Wingman",
"properties": {
"wingman.anthropic.apiKey": {
"type": "string",
"default": "",
"description": "Anthropic API Key"
},
"wingman.anthropic.model": {
"type": "string",
"default": "claude-instant-v1",
"description": "Model (a fallback if the command does not specify, e.g. 'claude-instant-v1', 'claude-v1-100k')"
},
"wingman.openai.apiKey": {
"order": 2,
"type": "string",
"default": "",
"description": "ChatGPT API Key"
},
"wingman.openai.model": {
"order": 4,
"type": "string",
Expand Down
50 changes: 29 additions & 21 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as vscode from "vscode";

import { type Provider } from "./providers";
import { providers, type Provider } from "./providers";
import { defaultCommands, buildCommandTemplate, type Command } from "./templates/render";
import { commandHandler } from "./templates/runner";
import { display, displayWarning, getConfig } from "./utils";
import { display, getConfig } from "./utils";
import { MainViewProvider, SecondaryViewProvider } from "./views";

let providerInstance: Provider | undefined;
Expand All @@ -30,11 +30,18 @@ export class ExtensionState {
public static set(key: string, value: any) {
ExtensionState.context.globalState.update(key, value);
}

public static async getSecret<T>(key: string): Promise<T> {
return ExtensionState.context.secrets.get(key) as Promise<T>;
}

public static createSecret(key: string, value: string) {
ExtensionState.context.secrets.store(key, value);
}
}

export function activate(context: vscode.ExtensionContext) {
ExtensionState.create(context);
warnDeprecations();

try {
const mainViewProvider = new MainViewProvider(context.extensionPath, context.extensionUri);
Expand All @@ -53,6 +60,25 @@ export function activate(context: vscode.ExtensionContext) {
}),
);

const setApiKeyCommand = vscode.commands.registerCommand("wingman.setApiKey", async () => {
const selectedProvider = await vscode.window.showQuickPick(
Object.keys(providers).map((key) => ({ label: key })),
{ placeHolder: "Select the provider you want to set the API key for" },
);

if (!selectedProvider) return;

const apiKey = await vscode.window.showInputBox({
placeHolder: `Enter your ${selectedProvider.label} API key`,
});

if (!apiKey) return;

ExtensionState.createSecret(`${selectedProvider.label}.apiKey`, apiKey);
});

context.subscriptions.push(setApiKeyCommand);

const registerCommand = (template: Command & { command: string }) => {
if (!template.command) return;

Expand Down Expand Up @@ -82,21 +108,3 @@ export function activate(context: vscode.ExtensionContext) {
}

export function deactivate() {}

// Introduced in 1.0.21. Should be removed after a short while.
function warnDeprecations() {
const deprecatedConfigSettings = ["apiKey", "apiBaseUrl", "model", "temperature"];

const warnings = deprecatedConfigSettings
.map((setting) => ({ key: `wingman.${setting}`, value: getConfig(setting) }))
.filter(({ value }) => value !== undefined)
.map(({ key }) => `${key} (now wingman.openai.${key})`);

if (warnings.length > 0) {
displayWarning(`
The following deprecated config settings were found in your settings.json: ${warnings.join(", ")}.
The values for these keys will still be used, but in the future they will be completely deprecated.
Please remove these old settings to avoid this warning on startup.
`);
}
}
9 changes: 5 additions & 4 deletions src/providers/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { type PostableViewProvider, type ProviderResponse, type Provider } from
import { Client, type CompletionResponse, type SamplingParameters, AI_PROMPT, HUMAN_PROMPT } from "./sdks/anthropic";
import { type Command } from "../templates/render";
import { handleResponseCallbackType } from "../templates/runner";
import { displayWarning, getConfig, getSelectionInfo } from "../utils";
import { displayWarning, getConfig, getSecret, getSelectionInfo } from "../utils";

let lastMessage: string | undefined;
let lastTemplate: Command | undefined;
Expand All @@ -17,9 +17,10 @@ export class AnthropicProvider implements Provider {
conversationTextHistory: string | undefined;
_abort: AbortController = new AbortController();

create(provider: PostableViewProvider, template?: Command) {
const { apiKey = "", apiBaseUrl } = {
apiKey: getConfig("anthropic.apiKey") as string,
async create(provider: PostableViewProvider, template?: Command) {
const apiKey = await getSecret<string>("openai.apiKey", "");

const { apiBaseUrl } = {
apiBaseUrl: getConfig("anthropic.apiBaseUrl") as string | undefined,
};

Expand Down
2 changes: 1 addition & 1 deletion src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface Provider {
* @param options
* @param provider
*/
create(provider: vscode.WebviewViewProvider, template?: Command): void;
create(provider: vscode.WebviewViewProvider, template?: Command): Promise<void>;
destroy(): void;
send: (message: string, systemMessage?: string, template?: Command) => Promise<any>;
repeatLast: () => Promise<void>;
Expand Down
8 changes: 4 additions & 4 deletions src/providers/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as vscode from "vscode";
import { type PostableViewProvider, type ProviderResponse, type Provider } from ".";
import { type Command } from "../templates/render";
import { handleResponseCallbackType } from "../templates/runner";
import { displayWarning, getConfig, getSelectionInfo } from "../utils";
import { displayWarning, getConfig, getSecret, getSelectionInfo } from "../utils";

interface ConversationState {
conversationId: string;
Expand All @@ -22,14 +22,14 @@ export class OpenAIProvider implements Provider {
conversationState: ConversationState = { conversationId: "", parentMessageId: "" };
_abort: AbortController = new AbortController();

create(provider: PostableViewProvider, template?: Command) {
async create(provider: PostableViewProvider, template?: Command) {
const apiKey = await getSecret<string>("openai.apiKey", "llama");

const {
apiKey = "",
apiBaseUrl = "https://api.openai.com/v1",
model = "gpt-3.5-turbo",
temperature = 0.8,
} = {
apiKey: getConfig<string>("openai.apiKey") ?? getConfig<string>("apiKey"),
apiBaseUrl: getConfig<string>("openai.apiBaseUrl") ?? getConfig<string>("apiBaseUrl"),
model: template?.model ?? getConfig<string>("openai.model") ?? getConfig<string>("model"),
temperature: template?.temperature ?? getConfig<number>("openai.temperature") ?? getConfig<number>("temperature"),
Expand Down
8 changes: 7 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import path from "node:path";
import Glob from "fast-glob";
import * as vscode from "vscode";

import { ExtensionState } from "./extension";

export function getSelectionInfo(editor: vscode.TextEditor): { selectedText: string; startLine: number; endLine: number } {
const { selection } = editor;
const startLine = selection.start.line;
Expand Down Expand Up @@ -69,14 +71,18 @@ function indentText(text: string, line: vscode.TextLine): string {
.join("\n");
}

export const getConfig = <T>(key: string, fallback?: unknown | undefined): T => {
export const getConfig = <T>(key: string, fallback?: T | undefined): T => {
const config = vscode.workspace.getConfiguration("wingman");
if (fallback) {
return config.get(key, fallback) as T;
}
return config.get(key) as T;
};

export const getSecret = async <T>(key: string, fallback?: T | undefined): Promise<T> => {
return (await ExtensionState.getSecret<T>(key)) ?? (fallback as T);
};

export const updateGlobalConfig = <T>(key: string, value: T): void => {
vscode.workspace.getConfiguration("wingman").update(key, value, true);
};
Expand Down
4 changes: 2 additions & 2 deletions src/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export class SecondaryViewProvider implements vscode.WebviewViewProvider {

constructor(private readonly _extensionPath: string, private readonly _extensionUri: vscode.Uri) {}

public static runCommand(command: Command) {
public static async runCommand(command: Command) {
const { provider, command: cmd } = command;

if (!provider) {
Expand All @@ -212,7 +212,7 @@ export class SecondaryViewProvider implements vscode.WebviewViewProvider {

// Every command run should instantiate a new provider, which is why we call destroy here.
getProviderInstance()?.destroy();
getProviderInstance()?.create(this as any, command);
await getProviderInstance()?.create(this as any, command);

vscode.commands.executeCommand(`wingman.${cmd}`);
}
Expand Down