summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yaml130
-rw-r--r--.vscode/launch.json31
-rw-r--r--.vscode/tasks.json16
-rw-r--r--continuedev/pyproject.toml2
-rw-r--r--continuedev/src/continuedev/core/autopilot.py15
-rw-r--r--continuedev/src/continuedev/core/context.py48
-rw-r--r--continuedev/src/continuedev/libs/llm/ggml.py5
-rw-r--r--continuedev/src/continuedev/libs/llm/replicate.py2
-rw-r--r--continuedev/src/continuedev/libs/llm/together.py122
-rw-r--r--continuedev/src/continuedev/plugins/context_providers/file.py22
-rw-r--r--continuedev/src/continuedev/plugins/steps/help.py3
-rw-r--r--continuedev/src/continuedev/server/meilisearch_server.py16
-rw-r--r--extension/.vscodeignore4
-rw-r--r--extension/manual-testing-sandbox/nested-folder/helloNested.py2
-rw-r--r--extension/package-lock.json4
-rw-r--r--extension/package.json4
-rw-r--r--extension/react-app/package.json5
-rw-r--r--extension/react-app/src/App.tsx2
-rw-r--r--extension/react-app/src/components/ComboBox.tsx21
-rw-r--r--extension/react-app/src/redux/slices/configSlice.ts8
-rw-r--r--extension/react-app/src/redux/slices/serverStateReducer.ts6
-rw-r--r--extension/react-app/src/redux/store.ts2
-rw-r--r--extension/scripts/package.js42
-rw-r--r--extension/src/activation/environmentSetup.ts2
-rw-r--r--extension/src/continueIdeClient.ts13
-rw-r--r--extension/src/debugPanel.ts16
-rw-r--r--extension/src/test-runner/runTestOnVSCodeHost.ts5
-rw-r--r--extension/src/test-suite/environmentSetup.test.ts17
28 files changed, 448 insertions, 117 deletions
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 03c33dba..94f7073b 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -4,7 +4,6 @@ on:
push:
branches:
- main
- - package-python
jobs:
pyinstaller:
@@ -15,6 +14,8 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
+ # Install Python requirements and build+upload binaries for each platform
+
- name: Check-out repository
uses: actions/checkout@v3
@@ -44,33 +45,46 @@ jobs:
name: ${{ runner.os }} Build
path: dist/*
- publish:
+ test-and-package:
needs: pyinstaller
- runs-on: ubuntu-latest
- permissions:
- contents: write
+ strategy:
+ matrix:
+ os: [macos-latest, ubuntu-20.04, windows-latest]
+
+ runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
+ # Download corresponding binary artifact for the platform
+
+ - name: Create exe directory
+ run: |
+ mkdir extension/server/exe
+
- name: Download Linux build
uses: actions/download-artifact@v2
with:
name: Linux Build
- path: exe/linux
+ path: extension/server/exe/linux
+ if: matrix.os == 'ubuntu-20.04'
- name: Download macOS build
uses: actions/download-artifact@v2
with:
name: macOS Build
- path: exe/mac
+ path: extension/server/exe/mac
+ if: matrix.os == 'macos-latest'
- name: Download Windows build
uses: actions/download-artifact@v2
with:
name: Windows Build
- path: exe/windows
+ path: extension/server/exe/windows
+ if: matrix.os == 'windows-latest'
+
+ # Setup Node.js and install dependencies
- name: Use Node.js 19.0.0
uses: actions/setup-node@v3
@@ -99,11 +113,83 @@ jobs:
cd extension/react-app
npm ci --legacy-peer-deps
- - name: Build and Publish
+ # Run tests
+
+ - name: Package the extension
run: |
cd extension
npm run package
- npx vsce publish patch -p ${{ secrets.VSCE_TOKEN }}
+
+ - name: Install Xvfb for Linux and run tests
+ run: |
+ sudo apt-get install -y xvfb # Install Xvfb
+ Xvfb :99 & # Start Xvfb
+ export DISPLAY=:99 # Export the display number to the environment
+ cd extension
+ npm run test
+ if: matrix.os == 'ubuntu-20.04'
+
+ - name: Run extension tests
+ run: |
+ cd extension
+ npm run test
+ if: matrix.os != 'ubuntu-20.04'
+
+ # Upload .vsix artifact
+
+ - name: Upload .vsix as an artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: vsix-artifact
+ path: extension/build/*
+ if: matrix.os == 'ubuntu-20.04'
+
+ publish:
+ needs: test-and-package
+ runs-on: ubuntu-20.04
+ permissions:
+ contents: write
+
+ steps:
+ # Checkout and download .vsix artifact
+
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Download .vsix artifact
+ uses: actions/download-artifact@v2
+ with:
+ name: vsix-artifact
+ path: extension/build
+
+ # Publish the extension and commit/push the version change
+
+ - name: Use Node.js 19.0.0
+ uses: actions/setup-node@v3
+ with:
+ node-version: 19.0.0
+
+ - name: Cache extension node_modules
+ uses: actions/cache@v2
+ with:
+ path: extension/node_modules
+ key: ${{ runner.os }}-node-${{ hashFiles('extension/package-lock.json') }}
+
+ - name: Install extension Dependencies
+ run: |
+ cd extension
+ npm ci
+
+ - name: Publish
+ run: |
+ cd extension
+ npx vsce publish -p ${{ secrets.VSCE_TOKEN }} --packagePath ./build/*.vsix
+
+ - name: Update version in package.json
+ run: |
+ cd extension
+ npm version patch
+
- name: Commit changes
run: |
git config --local user.email "action@github.com"
@@ -116,18 +202,32 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}
- - name: Upload .vsix artifact
- uses: actions/upload-artifact@v2
+ # Download binaries and upload to S3
+
+ - name: Download Linux build
+ uses: actions/download-artifact@v2
with:
- name: vsix-artifact
- path: extension/build/*
+ name: Linux Build
+ path: exe/linux
+
+ - name: Download macOS build
+ uses: actions/download-artifact@v2
+ with:
+ name: macOS Build
+ path: exe/mac
+
+ - name: Download Windows build
+ uses: actions/download-artifact@v2
+ with:
+ name: Windows Build
+ path: exe/windows
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- aws-region: us-west-2
+ aws-region: us-west-1
- name: Upload binaries to S3
uses: jakejarvis/s3-sync-action@master
diff --git a/.vscode/launch.json b/.vscode/launch.json
index bbe1fd2e..12cfaef8 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -7,18 +7,12 @@
{
"name": "Server + Extension (VSCode)",
"stopAll": true,
- "configurations": [
- "Server",
- "Extension (VSCode)"
- ]
+ "configurations": ["Server", "Extension (VSCode)"]
},
{
"name": "Server + Tests (VSCode)",
"stopAll": true,
- "configurations": [
- "Server",
- "Tests (VSCode)"
- ]
+ "configurations": ["Server", "Tests (VSCode)"]
}
],
"configurations": [
@@ -27,12 +21,9 @@
"type": "python",
"request": "launch",
"module": "continuedev.src.continuedev.server.main",
- "args": [
- "--port",
- "8001"
- ],
+ "args": ["--port", "8001"],
"justMyCode": false,
- "subProcess": false,
+ "subProcess": false
// Does it need a build task?
// What about a watch task? - type errors?
},
@@ -45,11 +36,9 @@
// Pass a directory to manually test in
"${workspaceFolder}/extension/manual-testing-sandbox",
"${workspaceFolder}/extension/manual-testing-sandbox/example.ts",
- "--extensionDevelopmentPath=${workspaceFolder}/extension",
- ],
- "outFiles": [
- "${workspaceFolder}/extension/out/**/*.js"
+ "--extensionDevelopmentPath=${workspaceFolder}/extension"
],
+ "outFiles": ["${workspaceFolder}/extension/out/**/*.js"],
"preLaunchTask": "vscode-extension:build",
"env": {
"CONTINUE_SERVER_URL": "http://localhost:8001"
@@ -77,10 +66,10 @@
"internalConsoleOptions": "openOnSessionStart",
"preLaunchTask": "vscode-extension:tests:build",
"env": {
- "CONTINUE_SERVER_URL": "http://localhost:8001",
+ "CONTINUE_SERVER_URL": "http://localhost:65432",
// Avoid timing out when stopping on breakpoints during debugging in VSCode
- "MOCHA_TIMEOUT": "0",
- },
+ "MOCHA_TIMEOUT": "0"
+ }
}
]
-} \ No newline at end of file
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index c15edf0d..1a29668b 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -7,6 +7,8 @@
"dependsOn": [
// To detect compile errors
"vscode-extension:tsc",
+ // To build the react-app that is used in the extension
+ "vscode-extension:continue-ui:build",
// To bundle the code the same way we do for publishing
"vscode-extension:esbuild"
],
@@ -50,6 +52,20 @@
"clear": true,
},
},
+ // Build the react-app. It gets bundled into the extension as a file resource and has a seprate build step
+ {
+ "label": "vscode-extension:continue-ui:build",
+ "type": "npm",
+ "script": "build",
+ "path": "extension/react-app",
+ "problemMatcher": [
+ "$tsc"
+ ],
+ "presentation": {
+ "revealProblems": "onProblem",
+ "clear": true,
+ },
+ },
//
// Compile and bundle tests
{
diff --git a/continuedev/pyproject.toml b/continuedev/pyproject.toml
index 49b3c5ed..90ff0572 100644
--- a/continuedev/pyproject.toml
+++ b/continuedev/pyproject.toml
@@ -9,7 +9,7 @@ readme = "README.md"
python = "^3.8.1"
fastapi = "^0.95.1"
typer = "^0.7.0"
-openai = "^0.27.8"
+openai = "^0.27.5"
boltons = "^23.0.0"
pydantic = "^1.10.7"
uvicorn = "^0.21.1"
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index 256f3439..9100c34e 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -81,11 +81,7 @@ class Autopilot(ContinueBaseModel):
self.continue_sdk.config.context_providers + [
HighlightedCodeContextProvider(ide=self.ide),
FileContextProvider(workspace_dir=self.ide.workspace_directory)
- ])
-
- logger.debug("Loading index")
- create_async_task(self.context_manager.load_index(
- self.ide.workspace_directory))
+ ], self.ide.workspace_directory)
if full_state is not None:
self.history = full_state.history
@@ -188,6 +184,9 @@ class Autopilot(ContinueBaseModel):
await self._run_singular_step(step)
async def handle_highlighted_code(self, range_in_files: List[RangeInFileWithContents]):
+ if "code" not in self.context_manager.context_providers:
+ return
+
# Add to context manager
await self.context_manager.context_providers["code"].handle_highlighted_code(
range_in_files)
@@ -212,10 +211,16 @@ class Autopilot(ContinueBaseModel):
await self.update_subscribers()
async def toggle_adding_highlighted_code(self):
+ if "code" not in self.context_manager.context_providers:
+ return
+
self.context_manager.context_providers["code"].adding_highlighted_code = not self.context_manager.context_providers["code"].adding_highlighted_code
await self.update_subscribers()
async def set_editing_at_ids(self, ids: List[str]):
+ if "code" not in self.context_manager.context_providers:
+ return
+
await self.context_manager.context_providers["code"].set_editing_at_ids(ids)
await self.update_subscribers()
diff --git a/continuedev/src/continuedev/core/context.py b/continuedev/src/continuedev/core/context.py
index b1f68b50..db1c770a 100644
--- a/continuedev/src/continuedev/core/context.py
+++ b/continuedev/src/continuedev/core/context.py
@@ -8,9 +8,10 @@ from pydantic import BaseModel
from .main import ChatMessage, ContextItem, ContextItemDescription, ContextItemId
-from ..server.meilisearch_server import check_meilisearch_running
+from ..server.meilisearch_server import poll_meilisearch_running
from ..libs.util.logging import logger
from ..libs.util.telemetry import posthog_logger
+from ..libs.util.create_async_task import create_async_task
SEARCH_INDEX_NAME = "continue_context_items"
@@ -140,31 +141,32 @@ class ContextManager:
self.context_providers = {}
self.provider_titles = set()
- async def start(self, context_providers: List[ContextProvider]):
+ async def start(self, context_providers: List[ContextProvider], workspace_directory: str):
"""
Starts the context manager.
"""
+ # Use only non-meilisearch-dependent providers until it is loaded
self.context_providers = {
- prov.title: prov for prov in context_providers}
+ title: provider for title, provider in self.context_providers.items() if title == "code"
+ }
self.provider_titles = {
provider.title for provider in context_providers}
- async with Client('http://localhost:7700') as search_client:
- meilisearch_running = True
+ # Start MeiliSearch in the background without blocking
+ async def start_meilisearch(context_providers):
try:
-
- health = await search_client.health()
- if not health.status == "available":
- meilisearch_running = False
- except:
- meilisearch_running = False
-
- if not meilisearch_running:
+ await asyncio.wait_for(poll_meilisearch_running(), timeout=20)
+ self.context_providers = {
+ prov.title: prov for prov in context_providers}
+ logger.debug("Loading Meilisearch index...")
+ await self.load_index(workspace_directory)
+ logger.debug("Loaded Meilisearch index")
+ except asyncio.TimeoutError:
+ logger.warning("MeiliSearch did not start within 5 seconds")
logger.warning(
"MeiliSearch not running, avoiding any dependent context providers")
- self.context_providers = {
- title: provider for title, provider in self.context_providers.items() if title == "code"
- }
+
+ create_async_task(start_meilisearch(context_providers))
async def load_index(self, workspace_dir: str):
for _, provider in self.context_providers.items():
@@ -176,14 +178,24 @@ class ContextManager:
"id": item.description.id.to_string(),
"name": item.description.name,
"description": item.description.description,
- "content": item.content
+ "content": item.content,
+ "workspace_dir": workspace_dir,
}
for item in context_items
]
if len(documents) > 0:
try:
async with Client('http://localhost:7700') as search_client:
- await asyncio.wait_for(search_client.index(SEARCH_INDEX_NAME).add_documents(documents), timeout=5)
+ # First, create the index if it doesn't exist
+ await search_client.create_index(SEARCH_INDEX_NAME)
+ # The index is currently shared by all workspaces
+ globalSearchIndex = await search_client.get_index(SEARCH_INDEX_NAME)
+ await asyncio.wait_for(asyncio.gather(
+ # Ensure that the index has the correct filterable attributes
+ globalSearchIndex.update_filterable_attributes(
+ ["workspace_dir"]),
+ globalSearchIndex.add_documents(documents)
+ ), timeout=5)
except Exception as e:
logger.debug(f"Error loading meilisearch index: {e}")
diff --git a/continuedev/src/continuedev/libs/llm/ggml.py b/continuedev/src/continuedev/libs/llm/ggml.py
index 2f131354..25a61e63 100644
--- a/continuedev/src/continuedev/libs/llm/ggml.py
+++ b/continuedev/src/continuedev/libs/llm/ggml.py
@@ -82,7 +82,10 @@ class GGML(LLM):
chunks = json_chunk.split("\n")
for chunk in chunks:
if chunk.strip() != "":
- yield json.loads(chunk[6:])["choices"][0]["delta"]
+ yield {
+ "role": "assistant",
+ "content": json.loads(chunk[6:])["choices"][0]["delta"]
+ }
except:
raise Exception(str(line[0]))
diff --git a/continuedev/src/continuedev/libs/llm/replicate.py b/continuedev/src/continuedev/libs/llm/replicate.py
index 235fd906..0dd359e7 100644
--- a/continuedev/src/continuedev/libs/llm/replicate.py
+++ b/continuedev/src/continuedev/libs/llm/replicate.py
@@ -25,7 +25,7 @@ class ReplicateLLM(LLM):
@property
def default_args(self):
- return {**DEFAULT_ARGS, "model": self.name, "max_tokens": 1024}
+ return {**DEFAULT_ARGS, "model": self.model, "max_tokens": 1024}
def count_tokens(self, text: str):
return count_tokens(self.name, text)
diff --git a/continuedev/src/continuedev/libs/llm/together.py b/continuedev/src/continuedev/libs/llm/together.py
new file mode 100644
index 00000000..c3f171c9
--- /dev/null
+++ b/continuedev/src/continuedev/libs/llm/together.py
@@ -0,0 +1,122 @@
+import json
+from typing import Any, Coroutine, Dict, Generator, List, Union
+
+import aiohttp
+from ...core.main import ChatMessage
+from ..llm import LLM
+from ..util.count_tokens import compile_chat_messages, DEFAULT_ARGS, count_tokens
+
+
+class TogetherLLM(LLM):
+ # this is model-specific
+ api_key: str
+ model: str = "togethercomputer/RedPajama-INCITE-7B-Instruct"
+ max_context_length: int = 2048
+ base_url: str = "https://api.together.xyz"
+ verify_ssl: bool = True
+
+ _client_session: aiohttp.ClientSession = None
+
+ async def start(self, **kwargs):
+ self._client_session = aiohttp.ClientSession(
+ connector=aiohttp.TCPConnector(verify_ssl=self.verify_ssl))
+
+ async def stop(self):
+ await self._client_session.close()
+
+ @property
+ def name(self):
+ return self.model
+
+ @property
+ def context_length(self):
+ return self.max_context_length
+
+ @property
+ def default_args(self):
+ return {**DEFAULT_ARGS, "model": self.model, "max_tokens": 1024}
+
+ def count_tokens(self, text: str):
+ return count_tokens(self.name, text)
+
+ def convert_to_prompt(self, chat_messages: List[ChatMessage]) -> str:
+ system_message = None
+ if chat_messages[0]["role"] == "system":
+ system_message = chat_messages.pop(0)["content"]
+
+ prompt = "\n"
+ if system_message:
+ prompt += f"<human>: Hi!\n<bot>: {system_message}\n"
+ for message in chat_messages:
+ prompt += f'<{"human" if message["role"] == "user" else "bot"}>: {message["content"]}\n'
+
+ prompt += "<bot>:"
+ return prompt
+
+ async def stream_complete(self, prompt, with_history: List[ChatMessage] = None, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ args = self.default_args.copy()
+ args.update(kwargs)
+ args["stream_tokens"] = True
+
+ args = {**self.default_args, **kwargs}
+ messages = compile_chat_messages(
+ self.name, with_history, self.context_length, args["max_tokens"], prompt, functions=args.get("functions", None), system_message=self.system_message)
+
+ async with self._client_session.post(f"{self.base_url}/inference", json={
+ "prompt": self.convert_to_prompt(messages),
+ **args
+ }, headers={
+ "Authorization": f"Bearer {self.api_key}"
+ }) as resp:
+ async for line in resp.content.iter_any():
+ if line:
+ try:
+ yield line.decode("utf-8")
+ except:
+ raise Exception(str(line))
+
+ async def stream_chat(self, messages: List[ChatMessage] = None, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+ args = {**self.default_args, **kwargs}
+ messages = compile_chat_messages(
+ self.name, messages, self.context_length, args["max_tokens"], None, functions=args.get("functions", None), system_message=self.system_message)
+ args["stream_tokens"] = True
+
+ async with self._client_session.post(f"{self.base_url}/inference", json={
+ "prompt": self.convert_to_prompt(messages),
+ **args
+ }, headers={
+ "Authorization": f"Bearer {self.api_key}"
+ }) as resp:
+ async for line in resp.content.iter_chunks():
+ if line[1]:
+ try:
+ json_chunk = line[0].decode("utf-8")
+ if json_chunk.startswith(": ping - ") or json_chunk.startswith("data: [DONE]"):
+ continue
+ chunks = json_chunk.split("\n")
+ for chunk in chunks:
+ if chunk.strip() != "":
+ yield {
+ "role": "assistant",
+ "content": json.loads(chunk[6:])["choices"][0]["text"]
+ }
+ except:
+ raise Exception(str(line[0]))
+
+ async def complete(self, prompt: str, with_history: List[ChatMessage] = None, **kwargs) -> Coroutine[Any, Any, str]:
+ args = {**self.default_args, **kwargs}
+
+ messages = compile_chat_messages(args["model"], with_history, self.context_length,
+ args["max_tokens"], prompt, functions=None, system_message=self.system_message)
+ async with self._client_session.post(f"{self.base_url}/inference", json={
+ "prompt": self.convert_to_prompt(messages),
+ **args
+ }, headers={
+ "Authorization": f"Bearer {self.api_key}"
+ }) as resp:
+ try:
+ text = await resp.text()
+ j = json.loads(text)
+ return j["output"]["choices"][0]["text"]
+ except:
+ raise Exception(await resp.text())
diff --git a/continuedev/src/continuedev/plugins/context_providers/file.py b/continuedev/src/continuedev/plugins/context_providers/file.py
index 31aa5423..b40092af 100644
--- a/continuedev/src/continuedev/plugins/context_providers/file.py
+++ b/continuedev/src/continuedev/plugins/context_providers/file.py
@@ -54,33 +54,37 @@ class FileContextProvider(ContextProvider):
list(filter(lambda d: f"**/{d}", DEFAULT_IGNORE_DIRS))
async def provide_context_items(self, workspace_dir: str) -> List[ContextItem]:
- filepaths = []
+ absolute_filepaths: List[str] = []
for root, dir_names, file_names in os.walk(workspace_dir):
dir_names[:] = [d for d in dir_names if not any(
fnmatch(d, pattern) for pattern in self.ignore_patterns)]
for file_name in file_names:
- filepaths.append(os.path.join(root, file_name))
+ absolute_filepaths.append(os.path.join(root, file_name))
- if len(filepaths) > 1000:
+ if len(absolute_filepaths) > 1000:
break
- if len(filepaths) > 1000:
+ if len(absolute_filepaths) > 1000:
break
items = []
- for file in filepaths:
- content = get_file_contents(file)
+ for absolute_filepath in absolute_filepaths:
+ content = get_file_contents(absolute_filepath)
if content is None:
continue # no pun intended
+
+ relative_to_workspace = os.path.relpath(absolute_filepath, workspace_dir)
items.append(ContextItem(
content=content[:min(2000, len(content))],
description=ContextItemDescription(
- name=os.path.basename(file),
- description=file,
+ name=os.path.basename(absolute_filepath),
+ # We should add the full path to the ContextItem
+ # It warrants a data modeling discussion and has no immediate use case
+ description=relative_to_workspace,
id=ContextItemId(
provider_title=self.title,
- item_id=remove_meilisearch_disallowed_chars(file)
+ item_id=remove_meilisearch_disallowed_chars(absolute_filepath)
)
)
))
diff --git a/continuedev/src/continuedev/plugins/steps/help.py b/continuedev/src/continuedev/plugins/steps/help.py
index ec670999..82f885d6 100644
--- a/continuedev/src/continuedev/plugins/steps/help.py
+++ b/continuedev/src/continuedev/plugins/steps/help.py
@@ -39,6 +39,7 @@ class HelpStep(Step):
if question.strip() == "":
self.description = help
else:
+ self.description = "The following output is generated by a language model, which may hallucinate. Type just '/help'to see a fixed answer. You can also learn more by reading [the docs](https://continue.dev/docs).\n\n"
prompt = dedent(f"""
Information:
@@ -48,7 +49,7 @@ class HelpStep(Step):
Please us the information below to provide a succinct answer to the following question: {question}
- Do not cite any slash commands other than those you've been told about, which are: /edit and /feedback.""")
+ Do not cite any slash commands other than those you've been told about, which are: /edit and /feedback. Never refer or link to any URL.""")
self.chat_context.append(ChatMessage(
role="user",
diff --git a/continuedev/src/continuedev/server/meilisearch_server.py b/continuedev/src/continuedev/server/meilisearch_server.py
index 7f460afc..f47c08ca 100644
--- a/continuedev/src/continuedev/server/meilisearch_server.py
+++ b/continuedev/src/continuedev/server/meilisearch_server.py
@@ -1,3 +1,4 @@
+import asyncio
import os
import shutil
import subprocess
@@ -58,15 +59,26 @@ async def check_meilisearch_running() -> bool:
async with Client('http://localhost:7700') as client:
try:
resp = await client.health()
- if resp["status"] != "available":
+ if resp.status != "available":
return False
return True
- except:
+ except Exception as e:
+ logger.debug(e)
return False
except Exception:
return False
+async def poll_meilisearch_running(frequency: int = 0.1) -> bool:
+ """
+ Polls MeiliSearch to see if it is running.
+ """
+ while True:
+ if await check_meilisearch_running():
+ return True
+ await asyncio.sleep(frequency)
+
+
async def start_meilisearch():
"""
Starts the MeiliSearch server, wait for it.
diff --git a/extension/.vscodeignore b/extension/.vscodeignore
index d3326fdc..51d66585 100644
--- a/extension/.vscodeignore
+++ b/extension/.vscodeignore
@@ -24,4 +24,6 @@ react-app/src
scripts/data
scripts/env
-scripts/.continue_env_installed \ No newline at end of file
+scripts/.continue_env_installed
+
+server/exe/** \ No newline at end of file
diff --git a/extension/manual-testing-sandbox/nested-folder/helloNested.py b/extension/manual-testing-sandbox/nested-folder/helloNested.py
new file mode 100644
index 00000000..195ad40c
--- /dev/null
+++ b/extension/manual-testing-sandbox/nested-folder/helloNested.py
@@ -0,0 +1,2 @@
+def main():
+ print("Hello Nested!")
diff --git a/extension/package-lock.json b/extension/package-lock.json
index 6f7d32b2..be88abdd 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "continue",
- "version": "0.0.296",
+ "version": "0.0.301",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "continue",
- "version": "0.0.296",
+ "version": "0.0.301",
"license": "Apache-2.0",
"dependencies": {
"@electron/rebuild": "^3.2.10",
diff --git a/extension/package.json b/extension/package.json
index 93b6e944..91b5606b 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -1,6 +1,7 @@
{
"name": "continue",
"icon": "media/terminal-continue.png",
+ "version": "0.0.301",
"repository": {
"type": "git",
"url": "https://github.com/continuedev/continue"
@@ -14,7 +15,6 @@
"displayName": "Continue",
"pricing": "Free",
"description": "The open-source coding autopilot",
- "version": "0.0.296",
"publisher": "Continue",
"engines": {
"vscode": "^1.67.0"
@@ -194,7 +194,7 @@
"lint": "eslint src --ext ts",
"build-test": "tsc && node esbuild.test.mjs",
"test": "npm run build-test && node ./out/test-runner/runTestOnVSCodeHost.js",
- "package": "npm install && npm run typegen && cd react-app && npm install && npm run build && cd .. && mkdir -p ./build && vsce package --out ./build"
+ "package": "node scripts/package.js"
},
"devDependencies": {
"@nestjs/common": "^8.4.7",
diff --git a/extension/react-app/package.json b/extension/react-app/package.json
index b4762990..23cdf9bb 100644
--- a/extension/react-app/package.json
+++ b/extension/react-app/package.json
@@ -1,11 +1,10 @@
{
"name": "react-app",
"private": true,
- "version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
- "build": "tsc && vite build --sourcemap",
+ "build": "tsc && vite build --sourcemap 'inline'",
"preview": "vite preview"
},
"dependencies": {
@@ -39,4 +38,4 @@
"typescript": "^4.9.3",
"vite": "^4.1.0"
}
-}
+} \ No newline at end of file
diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx
index 879373a0..05b322ff 100644
--- a/extension/react-app/src/App.tsx
+++ b/extension/react-app/src/App.tsx
@@ -11,6 +11,7 @@ import {
setSessionId,
setVscMediaUrl,
setDataSwitchOn,
+ setWorkspacePaths,
} from "./redux/slices/configSlice";
import { setHighlightedCode } from "./redux/slices/miscSlice";
import { postVscMessage } from "./vscode";
@@ -56,6 +57,7 @@ function App() {
dispatch(setSessionId(event.data.sessionId));
dispatch(setVscMediaUrl(event.data.vscMediaUrl));
dispatch(setDataSwitchOn(event.data.dataSwitchOn));
+ dispatch(setWorkspacePaths(event.data.workspacePaths));
break;
case "highlightedCode":
dispatch(setHighlightedCode(event.data.rangeInFile));
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
index 472e1b14..4e564000 100644
--- a/extension/react-app/src/components/ComboBox.tsx
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -24,7 +24,8 @@ import {
setBottomMessage,
setBottomMessageCloseTimeout,
} from "../redux/slices/uiStateSlice";
-import { useDispatch } from "react-redux";
+import { useDispatch, useSelector } from "react-redux";
+import { RootStore } from "../redux/store";
const SEARCH_INDEX_NAME = "continue_context_items";
@@ -136,6 +137,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
const searchClient = new MeiliSearch({ host: "http://127.0.0.1:7700" });
const client = useContext(GUIClientContext);
const dispatch = useDispatch();
+ const workspacePaths = useSelector((state: RootStore) => state.config.workspacePaths);
const [history, setHistory] = React.useState<string[]>([]);
// The position of the current command you are typing now, so the one that will be appended to history once you press enter
@@ -181,10 +183,16 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
// Get search results and return
setCurrentlyInContextQuery(true);
const providerAndQuery = segs[segs.length - 1] || "";
- const [provider, query] = providerAndQuery.split(" ");
+ // Only return context items from the current workspace - the index is currently shared between all sessions
+ const workspaceFilter =
+ workspacePaths && workspacePaths.length > 0
+ ? `workspace_dir IN [ ${workspacePaths.map((path) => `"${path}"`).join(", ")} ]`
+ : undefined;
searchClient
.index(SEARCH_INDEX_NAME)
- .search(providerAndQuery)
+ .search(providerAndQuery, {
+ filter: workspaceFilter,
+ })
.then((res) => {
setItems(
res.hits.map((hit) => {
@@ -410,7 +418,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
// Prevent Downshift's default 'Enter' behavior.
(event.nativeEvent as any).preventDownshiftDefault = true;
- if (props.onEnter) props.onEnter(event);
+ if (props.onEnter) {props.onEnter(event);}
setCurrentlyInContextQuery(false);
} else if (event.key === "Tab" && items.length > 0) {
downshiftProps.setInputValue(items[0].name);
@@ -423,7 +431,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
) {
(event.nativeEvent as any).preventDownshiftDefault = true;
} else if (event.key === "ArrowUp") {
- if (positionInHistory == 0) return;
+ if (positionInHistory == 0) {return;}
else if (
positionInHistory == history.length &&
(history.length === 0 ||
@@ -479,7 +487,8 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
selected={downshiftProps.selectedItem === item}
>
<span>
- {item.name}:{" "}
+ {item.name}
+ {" "}
<span style={{ color: lightGray }}>{item.description}</span>
</span>
</Li>
diff --git a/extension/react-app/src/redux/slices/configSlice.ts b/extension/react-app/src/redux/slices/configSlice.ts
index 59c76066..9cf5402f 100644
--- a/extension/react-app/src/redux/slices/configSlice.ts
+++ b/extension/react-app/src/redux/slices/configSlice.ts
@@ -7,13 +7,13 @@ export const configSlice = createSlice({
apiUrl: "http://localhost:65432",
} as RootStore["config"],
reducers: {
- setWorkspacePath: (
+ setWorkspacePaths: (
state: RootStore["config"],
- action: { type: string; payload: string }
+ action: { type: string; payload: string[] }
) => {
return {
...state,
- workspacePath: action.payload,
+ workspacePaths: action.payload,
};
},
setApiUrl: (
@@ -57,7 +57,7 @@ export const configSlice = createSlice({
export const {
setVscMachineId,
setApiUrl,
- setWorkspacePath,
+ setWorkspacePaths,
setSessionId,
setVscMediaUrl,
setDataSwitchOn,
diff --git a/extension/react-app/src/redux/slices/serverStateReducer.ts b/extension/react-app/src/redux/slices/serverStateReducer.ts
index bd60f1c7..a20476b2 100644
--- a/extension/react-app/src/redux/slices/serverStateReducer.ts
+++ b/extension/react-app/src/redux/slices/serverStateReducer.ts
@@ -9,9 +9,9 @@ const initialState: FullState = {
name: "Welcome to Continue",
hide: false,
description: `- Highlight code section and ask a question or give instructions
- - Use \`cmd+m\` (Mac) / \`ctrl+m\` (Windows) to open Continue
- - Use \`/help\` to ask questions about how to use Continue
- - [Customize Continue](https://continue.dev/docs/customization) (e.g. use your own API key) by typing '/config'.`,
+- Use \`cmd+m\` (Mac) / \`ctrl+m\` (Windows) to open Continue
+- Use \`/help\` to ask questions about how to use Continue
+- [Customize Continue](https://continue.dev/docs/customization) (e.g. use your own API key) by typing '/config'.`,
system_message: null,
chat_context: [],
manage_own_chat_context: false,
diff --git a/extension/react-app/src/redux/store.ts b/extension/react-app/src/redux/store.ts
index a9a45ec1..7959a067 100644
--- a/extension/react-app/src/redux/store.ts
+++ b/extension/react-app/src/redux/store.ts
@@ -14,7 +14,7 @@ export interface ChatMessage {
export interface RootStore {
config: {
- workspacePath: string | undefined;
+ workspacePaths: string[] | undefined;
apiUrl: string;
vscMachineId: string | undefined;
sessionId: string | undefined;
diff --git a/extension/scripts/package.js b/extension/scripts/package.js
new file mode 100644
index 00000000..59da9181
--- /dev/null
+++ b/extension/scripts/package.js
@@ -0,0 +1,42 @@
+const { exec } = require("child_process");
+const fs = require("fs");
+const path = require("path");
+
+exec("npm install", (error) => {
+ if (error) throw error;
+ console.log("npm install completed");
+
+ exec("npm run typegen", (error) => {
+ if (error) throw error;
+ console.log("npm run typegen completed");
+
+ process.chdir("react-app");
+
+ exec("npm install", (error) => {
+ if (error) throw error;
+ console.log("npm install in react-app completed");
+
+ exec("npm run build", (error) => {
+ if (error) throw error;
+ if (!fs.existsSync(path.join("dist", "assets", "index.js"))) {
+ throw new Error("react-app build did not produce index.js");
+ }
+ if (!fs.existsSync(path.join("dist", "assets", "index.css"))) {
+ throw new Error("react-app build did not produce index.css");
+ }
+ console.log("npm run build in react-app completed");
+
+ process.chdir("..");
+
+ if (!fs.existsSync("build")) {
+ fs.mkdirSync("build");
+ }
+
+ exec("vsce package --out ./build patch", (error) => {
+ if (error) throw error;
+ console.log("vsce package completed");
+ });
+ });
+ });
+ });
+});
diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts
index f0e41ca9..748a5984 100644
--- a/extension/src/activation/environmentSetup.ts
+++ b/extension/src/activation/environmentSetup.ts
@@ -240,7 +240,7 @@ export async function startContinuePythonServer() {
windowsHide: true,
});
child.stdout.on("data", (data: any) => {
- console.log(`stdout: ${data}`);
+ // console.log(`stdout: ${data}`);
});
child.stderr.on("data", (data: any) => {
console.log(`stderr: ${data}`);
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index d89093ca..5b9e285d 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -12,7 +12,7 @@ import {
rejectSuggestionCommand,
} from "./suggestions";
import { FileEditWithFullContents } from "../schema/FileEditWithFullContents";
-import * as fs from 'fs';
+import * as fs from "fs";
import { WebsocketMessenger } from "./util/messenger";
import { diffManager } from "./diffs";
const os = require("os");
@@ -30,13 +30,12 @@ class IdeProtocolClient {
private _lastReloadTime: number = 16;
private _reconnectionTimeouts: NodeJS.Timeout[] = [];
- private _sessionId: string | null = null;
+ sessionId: string | null = null;
private _serverUrl: string;
private _newWebsocketMessenger() {
const requestUrl =
- this._serverUrl +
- (this._sessionId ? `?session_id=${this._sessionId}` : "");
+ this._serverUrl + (this.sessionId ? `?session_id=${this.sessionId}` : "");
const messenger = new WebsocketMessenger(requestUrl);
this.messenger = messenger;
@@ -383,7 +382,9 @@ class IdeProtocolClient {
async getUserSecret(key: string) {
// Check if secret already exists in VS Code settings (global)
let secret = vscode.workspace.getConfiguration("continue").get(key);
- if (typeof secret !== "undefined" && secret !== null) {return secret;}
+ if (typeof secret !== "undefined" && secret !== null) {
+ return secret;
+ }
// If not, ask user for secret
secret = await vscode.window.showInputBox({
@@ -420,7 +421,7 @@ class IdeProtocolClient {
console.log("Getting session ID");
const resp = await this.messenger?.sendAndReceive("getSessionId", {});
console.log("New Continue session with ID: ", resp.sessionId);
- this._sessionId = resp.sessionId;
+ this.sessionId = resp.sessionId;
return resp.sessionId;
}
diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts
index 61ff455a..dbec45ea 100644
--- a/extension/src/debugPanel.ts
+++ b/extension/src/debugPanel.ts
@@ -67,7 +67,7 @@ class WebsocketConnection {
export let debugPanelWebview: vscode.Webview | undefined;
export function setupDebugPanel(
panel: vscode.WebviewPanel | vscode.WebviewView,
- sessionIdPromise: Promise<string> | string
+ sessionIdPromise: Promise<string>
): string {
debugPanelWebview = panel.webview;
panel.onDidDispose(() => {
@@ -180,16 +180,14 @@ export function setupDebugPanel(
panel.webview.onDidReceiveMessage(async (data) => {
switch (data.type) {
case "onLoad": {
- let sessionId: string;
- if (typeof sessionIdPromise === "string") {
- sessionId = sessionIdPromise;
- } else {
- sessionId = await sessionIdPromise;
- }
+ const sessionId = await sessionIdPromise;
panel.webview.postMessage({
type: "onLoad",
vscMachineId: vscode.env.machineId,
apiUrl: getContinueServerUrl(),
+ workspacePaths: vscode.workspace.workspaceFolders?.map(
+ (folder) => folder.uri.fsPath
+ ),
sessionId,
vscMediaUrl,
dataSwitchOn: vscode.workspace
@@ -323,9 +321,9 @@ export class ContinueGUIWebviewViewProvider
implements vscode.WebviewViewProvider
{
public static readonly viewType = "continue.continueGUIView";
- private readonly sessionIdPromise: Promise<string> | string;
+ private readonly sessionIdPromise: Promise<string>;
- constructor(sessionIdPromise: Promise<string> | string) {
+ constructor(sessionIdPromise: Promise<string>) {
this.sessionIdPromise = sessionIdPromise;
}
diff --git a/extension/src/test-runner/runTestOnVSCodeHost.ts b/extension/src/test-runner/runTestOnVSCodeHost.ts
index 2a542ffc..21267c2d 100644
--- a/extension/src/test-runner/runTestOnVSCodeHost.ts
+++ b/extension/src/test-runner/runTestOnVSCodeHost.ts
@@ -11,7 +11,10 @@ async function main() {
// The path to test runner
// Passed to --extensionTestsPath
- const extensionTestsPath = path.resolve(extensionDevelopmentPath, "out/test-runner/mochaRunner");
+ const extensionTestsPath = path.resolve(
+ extensionDevelopmentPath,
+ "out/test-runner/mochaRunner"
+ );
// Download VS Code, unzip it and run the integration test
await runTests({ extensionDevelopmentPath, extensionTestsPath });
diff --git a/extension/src/test-suite/environmentSetup.test.ts b/extension/src/test-suite/environmentSetup.test.ts
index 9a478522..d0406340 100644
--- a/extension/src/test-suite/environmentSetup.test.ts
+++ b/extension/src/test-suite/environmentSetup.test.ts
@@ -2,18 +2,27 @@ import { test, describe } from "mocha";
import * as assert from "assert";
import { getContinueServerUrl } from "../bridge";
-import { startContinuePythonServer } from "../activation/environmentSetup";
+import { ideProtocolClient } from "../activation/activate";
import fetch from "node-fetch";
+import fkill from "fkill";
describe("Can start python server", () => {
test("Can start python server in under 10 seconds", async function () {
- this.timeout(17_000);
- await startContinuePythonServer();
+ const allowedTime = 25_000;
+ this.timeout(allowedTime + 10_000);
+ try {
+ fkill(65432, { force: true, silent: true });
+ console.log("Killed existing server");
+ } catch (e) {
+ console.log("No existing server: ", e);
+ }
- await new Promise((resolve) => setTimeout(resolve, 15_000));
+ // If successful, the server is started by the extension while we wait
+ await new Promise((resolve) => setTimeout(resolve, allowedTime));
// Check if server is running
const serverUrl = getContinueServerUrl();
+ console.log("Server URL: ", serverUrl);
const response = await fetch(`${serverUrl}/health`);
assert.equal(response.status, 200);
});