Skip to content

Commit 963d1eb

Browse files
committed
merge: main
2 parents 50adea3 + 447ac6d commit 963d1eb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1332
-53
lines changed

core/config/ConfigHandler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,12 +360,12 @@ export class ConfigHandler {
360360
if (options.includeWorkspace) {
361361
const assistantFiles = await getAllDotContinueDefinitionFiles(
362362
this.ide,
363-
options,
363+
{ ...options, fileExtType: "yaml" },
364364
"assistants",
365365
);
366366
const agentFiles = await getAllDotContinueDefinitionFiles(
367367
this.ide,
368-
options,
368+
{ ...options, fileExtType: "yaml" },
369369
"agents",
370370
);
371371
const profiles = [...assistantFiles, ...agentFiles].map((assistant) => {

core/config/load.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@ async function intermediateToFinalConfig({
514514
autocomplete: [...tabAutocompleteModels],
515515
embed: newEmbedder ? [newEmbedder] : [],
516516
rerank: newReranker ? [newReranker] : [],
517+
subagent: [],
517518
},
518519
selectedModelByRole: {
519520
chat: null, // Not implemented (uses GUI defaultModel)
@@ -523,6 +524,7 @@ async function intermediateToFinalConfig({
523524
autocomplete: null,
524525
rerank: newReranker ?? null,
525526
summarize: null, // Not implemented
527+
subagent: null,
526528
},
527529
rules: [],
528530
};
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {
2+
ConfigValidationError,
3+
parseMarkdownRule,
4+
} from "@continuedev/config-yaml";
5+
import z from "zod";
6+
import { IDE, Skill } from "../..";
7+
import { walkDir } from "../../indexing/walkDir";
8+
import { localPathToUri } from "../../util/pathToUri";
9+
import { getGlobalFolderWithName } from "../../util/paths";
10+
import { findUriInDirs, joinPathsToUri } from "../../util/uri";
11+
import { getAllDotContinueDefinitionFiles } from "../loadLocalAssistants";
12+
13+
const skillFrontmatterSchema = z.object({
14+
name: z.string().min(1),
15+
description: z.string().min(1),
16+
});
17+
18+
const SKILLS_DIR = "skills";
19+
20+
/**
21+
* Get skills from .claude/skills directory
22+
*/
23+
async function getClaudeSkillsDir(ide: IDE) {
24+
const fullDirs = (await ide.getWorkspaceDirs()).map((dir) =>
25+
joinPathsToUri(dir, ".claude", SKILLS_DIR),
26+
);
27+
28+
fullDirs.push(localPathToUri(getGlobalFolderWithName(SKILLS_DIR)));
29+
30+
return (
31+
await Promise.all(
32+
fullDirs.map(async (dir) => {
33+
const exists = await ide.fileExists(dir);
34+
if (!exists) return [];
35+
const uris = await walkDir(dir, ide, {
36+
source: "get .claude skills files",
37+
});
38+
// filter markdown files only
39+
return uris.filter((uri) => uri.endsWith(".md"));
40+
}),
41+
)
42+
).flat();
43+
}
44+
45+
export async function loadMarkdownSkills(ide: IDE) {
46+
const errors: ConfigValidationError[] = [];
47+
const skills: Skill[] = [];
48+
49+
try {
50+
const yamlAndMarkdownFileUris = [
51+
...(
52+
await getAllDotContinueDefinitionFiles(
53+
ide,
54+
{
55+
includeGlobal: true,
56+
includeWorkspace: true,
57+
fileExtType: "markdown",
58+
},
59+
SKILLS_DIR,
60+
)
61+
).map((file) => file.path),
62+
...(await getClaudeSkillsDir(ide)),
63+
];
64+
65+
const skillFiles = yamlAndMarkdownFileUris.filter((path) =>
66+
path.endsWith("SKILL.md"),
67+
);
68+
69+
const workspaceDirs = await ide.getWorkspaceDirs();
70+
for (const fileUri of skillFiles) {
71+
try {
72+
const content = await ide.readFile(fileUri);
73+
const { frontmatter, markdown } = parseMarkdownRule(
74+
content,
75+
) as unknown as { frontmatter: Skill; markdown: string };
76+
77+
const validatedFrontmatter = skillFrontmatterSchema.parse(frontmatter);
78+
79+
const filesInSkillsDirectory = (
80+
await walkDir(fileUri.substring(0, fileUri.lastIndexOf("/")), ide, {
81+
source: "get skill files",
82+
})
83+
)
84+
// do not include SKILL.md as it is already in content
85+
.filter((file) => !file.endsWith("SKILL.md"));
86+
87+
const foundRelativeUri = findUriInDirs(fileUri, workspaceDirs);
88+
89+
skills.push({
90+
...validatedFrontmatter,
91+
content: markdown,
92+
path: foundRelativeUri.foundInDir
93+
? foundRelativeUri.relativePathOrBasename
94+
: fileUri,
95+
files: filesInSkillsDirectory,
96+
});
97+
} catch (error) {
98+
errors.push({
99+
fatal: false,
100+
message: `Failed to parse markdown skill file: ${error instanceof Error ? error.message : error}`,
101+
});
102+
}
103+
}
104+
} catch (err) {
105+
errors.push({
106+
fatal: false,
107+
message: `Error loading markdown skill files: ${err instanceof Error ? err.message : err}`,
108+
});
109+
}
110+
111+
return { skills, errors };
112+
}

core/config/profile/doLoadConfig.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ async function loadRules(ide: IDE) {
6969

7070
return { rules, errors };
7171
}
72+
7273
export default async function doLoadConfig(options: {
7374
ide: IDE;
7475
controlPlaneClient: ControlPlaneClient;
@@ -299,14 +300,15 @@ export default async function doLoadConfig(options: {
299300
}
300301

301302
newConfig.tools.push(
302-
...getConfigDependentToolDefinitions({
303+
...(await getConfigDependentToolDefinitions({
303304
rules: newConfig.rules,
304305
enableExperimentalTools:
305306
newConfig.experimental?.enableExperimentalTools ?? false,
306307
isSignedIn,
307308
isRemote: await ide.isWorkspaceRemote(),
308309
modelName: newConfig.selectedModelByRole.chat?.model,
309-
}),
310+
ide,
311+
})),
310312
);
311313

312314
// Detect duplicate tool names

core/config/usesFreeTrialApiKey.vitest.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ describe("usesFreeTrialApiKey", () => {
5858
autocomplete: [],
5959
rerank: [],
6060
embed: [],
61+
subagent: [],
6162
},
6263
selectedModelByRole: {
6364
chat: null,
@@ -67,6 +68,7 @@ describe("usesFreeTrialApiKey", () => {
6768
autocomplete: null,
6869
rerank: null,
6970
embed: null,
71+
subagent: null,
7072
},
7173
contextProviders: [],
7274
slashCommands: [],
@@ -106,6 +108,7 @@ describe("usesFreeTrialApiKey", () => {
106108
autocomplete: [],
107109
rerank: [],
108110
embed: [],
111+
subagent: [],
109112
},
110113
selectedModelByRole: {
111114
chat: null,
@@ -115,6 +118,7 @@ describe("usesFreeTrialApiKey", () => {
115118
autocomplete: null,
116119
rerank: null,
117120
embed: null,
121+
subagent: null,
118122
},
119123
contextProviders: [],
120124
slashCommands: [],
@@ -166,6 +170,7 @@ describe("usesFreeTrialApiKey", () => {
166170
autocomplete: [],
167171
rerank: [],
168172
embed: [],
173+
subagent: [],
169174
},
170175
selectedModelByRole: {
171176
chat: null,
@@ -175,6 +180,7 @@ describe("usesFreeTrialApiKey", () => {
175180
autocomplete: null,
176181
rerank: null,
177182
embed: null,
183+
subagent: null,
178184
},
179185
contextProviders: [],
180186
slashCommands: [],
@@ -227,6 +233,7 @@ describe("usesFreeTrialApiKey", () => {
227233
autocomplete: [],
228234
rerank: [],
229235
embed: [],
236+
subagent: [],
230237
},
231238
selectedModelByRole: {
232239
chat: null,
@@ -236,6 +243,7 @@ describe("usesFreeTrialApiKey", () => {
236243
autocomplete: null,
237244
rerank: null,
238245
embed: null,
246+
subagent: null,
239247
},
240248
contextProviders: [],
241249
slashCommands: [],
@@ -281,6 +289,7 @@ describe("usesFreeTrialApiKey", () => {
281289
autocomplete: [],
282290
rerank: [],
283291
embed: [],
292+
subagent: [],
284293
},
285294
selectedModelByRole: {
286295
chat: null,
@@ -290,6 +299,7 @@ describe("usesFreeTrialApiKey", () => {
290299
autocomplete: null,
291300
rerank: null,
292301
embed: null,
302+
subagent: null,
293303
},
294304
contextProviders: [],
295305
slashCommands: [],
@@ -323,6 +333,7 @@ describe("usesFreeTrialApiKey", () => {
323333
autocomplete: [],
324334
rerank: [],
325335
embed: [],
336+
subagent: [],
326337
},
327338
selectedModelByRole: {
328339
chat: null,
@@ -332,6 +343,7 @@ describe("usesFreeTrialApiKey", () => {
332343
autocomplete: null,
333344
rerank: null,
334345
embed: null,
346+
subagent: null,
335347
},
336348
contextProviders: [],
337349
slashCommands: [],

core/config/yaml/loadYaml.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export async function configYamlToContinueConfig(options: {
187187
autocomplete: [],
188188
rerank: [],
189189
summarize: [],
190+
subagent: [],
190191
},
191192
selectedModelByRole: {
192193
chat: null,
@@ -196,6 +197,7 @@ export async function configYamlToContinueConfig(options: {
196197
autocomplete: null,
197198
rerank: null,
198199
summarize: null,
200+
subagent: null,
199201
},
200202
rules: [],
201203
requestOptions: { ...config.requestOptions },
@@ -333,6 +335,10 @@ export async function configYamlToContinueConfig(options: {
333335
if (model.roles?.includes("rerank")) {
334336
continueConfig.modelsByRole.rerank.push(...llms);
335337
}
338+
339+
if (model.roles?.includes("subagent")) {
340+
continueConfig.modelsByRole.subagent.push(...llms);
341+
}
336342
} catch (e) {
337343
localErrors.push({
338344
fatal: false,

core/config/yaml/models.vitest.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ describe("llmsFromModelConfig requestOptions merging", () => {
6767
embed: null,
6868
rerank: null,
6969
summarize: null,
70+
subagent: null,
7071
},
7172
modelsByRole: {
7273
apply: [],
@@ -76,6 +77,7 @@ describe("llmsFromModelConfig requestOptions merging", () => {
7677
embed: [],
7778
rerank: [],
7879
summarize: [],
80+
subagent: [],
7981
},
8082
slashCommands: [],
8183
tools: [],

core/index.d.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1152,9 +1152,10 @@ export interface ConfigDependentToolParams {
11521152
isSignedIn: boolean;
11531153
isRemote: boolean;
11541154
modelName: string | undefined;
1155+
ide: IDE;
11551156
}
11561157

1157-
export type GetTool = (params: ConfigDependentToolParams) => Tool;
1158+
export type GetTool = (params: ConfigDependentToolParams) => Promise<Tool>;
11581159

11591160
export interface BaseCompletionOptions {
11601161
temperature?: number;
@@ -1895,6 +1896,15 @@ export interface RuleWithSource extends RuleMetadata {
18951896
rule: string;
18961897
}
18971898

1899+
export interface Skill {
1900+
name: string;
1901+
description: string;
1902+
path: string;
1903+
content: string;
1904+
files: string[];
1905+
license?: string;
1906+
}
1907+
18981908
export interface CompleteOnboardingPayload {
18991909
mode: OnboardingModes;
19001910
provider?: string;

core/tools/builtIn.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export enum BuiltInToolNames {
1616
RequestRule = "request_rule",
1717
FetchUrlContent = "fetch_url_content",
1818
CodebaseTool = "codebase",
19+
ReadSkill = "read_skill",
1920

2021
// excluded from allTools for now
2122
ViewRepoMap = "view_repo_map",

core/tools/callTool.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { readCurrentlyOpenFileImpl } from "./implementations/readCurrentlyOpenFi
1616
import { readFileImpl } from "./implementations/readFile";
1717

1818
import { readFileRangeImpl } from "./implementations/readFileRange";
19+
import { readSkillImpl } from "./implementations/readSkill";
1920
import { requestRuleImpl } from "./implementations/requestRule";
2021
import { runTerminalCommandImpl } from "./implementations/runTerminalCommand";
2122
import { searchWebImpl } from "./implementations/searchWeb";
@@ -179,6 +180,8 @@ export async function callBuiltInTool(
179180
return await requestRuleImpl(args, extras);
180181
case BuiltInToolNames.CodebaseTool:
181182
return await codebaseToolImpl(args, extras);
183+
case BuiltInToolNames.ReadSkill:
184+
return await readSkillImpl(args, extras);
182185
case BuiltInToolNames.ViewRepoMap:
183186
return await viewRepoMapImpl(args, extras);
184187
case BuiltInToolNames.ViewSubdirectory:

0 commit comments

Comments
 (0)