Skip to content

Commit 1469e0f

Browse files
authored
Merge pull request continuedev#5059 from continuedev/nate/local-blocks
Nate/local-blocks
2 parents 3cb27e4 + 31ea4be commit 1469e0f

30 files changed

+1141
-260
lines changed

.github/workflows/pr_checks.yaml

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
name: PR checks
22

3+
# PR Checks run on all PRs to the main branch and must pass before merging.
34
on:
45
pull_request:
56
branches:
@@ -415,6 +416,30 @@ jobs:
415416
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
416417
AZURE_FOUNDRY_API_KEY: ${{ secrets.AZURE_FOUNDRY_API_KEY }}
417418

419+
package-tests:
420+
runs-on: ubuntu-latest
421+
steps:
422+
- uses: actions/checkout@v4
423+
424+
- uses: actions/setup-node@v4
425+
with:
426+
node-version-file: ".nvmrc"
427+
428+
- uses: actions/cache@v4
429+
with:
430+
path: core/node_modules
431+
key: ${{ runner.os }}-config-yaml-node-modules-${{ hashFiles('packages/config-yaml/package-lock.json') }}
432+
433+
- name: Install dependencies
434+
run: |
435+
cd packages/config-yaml
436+
npm ci
437+
438+
- name: Run config-yaml tests
439+
run: |
440+
cd packages/config-yaml
441+
npm test
442+
418443
vscode-get-test-file-matrix:
419444
runs-on: ubuntu-latest
420445
needs: [install-root, install-vscode, install-config-yaml]
@@ -551,7 +576,7 @@ jobs:
551576
path: extensions/vscode/e2e/storage
552577

553578
vscode-e2e-tests:
554-
name: ${{ matrix.test_file }} (${{ matrix.command }})
579+
name: ${{ matrix.test_file || 'unknown' }} (${{ matrix.command }})
555580
needs:
556581
[
557582
vscode-download-e2e-dependencies,
@@ -652,6 +677,27 @@ jobs:
652677
name: e2e-failure-screenshots
653678
path: extensions/vscode/e2e/storage/screenshots
654679

680+
- name: Sanitize test file name
681+
id: sanitize_filename
682+
if: always()
683+
run: |
684+
FILENAME="${{ matrix.test_file }}"
685+
SANITIZED_FILENAME="${FILENAME//\//-}" # Replace / with - using bash parameter expansion
686+
echo "sanitized_test_file=${SANITIZED_FILENAME}" >> $GITHUB_OUTPUT
687+
688+
- name: Find e2e log file
689+
if: always()
690+
run: |
691+
echo "Searching for e2e.log file..."
692+
find $GITHUB_WORKSPACE -name "e2e.log" -type f
693+
694+
- name: Upload e2e logs
695+
if: always()
696+
uses: actions/upload-artifact@v4
697+
with:
698+
name: e2e-logs-${{ steps.sanitize_filename.outputs.sanitized_test_file || 'unknown' }}-${{ matrix.command == 'e2e:ci:run-yaml' && 'yaml' || 'json' }}
699+
path: extensions/vscode/e2e.log
700+
655701
gui-tests:
656702
needs: [install-gui, install-core, install-config-yaml]
657703
runs-on: ubuntu-latest
@@ -820,6 +866,7 @@ jobs:
820866
- install-vscode
821867
- vscode-checks
822868
- core-tests
869+
- package-tests
823870
- vscode-get-test-file-matrix
824871
- vscode-package-extension
825872
- vscode-download-e2e-dependencies

.gitignore

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,6 @@ ipython_config.py
8383
# pyenv
8484
.python-version
8585

86-
# pipenv
87-
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
88-
# However, in case of collaboration, if having platform-specific dependencies or dependencies
89-
# having no cross-platform support, pipenv may install dependencies that don't work, or not
90-
# install all needed dependencies.
91-
#Pipfile.lock
92-
93-
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
94-
__pypackages__/
95-
9686
# Celery stuff
9787
celerybeat-schedule
9888
celerybeat.pid

.idea/compiler.xml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/config/ConfigHandler.ts

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ import {
1515
} from "../index.js";
1616
import { GlobalContext } from "../util/GlobalContext.js";
1717

18-
import { getAllAssistantFiles } from "./loadLocalAssistants.js";
18+
import { logger } from "../util/logger.js";
19+
import {
20+
ASSISTANTS,
21+
getAllDotContinueYamlFiles,
22+
LoadAssistantFilesOptions,
23+
} from "./loadLocalAssistants.js";
1924
import LocalProfileLoader from "./profile/LocalProfileLoader.js";
2025
import PlatformProfileLoader from "./profile/PlatformProfileLoader.js";
2126
import {
@@ -185,7 +190,11 @@ export class ConfigHandler {
185190
private async getNonPersonalHubOrg(
186191
org: OrganizationDescription,
187192
): Promise<OrgWithProfiles> {
188-
const profiles = await this.getHubProfiles(org.id);
193+
const localProfiles = await this.getLocalProfiles({
194+
includeGlobal: false,
195+
includeWorkspace: true,
196+
});
197+
const profiles = [...(await this.getHubProfiles(org.id)), ...localProfiles];
189198
return this.rectifyProfilesForOrg(org, profiles);
190199
}
191200

@@ -196,15 +205,21 @@ export class ConfigHandler {
196205
slug: undefined,
197206
};
198207
private async getPersonalHubOrg() {
199-
const allLocalProfiles = await this.getAllLocalProfiles();
208+
const localProfiles = await this.getLocalProfiles({
209+
includeGlobal: true,
210+
includeWorkspace: true,
211+
});
200212
const hubProfiles = await this.getHubProfiles(null);
201-
const profiles = [...hubProfiles, ...allLocalProfiles];
213+
const profiles = [...hubProfiles, ...localProfiles];
202214
return this.rectifyProfilesForOrg(this.PERSONAL_ORG_DESC, profiles);
203215
}
204216

205217
private async getLocalOrg() {
206-
const allLocalProfiles = await this.getAllLocalProfiles();
207-
return this.rectifyProfilesForOrg(this.PERSONAL_ORG_DESC, allLocalProfiles);
218+
const localProfiles = await this.getLocalProfiles({
219+
includeGlobal: true,
220+
includeWorkspace: true,
221+
});
222+
return this.rectifyProfilesForOrg(this.PERSONAL_ORG_DESC, localProfiles);
208223
}
209224

210225
private async rectifyProfilesForOrg(
@@ -251,24 +266,41 @@ export class ConfigHandler {
251266
};
252267
}
253268

254-
async getAllLocalProfiles() {
269+
async getLocalProfiles(options: LoadAssistantFilesOptions) {
255270
/**
256271
* Users can define as many local assistants as they want in a `.continue/assistants` folder
257272
*/
258-
const assistantFiles = await getAllAssistantFiles(this.ide);
259-
const profiles = assistantFiles.map((assistant) => {
260-
return new LocalProfileLoader(
273+
const localProfiles: ProfileLifecycleManager[] = [];
274+
275+
if (options.includeGlobal) {
276+
localProfiles.push(this.globalLocalProfileManager);
277+
}
278+
279+
if (options.includeWorkspace) {
280+
const assistantFiles = await getAllDotContinueYamlFiles(
261281
this.ide,
262-
this.ideSettingsPromise,
263-
this.controlPlaneClient,
264-
this.llmLogger,
265-
assistant,
282+
{
283+
...options,
284+
includeGlobal: false, // Because the global comes from above
285+
},
286+
ASSISTANTS,
266287
);
267-
});
268-
const localAssistantProfiles = profiles.map(
269-
(profile) => new ProfileLifecycleManager(profile, this.ide),
270-
);
271-
return [this.globalLocalProfileManager, ...localAssistantProfiles];
288+
const profiles = assistantFiles.map((assistant) => {
289+
return new LocalProfileLoader(
290+
this.ide,
291+
this.ideSettingsPromise,
292+
this.controlPlaneClient,
293+
this.llmLogger,
294+
assistant,
295+
);
296+
});
297+
const localAssistantProfiles = profiles.map(
298+
(profile) => new ProfileLifecycleManager(profile, this.ide),
299+
);
300+
localProfiles.push(...localAssistantProfiles);
301+
}
302+
303+
return localProfiles;
272304
}
273305

274306
//////////////////
@@ -421,9 +453,14 @@ export class ConfigHandler {
421453
configLoadInterrupted: true,
422454
};
423455
}
424-
return await this.currentProfile.loadConfig(
456+
const config = await this.currentProfile.loadConfig(
425457
this.additionalContextProviders,
426458
);
459+
460+
if (config.errors?.length) {
461+
logger.warn("Errors loading config: ", config.errors);
462+
}
463+
return config;
427464
}
428465

429466
async openConfigProfile(profileId?: string) {

core/config/ProfileLifecycleManager.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ export class ProfileLifecycleManager {
9595
try {
9696
result = await this.profileLoader.doLoadConfig();
9797
} catch (e) {
98-
const message = e instanceof Error ? e.message : "Error loading config";
98+
const message =
99+
e instanceof Error
100+
? `${e.message}\n${e.stack ? e.stack : ""}`
101+
: "Error loading config";
99102
result = {
100103
errors: [
101104
{

core/config/loadLocalAssistants.ts

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import {
55
DEFAULT_IGNORE_FILETYPES,
66
} from "../indexing/ignore";
77
import { walkDir } from "../indexing/walkDir";
8-
import { getGlobalAssistantsPath } from "../util/paths";
8+
import { getGlobalFolderWithName } from "../util/paths";
99
import { localPathToUri } from "../util/pathToUri";
1010
import { joinPathsToUri } from "../util/uri";
1111

12-
export const ASSISTANTS_FOLDER = ".continue/assistants";
12+
export const ASSISTANTS = "assistants";
13+
export const ASSISTANTS_FOLDER = `.continue/${ASSISTANTS}`;
1314

1415
export function isLocalAssistantFile(uri: string): boolean {
1516
if (!uri.endsWith(".yaml") && !uri.endsWith(".yml")) {
@@ -20,7 +21,7 @@ export function isLocalAssistantFile(uri: string): boolean {
2021
return normalizedUri.includes(`/${ASSISTANTS_FOLDER}/`);
2122
}
2223

23-
export async function getAssistantFilesFromDir(
24+
export async function listYamlFilesInDir(
2425
ide: IDE,
2526
dir: string,
2627
): Promise<{ path: string; content: string }[]> {
@@ -53,21 +54,56 @@ export async function getAssistantFilesFromDir(
5354
}
5455
}
5556

56-
export async function getAllAssistantFiles(
57+
export interface LoadAssistantFilesOptions {
58+
includeGlobal: boolean;
59+
includeWorkspace: boolean;
60+
}
61+
62+
export function getDotContinueSubDirs(
5763
ide: IDE,
64+
options: LoadAssistantFilesOptions,
65+
workspaceDirs: string[],
66+
subDirName: string,
67+
): string[] {
68+
let fullDirs: string[] = [];
69+
70+
// Workspace .continue/<subDirName>
71+
if (options.includeWorkspace) {
72+
fullDirs = workspaceDirs.map((dir) =>
73+
joinPathsToUri(dir, ".continue", subDirName),
74+
);
75+
}
76+
77+
// ~/.continue/<subDirName>
78+
if (options.includeGlobal) {
79+
fullDirs.push(localPathToUri(getGlobalFolderWithName(subDirName)));
80+
}
81+
82+
return fullDirs;
83+
}
84+
85+
/**
86+
* This method searches in both ~/.continue and workspace .continue
87+
* for all YAML files in the specified subdirctory, for example .continue/assistants or .continue/prompts
88+
*/
89+
export async function getAllDotContinueYamlFiles(
90+
ide: IDE,
91+
options: LoadAssistantFilesOptions,
92+
subDirName: string,
5893
): Promise<{ path: string; content: string }[]> {
5994
const workspaceDirs = await ide.getWorkspaceDirs();
60-
let assistantFiles: { path: string; content: string }[] = [];
61-
62-
let dirsToCheck = [ASSISTANTS_FOLDER];
63-
const fullDirs = workspaceDirs
64-
.map((dir) => dirsToCheck.map((d) => joinPathsToUri(dir, d)))
65-
.flat();
6695

67-
fullDirs.push(localPathToUri(getGlobalAssistantsPath()));
96+
// Get all directories to check for assistant files
97+
const fullDirs = getDotContinueSubDirs(
98+
ide,
99+
options,
100+
workspaceDirs,
101+
subDirName,
102+
);
68103

69-
assistantFiles = (
70-
await Promise.all(fullDirs.map((dir) => getAssistantFilesFromDir(ide, dir)))
104+
// Get all assistant files from the directories
105+
const assistantFiles = (
106+
await Promise.all(fullDirs.map((dir) => listYamlFilesInDir(ide, dir)))
71107
).flat();
72108

73109
return await Promise.all(

core/config/profile/LocalProfileLoader.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,14 @@ export default class LocalProfileLoader implements IProfileLoader {
6060
ideSettingsPromise: this.ideSettingsPromise,
6161
controlPlaneClient: this.controlPlaneClient,
6262
llmLogger: this.llmLogger,
63-
overrideConfigJson: undefined,
64-
overrideConfigYaml: undefined,
65-
platformConfigMetadata: undefined,
6663
profileId: this.description.id,
6764
overrideConfigYamlByPath: this.overrideAssistantFile?.path,
6865
orgScopeId: null,
66+
packageIdentifier: {
67+
uriType: "file",
68+
filePath:
69+
this.overrideAssistantFile?.path ?? getPrimaryConfigFilePath(),
70+
},
6971
});
7072

7173
this.description.errors = result.errors;

core/config/profile/PlatformProfileLoader.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,17 @@ export default class PlatformProfileLoader implements IProfileLoader {
142142
ideSettingsPromise: this.ideSettingsPromise,
143143
controlPlaneClient: this.controlPlaneClient,
144144
llmLogger: this.llmLogger,
145-
overrideConfigJson: undefined,
146145
overrideConfigYaml: this.configResult.config,
147-
platformConfigMetadata: {
148-
ownerSlug: this.ownerSlug,
149-
packageSlug: this.packageSlug,
150-
},
151146
profileId: this.description.id,
152-
overrideConfigYamlByPath: undefined,
153147
orgScopeId: this.orgScopeId,
148+
packageIdentifier: {
149+
uriType: "slug",
150+
fullSlug: {
151+
ownerSlug: this.ownerSlug,
152+
packageSlug: this.packageSlug,
153+
versionSlug: this.versionSlug,
154+
},
155+
},
154156
});
155157

156158
return {

0 commit comments

Comments
 (0)