From feee76785a063851f3081421d282e30301053ab3 Mon Sep 17 00:00:00 2001 From: colinmcneil Date: Tue, 5 Nov 2024 17:24:37 -0500 Subject: [PATCH 1/4] Small improvements to UX - Resolves #52: add `tools` keyword - Resolves #50: Add DD check on activation --- src/commands/runPrompt.ts | 51 +--------------------------------- src/extension.ts | 5 ++++ src/promptmetadatagrammar.json | 2 +- src/utils/dockerDesktop.ts | 47 +++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 51 deletions(-) create mode 100644 src/utils/dockerDesktop.ts diff --git a/src/commands/runPrompt.ts b/src/commands/runPrompt.ts index 1fe4135..59d7c16 100644 --- a/src/commands/runPrompt.ts +++ b/src/commands/runPrompt.ts @@ -14,51 +14,6 @@ import { randomUUID } from "crypto"; type PromptOption = 'local-dir' | 'local-file' | 'remote'; -const START_DOCKER_COMMAND = { - 'win32': 'Start-Process -NoNewWindow -Wait -FilePath "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"', - 'darwin': 'open -a Docker', - 'linux': 'systemctl --user start docker-desktop', -}; - -const checkDockerDesktop = () => { - - // Coerce the error to have an exit code - type DockerSpawnError = Error & { code: number }; - - try { - const res = spawnSync("docker", ["version"]); - - if (res.error) { - // Using -1 to indicate docker is not installed - (res.error as DockerSpawnError).code = -1; - throw res.error; - } - - if (res.status !== 0) { - const err = new Error(`Docker command exited with code ${res.status} and output the following error: ${res.error || res.stderr.toString()}`); - // Using -1 as a fallback, should have already been caught by res.error - (err as DockerSpawnError).code = res.status || -1; - throw err; - } - - // @ts-expect-error - } catch (e: DockerSpawnError) { - const platform = process.platform; - const actionItems = e.code !== -1 ? [(platform in START_DOCKER_COMMAND ? "Start Docker" : "Try again")] : ["Install Docker Desktop", "Try again"]; - return vscode.window.showErrorMessage("Error starting Docker", { modal: true, detail: (e as DockerSpawnError).toString() }, ...actionItems).then(async (value) => { - switch (value) { - case "Start Docker": - spawnSync(START_DOCKER_COMMAND[platform as keyof typeof START_DOCKER_COMMAND], { shell: true }); - case "Install Docker Desktop": - vscode.env.openExternal(vscode.Uri.parse("https://www.docker.com/products/docker-desktop")); - return; - case "Try again": - return 'RETRY'; - } - }); - } -}; - const getWorkspaceFolder = async () => { const workspaceFolders = vscode.workspace.workspaceFolders; @@ -87,10 +42,6 @@ const getWorkspaceFolder = async () => { 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) => { progress.report({ increment: 1, message: "Starting..." }); postToBackendSocket({ event: 'eventLabsPromptRunPrepare', properties: { mode } }); - const result = await checkDockerDesktop(); - if (result === 'RETRY') { - return runPrompt(secrets, mode); - } progress.report({ increment: 5, message: "Checking for OpenAI key..." }); @@ -200,7 +151,7 @@ export const runPrompt: (secrets: vscode.SecretStorage, mode: PromptOption) => v await writeToEditor(`${header} ROLE ${role}${content ? ` (${content})` : ''}\n\n`); break; case 'functions-done': - await writeToEditor('\n```'+`\n\n*entering tool*\n\n`); + await writeToEditor('\n```' + `\n\n*entering tool*\n\n`); break; case 'message': await writeToEditor(json.params.content); diff --git a/src/extension.ts b/src/extension.ts index 78cd2ab..e3523a8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,6 +5,7 @@ import { spawn, spawnSync } from 'child_process'; import semver from 'semver'; import commands from './commands'; import { postToBackendSocket, setDefaultProperties } from './utils/ddSocket'; +import { checkDockerDesktop } from './utils/dockerDesktop'; export let ctx: vscode.ExtensionContext; @@ -74,6 +75,10 @@ const checkOutdatedVersionInstalled = async () => { }; export async function activate(context: vscode.ExtensionContext) { + const result = await checkDockerDesktop(); + if (result === 'RETRY') { + return vscode.commands.executeCommand('workbench.action.reloadWindow'); + } checkOutdatedVersionInstalled(); checkVersion(); setDefaultProperties(context); diff --git a/src/promptmetadatagrammar.json b/src/promptmetadatagrammar.json index 4dc2b1b..f73a8b9 100644 --- a/src/promptmetadatagrammar.json +++ b/src/promptmetadatagrammar.json @@ -8,7 +8,7 @@ ], "repository": { "prompt-metadata-keyword": { - "match": "(?i)(functions)|(extractors)|(model)|(stream)|(url)(?-i)", + "match": "(?i)(functions)|(extractors)|(model)|(stream)|(url)|(tools)(?-i)", "name": "keyword.function.prompt.metadata" } } diff --git a/src/utils/dockerDesktop.ts b/src/utils/dockerDesktop.ts new file mode 100644 index 0000000..875dc13 --- /dev/null +++ b/src/utils/dockerDesktop.ts @@ -0,0 +1,47 @@ +import { spawnSync } from "child_process"; +import * as vscode from "vscode"; + +const START_DOCKER_COMMAND = { + 'win32': 'Start-Process -NoNewWindow -Wait -FilePath "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"', + 'darwin': 'open -a Docker', + 'linux': 'systemctl --user start docker-desktop', +}; + +export const checkDockerDesktop = () => { + + // Coerce the error to have an exit code + type DockerSpawnError = Error & { code: number }; + + try { + const res = spawnSync("docker", ["version"]); + + if (res.error) { + // Using -1 to indicate docker is not installed + (res.error as DockerSpawnError).code = -1; + throw res.error; + } + + if (res.status !== 0) { + const err = new Error(`Docker command exited with code ${res.status} and output the following error: ${res.error || res.stderr.toString()}`); + // Using -1 as a fallback, should have already been caught by res.error + (err as DockerSpawnError).code = res.status || -1; + throw err; + } + + // @ts-expect-error + } catch (e: DockerSpawnError) { + const platform = process.platform; + const actionItems = e.code !== -1 ? [(platform in START_DOCKER_COMMAND ? "Start Docker" : "Try again")] : ["Install Docker Desktop", "Try again"]; + return vscode.window.showErrorMessage("Error starting Docker", { modal: true, detail: (e as DockerSpawnError).toString() }, ...actionItems).then(async (value) => { + switch (value) { + case "Start Docker": + spawnSync(START_DOCKER_COMMAND[platform as keyof typeof START_DOCKER_COMMAND], { shell: true }); + case "Install Docker Desktop": + vscode.env.openExternal(vscode.Uri.parse("https://www.docker.com/products/docker-desktop")); + return; + case "Try again": + return 'RETRY'; + } + }); + } +}; \ No newline at end of file From 06aaaa90db569aaa256ccca676bbb57ffddc7571 Mon Sep 17 00:00:00 2001 From: colinmcneil Date: Tue, 5 Nov 2024 17:25:01 -0500 Subject: [PATCH 2/4] Bump ver 0.1.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d6b6fd1..2688230 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "labs-ai-tools-vscode", "displayName": "Labs: AI Tools for VSCode", "description": "Run & Debug AI Prompts with Dockerized tools", - "version": "0.1.8-ghu-preview", + "version": "0.1.9", "publisher": "docker", "repository": { "type": "git", From 8c82dda2526747e555209d7c52f0f11ab675f878 Mon Sep 17 00:00:00 2001 From: colinmcneil Date: Mon, 11 Nov 2024 20:27:24 -0500 Subject: [PATCH 3/4] Update commands in `readme` --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b0eef51..275faef 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This project is a research prototype. It is ready to try and will give results f *Docker internal users: You must be opted-out of mandatory sign-in.* 1. Install latest VSIX file https://github.com/docker/labs-ai-tools-vscode/releases -2. Execute command `>Set OpenAI API key...` and enter your OpenAI secret key. +2. Execute command `>Docker AI: Set OpenAI API key...` and enter your OpenAI secret key. You can run a prompt with a local model. Docs coming soon. 3. Run a prompt @@ -49,7 +49,7 @@ My project has the following files: ``` -Run command `>Run current file as prompt` +Run command `>Docker AI: Run this prompt` ## Docs https://vonwig.github.io/prompts.docs From c56345e3626f993b87dd795b36e67cb4e57a8b81 Mon Sep 17 00:00:00 2001 From: colinmcneil Date: Fri, 15 Nov 2024 12:11:18 -0500 Subject: [PATCH 4/4] Add command to kill prompts --- package.json | 4 ++++ src/commands/index.ts | 2 ++ src/commands/killActivePrompts.ts | 13 +++++++++++ src/commands/runPrompt.ts | 2 -- src/utils/promptRunner.ts | 39 ++++++++++++++++++++----------- 5 files changed, 45 insertions(+), 15 deletions(-) create mode 100644 src/commands/killActivePrompts.ts diff --git a/package.json b/package.json index 2688230..3685955 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,10 @@ { "command": "docker.labs-ai-tools-vscode.toggle-debug", "title": "Docker AI: Toggle debug mode" + }, + { + "command": "docker.labs-ai-tools-vscode.kill-active-prompts", + "title": "Docker AI: Kill active prompts" } ] }, diff --git a/src/commands/index.ts b/src/commands/index.ts index 05b37bd..4eac029 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -5,6 +5,7 @@ import { runHotCommand } from './runHotCommand'; import { deletePrompt, savePrompt } from './manageSavedPrompts'; import { setProjectDir } from './setProjectDir'; import { setThreadId } from './setThreadId'; +import killActivePrompts from './killActivePrompts'; type CTX = { secrets: any } @@ -25,6 +26,7 @@ const commands = (context: CTX) => [ vscode.window.showInformationMessage(`Debug mode is now ${currentValue ? 'disabled' : 'enabled'}.`); } }, + { id: 'docker.labs-ai-tools-vscode.kill-active-prompts', callback: killActivePrompts }, ] export default (context: CTX) => commands(context).map((comm) => vscode.commands.registerCommand(comm.id, comm.callback)) \ No newline at end of file diff --git a/src/commands/killActivePrompts.ts b/src/commands/killActivePrompts.ts new file mode 100644 index 0000000..c68df0c --- /dev/null +++ b/src/commands/killActivePrompts.ts @@ -0,0 +1,13 @@ +import { window } from "vscode"; +import { sendKillSignalToActivePrompts } from "../utils/promptRunner"; + +const killActivePrompts = () => { + const result = sendKillSignalToActivePrompts(); + if (result.length > 0) { + window.showInformationMessage(`Sent kill signal to ${result.length} active prompts.`); + } else { + window.showInformationMessage(`No active prompts to kill.`); + } +} + +export default killActivePrompts; \ No newline at end of file diff --git a/src/commands/runPrompt.ts b/src/commands/runPrompt.ts index 59d7c16..6c0aced 100644 --- a/src/commands/runPrompt.ts +++ b/src/commands/runPrompt.ts @@ -42,8 +42,6 @@ const getWorkspaceFolder = async () => { 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) => { progress.report({ increment: 1, message: "Starting..." }); postToBackendSocket({ event: 'eventLabsPromptRunPrepare', properties: { mode } }); - - progress.report({ increment: 5, message: "Checking for OpenAI key..." }); const hasOpenAIKey = await verifyHasOpenAIKey(secrets, true); diff --git a/src/utils/promptRunner.ts b/src/utils/promptRunner.ts index 0ac5873..5b0dc88 100644 --- a/src/utils/promptRunner.ts +++ b/src/utils/promptRunner.ts @@ -1,11 +1,15 @@ -import { spawn } from "child_process"; +import { ChildProcessWithoutNullStreams, spawn } from "child_process"; import { CancellationToken, commands, window, workspace } from "vscode"; import { setThreadId } from "../commands/setThreadId"; import { notifications } from "./notifications"; import { extensionOutput } from "../extension"; import * as rpc from 'vscode-jsonrpc/node'; -const output = window.createOutputChannel("Docker Labs: AI Tools"); +const activePrompts: { [key: string]: Function } = {}; + +export const sendKillSignalToActivePrompts = () => + Object.values(activePrompts).map(kill => kill()); + export const getRunArgs = async (promptRef: string, projectDir: string, username: string, pat: string, platform: string, render = false) => { const isLocal = promptRef.startsWith('local://'); @@ -52,12 +56,19 @@ export const spawnPromptImage = async (promptArg: string, projectDir: string, us const args = await getRunArgs(promptArg!, projectDir!, username, platform, pat); callback({ method: 'message', params: { debug: `Running ${args.join(' ')}` } }); const childProcess = spawn("docker", args); + const pid = childProcess.pid; + + if (pid) { + activePrompts[pid] = childProcess.kill; + childProcess.on('exit', () => { + delete activePrompts[pid]; + }); + } let connection = rpc.createMessageConnection( new rpc.StreamMessageReader(childProcess.stdout), new rpc.StreamMessageWriter(childProcess.stdin) ); - const notificationBuffer: { method: string, params: object }[] = [] let processingBuffer = false; @@ -78,9 +89,9 @@ export const spawnPromptImage = async (promptArg: string, projectDir: string, us } } - for (const [type, properties] of Object.entries(notifications)){ + for (const [type, properties] of Object.entries(notifications)) { // @ts-expect-error - connection.onNotification(properties, (params)=> pushNotification(type, params)) + connection.onNotification(properties, (params) => pushNotification(type, params)) } connection.listen(); @@ -98,7 +109,7 @@ export const spawnPromptImage = async (promptArg: string, projectDir: string, us }; -const getJSONArgForPlatform = (json: object) =>{ +const getJSONArgForPlatform = (json: object) => { if (process.platform === 'win32') { return `"` + JSON.stringify(json).replace(/"/g, '\\"') + `"` } @@ -120,26 +131,28 @@ export const writeKeyToVolume = async (key: string) => { getJSONArgForPlatform({ files: [{ path: ".openai-api-key", content: key, executable: false }] }) ]; - extensionOutput.appendLine(JSON.stringify({"write-open-ai-key-to-volume": { - args1, args2 - }})); + extensionOutput.appendLine(JSON.stringify({ + "write-open-ai-key-to-volume": { + args1, args2 + } + })); const child1 = spawn("docker", args1); child1.stdout.on('data', (data) => { - extensionOutput.appendLine(JSON.stringify({stdout:data.toString()})); + extensionOutput.appendLine(JSON.stringify({ stdout: data.toString() })); }); child1.stderr.on('data', (data) => { - extensionOutput.appendLine(JSON.stringify({stderr:data.toString()})); + extensionOutput.appendLine(JSON.stringify({ stderr: data.toString() })); }); const child2 = spawn("docker", args2, { shell: true }); child2.stdout.on('data', (data) => { - extensionOutput.appendLine(JSON.stringify({stdout:data.toString()})); + extensionOutput.appendLine(JSON.stringify({ stdout: data.toString() })); }); child2.stderr.on('data', (data) => { - extensionOutput.appendLine(JSON.stringify({stderr:data.toString()})); + extensionOutput.appendLine(JSON.stringify({ stderr: data.toString() })); }); }; \ No newline at end of file