Skip to content

Commit 0ff2d0a

Browse files
committed
sdk: skills dir parameter
1 parent 755a22f commit 0ff2d0a

File tree

5 files changed

+73
-2
lines changed

5 files changed

+73
-2
lines changed

sdk/src/__tests__/initial-session-state.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'
2+
import os from 'os'
3+
import path from 'path'
4+
15
import { describe, expect, test, beforeEach } from 'bun:test'
26
import { z } from 'zod/v4'
37

@@ -310,6 +314,62 @@ describe('Initial Session State', () => {
310314
expect(sessionState.fileContext.systemInfo.cpus).toBeGreaterThan(0)
311315
})
312316

317+
test('loads skills from skillsDir when provided', async () => {
318+
const tmpDir = mkdtempSync(path.join(os.tmpdir(), 'sdk-skills-test-'))
319+
try {
320+
const skillDir = path.join(tmpDir, 'my-skill')
321+
mkdirSync(skillDir, { recursive: true })
322+
writeFileSync(
323+
path.join(skillDir, 'SKILL.md'),
324+
[
325+
'---',
326+
'name: my-skill',
327+
'description: A test skill',
328+
'---',
329+
'',
330+
'# My Skill',
331+
'',
332+
'Some instructions here.',
333+
].join('\n'),
334+
)
335+
336+
const sessionState = await initialSessionState({
337+
cwd: '/test-project',
338+
skillsDir: tmpDir,
339+
projectFiles: { 'src/index.ts': 'console.log("hello");' },
340+
fs: mockFs,
341+
logger: mockLogger,
342+
})
343+
344+
expect(sessionState.fileContext.skills).toBeDefined()
345+
expect(sessionState.fileContext.skills!['my-skill']).toBeDefined()
346+
expect(sessionState.fileContext.skills!['my-skill'].name).toBe('my-skill')
347+
expect(sessionState.fileContext.skills!['my-skill'].description).toBe(
348+
'A test skill',
349+
)
350+
} finally {
351+
rmSync(tmpDir, { recursive: true, force: true })
352+
}
353+
})
354+
355+
test('skillsDir with no valid skills results in empty skills map', async () => {
356+
const tmpDir = mkdtempSync(path.join(os.tmpdir(), 'sdk-skills-test-'))
357+
try {
358+
const sessionState = await initialSessionState({
359+
cwd: '/test-project',
360+
skillsDir: tmpDir,
361+
projectFiles: { 'src/index.ts': 'console.log("hello");' },
362+
fs: mockFs,
363+
logger: mockLogger,
364+
})
365+
366+
expect(sessionState.fileContext.skills).toBeDefined()
367+
expect(Object.keys(sessionState.fileContext.skills!)).toHaveLength(0)
368+
} finally {
369+
rmSync(tmpDir, { recursive: true, force: true })
370+
}
371+
})
372+
313373
test('initializes empty agent state correctly', async () => {
314374
const projectFiles = {
315375
'src/index.ts': 'console.log("Hello world");',

sdk/src/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export class CodebuffClient {
4747
* @param knowledgeFiles - (Optional) Knowledge files to inject into every run() call. Uses the same schema as projectFiles - keys are file paths and values are file contents. These files are added directly to the agent's context.
4848
* @param agentDefinitions - (Optional) Array of custom agent definitions. Each object should satisfy the AgentDefinition type. You can input the agent's id field into the agent parameter to run that agent.
4949
* @param customToolDefinitions - (Optional) Array of custom tool definitions that extend the agent's capabilities. Each tool definition includes a name, Zod schema for input validation, and a handler function. These tools can be called by the agent during execution.
50+
* @param skillsDir - (Optional) Path to a directory containing skills to load. Each skill should be in its own subdirectory with a SKILL.md file (e.g., `skillsDir/my-skill/SKILL.md`). When provided, skills are loaded from this directory instead of the default locations. The loaded skills will be listed in the `skill` tool's description and can be loaded by the agent.
5051
* @param maxAgentSteps - (Optional) Maximum number of steps the agent can take before stopping. Use this as a safety measure in case your agent starts going off the rails. A reasonable number is around 20.
5152
* @param env - (Optional) Environment variables to pass to terminal commands executed by the agent. These will be merged with the current process environment, with the custom values taking precedence. Can also be provided in individual run() calls to override.
5253
*

sdk/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export { run } from './run'
1010
export { getFiles } from './tools/read-files'
1111
export type { FileFilter, FileFilterResult } from './tools/read-files'
1212
export type {
13+
CodebuffClientOptions,
1314
RunOptions,
1415
MessageContent,
1516
TextContent,

sdk/src/run-state.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ export type RunState = {
6767

6868
export type InitialSessionStateOptions = {
6969
cwd?: string
70+
/** Optional directory path to load skills from. When provided, skills are loaded from this directory instead of the default locations. */
71+
skillsDir?: string
7072
projectFiles?: Record<string, string>
7173
knowledgeFiles?: Record<string, string>
7274
/** User-provided knowledge files that will be merged with home directory files */
@@ -407,7 +409,7 @@ function deriveKnowledgeFiles(
407409
export async function initialSessionState(
408410
params: InitialSessionStateOptions,
409411
): Promise<SessionState> {
410-
const { cwd, maxAgentSteps } = params
412+
const { cwd, maxAgentSteps, skillsDir } = params
411413
let {
412414
agentDefinitions,
413415
customToolDefinitions,
@@ -488,7 +490,7 @@ export async function initialSessionState(
488490
}
489491

490492
// Load skills from project and home directories
491-
const skills = await loadSkills({ cwd: cwd ?? process.cwd(), verbose: false })
493+
const skills = await loadSkills({ cwd: cwd ?? process.cwd(), skillsPath: skillsDir, verbose: false })
492494

493495
const initialState = getInitialSessionState({
494496
projectRoot: cwd ?? process.cwd(),
@@ -523,6 +525,7 @@ export async function initialSessionState(
523525

524526
export async function generateInitialRunState({
525527
cwd,
528+
skillsDir,
526529
projectFiles,
527530
knowledgeFiles,
528531
userKnowledgeFiles,
@@ -532,6 +535,7 @@ export async function generateInitialRunState({
532535
fs,
533536
}: {
534537
cwd: string
538+
skillsDir?: string
535539
projectFiles?: Record<string, string>
536540
knowledgeFiles?: Record<string, string>
537541
userKnowledgeFiles?: Record<string, string>
@@ -543,6 +547,7 @@ export async function generateInitialRunState({
543547
return {
544548
sessionState: await initialSessionState({
545549
cwd,
550+
skillsDir,
546551
projectFiles,
547552
knowledgeFiles,
548553
userKnowledgeFiles,

sdk/src/run.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ export type CodebuffClientOptions = {
7171
apiKey?: string
7272

7373
cwd?: string
74+
/** Optional directory path to load skills from. Skills found here will be available to the `skill` tool. */
75+
skillsDir?: string
7476
projectFiles?: Record<string, string>
7577
knowledgeFiles?: Record<string, string>
7678
agentDefinitions?: AgentDefinition[]
@@ -180,6 +182,7 @@ async function runOnce({
180182
fingerprintId,
181183

182184
cwd,
185+
skillsDir,
183186
projectFiles,
184187
knowledgeFiles,
185188
agentDefinitions,
@@ -244,6 +247,7 @@ async function runOnce({
244247
// No previous run, so create a fresh session state
245248
sessionState = await initialSessionState({
246249
cwd,
250+
skillsDir,
247251
knowledgeFiles,
248252
agentDefinitions,
249253
customToolDefinitions,

0 commit comments

Comments
 (0)