Skip to content

Commit edcf5b3

Browse files
committed
load multiple local assistants
1 parent 6bc7c55 commit edcf5b3

File tree

15 files changed

+135
-77
lines changed

15 files changed

+135
-77
lines changed

core/config/ConfigHandler.ts

+27-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import * as fs from "node:fs";
2-
31
import { ConfigResult, FullSlug } from "@continuedev/config-yaml";
42

53
import {
@@ -17,8 +15,6 @@ import {
1715
} from "../index.js";
1816
import Ollama from "../llm/llms/Ollama.js";
1917
import { GlobalContext } from "../util/GlobalContext.js";
20-
import { getConfigJsonPath, getConfigYamlPath } from "../util/paths.js";
21-
import { localPathToUri } from "../util/pathToUri.js";
2218

2319
import { getAllAssistantFiles } from "./getSystemPromptDotFile.js";
2420
import {
@@ -98,7 +94,7 @@ export class ConfigHandler {
9894
this.ideSettingsPromise,
9995
this.controlPlaneClient,
10096
this.writeLog,
101-
assistant.path,
97+
assistant,
10298
);
10399
});
104100
return profiles.map(
@@ -162,14 +158,12 @@ export class ConfigHandler {
162158

163159
async openConfigProfile(profileId?: string) {
164160
let openProfileId = profileId || this.selectedProfileId;
165-
if (openProfileId === "local") {
161+
const profile = this.profiles?.find(
162+
(p) => p.profileDescription.id === openProfileId,
163+
);
164+
if (profile?.profileDescription.profileType === "local") {
166165
const ideInfo = await this.ide.getIdeInfo();
167-
const configYamlPath = getConfigYamlPath(ideInfo.ideType);
168-
if (fs.existsSync(configYamlPath)) {
169-
await this.ide.openFile(localPathToUri(configYamlPath));
170-
} else {
171-
await this.ide.openFile(localPathToUri(getConfigJsonPath()));
172-
}
166+
await this.ide.openFile(profile.profileDescription.uri);
173167
} else {
174168
const env = await getControlPlaneEnv(this.ide.getIdeSettings());
175169
await this.ide.openUrl(`${env.APP_URL}${openProfileId}`);
@@ -194,25 +188,27 @@ export class ConfigHandler {
194188
const assistants =
195189
await this.controlPlaneClient.listAssistants(selectedOrgId);
196190

197-
const hubProfiles = assistants.map((assistant) => {
198-
const profileLoader = new PlatformProfileLoader(
199-
{
200-
...assistant.configResult,
201-
config: assistant.configResult.config,
202-
},
203-
assistant.ownerSlug,
204-
assistant.packageSlug,
205-
assistant.iconUrl,
206-
assistant.configResult.config?.version ?? "latest",
207-
this.controlPlaneClient,
208-
this.ide,
209-
this.ideSettingsPromise,
210-
this.writeLog,
211-
this.reloadConfig.bind(this),
212-
);
213-
214-
return new ProfileLifecycleManager(profileLoader, this.ide);
215-
});
191+
const hubProfiles = await Promise.all(
192+
assistants.map(async (assistant) => {
193+
const profileLoader = await PlatformProfileLoader.create(
194+
{
195+
...assistant.configResult,
196+
config: assistant.configResult.config,
197+
},
198+
assistant.ownerSlug,
199+
assistant.packageSlug,
200+
assistant.iconUrl,
201+
assistant.configResult.config?.version ?? "latest",
202+
this.controlPlaneClient,
203+
this.ide,
204+
this.ideSettingsPromise,
205+
this.writeLog,
206+
this.reloadConfig.bind(this),
207+
);
208+
209+
return new ProfileLifecycleManager(profileLoader, this.ide);
210+
}),
211+
);
216212

217213
if (selectedOrgId === null) {
218214
// Personal

core/config/ProfileLifecycleManager.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface ProfileDescription {
2121
id: string;
2222
iconUrl: string;
2323
errors: ConfigValidationError[] | undefined;
24+
uri: string;
2425
}
2526

2627
export interface OrganizationDescription {

core/config/profile/ControlPlaneProfileLoader.ts

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from "../../index.js";
1111
import { ProfileDescription } from "../ProfileLifecycleManager.js";
1212

13+
import { PRODUCTION_ENV } from "../../control-plane/env.js";
1314
import doLoadConfig from "./doLoadConfig.js";
1415
import { IProfileLoader } from "./IProfileLoader.js";
1516

@@ -40,6 +41,7 @@ export default class ControlPlaneProfileLoader implements IProfileLoader {
4041
},
4142
title: workspaceTitle,
4243
errors: undefined,
44+
uri: `${PRODUCTION_ENV.APP_URL}workspaces/${workspaceId}`,
4345
};
4446

4547
setInterval(async () => {

core/config/profile/LocalProfileLoader.ts

+36-16
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,54 @@
1-
import { ConfigResult } from "@continuedev/config-yaml";
1+
import { ConfigResult, parseConfigYaml } from "@continuedev/config-yaml";
22

33
import { ControlPlaneClient } from "../../control-plane/client.js";
44
import { ContinueConfig, IDE, IdeSettings } from "../../index.js";
55
import { ProfileDescription } from "../ProfileLifecycleManager.js";
66

7+
import { getPrimaryConfigFilePath } from "../../util/paths.js";
8+
import { localPathToUri } from "../../util/pathToUri.js";
79
import doLoadConfig from "./doLoadConfig.js";
810
import { IProfileLoader } from "./IProfileLoader.js";
911

1012
export default class LocalProfileLoader implements IProfileLoader {
1113
static ID = "local";
12-
description: ProfileDescription = {
13-
id: LocalProfileLoader.ID,
14-
profileType: "local",
15-
fullSlug: {
16-
ownerSlug: "",
17-
packageSlug: "",
18-
versionSlug: "",
19-
},
20-
iconUrl: "",
21-
title: "Local Config",
22-
errors: undefined,
23-
};
2414

2515
constructor(
2616
private ide: IDE,
2717
private ideSettingsPromise: Promise<IdeSettings>,
2818
private controlPlaneClient: ControlPlaneClient,
2919
private writeLog: (message: string) => Promise<void>,
30-
private overrideConfigYamlByPath?: string | undefined,
31-
) {}
20+
private overrideAssistantFile?:
21+
| { path: string; content: string }
22+
| undefined,
23+
) {
24+
const description: ProfileDescription = {
25+
id: overrideAssistantFile?.path ?? LocalProfileLoader.ID,
26+
profileType: "local",
27+
fullSlug: {
28+
ownerSlug: "",
29+
packageSlug: "",
30+
versionSlug: "",
31+
},
32+
iconUrl: "",
33+
title: overrideAssistantFile?.path ?? "Local Config",
34+
errors: undefined,
35+
uri:
36+
overrideAssistantFile?.path ??
37+
localPathToUri(getPrimaryConfigFilePath()),
38+
};
39+
this.description = description;
40+
if (overrideAssistantFile?.content) {
41+
try {
42+
const parsedAssistant = parseConfigYaml(
43+
overrideAssistantFile?.content ?? "",
44+
);
45+
this.description.title = parsedAssistant.name;
46+
} catch (e) {
47+
console.error("Failed to parse assistant file: ", e);
48+
}
49+
}
50+
}
51+
description: ProfileDescription;
3252

3353
async doLoadConfig(): Promise<ConfigResult<ContinueConfig>> {
3454
const result = await doLoadConfig(
@@ -40,7 +60,7 @@ export default class LocalProfileLoader implements IProfileLoader {
4060
undefined,
4161
undefined,
4262
this.description.id,
43-
this.overrideConfigYamlByPath,
63+
this.overrideAssistantFile?.path,
4464
);
4565

4666
this.description.errors = result.errors;

core/config/profile/PlatformProfileLoader.ts

+38-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ContinueConfig, IDE, IdeSettings } from "../../index.js";
55

66
import { ProfileDescription } from "../ProfileLifecycleManager.js";
77

8+
import { getControlPlaneEnv } from "../../control-plane/env.js";
89
import doLoadConfig from "./doLoadConfig.js";
910
import { IProfileLoader } from "./IProfileLoader.js";
1011

@@ -21,21 +22,35 @@ export interface PlatformConfigMetadata {
2122
export default class PlatformProfileLoader implements IProfileLoader {
2223
static RELOAD_INTERVAL = 1000 * 5; // 5 seconds
2324

24-
description: ProfileDescription;
25-
26-
constructor(
25+
private constructor(
2726
private configResult: ConfigResult<AssistantUnrolled>,
2827
private readonly ownerSlug: string,
2928
private readonly packageSlug: string,
3029
private readonly iconUrl: string,
31-
versionSlug: string,
30+
private readonly versionSlug: string,
3231
private readonly controlPlaneClient: ControlPlaneClient,
3332
private readonly ide: IDE,
3433
private ideSettingsPromise: Promise<IdeSettings>,
3534
private writeLog: (message: string) => Promise<void>,
3635
private readonly onReload: () => void,
37-
) {
38-
this.description = {
36+
readonly description: ProfileDescription,
37+
) {}
38+
39+
static async create(
40+
configResult: ConfigResult<AssistantUnrolled>,
41+
ownerSlug: string,
42+
packageSlug: string,
43+
iconUrl: string,
44+
versionSlug: string,
45+
controlPlaneClient: ControlPlaneClient,
46+
ide: IDE,
47+
ideSettingsPromise: Promise<IdeSettings>,
48+
writeLog: (message: string) => Promise<void>,
49+
onReload: () => void,
50+
): Promise<PlatformProfileLoader> {
51+
const controlPlaneEnv = await getControlPlaneEnv(ideSettingsPromise);
52+
53+
const description: ProfileDescription = {
3954
id: `${ownerSlug}/${packageSlug}`,
4055
profileType: "platform",
4156
fullSlug: {
@@ -45,8 +60,23 @@ export default class PlatformProfileLoader implements IProfileLoader {
4560
},
4661
title: configResult.config?.name ?? `${ownerSlug}/${packageSlug}`,
4762
errors: configResult.errors,
48-
iconUrl: this.iconUrl,
63+
iconUrl: iconUrl,
64+
uri: `${controlPlaneEnv}${ownerSlug}/${packageSlug}`,
4965
};
66+
67+
return new PlatformProfileLoader(
68+
configResult,
69+
ownerSlug,
70+
packageSlug,
71+
iconUrl,
72+
versionSlug,
73+
controlPlaneClient,
74+
ide,
75+
ideSettingsPromise,
76+
writeLog,
77+
onReload,
78+
description,
79+
);
5080
}
5181

5282
async doLoadConfig(): Promise<ConfigResult<ContinueConfig>> {
@@ -70,7 +100,7 @@ export default class PlatformProfileLoader implements IProfileLoader {
70100
packageSlug: this.packageSlug,
71101
},
72102
this.description.id,
73-
undefined
103+
undefined,
74104
);
75105

76106
return {

core/config/profile/doLoadConfig.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ import { getControlPlaneEnv } from "../../control-plane/env.js";
1919
import { TeamAnalytics } from "../../control-plane/TeamAnalytics.js";
2020
import ContinueProxy from "../../llm/llms/stubs/ContinueProxy";
2121
import { getConfigJsonPath, getConfigYamlPath } from "../../util/paths";
22+
import { localPathOrUriToPath } from "../../util/pathToUri";
2223
import { Telemetry } from "../../util/posthog";
2324
import { TTS } from "../../util/tts";
2425
import { loadContinueConfigFromJson } from "../load";
2526
import { migrateJsonSharedConfig } from "../migrateSharedConfig";
27+
import { rectifySelectedModelsFromGlobalContext } from "../selectedModels";
2628
import { loadContinueConfigFromYaml } from "../yaml/loadYaml";
2729
import { PlatformConfigMetadata } from "./PlatformProfileLoader";
28-
import { rectifySelectedModelsFromGlobalContext } from "../selectedModels";
2930

3031
export default async function doLoadConfig(
3132
ide: IDE,
@@ -36,7 +37,7 @@ export default async function doLoadConfig(
3637
overrideConfigYaml: AssistantUnrolled | undefined,
3738
platformConfigMetadata: PlatformConfigMetadata | undefined,
3839
profileId: string,
39-
overrideConfigYamlByPath: string | undefined
40+
overrideConfigYamlByPath: string | undefined,
4041
): Promise<ConfigResult<ContinueConfig>> {
4142
const workspaceConfigs = await getWorkspaceConfigs(ide);
4243
const ideInfo = await ide.getIdeInfo();
@@ -51,7 +52,9 @@ export default async function doLoadConfig(
5152
migrateJsonSharedConfig(configJsonPath, ide);
5253
}
5354

54-
const configYamlPath = overrideConfigYamlByPath || getConfigYamlPath(ideInfo.ideType);
55+
const configYamlPath = localPathOrUriToPath(
56+
overrideConfigYamlByPath || getConfigYamlPath(ideInfo.ideType),
57+
);
5558

5659
let newConfig: ContinueConfig | undefined;
5760
let errors: ConfigValidationError[] | undefined;
@@ -69,6 +72,7 @@ export default async function doLoadConfig(
6972
overrideConfigYaml,
7073
platformConfigMetadata,
7174
controlPlaneClient,
75+
configYamlPath,
7276
);
7377
newConfig = result.config;
7478
errors = result.errors;

core/config/yaml/loadYaml.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,14 @@ export async function loadContinueConfigFromYaml(
366366
overrideConfigYaml: AssistantUnrolled | undefined,
367367
platformConfigMetadata: PlatformConfigMetadata | undefined,
368368
controlPlaneClient: ControlPlaneClient,
369+
configYamlPath: string | undefined,
369370
): Promise<ConfigResult<ContinueConfig>> {
370371
const rawYaml =
371372
overrideConfigYaml === undefined
372-
? fs.readFileSync(getConfigYamlPath(ideInfo.ideType), "utf-8")
373+
? fs.readFileSync(
374+
configYamlPath ?? getConfigYamlPath(ideInfo.ideType),
375+
"utf-8",
376+
)
373377
: "";
374378

375379
const configYamlResult = await loadConfigYaml(

core/control-plane/env.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const WORKOS_CLIENT_ID_STAGING = "client_01J0FW6XCPMJMQ3CG51RB4HBZQ";
1818
const WORKOS_ENV_ID_PRODUCTION = "continue";
1919
const WORKOS_ENV_ID_STAGING = "continue-staging";
2020

21-
const PRODUCTION_ENV: ControlPlaneEnv = {
21+
export const PRODUCTION_ENV: ControlPlaneEnv = {
2222
DEFAULT_CONTROL_PLANE_PROXY_URL:
2323
"https://control-plane-api-service-i3dqylpbqa-uc.a.run.app/",
2424
CONTROL_PLANE_URL:

core/util/paths.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export function getConfigJsonPath(ideType: IdeType = "vscode"): string {
103103
return p;
104104
}
105105

106-
export function getConfigYamlPath(ideType: IdeType): string {
106+
export function getConfigYamlPath(ideType?: IdeType): string {
107107
const p = path.join(getContinueGlobalPath(), "config.yaml");
108108
// if (!fs.existsSync(p)) {
109109
// if (ideType === "jetbrains") {
@@ -115,6 +115,14 @@ export function getConfigYamlPath(ideType: IdeType): string {
115115
return p;
116116
}
117117

118+
export function getPrimaryConfigFilePath(): string {
119+
const configYamlPath = getConfigYamlPath();
120+
if (fs.existsSync(configYamlPath)) {
121+
return configYamlPath;
122+
}
123+
return getConfigJsonPath();
124+
}
125+
118126
export function getConfigTsPath(): string {
119127
const p = path.join(getContinueGlobalPath(), "config.ts");
120128
if (!fs.existsSync(p)) {

gui/src/components/AddModelButtonSubtext.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function AddModelButtonSubtext() {
1212
className="cursor-pointer underline"
1313
onClick={() =>
1414
ideMessenger.post("config/openProfile", {
15-
profileId: "local",
15+
profileId: undefined,
1616
})
1717
}
1818
>

0 commit comments

Comments
 (0)