Skip to content

Commit e81e8bf

Browse files
committed
cli: load local agent definitions
1 parent 0175678 commit e81e8bf

File tree

2 files changed

+123
-1
lines changed

2 files changed

+123
-1
lines changed

cli/src/hooks/use-send-message.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import { useCallback, useEffect, useRef } from 'react'
33
import { getCodebuffClient, formatToolOutput } from '../utils/codebuff-client'
44
import { shouldHideAgent } from '../utils/constants'
55
import { formatTimestamp } from '../utils/helpers'
6+
import { loadAgentDefinitions } from '../utils/load-agent-definitions'
67
import { logger } from '../utils/logger'
78

89
import type { ChatMessage, ContentBlock } from '../chat'
9-
import type { ToolName } from '@codebuff/sdk'
10+
import type { AgentDefinition, ToolName } from '@codebuff/sdk'
1011
import type { SetStateAction } from 'react'
1112

1213
const completionMessages = [
@@ -106,6 +107,7 @@ interface UseSendMessageOptions {
106107
setIsStreaming: (streaming: boolean) => void
107108
setCanProcessQueue: (can: boolean) => void
108109
abortControllerRef: React.MutableRefObject<AbortController | null>
110+
agentMode: 'FAST' | 'MAX'
109111
}
110112

111113
export const useSendMessage = ({
@@ -125,6 +127,7 @@ export const useSendMessage = ({
125127
setIsStreaming,
126128
setCanProcessQueue,
127129
abortControllerRef,
130+
agentMode,
128131
}: UseSendMessageOptions) => {
129132
const previousRunStateRef = useRef<any>(null)
130133
const spawnAgentsMapRef = useRef<
@@ -551,11 +554,15 @@ export const useSendMessage = ({
551554
abortControllerRef.current = abortController
552555

553556
try {
557+
// Load local agent definitions from .agents directory
558+
const agentDefinitions = loadAgentDefinitions()
559+
554560
const result = await client.run({
555561
agent: 'base',
556562
prompt: content,
557563
previousRun: previousRunStateRef.current,
558564
signal: abortController.signal,
565+
agentDefinitions: agentDefinitions as AgentDefinition[],
559566

560567
handleStreamChunk: (chunk: any) => {
561568
if (typeof chunk !== 'string' || !chunk) {
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import type { AgentDefinition } from '@codebuff/common/templates/initial-agents-dir/types/agent-definition'
4+
5+
const AGENTS_DIR_NAME = '.agents'
6+
const DISPLAY_NAME_REGEX = /displayName\s*:\s*['"`]([^'"`]+)['"`]/i
7+
const ID_REGEX = /id\s*:\s*['"`]([^'"`]+)['"`]/i
8+
9+
const shouldSkipDirectory = (dirName: string): boolean => {
10+
if (!dirName) return true
11+
if (dirName.startsWith('.')) return true
12+
const skipped = new Set([
13+
'types',
14+
'prompts',
15+
'registry',
16+
'constants',
17+
'__tests__',
18+
'factory',
19+
'node_modules',
20+
])
21+
return skipped.has(dirName)
22+
}
23+
24+
const findAgentsDir = (): string | null => {
25+
let currentDir = process.cwd()
26+
const rootDir = path.parse(currentDir).root
27+
28+
while (true) {
29+
const candidate = path.join(currentDir, AGENTS_DIR_NAME)
30+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
31+
return candidate
32+
}
33+
34+
if (currentDir === rootDir) {
35+
break
36+
}
37+
38+
const parentDir = path.dirname(currentDir)
39+
if (parentDir === currentDir) {
40+
break
41+
}
42+
currentDir = parentDir
43+
}
44+
45+
return null
46+
}
47+
48+
const gatherAgentFiles = (dir: string): string[] => {
49+
const results: string[] = []
50+
const entries = fs.readdirSync(dir, { withFileTypes: true })
51+
52+
for (const entry of entries) {
53+
const fullPath = path.join(dir, entry.name)
54+
55+
if (entry.isDirectory()) {
56+
if (shouldSkipDirectory(entry.name)) {
57+
continue
58+
}
59+
results.push(...gatherAgentFiles(fullPath))
60+
continue
61+
}
62+
63+
if (!entry.isFile() || !entry.name.endsWith('.ts')) {
64+
continue
65+
}
66+
67+
try {
68+
const content = fs.readFileSync(fullPath, 'utf8')
69+
const displayMatch = content.match(DISPLAY_NAME_REGEX)
70+
const idMatch = content.match(ID_REGEX)
71+
72+
if (displayMatch || idMatch) {
73+
results.push(fullPath)
74+
}
75+
} catch {
76+
continue
77+
}
78+
}
79+
80+
return results
81+
}
82+
83+
/**
84+
* Load local agent definitions from .agents directory and pass to SDK
85+
* Note: The SDK's processAgentDefinitions will handle converting handleSteps functions to strings
86+
*/
87+
export const loadAgentDefinitions = (): AgentDefinition[] => {
88+
const agentsDir = findAgentsDir()
89+
if (!agentsDir) {
90+
return []
91+
}
92+
93+
const agentFiles = gatherAgentFiles(agentsDir)
94+
const definitions: AgentDefinition[] = []
95+
96+
for (const filePath of agentFiles) {
97+
try {
98+
// Use require to load the TypeScript file (works with ts-node/bun)
99+
const agentModule = require(filePath)
100+
const agentDef = agentModule.default
101+
102+
if (!agentDef || !agentDef.id || !agentDef.model) {
103+
continue
104+
}
105+
106+
// No need to convert handleSteps - SDK's processAgentDefinitions handles it
107+
definitions.push(agentDef as AgentDefinition)
108+
} catch (error) {
109+
// Skip files that can't be loaded
110+
continue
111+
}
112+
}
113+
114+
return definitions
115+
}

0 commit comments

Comments
 (0)