Skip to content

Commit 3c6ab1a

Browse files
author
Colin McNeil
authored
Merge pull request #57 from docker/cm/anthropic-provider
Model Provider Secrets
2 parents 8ec969b + 3540aee commit 3c6ab1a

File tree

9 files changed

+125
-87
lines changed

9 files changed

+125
-87
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ This project is a research prototype. It is ready to try and will give results f
1414
*Docker internal users: You must be opted-out of mandatory sign-in.*
1515

1616
1. Install latest VSIX file https://github.com/docker/labs-ai-tools-vscode/releases
17-
2. Execute command `>Docker AI: Set OpenAI API key...` and enter your OpenAI secret key.
18-
You can run a prompt with a local model. Docs coming soon.
17+
2. Execute command `>Docker AI: Set Secret Key...` to enter the api key for your model provider. This stop is optional if your pompt specifies a local model via `url:` and `model:` attributes.
1918
3. Run a prompt
2019

2120
### Local Prompt:

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "labs-ai-tools-vscode",
33
"displayName": "Labs: AI Tools for VSCode",
44
"description": "Run & Debug AI Prompts with Dockerized tools",
5-
"version": "0.1.9",
5+
"version": "0.1.10",
66
"publisher": "docker",
77
"repository": {
88
"type": "git",
@@ -57,8 +57,8 @@
5757
"title": "Docker AI: Run markdown commands"
5858
},
5959
{
60-
"command": "docker.labs-ai-tools-vscode.set-openai-api-key",
61-
"title": "Docker AI: Set OpenAI API key"
60+
"command": "docker.labs-ai-tools-vscode.set-secret",
61+
"title": "Docker AI: Set Secret Key"
6262
},
6363
{
6464
"command": "docker.labs-ai-tools-vscode.save-prompt",

src/commands/runPrompt.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import * as vscode from "vscode";
55
import { showPromptPicker } from "../utils/promptPicker";
66
import { createOutputBuffer } from "../utils/promptFilename";
77
import { spawnPromptImage, writeKeyToVolume } from "../utils/promptRunner";
8-
import { verifyHasOpenAIKey } from "./setOpenAIKey";
98
import { getCredential } from "../utils/credential";
109
import { setProjectDir } from "./setProjectDir";
1110
import { postToBackendSocket } from "../utils/ddSocket";
1211
import { extensionOutput } from "../extension";
1312
import { randomUUID } from "crypto";
1413

14+
const modelProviders = require('../modelproviders.json') as { label: string, id: string, file: string, patterns: string[] }[];
15+
1516
type PromptOption = 'local-dir' | 'local-file' | 'remote';
1617

1718
const getWorkspaceFolder = async () => {
@@ -42,12 +43,6 @@ const getWorkspaceFolder = async () => {
4243
export const runPrompt: (secrets: vscode.SecretStorage, mode: PromptOption) => void = (secrets: vscode.SecretStorage, mode: PromptOption) => vscode.window.withProgress({ location: vscode.ProgressLocation.Window, cancellable: true }, async (progress, token) => {
4344
progress.report({ increment: 1, message: "Starting..." });
4445
postToBackendSocket({ event: 'eventLabsPromptRunPrepare', properties: { mode } });
45-
progress.report({ increment: 5, message: "Checking for OpenAI key..." });
46-
47-
const hasOpenAIKey = await verifyHasOpenAIKey(secrets, true);
48-
if (!hasOpenAIKey) {
49-
return;
50-
}
5146

5247
progress.report({ increment: 5, message: "Checking for workspace..." });
5348

@@ -90,8 +85,6 @@ export const runPrompt: (secrets: vscode.SecretStorage, mode: PromptOption) => v
9085

9186
progress.report({ increment: 5, message: "Writing prompt output file..." });
9287

93-
const apiKey = await secrets.get("openAIKey");
94-
9588
const { editor, doc } = await createOutputBuffer('prompt-output' + randomUUID() + '.md', hostDir);
9689

9790
if (!editor || !doc) {
@@ -118,7 +111,19 @@ export const runPrompt: (secrets: vscode.SecretStorage, mode: PromptOption) => v
118111

119112
try {
120113
progress.report({ increment: 5, message: "Mounting secrets..." });
121-
await writeKeyToVolume(apiKey!);
114+
for (const provider of modelProviders) {
115+
const secret = await secrets.get(provider.id);
116+
if (secret) {
117+
await writeKeyToVolume(provider.file, secret);
118+
}
119+
if (provider.id === 'openai' && !secret) {
120+
const oldOpenAIKey = await secrets.get('openAIKey');
121+
if (oldOpenAIKey) {
122+
await writeKeyToVolume(provider.file, oldOpenAIKey);
123+
}
124+
125+
}
126+
}
122127
progress.report({ increment: 5, message: "Running..." });
123128
const ranges: Record<string, vscode.Range> = {};
124129
const getBaseFunctionRange = () => new vscode.Range(doc.lineCount, 0, doc.lineCount, 0);
@@ -149,7 +154,7 @@ export const runPrompt: (secrets: vscode.SecretStorage, mode: PromptOption) => v
149154
await writeToEditor(`${header} ROLE ${role}${content ? ` (${content})` : ''}\n\n`);
150155
break;
151156
case 'functions-done':
152-
await writeToEditor('\n```' + `\n\n*entering tool*\n\n`);
157+
await writeToEditor('\n```\n');
153158
break;
154159
case 'message':
155160
await writeToEditor(json.params.content);
@@ -169,12 +174,12 @@ export const runPrompt: (secrets: vscode.SecretStorage, mode: PromptOption) => v
169174
await writeToEditor(json.params.messages.map((m: any) => `# ${m.role}\n${m.content}`).join('\n') + '\n');
170175
break;
171176
case 'error':
172-
const errorMSG = String(json.params.content) + String(json.params.message) + String(json.params.message)
177+
const errorMSG = String(json.params.content) || String(json.params.message) || String(json.params.message)
173178
await writeToEditor('```error\n' + errorMSG + '\n```\n');
174179
postToBackendSocket({ event: 'eventLabsPromptError', properties: { error: errorMSG } });
175180
break;
176181
default:
177-
await writeToEditor(JSON.stringify(json, null, 2));
182+
break;
178183
}
179184
}, token);
180185
await doc.save();

src/commands/secrets.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { SecretStorage, ThemeIcon, window } from "vscode";
2+
3+
export const showSetSecretDialog = async (secrets: SecretStorage) => {
4+
const modelProviders = require('../modelproviders.json') as { label: string, id: string, patterns: string[] }[];
5+
6+
type QuickPickItem = {
7+
label: string;
8+
id: string;
9+
buttons: {
10+
iconPath: ThemeIcon;
11+
tooltip: string;
12+
onClick: () => void;
13+
}[];
14+
};
15+
16+
const quickPick = window.createQuickPick<QuickPickItem>();
17+
18+
19+
quickPick.items = modelProviders.map(provider => ({
20+
label: provider.label,
21+
id: provider.id,
22+
buttons: [{
23+
iconPath: new ThemeIcon('trashcan'),
24+
tooltip: 'Clear', onClick: () => {
25+
secrets.delete(provider.id);
26+
void window.showInformationMessage(`${provider.label} key cleared.`);
27+
}
28+
}]
29+
}));
30+
31+
const modelProvider = await new Promise<QuickPickItem | undefined>((resolve) => {
32+
quickPick.onDidAccept(() => {
33+
resolve(quickPick.selectedItems[0]);
34+
quickPick.hide();
35+
});
36+
quickPick.onDidHide(() => {
37+
resolve(undefined);
38+
});
39+
quickPick.onDidTriggerItemButton((event) => {
40+
secrets.delete(event.item.id);
41+
void window.showInformationMessage(`${event.item.label} key cleared.`);
42+
resolve(undefined);
43+
quickPick.hide();
44+
});
45+
quickPick.show();
46+
});
47+
48+
if (!modelProvider) {
49+
return;
50+
}
51+
52+
const secret = await window.showInputBox({
53+
title: `Enter your ${modelProvider.label} API key`,
54+
password: true,
55+
prompt: `Enter your ${modelProvider.label} API key`,
56+
ignoreFocusOut: true,
57+
});
58+
59+
if (!secret) {
60+
return void window.showInformationMessage(`${modelProvider.label} key not set.`);
61+
}
62+
63+
64+
await secrets.store(modelProvider.id, secret);
65+
void window.showInformationMessage(`${modelProvider.label} key set.`);
66+
67+
return modelProvider.id;
68+
};

src/commands/setOpenAIKey.ts

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/extension.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as vscode from 'vscode';
2-
import { setOpenAIKey } from './commands/setOpenAIKey';
2+
import { showSetSecretDialog } from './commands/secrets';
33
import { nativeClient } from './utils/lsp';
44
import { spawn, spawnSync } from 'child_process';
55
import semver from 'semver';
@@ -84,10 +84,10 @@ export async function activate(context: vscode.ExtensionContext) {
8484
setDefaultProperties(context);
8585
postToBackendSocket({ event: 'eventLabsPromptActivated' });
8686
ctx = context;
87-
let setOpenAIKeyCommand = vscode.commands.registerCommand('docker.labs-ai-tools-vscode.set-openai-api-key', () => {
88-
setOpenAIKey(context.secrets);
87+
let setProviderSecretCommand = vscode.commands.registerCommand('docker.labs-ai-tools-vscode.set-secret', () => {
88+
showSetSecretDialog(context.secrets);
8989
});
90-
context.subscriptions.push(setOpenAIKeyCommand);
90+
context.subscriptions.push(setProviderSecretCommand);
9191

9292
const pullPromptImage = () => {
9393
const process = spawn('docker', ['pull', "vonwig/prompts:latest"]);

src/modelproviders.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"label": "Anthropic Claude",
4+
"id": "anthropic",
5+
"file": ".claude-api-key",
6+
"patterns": [
7+
"claude-*"
8+
]
9+
},
10+
{
11+
"label": "Open AI",
12+
"id": "openai",
13+
"file": ".openai-api-key",
14+
"patterns": [
15+
"gpt-*"
16+
]
17+
}
18+
]

src/utils/promptRunner.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { notifications } from "./notifications";
55
import { extensionOutput } from "../extension";
66
import * as rpc from 'vscode-jsonrpc/node';
77
import path from "path";
8+
import modelProviders from "../modelproviders.json";
89

910
const activePrompts: { [key: string]: Function } = {};
1011

@@ -34,9 +35,10 @@ export const getRunArgs = async (promptRef: string, projectDir: string, username
3435
'run',
3536
'--rm',
3637
'-v', '/var/run/docker.sock:/var/run/docker.sock',
37-
'-v', 'openai_key:/secret',
38+
'-v', 'docker-vsc-secrets:/root/secrets',
39+
'-e', 'OPENAI_API_KEY_LOCATION=/root/secrets',
40+
'-e', 'CLAUDE_API_KEY_LOCATION=/root/secrets',
3841
'--mount', 'type=volume,source=docker-prompts,target=/prompts',
39-
'-e', 'OPENAI_API_KEY_LOCATION=/secret',
4042
'-v', "/run/host-services/backend.sock:/host-services/docker-desktop-backend.sock",
4143
'-e', "DOCKER_DESKTOP_SOCKET_PATH=/host-services/docker-desktop-backend.sock",
4244
];
@@ -122,22 +124,25 @@ const getJSONArgForPlatform = (json: object) => {
122124
}
123125
}
124126

125-
export const writeKeyToVolume = async (key: string) => {
127+
export const writeKeyToVolume = async (keyFile: string, keyVal: string) => {
126128

127129
const args1 = ["pull", "vonwig/function_write_files"];
128130

129131
const args2 = [
130132
"run",
131-
"-v", "openai_key:/secret",
133+
"-v", `docker-vsc-secrets:/secret`,
132134
"--rm",
133135
"--workdir", "/secret",
134136
"vonwig/function_write_files",
135-
getJSONArgForPlatform({ files: [{ path: ".openai-api-key", content: key, executable: false }] })
137+
getJSONArgForPlatform({ files: [{ path: keyFile, content: keyVal, executable: false }] })
136138
];
137139

138140
extensionOutput.appendLine(JSON.stringify({
139-
"write-open-ai-key-to-volume": {
140-
args1, args2
141+
"write-secret-to-volume": {
142+
keyFile,
143+
keyVal,
144+
args1,
145+
args2
141146
}
142147
}));
143148

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"include": [
1919
"src/promptgrammar.json",
2020
"src/promptmetadatagrammar.json",
21+
"src/modelproviders.json",
2122
"src/**/*.ts"
2223
]
2324
}

0 commit comments

Comments
 (0)